返回 DeepSeek-Reasonix
interrupted_display.go
根目录 / internal / agent / interrupted_display.go
1 package agent
2
3 import (
4 "context"
5 "errors"
6 "reasonix/internal/event"
7 "reasonix/internal/provider"
8 "strings"
9 )
10
11 func (a *Agent) recordInterruptedDisplay(text, reasoning string, calls []provider.ToolCall, pending bool, terminalErr error, workDurationMs int64, messageIDs ...string) {
12 var messageID string
13 if len(messageIDs) > 0 {
14 messageID = messageIDs[0]
15 }
16 record := interruptedDisplayRecord(messageID, text, reasoning, calls, pending, terminalErr)
17 record.WorkDurationMs = workDurationMs
18 err := a.appendCommittedMessages(context.Background(), "interrupted-attempt", record)
19 if err != nil {
20 a.svc.sink.Emit(event.Event{Kind: event.Notice, Level: event.LevelWarn, Code: "transcript_save_failed", Text: "Interrupted output could not be saved; displayed output is retained."})
21 } else if messageID != "" {
22 a.emitStreamAttempt(messageID, event.StreamAttemptCommit, 0, "interrupted", nil)
23 }
24 }
25
26 // InterruptedStreamRecord is the record a cancelled stream leaves, built for a
27 // stream that is still open so a host can hold it durably until the stream
28 // either commits its message or never returns.
29 func InterruptedStreamRecord(messageID, text, reasoning string) provider.Message {
30 return interruptedDisplayRecord(messageID, text, reasoning, nil, true, nil)
31 }
32
33 func interruptedDisplayRecord(messageID, text, reasoning string, calls []provider.ToolCall, pending bool, terminalErr error) provider.Message {
34 displayCalls := make([]provider.ToolCall, 0, len(calls))
35 interrupted := make([]string, 0, len(calls))
36 notStarted := make([]provider.InterruptedToolSummary, 0, len(calls))
37 seen := make(map[string]struct{}, len(calls))
38 for _, call := range calls {
39 name := strings.TrimSpace(call.Name)
40 key := call.ID + "\x00" + name
41 if _, ok := seen[key]; ok {
42 continue
43 }
44 seen[key] = struct{}{}
45 displayCalls = append(displayCalls, provider.ToolCall{ID: call.ID, Name: name})
46 if name != "" {
47 interrupted = append(interrupted, name)
48 notStarted = append(notStarted, provider.InterruptedToolSummary{ID: call.ID, Name: name})
49 }
50 }
51 terminalStatus := "interrupted"
52 var failureDiagnostic *provider.FailureDiagnostic
53 if terminalErr != nil && !errors.Is(terminalErr, context.Canceled) {
54 terminalStatus = "failed"
55 failureDiagnostic = provider.DiagnoseFailure(terminalErr)
56 }
57 return provider.Message{
58 ID: messageID,
59 Role: provider.RoleTool,
60 Content: text,
61 ReasoningContent: reasoning,
62 ToolCalls: displayCalls,
63 ToolCallID: provider.LocalOnlyToolID,
64 Name: provider.LocalOnlyToolName,
65 LocalOnly: true,
66 InterruptedTurn: &provider.InterruptedTurnRecovery{
67 TerminalStatus: terminalStatus,
68 FailureDiagnostic: failureDiagnostic,
69 Pending: pending,
70 InterruptedTools: interrupted,
71 NotStartedTools: notStarted,
72 DroppedPartialText: strings.TrimSpace(text) != "",
73 DroppedPartialReasoning: strings.TrimSpace(reasoning) != "",
74 },
75 }
76 }
77
77 lines GO