返回 DeepSeek-Reasonix
tool_dispatch.go
根目录 / internal / agent / tool_dispatch.go
1 package agent
2
3 import (
4 "context"
5 "errors"
6 "os"
7 "strings"
8 "time"
9
10 "reasonix/internal/evidence"
11 "reasonix/internal/tool"
12 )
13
14 func (a *Agent) dispatchResolvedTool(ctx context.Context, plan *toolCallPlan) (result string, images []string, execution *tool.ShellExecution, err error) {
15 result, images, execution, err = a.invokeResolvedTool(ctx, plan)
16 if err == nil || ctx.Err() != nil || !plan.readOnly || !isTransientToolError(err) {
17 return result, images, execution, err
18 }
19 if plan.effects.StateMutation {
20 return result, images, execution, err
21 }
22 retryResult, retryImages, retryExec, retryErr := a.invokeResolvedTool(ctx, plan)
23 if retryExec != nil {
24 execution = retryExec
25 }
26 return retryResult, retryImages, execution, retryErr
27 }
28
29 func (a *Agent) invokeResolvedTool(ctx context.Context, plan *toolCallPlan) (result string, images []string, execution *tool.ShellExecution, err error) {
30 runTool, runArgs := plan.runTool, plan.runArgs
31 if reader, ok := runTool.(tool.ReadExecutor); ok {
32 start := time.Now()
33 var env tool.ReadResultEnvelope
34 result, env, err = reader.ExecuteRead(ctx, runArgs)
35 plan.readActiveMillis += max(1, time.Since(start).Milliseconds())
36 if err == nil {
37 plan.readEnvelope = &env
38 }
39 return result, nil, nil, err
40 }
41 if de, ok := runTool.(tool.DetailedExecutor); ok {
42 var detailed tool.DetailedResult
43 detailed, err = de.ExecuteDetailed(ctx, runArgs)
44 result, images, execution = detailed.Output, detailed.Images, detailed.Execution
45 if execution != nil && plan.verification {
46 switch {
47 case err != nil || (execution.ExitCode != nil && *execution.ExitCode != 0):
48 execution.Verification = tool.ShellVerificationFailed
49 default:
50 execution.Verification = tool.ShellVerificationPassed
51 }
52 } else if execution != nil && execution.Verification == "" {
53 execution.Verification = tool.ShellVerificationNotVerification
54 }
55 if execution != nil && evidence.BashCommandMayBeOpaqueMutation(runArgs) &&
56 execution.MutationRisk == tool.ShellMutationMayHaveCompleted {
57 execution.MutationRisk = tool.ShellMutationUnknown
58 }
59 return result, images, execution, err
60 }
61 if it, ok := runTool.(tool.ImageTool); ok {
62 result, images, err = it.ExecuteWithImages(ctx, runArgs)
63 return result, images, execution, err
64 }
65 result, err = runTool.Execute(ctx, runArgs)
66 var missing *os.PathError
67 if errors.Is(err, os.ErrNotExist) && errors.As(err, &missing) {
68 err = &tool.OperationError{Diagnostic: tool.OperationDiagnostic{Code: tool.FSNotFound, Path: missing.Path, Recovery: "read the target at its current path, or create a new file when required"}, Cause: err}
69 }
70 return result, images, execution, err
71 }
72
73 func isTransientToolError(err error) bool {
74 if err == nil {
75 return false
76 }
77 var classified interface{ RetryableToolError() bool }
78 if errors.As(err, &classified) {
79 return classified.RetryableToolError()
80 }
81 if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
82 return false
83 }
84 msg := strings.ToLower(err.Error())
85 for _, token := range []string{
86 "execution may have completed", "execution result is unknown",
87 "after dispatch", "was not retried",
88 } {
89 if strings.Contains(msg, token) {
90 return false
91 }
92 }
93 for _, token := range []string{
94 "timeout", "temporar", "connection reset", "connection refused",
95 "broken pipe", "eof", "i/o timeout", "tls handshake", "unavailable",
96 } {
97 if strings.Contains(msg, token) {
98 return true
99 }
100 }
101 return false
102 }
103
103 lines GO