Skip to content

Commit ffb1dfe

Browse files
committed
Wire registry skills into skills service and API
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
1 parent b5296bc commit ffb1dfe

13 files changed

Lines changed: 153 additions & 17 deletions

File tree

pkg/api/server.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/stacklok/toolhive/pkg/container/runtime"
4040
"github.com/stacklok/toolhive/pkg/groups"
4141
"github.com/stacklok/toolhive/pkg/recovery"
42+
"github.com/stacklok/toolhive/pkg/registry"
4243
"github.com/stacklok/toolhive/pkg/skills"
4344
"github.com/stacklok/toolhive/pkg/skills/skillsvc"
4445
"github.com/stacklok/toolhive/pkg/storage/sqlite"
@@ -251,20 +252,29 @@ func (b *ServerBuilder) createDefaultManagers(ctx context.Context) error {
251252
_ = store.Close()
252253
return fmt.Errorf("failed to create OCI skill store: %w", ociErr)
253254
}
254-
registry, regErr := ociskills.NewRegistry()
255+
ociRegistry, regErr := ociskills.NewRegistry()
255256
if regErr != nil {
256257
_ = store.Close()
257258
// ociStore is directory-backed with no open handles; no cleanup needed.
258259
return fmt.Errorf("failed to create OCI registry client: %w", regErr)
259260
}
260261
packager := ociskills.NewPackager(ociStore)
261262

263+
// Get registry provider for skill discovery (best-effort)
264+
regProvider, regProviderErr := registry.GetDefaultProvider()
265+
var regOpts []skillsvc.Option
266+
if regProviderErr == nil {
267+
regOpts = append(regOpts, skillsvc.WithRegistryProvider(regProvider))
268+
}
269+
262270
b.skillManager = skillsvc.New(store,
263-
skillsvc.WithPathResolver(&clientPathAdapter{cm: cm}),
264-
skillsvc.WithOCIStore(ociStore),
265-
skillsvc.WithPackager(packager),
266-
skillsvc.WithRegistryClient(registry),
267-
skillsvc.WithGroupManager(b.groupManager),
271+
append([]skillsvc.Option{
272+
skillsvc.WithPathResolver(&clientPathAdapter{cm: cm}),
273+
skillsvc.WithOCIStore(ociStore),
274+
skillsvc.WithPackager(packager),
275+
skillsvc.WithRegistryClient(ociRegistry),
276+
skillsvc.WithGroupManager(b.groupManager),
277+
}, regOpts...)...,
268278
)
269279
}
270280

