返回 DeepSeek-Reasonix
read_tool.go
根目录 / internal / skill / read_tool.go
1 package skill
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "reasonix/internal/tool"
10 )
11
12 // readSkillTool loads an inline skill body into context without running anything.
13 type readSkillTool struct {
14 store *Store
15 }
16
17 // NewReadSkillTool builds a read-only inline-skill loader so a plan can consult
18 // playbooks without starting a subagent.
19 func NewReadSkillTool(store *Store) tool.Tool { return &readSkillTool{store: store} }
20
21 func (*readSkillTool) Name() string { return tool.HostReadSkill }
22
23 // ReadOnly is true: read_skill only renders an inline skill body, with no
24 // subagent or side effects.
25 func (*readSkillTool) ReadOnly() bool { return true }
26
27 func (*readSkillTool) Description() string {
28 return "Read an inline skill or one of its embedded references without executing work. Pass the bare skill name. Subagent skills require run_skill or their dedicated tool."
29 }
30
31 func (*readSkillTool) Schema() json.RawMessage {
32 return json.RawMessage(`{
33 "type":"object",
34 "properties":{
35 "name":{"type":"string","description":"Bare inline skill identifier from the skills catalog."},
36 "arguments":{"type":"string","description":"Optional task arguments for reading the skill body."},
37 "reference":{"type":"string","description":"Optional references/*.md path from an embedded skill's router. Reads only that page; omit to read the skill body."}
38 },
39 "required":["name"]
40 }`)
41 }
42
43 func (t *readSkillTool) Execute(_ context.Context, args json.RawMessage) (string, error) {
44 var p struct {
45 Name string `json:"name"`
46 Arguments string `json:"arguments"`
47 Reference string `json:"reference"`
48 }
49 if err := json.Unmarshal(args, &p); err != nil {
50 return "", fmt.Errorf("invalid args: %w", err)
51 }
52 name := cleanSkillName(p.Name)
53 if name == "" {
54 return "", fmt.Errorf("read_skill requires a 'name' argument (got %q, which is just a marker/tag)", p.Name)
55 }
56 sk, ok := t.store.Read(name)
57 if !ok {
58 return "", fmt.Errorf("unknown skill %q — available: %s", name, availableNames(t.store))
59 }
60 if err := t.store.ValidateInvocation(sk); err != nil {
61 return "", fmt.Errorf("read_skill: %w", err)
62 }
63 sk = t.store.Prepare(sk)
64 if sk.RunAs == RunSubagent {
65 return "", fmt.Errorf("read_skill: skill %q is a subagent and must be executed, not read — use run_skill (or the dedicated %s tool)", name, name)
66 }
67 if reference := strings.TrimSpace(p.Reference); reference != "" {
68 if strings.TrimSpace(p.Arguments) != "" {
69 return "", fmt.Errorf("read_skill: reference and arguments are mutually exclusive")
70 }
71 return renderEmbeddedReference(sk, reference)
72 }
73 return renderInline(sk, strings.TrimSpace(p.Arguments)), nil
74 }
75
75 lines GO