返回 DeepSeek-Reasonix
tools.go
根目录 / internal / browser / tools.go
1 package browser
2
3 import (
4 "context"
5 "encoding/json"
6 "strings"
7
8 "reasonix/internal/tool"
9 )
10
11 // Names lists the tool names in the order Tools returns them.
12 func Names() []string {
13 return []string{
14 "browser_tabs", "browser_open", "browser_preview", "browser_navigate", "browser_snapshot", "browser_screenshot",
15 "browser_click", "browser_type", "browser_press", "browser_scroll", "browser_select", "browser_upload",
16 "browser_download", "browser_close",
17 }
18 }
19
20 // Tools builds the thirteen browser tools over exec. A nil exec still yields
21 // every tool, each reporting ProviderVisible false so it fails closed.
22 func Tools(exec Executor) []tool.Tool {
23 return []tool.Tool{
24 tabsTool(exec), openTool(exec), previewTool(exec), navigateTool(exec), snapshotTool(exec), screenshotTool(exec),
25 clickTool(exec), typeTool(exec), pressTool(exec), scrollTool(exec), selectTool(exec), uploadTool(exec),
26 downloadTool(exec), closeTool(exec),
27 }
28 }
29
30 type previewFileTool struct {
31 writeTool
32 }
33
34 func (t previewFileTool) ProviderVisible(ctx context.Context) bool {
35 if _, ok := t.exec.(FilePreviewer); !ok {
36 return false
37 }
38 return t.writeTool.ProviderVisible(ctx)
39 }
40
41 func (t previewFileTool) UnavailableReason(ctx context.Context) string {
42 if _, ok := t.exec.(FilePreviewer); !ok {
43 return "the attached browser does not support local file previews"
44 }
45 return t.writeTool.UnavailableReason(ctx)
46 }
47
48 type runFunc func(ctx context.Context, exec Executor, args json.RawMessage) (string, error)
49
50 // base carries one tool's identity and its availability check.
51 type base struct {
52 exec Executor
53 name string
54 description string
55 schema json.RawMessage
56 snip tool.SnipHint
57 }
58
59 func (b base) Name() string { return b.name }
60 func (b base) Description() string { return b.description }
61 func (b base) Schema() json.RawMessage { return b.schema }
62 func (b base) SnipHint() tool.SnipHint { return b.snip }
63 func (b base) ProviderVisible(ctx context.Context) bool {
64 if b.exec == nil {
65 return false
66 }
67 if a, ok := b.exec.(Availability); ok {
68 return a.Available(ctx)
69 }
70 return true
71 }
72
73 func (b base) UnavailableReason(ctx context.Context) string {
74 if b.exec == nil {
75 return strings.TrimPrefix(noBrowserText, "blocked: ")
76 }
77 if diagnostic, ok := b.exec.(tool.ContextualAvailabilityReason); ok {
78 if reason := diagnostic.UnavailableReason(ctx); reason != "" {
79 return reason
80 }
81 }
82 return strings.TrimPrefix(noGrantText, "blocked: ")
83 }
84
85 func (b base) ready(ctx context.Context) error {
86 if b.exec == nil {
87 return tool.Blocked(noBrowserText)
88 }
89 if !b.ProviderVisible(ctx) {
90 return tool.Blocked("blocked: " + b.UnavailableReason(ctx))
91 }
92 return ctx.Err()
93 }
94
95 type readTool struct {
96 base
97 run runFunc
98 }
99
100 func (readTool) ReadOnly() bool { return true }
101 func (readTool) PlanModeSafe() bool { return true }
102 func (t readTool) Execute(ctx context.Context, args json.RawMessage) (string, error) {
103 if err := t.ready(ctx); err != nil {
104 return "", err
105 }
106 return t.run(ctx, t.exec, args)
107 }
108
109 type writeTool struct {
110 base
111 run runFunc
112 }
113
114 func (writeTool) ReadOnly() bool { return false }
115 func (writeTool) PlanModeSafe() bool { return false }
116 func (t writeTool) Execute(ctx context.Context, args json.RawMessage) (string, error) {
117 if err := t.ready(ctx); err != nil {
118 return "", err
119 }
120 return t.run(ctx, t.exec, args)
121 }
122
123 // EffectHint names the tab a write touches, or the URL when no tab exists
124 // yet (browser_open); every write reaches the network and none is destructive.
125 func (writeTool) EffectHint(args json.RawMessage) tool.EffectHint {
126 var p struct {
127 TabID string `json:"tabId"`
128 URL string `json:"url"`
129 }
130 _ = json.Unmarshal(args, &p)
131 hint := tool.EffectHint{Known: true, UsesNetwork: true}
132 switch {
133 case p.TabID != "":
134 hint.Targets = []string{p.TabID}
135 case p.URL != "":
136 hint.Targets = []string{p.URL}
137 }
138 return hint
139 }
140
141 var (
142 shortSnip = tool.SnipHint{Head: 8, Tail: 4, HeadChars: 800, TailChars: 400}
143 listSnip = tool.SnipHint{Head: 40, Tail: 8, HeadChars: 4000, TailChars: 800}
144 treeSnip = tool.SnipHint{Head: 200, Tail: 20, HeadChars: 16000, TailChars: 2000}
145 )
146
146 lines GO