返回 DeepSeek-Reasonix
tool_recovery.go
根目录 / internal / provider / tool_recovery.go
1 package provider
2
3 import (
4 "encoding/json"
5 "strings"
6 )
7
8 // ToolRunState is local execution evidence; it is never sent as a wire field.
9 // Unknown legacy results are classified conservatively when interrupted.
10 type ToolRunState string
11
12 const (
13 ToolRunPending ToolRunState = "pending"
14 ToolRunStarted ToolRunState = "started"
15 ToolRunRunning ToolRunState = "running"
16 ToolRunCompleted ToolRunState = "completed"
17 ToolRunFailed ToolRunState = "failed"
18 ToolRunCancelled ToolRunState = "cancelled"
19 ToolRunNotStarted ToolRunState = "not_started"
20 ToolRunUnknown ToolRunState = "unknown"
21 ToolRunUserConfirmed ToolRunState = "user_confirmed"
22 )
23
24 // ActionIdentity is the stable local identity of one logical tool action.
25 // It is provider-excluded and must not be inferred from an attempt alone.
26 type ActionIdentity struct {
27 SessionID string `json:"session_id,omitempty"`
28 TurnID string `json:"turn_id,omitempty"`
29 AttemptID string `json:"attempt_id,omitempty"`
30 CallID string `json:"call_id,omitempty"`
31 CanonicalTool string `json:"canonical_tool,omitempty"`
32 ArgumentDigest string `json:"argument_digest,omitempty"`
33 ResourceScope string `json:"resource_scope,omitempty"`
34 }
35
36 // ToolCallRecord is a durable, provider-excluded execution receipt.
37 type ToolCallRecord struct {
38 Identity ActionIdentity `json:"identity"`
39 Arguments json.RawMessage `json:"arguments,omitempty"`
40 State ToolRunState `json:"state"`
41 ReadOnly bool `json:"read_only"`
42 IdempotencyKey string `json:"idempotency_key,omitempty"`
43 StartedAt int64 `json:"started_at,omitempty"`
44 FinishedAt int64 `json:"finished_at,omitempty"`
45 ResultDigest string `json:"result_digest,omitempty"`
46 EffectSummary string `json:"effect_summary,omitempty"`
47 Resolution string `json:"resolution,omitempty"`
48 ResolvedAt int64 `json:"resolved_at,omitempty"`
49 ResolutionSource string `json:"resolution_source,omitempty"`
50 InspectionID string `json:"inspection_id,omitempty"`
51 InspectionState string `json:"inspection_state,omitempty"`
52 SupersededBy string `json:"superseded_by,omitempty"`
53 }
54
55 func ToolResultRunState(m Message) ToolRunState {
56 switch m.ToolRunState {
57 case ToolRunPending, ToolRunStarted, ToolRunRunning, ToolRunCompleted, ToolRunFailed, ToolRunCancelled, ToolRunNotStarted, ToolRunUnknown, ToolRunUserConfirmed:
58 return m.ToolRunState
59 case "":
60 default:
61 return ToolRunUnknown
62 }
63 text := strings.ToLower(strings.TrimSpace(m.Content))
64 if strings.Contains(text, "write outcome unknown:") || text == interruptedToolResult {
65 return ToolRunUnknown
66 }
67 if strings.HasPrefix(text, "cancelled: context cancelled before execution") || strings.HasPrefix(text, "cancelled: tool dispatch was not durable") {
68 return ToolRunNotStarted
69 }
70 if strings.HasPrefix(text, "cancelled:") || strings.Contains(text, "context canceled") || strings.Contains(text, "context cancelled") {
71 return ToolRunUnknown
72 }
73 return ToolRunCompleted
74 }
75
76 // IsInterruptedPlaceholder identifies the synthetic result inserted while a
77 // session is loaded. It is not execution evidence and must not override the
78 // ledger's durable start fact.
79 func IsInterruptedPlaceholder(m Message) bool {
80 return m.Role == RoleTool && m.ToolRunState == "" && strings.TrimSpace(m.Content) == interruptedToolResult
81 }
82
83 // RecordToolRecovery retains legacy interrupted names for older readers while
84 // new readers distinguish calls proven not to have run from uncertain effects.
85 func RecordToolRecovery(r *InterruptedTurnRecovery, call InterruptedToolSummary, state ToolRunState) {
86 if call.Name == "" {
87 return
88 }
89 switch state {
90 case ToolRunCompleted:
91 r.CompletedTools = append(r.CompletedTools, call)
92 case ToolRunFailed:
93 r.FailedTools = append(r.FailedTools, call)
94 case ToolRunUserConfirmed:
95 r.UserConfirmedTools = append(r.UserConfirmedTools, call)
96 case ToolRunNotStarted, ToolRunCancelled, ToolRunPending:
97 r.NotStartedTools = append(r.NotStartedTools, call)
98 r.InterruptedTools = append(r.InterruptedTools, call.Name)
99 default:
100 r.UnknownTools = append(r.UnknownTools, call)
101 r.InterruptedTools = append(r.InterruptedTools, call.Name)
102 }
103 }
104
105 // InterruptedTurnRecovery is the durable,
106 // provider-excluded handoff for an unfinished turn. It contains bounded facts;
107 // raw partial reasoning remains local for display.
108 type InterruptedTurnRecovery struct {
109 TurnID string `json:"turn_id,omitempty"`
110 AttemptID string `json:"attempt_id,omitempty"`
111 Cause string `json:"cause,omitempty"`
112 TerminalStatus string `json:"terminalStatus,omitempty"` // failed | interrupted; absent preserves legacy display
113 FailureDiagnostic *FailureDiagnostic `json:"failureDiagnostic,omitempty"`
114 WriteChecks []WriteRecoveryCheck `json:"write_checks,omitempty"`
115 SatisfiedWrites []InterruptedToolSummary `json:"satisfied_writes,omitempty"`
116 Pending bool `json:"pending,omitempty"`
117 CompletedTools []InterruptedToolSummary `json:"completed_tools,omitempty"`
118 FailedTools []InterruptedToolSummary `json:"failed_tools,omitempty"`
119 UserConfirmedTools []InterruptedToolSummary `json:"user_confirmed_tools,omitempty"`
120 InterruptedTools []string `json:"interrupted_tools,omitempty"`
121 NotStartedTools []InterruptedToolSummary `json:"not_started_tools,omitempty"`
122 UnknownTools []InterruptedToolSummary `json:"unknown_tools,omitempty"`
123 ToolCalls []ToolCallRecord `json:"tool_calls,omitempty"`
124 RequiresUserDecision bool `json:"requires_user_decision,omitempty"`
125 SilentInterruption bool `json:"silent_interruption,omitempty"`
126 DroppedPartialText bool `json:"dropped_partial_text,omitempty"`
127 DroppedPartialReasoning bool `json:"dropped_partial_reasoning,omitempty"`
128 }
129
130 // InterruptedToolSummary records a completed, fully paired tool call without duplicating arguments or results.
131 // The canonical assistant/tool messages immediately before the recovery record remain the source of truth.
132 type InterruptedToolSummary struct {
133 ID string `json:"id,omitempty"`
134 Name string `json:"name"`
135 Files []string `json:"files,omitempty"`
136 Added int `json:"added,omitempty"`
137 Removed int `json:"removed,omitempty"`
138 }
139
140 // WriteRecoveryCheck reports a current postcondition, not an execution receipt.
141 type WriteRecoveryCheck struct {
142 CallID string `json:"call_id"`
143 Path string `json:"path"`
144 State string `json:"state"`
145 }
146
146 lines GO