返回 DeepSeek-Reasonix
history_presentation.go
根目录 / internal / transcript / history_presentation.go
1 package transcript
2
3 import (
4 "encoding/json"
5 "fmt"
6 "regexp"
7 "strings"
8
9 "reasonix/internal/event"
10 "reasonix/internal/evidence"
11 "reasonix/internal/i18n"
12 "reasonix/internal/provider"
13 )
14
15 var pastedLabel = regexp.MustCompile(`^\[(?:已粘贴文本|已貼上文字|Pasted text) #[0-9]+ · [0-9]+ (?:行|lines)\]$`)
16
17 // CollapseExpandedPaste keeps the display card while SubmitText retains the
18 // original expanded payload used to reconstruct an edited prompt.
19 func CollapseExpandedPaste(content string) string {
20 const prefix = "--- Begin "
21 for scan := 0; scan < len(content); {
22 begin := strings.Index(content[scan:], prefix)
23 if begin < 0 {
24 break
25 }
26 begin += scan
27 start := begin + len(prefix)
28 end := strings.Index(content[start:], " ---")
29 if end < 0 {
30 break
31 }
32 end += start
33 label := content[start:end]
34 body := end + len(" ---")
35 scan = body
36 if !pastedLabel.MatchString(label) {
37 continue
38 }
39 marker := "--- End " + label + " ---"
40 last := strings.Index(content[body:], marker)
41 if last < 0 {
42 continue
43 }
44 copy := strings.LastIndex(content[:begin], label)
45 if copy < 0 || strings.TrimSpace(content[copy+len(label):begin]) != "" {
46 continue
47 }
48 content = content[:copy+len(label)] + content[body+last+len(marker):]
49 scan = copy + len(label)
50 }
51 return strings.TrimSpace(content)
52 }
53
54 func interruptedNotice(recovery *provider.InterruptedTurnRecovery) Message {
55 if recovery != nil && recovery.TerminalStatus == "failed" {
56 diagnostic := recovery.FailureDiagnostic
57 text := "The provider request failed. Check the connection settings and try again."
58 if diagnostic != nil {
59 if status := i18n.M.ProviderStatusMessage(diagnostic.Status); status != "" {
60 text = status
61 } else if diagnostic.Status > 0 {
62 text = fmt.Sprintf("Provider request failed (HTTP %d).", diagnostic.Status)
63 }
64 if label := provider.ProviderDisplayLabel(diagnostic.ProviderID, diagnostic.ProviderDisplayName, diagnostic.Protocol); label != "" {
65 text = label + ": " + text
66 }
67 }
68 return Message{Role: "notice", Level: "warn", Code: event.NoticeCodeProviderRequestFailed, Content: text, Detail: provider.FailureDiagnosticDetail(diagnostic), Diagnostic: diagnostic}
69 }
70 return Message{Role: "notice", Level: "info", Code: event.NoticeCodeCancelledTurn,
71 Content: "This turn was interrupted. Partial output is kept for reference; only completed tool pairs and a bounded recovery summary enter the next model turn. Inspect the workspace before continuing or reverting changes."}
72 }
73
74 func toolFailed(content string) bool {
75 content = strings.TrimSpace(content)
76 return strings.HasPrefix(content, "error:") || strings.HasPrefix(content, "blocked:") || strings.HasPrefix(content, "Error:") || strings.HasPrefix(content, "[error")
77 }
78
79 func completedTodoArguments(messages []provider.Message) map[string]string {
80 successful := make(map[string]bool)
81 outputs := make(map[string]string)
82 for _, message := range messages {
83 if message.Role == provider.RoleTool && message.ToolCallID != "" && !toolFailed(message.Content) {
84 successful[message.ToolCallID] = true
85 outputs[message.ToolCallID] = message.Content
86 }
87 }
88 out := make(map[string]string)
89 var todos []evidence.TodoItem
90 var latest string
91 for _, message := range messages {
92 for _, call := range message.ToolCalls {
93 if call.ID == "" || !successful[call.ID] {
94 continue
95 }
96 switch call.Name {
97 case "todo_write":
98 receipt := evidence.ReceiptFromToolCall(call.Name, json.RawMessage(call.Arguments), true, true)
99 if len(receipt.Todos) == 0 {
100 continue
101 }
102 todos, latest = evidence.ReplayTodoList(receipt.Todos, outputs[call.ID]), call.ID
103 case "complete_step":
104 if latest == "" || len(todos) == 0 {
105 continue
106 }
107 receipt := evidence.ReceiptFromToolCall(call.Name, json.RawMessage(call.Arguments), true, true)
108 match, ok := evidence.MatchStep(receipt.Step, todos)
109 if !ok || !evidence.ReplayTodoCompletion(todos, match.Index-1, outputs[call.ID]) {
110 continue
111 }
112 default:
113 continue
114 }
115 if encoded, err := json.Marshal(map[string]any{"todos": todos}); err == nil {
116 out[latest] = string(encoded)
117 }
118 }
119 }
120 return out
121 }
122
122 lines GO