Files
Memoh/internal/agent/tools/skill.go
Chrys 33e18e7e64 feat(skills): add effective skill resolution and actions (#377)
* feat(skills): add effective skill resolution and actions

* refactor(workspace): normalize skill-related env and prompt

* chore(api): regenerate skills OpenAPI and SDK artifacts

* feat(web): surface effective skill state in console

* test(skills): cover API and runtime effective state

* fix(web): show adopt action for discovered skills

* fix(web): align skill header and show stateful visibility icon

* refactor(web): compact skill metadata on narrow layouts

* fix(web): constrain long skill text in cards

* refactor(skills): narrow default discovery roots

* fix(skills): harden managed skill path validation

* feat: add path in the results of `use_skill`

---------

Co-authored-by: Acbox <acbox0328@gmail.com>
2026-04-16 13:50:39 +08:00

70 lines
1.8 KiB
Go

package tools
import (
"context"
"errors"
"fmt"
"log/slog"
sdk "github.com/memohai/twilight-ai/sdk"
)
type SkillProvider struct {
logger *slog.Logger
}
func NewSkillProvider(log *slog.Logger) *SkillProvider {
if log == nil {
log = slog.Default()
}
return &SkillProvider{logger: log.With(slog.String("tool", "skill"))}
}
func (*SkillProvider) Tools(_ context.Context, session SessionContext) ([]sdk.Tool, error) {
if session.IsSubagent || len(session.Skills) == 0 {
return nil, nil
}
skills := session.Skills
return []sdk.Tool{
{
Name: "use_skill",
Description: "Activate a skill to get its full instructions. Call this when you think a skill is relevant to the current task.",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"skillName": map[string]any{
"type": "string",
"description": "The name of the skill to activate",
},
"reason": map[string]any{
"type": "string",
"description": "Why this skill is relevant to the current task",
},
},
"required": []string{"skillName", "reason"},
},
Execute: func(_ *sdk.ToolExecContext, input any) (any, error) {
args := inputAsMap(input)
skillName := StringArg(args, "skillName")
if skillName == "" {
return nil, errors.New("skillName is required")
}
skill, ok := skills[skillName]
if !ok {
return map[string]any{
"success": false,
"error": fmt.Sprintf("skill %q not found — check available skills in the system prompt", skillName),
}, nil
}
return map[string]any{
"success": true,
"skillName": skillName,
"description": skill.Description,
"content": skill.Content,
"path": skill.Path,
}, nil
},
},
}, nil
}