pkg/api/v1/skills.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func SkillsRouter(skillService skills.SkillService) http.Handler {
2828

2929
r := chi.NewRouter()
3030
r.Get("/", apierrors.ErrorHandler(routes.listSkills))
31+
r.Get("/available", apierrors.ErrorHandler(routes.listAvailableSkills))
3132
r.Post("/", apierrors.ErrorHandler(routes.installSkill))
3233
r.Delete("/{name}", apierrors.ErrorHandler(routes.uninstallSkill))
3334
r.Get("/{name}", apierrors.ErrorHandler(routes.getSkillInfo))
@@ -38,6 +39,25 @@ func SkillsRouter(skillService skills.SkillService) http.Handler {
3839
return r
3940
}
4041

42+
// listAvailableSkills returns skills available from the registry.
43+
//
44+
// @Summary List available skills
45+
// @Description Get a list of skills available from the registry
46+
// @Tags skills
47+
// @Produce json
48+
// @Success 200 {object} availableSkillsResponse
49+
// @Failure 500 {string} string "Internal Server Error"
50+
// @Router /api/v1beta/skills/available [get]
51+
func (s *SkillsRoutes) listAvailableSkills(w http.ResponseWriter, r *http.Request) error {
52+
result, err := s.skillService.ListAvailable(r.Context())
53+
if err != nil {
54+
return err
55+
}
56+
57+
w.Header().Set("Content-Type", "application/json")
58+
return json.NewEncoder(w).Encode(availableSkillsResponse{Skills: result})
59+
}
60+
4161
// listSkills returns a list of installed skills.
4262
//
4363
// @Summary List all installed skills

pkg/api/v1/skills_types.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
package v1
55

6-
import "github.com/stacklok/toolhive/pkg/skills"
6+
import (
7+
types "github.com/stacklok/toolhive-core/registry/types"
8+
"github.com/stacklok/toolhive/pkg/skills"
9+
)
710

811
// skillListResponse represents the response for listing skills.
912
//
@@ -66,3 +69,11 @@ type pushSkillRequest struct {
6669
// OCI reference to push
6770
Reference string `json:"reference"`
6871
}
72+
73+
// availableSkillsResponse represents the response for listing available skills from the registry.
74+
//
75+
// @Description Response containing skills available from the registry
76+
type availableSkillsResponse struct {
77+
// List of available skills from the registry
78+
Skills []types.Skill `json:"skills"`
79+
}

pkg/registry/mocks/mock_provider.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/registry/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@ type Provider interface {
2020

2121
// ListServers returns all available servers (both container and remote)
2222
ListServers() ([]types.ServerMetadata, error)
23+
24+
// ListAvailableSkills returns skills discovered from the registry data
25+
ListAvailableSkills() ([]types.Skill, error)
2326
}

pkg/registry/provider_base.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ func (p *BaseProvider) ListServers() ([]types.ServerMetadata, error) {
7878
return reg.GetAllServers(), nil
7979
}
8080

81+
// ListAvailableSkills returns an empty slice by default.
82+
// Providers that support skills (local, remote) override this.
83+
func (*BaseProvider) ListAvailableSkills() ([]types.Skill, error) {
84+
return nil, nil
85+
}
86+
8187
// matchesQuery checks if a server matches the search query
8288
func matchesQuery(name, description string, tags []string, query string) bool {
8389
// Search in name

pkg/registry/provider_local.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ func (p *LocalRegistryProvider) setSkills(skills []types.Skill) {
104104
p.skills = skills
105105
}
106106

107+
// ListAvailableSkills returns skills discovered from the upstream registry data.
108+
func (p *LocalRegistryProvider) ListAvailableSkills() ([]types.Skill, error) {
109+
p.skillsMu.RLock()
110+
defer p.skillsMu.RUnlock()
111+
return p.skills, nil
112+
}
113+
107114
// parseRegistryData parses JSON data into a Registry struct
108115
func parseRegistryData(data []byte) (*types.Registry, error) {
109116
registry := &types.Registry{}

pkg/registry/provider_remote.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ func (p *RemoteRegistryProvider) GetRegistry() (*types.Registry, error) {
174174
return registry, nil
175175
}
176176

177+
// ListAvailableSkills returns skills discovered from the remote registry data.
178+
func (p *RemoteRegistryProvider) ListAvailableSkills() ([]types.Skill, error) {
179+
p.skillsMu.RLock()
180+
defer p.skillsMu.RUnlock()
181+
return p.skills, nil
182+
}
183+
177184
func (p *RemoteRegistryProvider) setSkills(skills []types.Skill) {
178185
p.skillsMu.Lock()
179186
defer p.skillsMu.Unlock()

pkg/skills/client/client.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/stacklok/toolhive-core/env"
2020
"github.com/stacklok/toolhive-core/httperr"
21+
types "github.com/stacklok/toolhive-core/registry/types"
2122
"github.com/stacklok/toolhive/pkg/skills"
2223
)
2324

@@ -253,6 +254,15 @@ func (c *Client) doJSONRequest(
253254
return nil
254255
}
255256

257+
// ListAvailable returns skills available from the registry via the API.
258+
func (c *Client) ListAvailable(ctx context.Context) ([]types.Skill, error) {
259+
var resp availableSkillsResponse
260+
if err := c.doJSONRequest(ctx, http.MethodGet, "/available", nil, nil, &resp); err != nil {
261+
return nil, err
262+
}
263+
return resp.Skills, nil
264+
}
265+
256266
// handleErrorResponse reads the response body and returns an *httperr.CodedError.
257267
func handleErrorResponse(resp *http.Response) error {
258268
body, err := io.ReadAll(io.LimitReader(resp.Body, maxErrorBodySize))

pkg/skills/client/dto.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
package client
55

6-
import "github.com/stacklok/toolhive/pkg/skills"
6+
import (
7+
types "github.com/stacklok/toolhive-core/registry/types"
8+
"github.com/stacklok/toolhive/pkg/skills"
9+
)
710

811
// --- request/response dto (mirror pkg/api/v1/skills_types.go) ---
912

@@ -36,3 +39,7 @@ type listResponse struct {
3639
type installResponse struct {
3740
Skill skills.InstalledSkill `json:"skill"`
3841
}
42+
43+
type availableSkillsResponse struct {
44+
Skills []types.Skill `json:"skills"`
45+
}

0 commit comments

Comments
 (0)