140 lines
3.8 KiB
Go
140 lines
3.8 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"ollie/skills"
|
|
)
|
|
|
|
// SetSkillsRegistry attaches a skills registry to the execute server.
|
|
// The server uses it to serve skill_list, skill_load, and skill_active.
|
|
func (e *Server) SetSkillsRegistry(r *skills.Registry) {
|
|
e.skillsRegistry = r
|
|
}
|
|
|
|
// ListSkillsTools returns ToolInfo entries for the skill_* built-ins.
|
|
// These are included alongside the standard tool_* tools.
|
|
func ListSkillsTools(skillsReg *skills.Registry, sessionID string) []ToolInfo {
|
|
if skillsReg == nil {
|
|
return nil
|
|
}
|
|
tools := []ToolInfo{
|
|
{
|
|
Name: "skill_list",
|
|
Description: `List available skill modules with name and description.
|
|
|
|
Usage: {"name": "skillname"} — if name is provided, loads that skill.
|
|
Otherwise lists all skills with descriptions.`,
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {}
|
|
}`),
|
|
},
|
|
{
|
|
Name: "skill_load",
|
|
Description: `Load a skill module into the current session's context.
|
|
|
|
Usage: {"name": "skillname"}
|
|
|
|
The skill content (SKILL.md body) is injected into the agent's context
|
|
and persists across turns.`,
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": {"type": "string", "description": "Skill name to load."}
|
|
}
|
|
}`),
|
|
},
|
|
{
|
|
Name: "skill_active",
|
|
Description: `List skills currently loaded in this session.
|
|
|
|
Usage: (no arguments)
|
|
|
|
Returns loaded skill names, one per line.`,
|
|
InputSchema: json.RawMessage(`{
|
|
"type": "object",
|
|
"properties": {}
|
|
}`),
|
|
},
|
|
}
|
|
return tools
|
|
}
|
|
|
|
// dispatchSkillList lists all available skills from the global registry.
|
|
func dispatchSkillList(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
|
|
if e.skillsRegistry == nil {
|
|
return "", fmt.Errorf("skill_list: no skills registry available")
|
|
}
|
|
summaries := e.skillsRegistry.Summaries()
|
|
if len(summaries) == 0 {
|
|
return "(no skills found)", nil
|
|
}
|
|
var out strings.Builder
|
|
for _, s := range summaries {
|
|
out.WriteString(s.Name)
|
|
if s.Description != "" {
|
|
out.WriteString(" — ")
|
|
out.WriteString(s.Description)
|
|
}
|
|
out.WriteString("\n")
|
|
}
|
|
return strings.TrimRight(out.String(), "\n"), nil
|
|
}
|
|
|
|
// dispatchSkillLoad loads a skill into the current session.
|
|
func dispatchSkillLoad(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
|
|
var a struct {
|
|
Name string `json:"name"`
|
|
}
|
|
if err := json.Unmarshal(args, &a); err != nil {
|
|
return "", fmt.Errorf("skill_load: bad args: %w", err)
|
|
}
|
|
if a.Name == "" {
|
|
return "", fmt.Errorf("skill_load: name is required")
|
|
}
|
|
if e.skillsRegistry == nil || e.sessionID == "" {
|
|
return "", fmt.Errorf("skill_load: no session registry")
|
|
}
|
|
if err := e.skillsRegistry.Load(e.sessionID, a.Name); err != nil {
|
|
return "", fmt.Errorf("skill_load: %w", err)
|
|
}
|
|
content, err := e.skillsRegistry.SkillContent(a.Name)
|
|
if err != nil {
|
|
return "", fmt.Errorf("skill_load: %w", err)
|
|
}
|
|
|
|
// Inject skill content into the agent context.
|
|
// Skill content is sent as a system message so it persists across turns.
|
|
injectMsg := fmt.Sprintf("[system: loaded skill %s]\n\n%s", a.Name, content)
|
|
if e.OnInjection != nil {
|
|
e.OnInjection(injectMsg)
|
|
}
|
|
return fmt.Sprintf("loaded: %s (%d bytes)", a.Name, len(content)), nil
|
|
}
|
|
|
|
// dispatchSkillActive lists skills currently loaded in this session.
|
|
func dispatchSkillActive(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
|
|
if e.skillsRegistry == nil || e.sessionID == "" {
|
|
return "(no skills loaded)", nil
|
|
}
|
|
loaded := e.skillsRegistry.Loaded(e.sessionID)
|
|
if len(loaded) == 0 {
|
|
return "(no skills loaded)", nil
|
|
}
|
|
var out strings.Builder
|
|
for _, s := range loaded {
|
|
out.WriteString(s.Name)
|
|
if s.Description != "" {
|
|
out.WriteString(" — ")
|
|
out.WriteString(s.Description)
|
|
}
|
|
out.WriteString("\n")
|
|
}
|
|
return strings.TrimRight(out.String(), "\n"), nil
|
|
}
|