返回 DeepSeek-Reasonix
capabilities.go
根目录 / internal / browser / capabilities.go
1 package browser
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "reasonix/internal/tool"
10 )
11
12 // CapabilityExecutor is optional. CDP and older remote implementations retain
13 // the original Executor contract and never silently route enhanced calls elsewhere.
14 type CapabilityExecutor interface {
15 BrowserCapability(context.Context, string, json.RawMessage) (json.RawMessage, error)
16 }
17
18 // CapabilityTools is registered only in the on-demand inventory. Its schemas do
19 // not replace any existing browser tool or alter the permanent provider prefix.
20 func CapabilityTools(exec Executor) []tool.Tool {
21 query := []property{tabIDProp(), documentTokenProp(), str("role", "Exact ARIA role."), str("name", "Exact accessible name, paired with role."), str("text", "Exact text."), str("testId", "Exact data-testid."), str("containerRef", "Optional existing container ref."), str("frameRef", "Optional existing element ref identifying the frame to search."), enum("state", "Optional element state.", "visible", "hidden", "enabled", "disabled", "checked", "editable")}
22 return []tool.Tool{
23 newCapability(exec, "query", "Find elements by semantic criteria within the current snapshot. Returns count and refs; multiple results are ambiguous and must be refined before acting.", false, []string{"tabId", "documentToken"}, query...),
24 newCapability(exec, "wait", "Wait with a bounded deadline for DOMContentLoaded, an exact URL, or a semantic element state. Continuous network activity does not prevent completion.", false, []string{"tabId"}, append(query, str("url", "Exact target URL."), bounded(integer("timeoutMs", "Maximum wait, default 3000 ms."), 1, 8000))...),
25 newCapability(exec, "viewport", "Read, set or reset the CSS viewport. Mutations invalidate previous coordinate observations and are recorded operations.", true, []string{"tabId", "action"}, tabIDProp(), enum("action", "Viewport operation.", "get", "set", "reset"), operationIDProp(), bounded(integer("width", "CSS width."), 320, 3840), bounded(integer("height", "CSS height."), 320, 2160)),
26 newCapability(exec, "pointer", "Hover, drag, or click coordinates grounded in a current screenshot observation. Never reuse stale observations or retry unknown outcomes.", true, []string{"tabId", "operationId", "action", "documentToken"}, tabIDProp(), operationIDProp(), documentTokenProp(), enum("action", "Pointer operation.", "hover", "drag", "click"), refProp("Source element reference."), str("targetRef", "Drag destination reference."), str("observationToken", "Screenshot observation token required for coordinates."), integer("x", "CSS x coordinate."), integer("y", "CSS y coordinate.")),
27 newCapability(exec, "diagnostics", "Read bounded, redacted page errors and failed requests. Unavailable collection is distinct from zero errors.", false, []string{"tabId"}, tabIDProp(), integer("after", "Return entries after this sequence cursor."), enum("kind", "Optional event type.", "console", "exception", "network", "navigation")),
28 newCapability(exec, "record", "Record the existing task tab without audio. Query status, stop and finalize, or cancel. Only completed validated WebM is an artifact.", true, []string{"tabId", "action"}, tabIDProp(), enum("action", "Recording operation.", "start", "status", "stop", "cancel"), operationIDProp(), str("recordingId", "ID from start."), bounded(integer("durationSeconds", "Default 20, maximum 90."), 1, 90)),
29 }
30 }
31
32 type capabilityTool struct {
33 base
34 capability string
35 write bool
36 fields map[string]bool
37 }
38
39 func newCapability(exec Executor, name, description string, write bool, required []string, props ...property) tool.Tool {
40 fields := map[string]bool{}
41 for _, p := range props {
42 fields[p.name] = true
43 }
44 return capabilityTool{base: base{exec: exec, name: "browser_" + name, description: description, schema: objectSchema(required, props...), snip: listSnip}, capability: name, write: write, fields: fields}
45 }
46 func (t capabilityTool) ReadOnly() bool { return !t.write }
47 func (t capabilityTool) PlanModeSafe() bool { return !t.write }
48 func (t capabilityTool) Execute(ctx context.Context, args json.RawMessage) (string, error) {
49 if err := t.ready(ctx); err != nil {
50 return "", err
51 }
52 executor, ok := t.exec.(CapabilityExecutor)
53 if !ok {
54 return "", fmt.Errorf("capability_unsupported: attached browser does not support %s", t.Name())
55 }
56 var params map[string]json.RawMessage
57 if err := json.Unmarshal(args, &params); err != nil {
58 return "", err
59 }
60 for key := range params {
61 if !t.fields[key] {
62 return "", fmt.Errorf("unknown field %q", key)
63 }
64 }
65 var tabID, operationID, action string
66 _ = json.Unmarshal(params["tabId"], &tabID)
67 if err := requireTab(tabID); err != nil {
68 return "", err
69 }
70 _ = json.Unmarshal(params["operationId"], &operationID)
71 _ = json.Unmarshal(params["action"], &action)
72 if t.write && action != "get" && action != "status" && !operationIDRe.MatchString(operationID) {
73 return "", fmt.Errorf("a valid fresh operationId is required")
74 }
75 result, err := executor.BrowserCapability(ctx, t.capability, args)
76 if err != nil {
77 return "", translate(err, t.Name())
78 }
79 if !json.Valid(result) {
80 return "", fmt.Errorf("invalid browser capability response")
81 }
82 return strings.TrimSpace(string(result)), nil
83 }
84
84 lines GO