-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathsession_replay.cpp
More file actions
191 lines (169 loc) · 7.09 KB
/
Copy pathsession_replay.cpp
File metadata and controls
191 lines (169 loc) · 7.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// 见 session_replay.hpp 头注释。本文件实现 replay_session_messages 纯函数。
#include "session_replay.hpp"
#include "compact_checkpoint.hpp"
#include "compact_notice.hpp"
#include "session_rewind.hpp"
#include "tool_metadata_codec.hpp"
#include "tool_result_storage.hpp"
#include "output_attachments.hpp"
#include "turn_net_diff.hpp"
#include "turn_timing.hpp"
#include "../tool/ask_user_question_tool.hpp"
#include "../tool/tool_executor.hpp"
#include "../tui/compact_notice_row.hpp"
#include <nlohmann/json.hpp>
#include <deque>
namespace acecode {
namespace {
// 从 ChatMessage.tool_calls(数组形态)取第 i 项的 function.name / arguments。
// 失败(类型错或缺字段)时把对应输出参数留空 —— 调用方据此降级到 legacy 显示。
void extract_tool_call_function(
const nlohmann::json& tool_calls,
size_t i,
std::string& name,
std::string& args)
{
if (!tool_calls.is_array() || i >= tool_calls.size()) return;
const auto& tc = tool_calls[i];
if (!tc.is_object() || !tc.contains("function")) return;
const auto& fn = tc["function"];
if (!fn.is_object()) return;
if (fn.contains("name") && fn["name"].is_string()) {
name = fn["name"].get<std::string>();
}
if (fn.contains("arguments") && fn["arguments"].is_string()) {
args = fn["arguments"].get<std::string>();
}
}
} // namespace
std::vector<TuiState::Message> replay_session_messages(
const std::vector<ChatMessage>& messages,
const ToolExecutor& tools)
{
std::vector<TuiState::Message> out;
out.reserve(messages.size());
// 调用/结果交错:canonical 流是「assistant(tool_calls[N]) → tool×N」,
// 直接顺序回放会先挤 N 行调用再挤 N 行结果。这里把调用行先攒着,等
// 配对的 tool 结果到来时成对推出(FIFO,与运行时 dispatch 顺序一致);
// 没等到结果的孤儿调用(abort / 流中断)在遇到下一条非工具消息或流
// 结束时统一补推,渲染端配对逻辑会给它们灰色 Pending 指示灯。
std::deque<TuiState::Message> pending_calls;
auto flush_pending_calls = [&out, &pending_calls]() {
while (!pending_calls.empty()) {
out.push_back(std::move(pending_calls.front()));
pending_calls.pop_front();
}
};
for (const auto& msg : messages) {
if (is_file_checkpoint_message(msg)) {
continue;
}
if (is_content_replacement_message(msg)) {
continue;
}
if (is_turn_timing_message(msg)) {
continue;
}
if (is_turn_net_diff_message(msg)) {
continue;
}
if (is_compact_checkpoint_message(msg)) {
continue;
}
if (msg.metadata.is_object() &&
msg.metadata.value("hidden_goal_context", false)) {
continue;
}
if (decode_compact_notice(msg).has_value()) {
flush_pending_calls();
(void)tui::append_compact_notice_row(out, msg);
continue;
}
if (msg.role == "user" || msg.role == "system") {
// 规范角色,文本承载所有信息,直接推入。
flush_pending_calls();
out.push_back({msg.role, msg.content, /*is_tool=*/false});
continue;
}
if (msg.role == "assistant") {
// 文本前奏(若有)先 push,顺序与运行时 on_delta+on_message 累积一致。
flush_pending_calls();
if (!msg.content.empty()) {
out.push_back({"assistant", msg.content, /*is_tool=*/false});
}
// 每个 tool_call 单独成一行,先攒进 pending 等结果配对。
// display_override 用 build_tool_call_preview 现算,失败
// (非法 JSON)时返回空,TUI 会回退到 legacy 显示。
if (msg.tool_calls.is_array()) {
for (size_t i = 0; i < msg.tool_calls.size(); ++i) {
std::string name, args;
extract_tool_call_function(msg.tool_calls, i, name, args);
TuiState::Message tc_row;
tc_row.role = "tool_call";
tc_row.content = "[Tool: " + name + "] " + args;
tc_row.is_tool = true;
tc_row.display_override =
ToolExecutor::build_tool_call_preview(name, args);
pending_calls.push_back(std::move(tc_row));
}
}
continue;
}
if (msg.role == "tool") {
// 规范工具结果:role 改名为 tool_result,is_tool=true。
// 视觉字段(summary / hunks)从 metadata 子键还原;缺失或解码失败 →
// 字段留空,渲染走 fold 降级。
// 先推出配对的调用行(FIFO 队首),再推结果行 —— 成对相邻。
if (!pending_calls.empty()) {
out.push_back(std::move(pending_calls.front()));
pending_calls.pop_front();
}
TuiState::Message tr_row;
tr_row.role = "tool_result";
tr_row.content = msg.content;
std::string ask_display =
format_ask_user_question_result_display(msg.metadata);
if (!ask_display.empty()) {
tr_row.content = std::move(ask_display);
}
std::string attachment_fallback =
output_attachments_fallback_text(msg.content_parts);
if (!attachment_fallback.empty()) {
if (!tr_row.content.empty() && tr_row.content.back() != '\n') {
tr_row.content.push_back('\n');
}
tr_row.content += attachment_fallback;
}
tr_row.is_tool = true;
if (msg.metadata.is_object()) {
if (msg.metadata.contains("tool_summary")) {
auto s = decode_tool_summary(msg.metadata["tool_summary"]);
if (s.has_value()) {
tr_row.summary = std::move(*s);
}
}
if (msg.metadata.contains("tool_hunks")) {
auto h = decode_tool_hunks(msg.metadata["tool_hunks"]);
if (h.has_value()) {
tr_row.hunks = std::move(*h);
}
}
}
out.push_back(std::move(tr_row));
continue;
}
// 未知 role(forward-compat):原样推入,is_tool 默认 false。
// shell-mode 的 "tool_result" 伪角色如果没被 main.cpp 配对识别消化,
// 会落进这里 —— 也是正确的降级,TUI 渲染端有 "tool_result" 分支会接住。
flush_pending_calls();
TuiState::Message m;
m.role = msg.role;
m.content = msg.content;
m.is_tool = (msg.role == "tool_result"); // 让 shell-mode 落盘的伪角色保持 is_tool=true
out.push_back(std::move(m));
}
// 流结束:还没等到结果的孤儿调用统一补推(渲染成灰色 Pending)。
flush_pending_calls();
return out;
}
} // namespace acecode