返回 DeepSeek-Reasonix
tool_call_parse.go
根目录 / internal / agent / tool_call_parse.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "reasonix/internal/evidence"
10 "reasonix/internal/tool"
11 )
12
13 // parseToolCall resolves the canonical tool and rejects ambiguity/unknown tools.
14 func (a *Agent) parseToolCall(ctx context.Context, turn *turnRuntime, plan *toolCallPlan) (toolOutcome, bool) {
15 if retiredTool(plan.call.Name) {
16 msg := fmt.Sprintf("tool_retired: %s is no longer part of the execution protocol; use file tools, todo_write, or provide the final answer directly", plan.call.Name)
17 return toolOutcome{output: msg, errMsg: "tool_retired"}, true
18 }
19 t, canonicalName, ambiguous := a.svc.tools.ResolveCall(plan.call.Name)
20 if len(ambiguous) > 0 {
21 msg := fmt.Sprintf("ambiguous MCP tool reference %q; use one of: %s", plan.call.Name, strings.Join(ambiguous, ", "))
22 return toolOutcome{
23 output: "error: " + msg,
24 errMsg: msg,
25 }, true
26 }
27 if t == nil {
28 if server, ok := completedMCPConnect(a.svc.tools, plan.call.Name); ok {
29 return toolOutcome{
30 output: fmt.Sprintf("MCP server %q is connected; its real tools are now available", server),
31 }, true
32 }
33 return toolOutcome{
34 output: fmt.Sprintf("error: unknown tool %q", plan.call.Name),
35 errMsg: fmt.Sprintf("unknown tool %q", plan.call.Name),
36 }, true
37 }
38 plan.tool = t
39 plan.canonicalName = canonicalName
40 plan.permName = canonicalName
41 plan.permArgs = json.RawMessage(plan.call.Arguments)
42 plan.execTool = t
43 plan.execArgs = json.RawMessage(plan.call.Arguments)
44 plan.evidenceName = canonicalName
45 plan.evidenceArgs = json.RawMessage(plan.call.Arguments)
46 plan.readOnly = t.ReadOnly()
47 if canonicalName == "read_file" {
48 if out, blocked := a.resolveReadCursor(plan); blocked {
49 return out, true
50 }
51 }
52 if tool.IsShellToolName(canonicalName) {
53 var permissionReader bool
54 plan.effects, permissionReader = evidence.ClassifyBashToolCall(plan.execArgs)
55 if permissionReader {
56 // Carry the resolved read-only classification without changing the schema.
57 plan.readOnly = true
58 plan.resolvedMeta = &tool.ResolvedCall{TargetName: canonicalName, ReadOnly: true}
59 }
60 } else {
61 plan.effects = evidence.ClassifyToolCall(plan.evidenceName, plan.evidenceArgs, plan.readOnly)
62 }
63 return toolOutcome{}, false
64 }
65
66 func retiredTool(name string) bool {
67 switch strings.ToLower(strings.TrimSpace(name)) {
68 case "complete_step", "review_report", "read_policy_receipt", "session_read_strategy_receipt":
69 return true
70 default:
71 return false
72 }
73 }
74
74 lines GO