@downcity/agent 1.1.217 → 1.1.224

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/bin/agent/local/Agent.d.ts +1 -34
  2. package/bin/agent/local/Agent.d.ts.map +1 -1
  3. package/bin/agent/local/Agent.js +1 -44
  4. package/bin/agent/local/Agent.js.map +1 -1
  5. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
  6. package/bin/agent/local/services/AgentAssemblyService.js +35 -2
  7. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
  8. package/bin/agent/local/services/AgentSessions.d.ts +6 -0
  9. package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
  10. package/bin/agent/local/services/AgentSessions.js +43 -4
  11. package/bin/agent/local/services/AgentSessions.js.map +1 -1
  12. package/bin/agent/remote/RemoteAgent.d.ts +1 -35
  13. package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
  14. package/bin/agent/remote/RemoteAgent.js +1 -37
  15. package/bin/agent/remote/RemoteAgent.js.map +1 -1
  16. package/bin/agent/remote/RemoteSession.d.ts +26 -41
  17. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  18. package/bin/agent/remote/RemoteSession.js +75 -107
  19. package/bin/agent/remote/RemoteSession.js.map +1 -1
  20. package/bin/agent/remote/RemoteTransport.d.ts +15 -27
  21. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  22. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +10 -21
  23. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  24. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +24 -46
  25. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  26. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +10 -20
  27. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  28. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +10 -16
  29. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  30. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +2 -2
  31. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  32. package/bin/executor/types/SessionHistoryPaths.d.ts +1 -1
  33. package/bin/executor/types/SessionRecords.d.ts +2 -0
  34. package/bin/executor/types/SessionRecords.d.ts.map +1 -1
  35. package/bin/executor/types/SessionRecords.js.map +1 -1
  36. package/bin/index.d.ts +6 -3
  37. package/bin/index.d.ts.map +1 -1
  38. package/bin/index.js +1 -0
  39. package/bin/index.js.map +1 -1
  40. package/bin/rpc/Client.d.ts +12 -34
  41. package/bin/rpc/Client.d.ts.map +1 -1
  42. package/bin/rpc/Client.js +31 -70
  43. package/bin/rpc/Client.js.map +1 -1
  44. package/bin/session/Session.d.ts +22 -4
  45. package/bin/session/Session.d.ts.map +1 -1
  46. package/bin/session/Session.js +95 -21
  47. package/bin/session/Session.js.map +1 -1
  48. package/bin/session/browse/Browse.d.ts.map +1 -1
  49. package/bin/session/browse/Browse.js +49 -18
  50. package/bin/session/browse/Browse.js.map +1 -1
  51. package/bin/session/recorder/JsonlSessionMessageStore.d.ts +51 -0
  52. package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -0
  53. package/bin/session/recorder/JsonlSessionMessageStore.js +231 -0
  54. package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -0
  55. package/bin/session/recorder/SessionMessageCodec.d.ts +20 -0
  56. package/bin/session/recorder/SessionMessageCodec.d.ts.map +1 -0
  57. package/bin/session/recorder/SessionMessageCodec.js +257 -0
  58. package/bin/session/recorder/SessionMessageCodec.js.map +1 -0
  59. package/bin/session/recorder/SessionRecorder.d.ts +186 -0
  60. package/bin/session/recorder/SessionRecorder.d.ts.map +1 -0
  61. package/bin/session/recorder/SessionRecorder.js +655 -0
  62. package/bin/session/recorder/SessionRecorder.js.map +1 -0
  63. package/bin/session/recorder/SessionRecorderCompaction.d.ts +18 -0
  64. package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -0
  65. package/bin/session/recorder/SessionRecorderCompaction.js +142 -0
  66. package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -0
  67. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +66 -0
  68. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -0
  69. package/bin/session/recorder/SessionRecorderHistoryStore.js +195 -0
  70. package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -0
  71. package/bin/session/runtime/SessionEventHub.d.ts +12 -5
  72. package/bin/session/runtime/SessionEventHub.d.ts.map +1 -1
  73. package/bin/session/runtime/SessionEventHub.js +11 -6
  74. package/bin/session/runtime/SessionEventHub.js.map +1 -1
  75. package/bin/session/runtime/SessionPromptRuntime.d.ts +9 -15
  76. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  77. package/bin/session/runtime/SessionPromptRuntime.js +48 -30
  78. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  79. package/bin/session/services/SessionStateService.d.ts +9 -5
  80. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  81. package/bin/session/services/SessionStateService.js +138 -74
  82. package/bin/session/services/SessionStateService.js.map +1 -1
  83. package/bin/session/services/SessionTurnService.d.ts +6 -7
  84. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  85. package/bin/session/services/SessionTurnService.js +84 -60
  86. package/bin/session/services/SessionTurnService.js.map +1 -1
  87. package/bin/session/services/SessionViewService.d.ts +8 -3
  88. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  89. package/bin/session/services/SessionViewService.js +10 -5
  90. package/bin/session/services/SessionViewService.js.map +1 -1
  91. package/bin/session/storage/Paths.d.ts +1 -1
  92. package/bin/session/storage/Paths.d.ts.map +1 -1
  93. package/bin/session/storage/Paths.js +2 -2
  94. package/bin/session/storage/Paths.js.map +1 -1
  95. package/bin/session/storage/Persistence.d.ts +6 -0
  96. package/bin/session/storage/Persistence.d.ts.map +1 -1
  97. package/bin/session/storage/Persistence.js +7 -0
  98. package/bin/session/storage/Persistence.js.map +1 -1
  99. package/bin/session/storage/RuntimeSessionPort.d.ts +3 -3
  100. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  101. package/bin/session/storage/RuntimeSessionPort.js +2 -2
  102. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  103. package/bin/types/agent/SessionActor.d.ts +15 -5
  104. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  105. package/bin/types/rpc/RpcProtocol.d.ts +48 -61
  106. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  107. package/bin/types/runtime/agent/AgentContext.d.ts +5 -4
  108. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  109. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  110. package/bin/types/session/SessionApproval.d.ts +97 -0
  111. package/bin/types/session/SessionApproval.d.ts.map +1 -0
  112. package/bin/types/session/SessionApproval.js +7 -0
  113. package/bin/types/session/SessionApproval.js.map +1 -0
  114. package/bin/types/session/SessionMessage.d.ts +223 -0
  115. package/bin/types/session/SessionMessage.d.ts.map +1 -0
  116. package/bin/types/session/SessionMessage.js +8 -0
  117. package/bin/types/session/SessionMessage.js.map +1 -0
  118. package/bin/types/session/SessionMutation.d.ts +116 -0
  119. package/bin/types/session/SessionMutation.d.ts.map +1 -0
  120. package/bin/types/session/SessionMutation.js +19 -0
  121. package/bin/types/session/SessionMutation.js.map +1 -0
  122. package/bin/types/session/SessionOptions.d.ts +9 -0
  123. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  124. package/package.json +3 -3
  125. package/scripts/agent-env.test.mjs +4 -4
  126. package/scripts/remote-session-reconnect.test.mjs +7 -3
  127. package/scripts/session-action-message.test.mjs +98 -27
  128. package/scripts/session-history-inflight.test.mjs +2 -2
  129. package/scripts/session-list-title.test.mjs +6 -19
  130. package/scripts/session-prompt-runtime.test.mjs +7 -19
  131. package/scripts/session-recorder.test.mjs +199 -0
  132. package/scripts/session-title-event.test.mjs +10 -18
  133. package/src/agent/local/Agent.ts +1 -55
  134. package/src/agent/local/services/AgentAssemblyService.ts +39 -3
  135. package/src/agent/local/services/AgentSessions.ts +48 -3
  136. package/src/agent/remote/RemoteAgent.ts +1 -54
  137. package/src/agent/remote/RemoteSession.ts +106 -136
  138. package/src/agent/remote/RemoteTransport.ts +30 -30
  139. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +52 -77
  140. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +30 -34
  141. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +2 -2
  142. package/src/executor/types/SessionHistoryPaths.ts +1 -1
  143. package/src/executor/types/SessionRecords.ts +2 -0
  144. package/src/index.ts +36 -10
  145. package/src/rpc/Client.ts +58 -95
  146. package/src/session/Session.ts +128 -37
  147. package/src/session/browse/Browse.ts +51 -22
  148. package/src/session/recorder/JsonlSessionMessageStore.ts +246 -0
  149. package/src/session/recorder/SessionMessageCodec.ts +284 -0
  150. package/src/session/recorder/SessionRecorder.ts +871 -0
  151. package/src/session/recorder/SessionRecorderCompaction.ts +188 -0
  152. package/src/session/recorder/SessionRecorderHistoryStore.ts +276 -0
  153. package/src/session/runtime/SessionEventHub.ts +25 -12
  154. package/src/session/runtime/SessionPromptRuntime.ts +60 -43
  155. package/src/session/services/SessionStateService.ts +159 -86
  156. package/src/session/services/SessionTurnService.ts +91 -73
  157. package/src/session/services/SessionViewService.ts +25 -13
  158. package/src/session/storage/Paths.ts +2 -2
  159. package/src/session/storage/Persistence.ts +13 -0
  160. package/src/session/storage/RuntimeSessionPort.ts +8 -9
  161. package/src/types/agent/SessionActor.ts +31 -8
  162. package/src/types/rpc/RpcProtocol.ts +44 -67
  163. package/src/types/runtime/agent/AgentContext.ts +9 -7
  164. package/src/types/session/SessionApproval.ts +108 -0
  165. package/src/types/session/SessionMessage.ts +253 -0
  166. package/src/types/session/SessionMutation.ts +141 -0
  167. package/src/types/session/SessionOptions.ts +25 -0
  168. package/tsconfig.tsbuildinfo +1 -1
  169. package/bin/session/SessionEventMapper.d.ts +0 -32
  170. package/bin/session/SessionEventMapper.d.ts.map +0 -1
  171. package/bin/session/SessionEventMapper.js +0 -131
  172. package/bin/session/SessionEventMapper.js.map +0 -1
  173. package/bin/types/sdk/AgentSessionEvent.d.ts +0 -302
  174. package/bin/types/sdk/AgentSessionEvent.d.ts.map +0 -1
  175. package/bin/types/sdk/AgentSessionEvent.js +0 -9
  176. package/bin/types/sdk/AgentSessionEvent.js.map +0 -1
  177. package/bin/types/sdk/InternalUiChunkEvent.d.ts +0 -100
  178. package/bin/types/sdk/InternalUiChunkEvent.d.ts.map +0 -1
  179. package/bin/types/sdk/InternalUiChunkEvent.js +0 -9
  180. package/bin/types/sdk/InternalUiChunkEvent.js.map +0 -1
  181. package/scripts/session-history-archive.test.mjs +0 -188
  182. package/src/session/SessionEventMapper.ts +0 -148
  183. package/src/types/sdk/AgentSessionEvent.ts +0 -370
  184. package/src/types/sdk/InternalUiChunkEvent.ts +0 -108
@@ -0,0 +1,655 @@
1
+ /**
2
+ * SessionRecorder:Session Message 的唯一持久化与发布入口。
3
+ *
4
+ * 完整 Message 与 Assistant 草稿先持久化,成功后再发布实时 Mutation。
5
+ */
6
+ import { generateId } from "../../utils/Id.js";
7
+ /** 唯一 Session Message 写入服务。 */
8
+ export class SessionRecorder {
9
+ session_id;
10
+ store;
11
+ publish;
12
+ messages_by_id = new Map();
13
+ initialized = false;
14
+ constructor(options) {
15
+ this.session_id = String(options.session_id || "").trim();
16
+ this.store = options.store;
17
+ this.publish = options.publish;
18
+ if (!this.session_id)
19
+ throw new Error("SessionRecorder requires session_id");
20
+ }
21
+ /** 恢复已有 Message,并收口进程中断遗留的运行状态。 */
22
+ async initialize() {
23
+ if (this.initialized)
24
+ return;
25
+ await this.store.initialize();
26
+ for (const message of await this.store.list_messages()) {
27
+ this.messages_by_id.set(message.message_id, message);
28
+ }
29
+ this.initialized = true;
30
+ const unfinished = [...this.messages_by_id.values()];
31
+ for (const message of unfinished) {
32
+ if (message.type === "assistant" && message.status === "streaming") {
33
+ await this.complete_assistant_message(message.message_id, "stopped");
34
+ }
35
+ if (message.type === "action" && message.status === "running") {
36
+ await this.update_action_message(message.message_id, "failed", {
37
+ description: message.description || "Action interrupted before completion.",
38
+ });
39
+ }
40
+ }
41
+ }
42
+ /** 同步读取当前内存 Message。 */
43
+ get_message(message_id) {
44
+ return this.messages_by_id.get(message_id);
45
+ }
46
+ /** 追加普通 prompt 或 steering User Message。 */
47
+ async append_user_message(input) {
48
+ const message = await this.create_message((sequence, created_at) => ({
49
+ message_id: String(input.message_id || "").trim() ||
50
+ `user:${this.session_id}:${generateId()}`,
51
+ session_id: this.session_id,
52
+ turn_id: input.turn_id,
53
+ sequence,
54
+ revision: 1,
55
+ visibility: input.visibility || "visible",
56
+ created_at,
57
+ updated_at: created_at,
58
+ type: "user",
59
+ input_type: input.input_type,
60
+ parts: structuredClone(input.parts),
61
+ }));
62
+ return message;
63
+ }
64
+ /** 创建可持续接收 chunk 的 Assistant segment。 */
65
+ async open_assistant_message(input) {
66
+ const message = (await this.create_message((sequence, created_at) => ({
67
+ message_id: String(input.message_id || "").trim() ||
68
+ `assistant:${this.session_id}:${generateId()}`,
69
+ session_id: this.session_id,
70
+ turn_id: input.turn_id,
71
+ sequence,
72
+ revision: 1,
73
+ visibility: input.visibility || "visible",
74
+ created_at,
75
+ updated_at: created_at,
76
+ type: "assistant",
77
+ kind: input.kind || "normal",
78
+ segment_index: input.segment_index,
79
+ status: "streaming",
80
+ parts: [],
81
+ ...(input.summary_through_message_id
82
+ ? { summary_through_message_id: input.summary_through_message_id }
83
+ : {}),
84
+ }), true));
85
+ return new SessionAssistantMessageWriter(this, message.message_id);
86
+ }
87
+ /** 直接写入一条已完成 Assistant Message。 */
88
+ async append_completed_assistant_message(input) {
89
+ const turn_id = input.turn_id || `external:${this.session_id}:${generateId()}`;
90
+ const writer = await this.open_assistant_message({
91
+ turn_id,
92
+ segment_index: 1,
93
+ kind: input.kind || "normal",
94
+ visibility: input.visibility || "visible",
95
+ ...(input.summary_through_message_id
96
+ ? { summary_through_message_id: input.summary_through_message_id }
97
+ : {}),
98
+ });
99
+ for (const part of input.parts)
100
+ await writer.upsert_part(part);
101
+ await writer.complete();
102
+ return this.get_message(writer.message_id);
103
+ }
104
+ /** 创建 running Action Message。 */
105
+ async open_action_message(input) {
106
+ const message = (await this.create_message((sequence, created_at) => ({
107
+ message_id: String(input.message_id || "").trim() ||
108
+ `action:${this.session_id}:${generateId()}`,
109
+ session_id: this.session_id,
110
+ ...(input.turn_id ? { turn_id: input.turn_id } : {}),
111
+ sequence,
112
+ revision: 1,
113
+ visibility: "visible",
114
+ created_at,
115
+ updated_at: created_at,
116
+ type: "action",
117
+ action_type: input.action_type,
118
+ status: "running",
119
+ title: input.title,
120
+ ...(input.description ? { description: input.description } : {}),
121
+ ...(input.data ? { data: structuredClone(input.data) } : {}),
122
+ })));
123
+ return new SessionActionMessageWriter(this, message.message_id);
124
+ }
125
+ /** 更新 Action 状态,同时保持 message_id 与 sequence 不变。 */
126
+ async update_action_message(message_id, status, changes) {
127
+ const message = await this.store.append_message((state) => {
128
+ const current = require_message(state.messages, message_id, "action");
129
+ const created_at = Date.now();
130
+ return {
131
+ ...current,
132
+ status,
133
+ ...(changes?.title ? { title: changes.title } : {}),
134
+ ...(changes?.description !== undefined
135
+ ? { description: changes.description }
136
+ : {}),
137
+ ...(changes?.data ? { data: structuredClone(changes.data) } : {}),
138
+ revision: current.revision + 1,
139
+ updated_at: created_at,
140
+ };
141
+ });
142
+ this.accept_message(message);
143
+ return message;
144
+ }
145
+ /** 创建用户可见 Error Message。 */
146
+ async append_error_message(input) {
147
+ return (await this.create_message((sequence, created_at) => ({
148
+ message_id: `error:${this.session_id}:${generateId()}`,
149
+ session_id: this.session_id,
150
+ ...(input.turn_id ? { turn_id: input.turn_id } : {}),
151
+ sequence,
152
+ revision: 1,
153
+ visibility: "visible",
154
+ created_at,
155
+ updated_at: created_at,
156
+ type: "error",
157
+ scope: input.scope,
158
+ code: input.code,
159
+ message: input.message,
160
+ recoverable: input.recoverable,
161
+ })));
162
+ }
163
+ /** 读取折叠后的 Message snapshot。 */
164
+ async list_messages(input) {
165
+ await this.ensure_initialized();
166
+ const include_internal = input?.include_internal === true;
167
+ const through_sequence = Number.isFinite(input?.through_sequence)
168
+ ? Number(input?.through_sequence)
169
+ : Number.POSITIVE_INFINITY;
170
+ const before_sequence = Number.isFinite(input?.before_sequence)
171
+ ? Number(input?.before_sequence)
172
+ : Number.POSITIVE_INFINITY;
173
+ const all = [...this.messages_by_id.values()]
174
+ .filter((message) => (include_internal || message.visibility === "visible") &&
175
+ message.sequence <= through_sequence &&
176
+ message.sequence < before_sequence)
177
+ .sort((left, right) => left.sequence - right.sequence);
178
+ const offset = decode_cursor(input?.cursor);
179
+ const limit = normalize_limit(input?.limit, 100, 500);
180
+ const items = all.slice(offset, offset + limit).map((item) => structuredClone(item));
181
+ const next_offset = offset + items.length;
182
+ return {
183
+ items,
184
+ total: all.length,
185
+ ...(next_offset < all.length ? { next_cursor: encode_cursor(next_offset) } : {}),
186
+ has_more: next_offset < all.length,
187
+ };
188
+ }
189
+ /** 向当前 Session 导入 fork 来源 Message,并重新分配全部身份和顺序。 */
190
+ async import_messages(messages) {
191
+ const turn_ids = new Map();
192
+ const message_ids = new Map();
193
+ for (const source of [...messages].sort((a, b) => a.sequence - b.sequence)) {
194
+ const turn_id = source.turn_id
195
+ ? resolve_import_id(turn_ids, source.turn_id, "turn")
196
+ : undefined;
197
+ const message_id = resolve_import_id(message_ids, source.message_id, source.type);
198
+ await this.create_message((sequence, created_at) => ({
199
+ ...structuredClone(source),
200
+ message_id,
201
+ session_id: this.session_id,
202
+ ...(turn_id ? { turn_id } : {}),
203
+ sequence,
204
+ revision: 1,
205
+ created_at,
206
+ updated_at: created_at,
207
+ origin: {
208
+ session_id: source.session_id,
209
+ message_id: source.message_id,
210
+ ...(source.turn_id ? { turn_id: source.turn_id } : {}),
211
+ },
212
+ }));
213
+ }
214
+ }
215
+ /** @internal 写入 Assistant 原始文本 delta。 */
216
+ async append_assistant_delta(message_id, part_id, type, delta) {
217
+ if (!delta)
218
+ return;
219
+ const current = require_message([...this.messages_by_id.values()], message_id, "assistant");
220
+ require_streaming_assistant(current);
221
+ const part = current.parts.find((item) => item.part_id === part_id);
222
+ if (!part || (part.type !== "text" && part.type !== "reasoning")) {
223
+ throw new Error(`Delta target Part does not exist: ${part_id}`);
224
+ }
225
+ if (part.type !== type)
226
+ throw new Error(`Delta type changed for Part: ${part_id}`);
227
+ const created_at = Date.now();
228
+ const message = {
229
+ ...current,
230
+ revision: current.revision + 1,
231
+ updated_at: created_at,
232
+ parts: current.parts.map((item) => item.part_id === part_id && (item.type === "text" || item.type === "reasoning")
233
+ ? { ...item, text: item.text + delta }
234
+ : item),
235
+ };
236
+ await this.store.write_assistant_message(message);
237
+ this.accept_mutation({
238
+ mutation_id: generateId(),
239
+ variant: "delta",
240
+ type,
241
+ message_id,
242
+ revision: message.revision,
243
+ session_id: this.session_id,
244
+ turn_id: message.turn_id,
245
+ created_at,
246
+ part_id,
247
+ delta,
248
+ }, message);
249
+ }
250
+ /** @internal 写入 Assistant 完整 part。 */
251
+ async update_assistant_part(message_id, part) {
252
+ const current = require_message([...this.messages_by_id.values()], message_id, "assistant");
253
+ require_streaming_assistant(current);
254
+ const existing = current.parts.find((item) => item.part_id === part.part_id);
255
+ if (existing && existing.sequence !== part.sequence) {
256
+ throw new Error(`Assistant Part sequence changed: ${part.part_id}`);
257
+ }
258
+ const created_at = Date.now();
259
+ const next_part = structuredClone(part);
260
+ const message = {
261
+ ...current,
262
+ revision: current.revision + 1,
263
+ updated_at: created_at,
264
+ parts: (existing
265
+ ? current.parts.map((item) => item.part_id === part.part_id ? next_part : item)
266
+ : [...current.parts, next_part]).sort((left, right) => left.sequence - right.sequence),
267
+ };
268
+ await this.store.write_assistant_message(message);
269
+ this.accept_mutation({
270
+ mutation_id: generateId(),
271
+ variant: "part",
272
+ type: next_part.type,
273
+ message_id,
274
+ revision: message.revision,
275
+ session_id: this.session_id,
276
+ turn_id: message.turn_id,
277
+ created_at,
278
+ part_id: next_part.part_id,
279
+ part: next_part,
280
+ }, message);
281
+ }
282
+ /** @internal 收口 Assistant Message。 */
283
+ async complete_assistant_message(message_id, status) {
284
+ const current = require_message([...this.messages_by_id.values()], message_id, "assistant");
285
+ require_streaming_assistant(current);
286
+ const created_at = Date.now();
287
+ const message = {
288
+ ...current,
289
+ revision: current.revision + 1,
290
+ status,
291
+ updated_at: created_at,
292
+ parts: current.parts.map((part) => part.type === "text" || part.type === "reasoning"
293
+ ? { ...part, state: "done" }
294
+ : part),
295
+ };
296
+ await this.store.finalize_assistant_message(message);
297
+ this.accept_message(message);
298
+ }
299
+ /** @internal 将 Shell Runtime 审批请求更新到当前 Tool Part。 */
300
+ async require_tool_approval(input) {
301
+ const tool = this.find_streaming_tool(input.tool_call_id);
302
+ await this.update_assistant_part(tool.message_id, {
303
+ ...tool.part,
304
+ state: "approval-required",
305
+ approval_id: input.approval_id,
306
+ });
307
+ }
308
+ /** @internal 将审批结果更新到当前 Tool Part。 */
309
+ async resolve_tool_approval(input) {
310
+ const tool = this.find_streaming_tool(input.tool_call_id);
311
+ if (tool.part.approval_id && tool.part.approval_id !== input.approval_id) {
312
+ throw new Error(`Tool approval identity mismatch: ${input.approval_id}`);
313
+ }
314
+ await this.update_assistant_part(tool.message_id, {
315
+ ...tool.part,
316
+ state: input.decision === "approved" ? "running" : "failed",
317
+ ...(input.decision === "approved"
318
+ ? {}
319
+ : { error: input.decision === "expired" ? "Approval expired" : "Approval denied" }),
320
+ });
321
+ }
322
+ async create_message(factory, draft = false) {
323
+ if (draft) {
324
+ const message = await this.store.create_assistant_message((state) => {
325
+ const candidate = factory(state.message_sequence, Date.now());
326
+ if (candidate.type !== "assistant" || candidate.status !== "streaming") {
327
+ throw new Error("Draft Message must be a streaming Assistant");
328
+ }
329
+ return candidate;
330
+ });
331
+ this.accept_message(message);
332
+ return message;
333
+ }
334
+ const message = await this.store.append_message((state) => factory(state.message_sequence, Date.now()));
335
+ this.accept_message(message);
336
+ return message;
337
+ }
338
+ find_streaming_tool(tool_call_id) {
339
+ for (const message of this.messages_by_id.values()) {
340
+ if (message.type !== "assistant" || message.status !== "streaming")
341
+ continue;
342
+ const part = message.parts.find((item) => item.type === "tool" && item.tool_call_id === tool_call_id);
343
+ if (part)
344
+ return { message_id: message.message_id, part };
345
+ }
346
+ throw new Error(`Streaming Tool Part not found: ${tool_call_id}`);
347
+ }
348
+ build_message_mutation(message) {
349
+ return {
350
+ mutation_id: generateId(),
351
+ variant: "message",
352
+ type: message.type,
353
+ message_id: message.message_id,
354
+ sequence: message.sequence,
355
+ revision: message.revision,
356
+ session_id: this.session_id,
357
+ ...(message.turn_id ? { turn_id: message.turn_id } : {}),
358
+ created_at: message.updated_at,
359
+ message,
360
+ };
361
+ }
362
+ accept_message(message) {
363
+ this.accept_mutation(this.build_message_mutation(message), message);
364
+ }
365
+ accept_mutation(mutation, message) {
366
+ this.messages_by_id.set(message.message_id, structuredClone(message));
367
+ this.publish(mutation);
368
+ }
369
+ async ensure_initialized() {
370
+ if (!this.initialized)
371
+ await this.initialize();
372
+ }
373
+ }
374
+ /** 单个 Assistant segment 的流式 writer。 */
375
+ export class SessionAssistantMessageWriter {
376
+ message_id;
377
+ recorder;
378
+ closed = false;
379
+ constructor(recorder, message_id) {
380
+ this.recorder = recorder;
381
+ this.message_id = message_id;
382
+ }
383
+ /** 应用一个原始 AI SDK UI chunk。 */
384
+ async apply_chunk(chunk) {
385
+ if (this.closed)
386
+ throw new Error("Assistant Message writer is closed");
387
+ const current = this.current_message();
388
+ switch (chunk.type) {
389
+ case "text-start":
390
+ case "reasoning-start":
391
+ await this.upsert_part({
392
+ part_id: `${chunk.type === "text-start" ? "text" : "reasoning"}:${chunk.id}`,
393
+ sequence: this.next_part_sequence(),
394
+ type: chunk.type === "text-start" ? "text" : "reasoning",
395
+ text: "",
396
+ state: "streaming",
397
+ });
398
+ return;
399
+ case "text-delta":
400
+ case "reasoning-delta":
401
+ await this.recorder.append_assistant_delta(this.message_id, `${chunk.type === "text-delta" ? "text" : "reasoning"}:${chunk.id}`, chunk.type === "text-delta" ? "text" : "reasoning", chunk.delta);
402
+ return;
403
+ case "text-end":
404
+ case "reasoning-end": {
405
+ const part_id = `${chunk.type === "text-end" ? "text" : "reasoning"}:${chunk.id}`;
406
+ const part = current.parts.find((item) => item.part_id === part_id);
407
+ if (part?.type === "text" || part?.type === "reasoning") {
408
+ await this.upsert_part({ ...part, state: "done" });
409
+ }
410
+ return;
411
+ }
412
+ case "tool-input-start":
413
+ await this.upsert_tool(chunk.toolCallId, {
414
+ tool_name: chunk.toolName,
415
+ state: "input-streaming",
416
+ input_text: "",
417
+ });
418
+ return;
419
+ case "tool-input-delta": {
420
+ const tool = this.find_tool(chunk.toolCallId);
421
+ await this.upsert_tool(chunk.toolCallId, {
422
+ tool_name: tool?.tool_name || "unknown",
423
+ state: "input-streaming",
424
+ input_text: `${tool?.input_text || ""}${chunk.inputTextDelta}`,
425
+ });
426
+ return;
427
+ }
428
+ case "tool-input-available":
429
+ await this.upsert_tool(chunk.toolCallId, {
430
+ tool_name: chunk.toolName,
431
+ state: "ready",
432
+ input: to_json_value(chunk.input),
433
+ });
434
+ return;
435
+ case "tool-input-error":
436
+ await this.upsert_tool(chunk.toolCallId, {
437
+ tool_name: chunk.toolName,
438
+ state: "failed",
439
+ input: to_json_value(chunk.input),
440
+ error: chunk.errorText,
441
+ });
442
+ return;
443
+ case "tool-approval-request": {
444
+ const tool = this.find_tool(chunk.toolCallId);
445
+ await this.upsert_tool(chunk.toolCallId, {
446
+ tool_name: tool?.tool_name || "unknown",
447
+ state: "approval-required",
448
+ approval_id: chunk.approvalId,
449
+ });
450
+ return;
451
+ }
452
+ case "tool-output-available": {
453
+ const tool = this.find_tool(chunk.toolCallId);
454
+ await this.upsert_tool(chunk.toolCallId, {
455
+ tool_name: tool?.tool_name || "unknown",
456
+ state: "completed",
457
+ output: to_json_value(chunk.output),
458
+ });
459
+ return;
460
+ }
461
+ case "tool-output-error":
462
+ case "tool-output-denied": {
463
+ const tool = this.find_tool(chunk.toolCallId);
464
+ await this.upsert_tool(chunk.toolCallId, {
465
+ tool_name: tool?.tool_name || "unknown",
466
+ state: "failed",
467
+ error: chunk.type === "tool-output-error"
468
+ ? chunk.errorText
469
+ : "Tool output denied",
470
+ });
471
+ return;
472
+ }
473
+ case "file":
474
+ await this.upsert_part({
475
+ part_id: `file:${generateId()}`,
476
+ sequence: this.next_part_sequence(),
477
+ type: "file",
478
+ media_type: chunk.mediaType,
479
+ url: chunk.url,
480
+ });
481
+ return;
482
+ default:
483
+ return;
484
+ }
485
+ }
486
+ /** 写入一个完整 Assistant part。 */
487
+ async upsert_part(part) {
488
+ await this.recorder.update_assistant_part(this.message_id, part);
489
+ }
490
+ /** 当前实现逐 chunk 等待落盘,因此 flush 在返回时天然完成。 */
491
+ async flush() { }
492
+ /** 正常完成当前 assistant segment。 */
493
+ async complete() {
494
+ await this.close("completed");
495
+ }
496
+ /** 停止当前 assistant segment,并保留已有 parts。 */
497
+ async stop() {
498
+ await this.close("stopped");
499
+ }
500
+ /** 以失败状态关闭当前 assistant segment。 */
501
+ async fail(_error) {
502
+ await this.close("failed");
503
+ }
504
+ current_message() {
505
+ const message = this.recorder.get_message(this.message_id);
506
+ if (!message || message.type !== "assistant") {
507
+ throw new Error(`Assistant Message not found: ${this.message_id}`);
508
+ }
509
+ return message;
510
+ }
511
+ find_tool(tool_call_id) {
512
+ return this.current_message().parts.find((part) => part.type === "tool" && part.tool_call_id === tool_call_id);
513
+ }
514
+ next_part_sequence() {
515
+ return this.current_message().parts.reduce((value, part) => Math.max(value, part.sequence + 1), 1);
516
+ }
517
+ async upsert_tool(tool_call_id, changes) {
518
+ const current = this.find_tool(tool_call_id);
519
+ await this.upsert_part({
520
+ ...(current || {}),
521
+ part_id: `tool:${tool_call_id}`,
522
+ sequence: current?.sequence || this.next_part_sequence(),
523
+ type: "tool",
524
+ tool_call_id,
525
+ ...changes,
526
+ });
527
+ }
528
+ async close(status) {
529
+ if (this.closed)
530
+ return;
531
+ await this.recorder.complete_assistant_message(this.message_id, status);
532
+ this.closed = true;
533
+ }
534
+ }
535
+ /** 单个 Action Message 的生命周期 writer。 */
536
+ export class SessionActionMessageWriter {
537
+ message_id;
538
+ recorder;
539
+ closed = false;
540
+ constructor(recorder, message_id) {
541
+ this.recorder = recorder;
542
+ this.message_id = message_id;
543
+ }
544
+ /** 把 Action 更新为 completed。 */
545
+ async complete(input) {
546
+ if (this.closed)
547
+ return;
548
+ await this.recorder.update_action_message(this.message_id, "completed", input);
549
+ this.closed = true;
550
+ }
551
+ /** 把 Action 更新为 failed。 */
552
+ async fail(error) {
553
+ if (this.closed)
554
+ return;
555
+ await this.recorder.update_action_message(this.message_id, "failed", {
556
+ description: error instanceof Error ? error.message : String(error),
557
+ });
558
+ this.closed = true;
559
+ }
560
+ }
561
+ /** 把 AI SDK User parts 归一为 canonical User parts。 */
562
+ export function normalize_session_user_parts(parts) {
563
+ if (!Array.isArray(parts))
564
+ return [];
565
+ return parts.flatMap((part, index) => {
566
+ if (!part || typeof part !== "object")
567
+ return [];
568
+ const candidate = part;
569
+ if (candidate.type === "text") {
570
+ return [{
571
+ part_id: `user-text:${index + 1}`,
572
+ type: "text",
573
+ text: String(candidate.text || ""),
574
+ state: "done",
575
+ }];
576
+ }
577
+ if (candidate.type === "file") {
578
+ return [{
579
+ part_id: `user-file:${index + 1}`,
580
+ type: "file",
581
+ url: String(candidate.url || ""),
582
+ media_type: String(candidate.mediaType || "application/octet-stream"),
583
+ ...(candidate.filename ? { filename: String(candidate.filename) } : {}),
584
+ }];
585
+ }
586
+ if (String(candidate.type || "").startsWith("data-")) {
587
+ return [{
588
+ part_id: `user-data:${index + 1}`,
589
+ type: "data",
590
+ data_type: String(candidate.type),
591
+ data: to_json_value(candidate.data),
592
+ }];
593
+ }
594
+ return [];
595
+ });
596
+ }
597
+ function require_message(messages, message_id, type) {
598
+ const message = messages.find((item) => item.message_id === message_id);
599
+ if (!message || message.type !== type) {
600
+ throw new Error(`Session ${type} Message not found: ${message_id}`);
601
+ }
602
+ return message;
603
+ }
604
+ function require_streaming_assistant(message) {
605
+ if (message.type !== "assistant") {
606
+ throw new Error(`Session Message is not assistant: ${message.message_id}`);
607
+ }
608
+ if (message.status !== "streaming") {
609
+ throw new Error(`Assistant Message is already closed: ${message.message_id}`);
610
+ }
611
+ return message;
612
+ }
613
+ function to_json_value(input) {
614
+ if (input === undefined || input === null)
615
+ return null;
616
+ if (typeof input === "string" || typeof input === "number" || typeof input === "boolean")
617
+ return input;
618
+ try {
619
+ return JSON.parse(JSON.stringify(input));
620
+ }
621
+ catch {
622
+ return String(input);
623
+ }
624
+ }
625
+ function normalize_limit(value, fallback, maximum) {
626
+ if (!Number.isFinite(value))
627
+ return fallback;
628
+ return Math.min(maximum, Math.max(1, Math.floor(Number(value))));
629
+ }
630
+ function encode_cursor(offset) {
631
+ return Buffer.from(JSON.stringify({ offset }), "utf8").toString("base64url");
632
+ }
633
+ function decode_cursor(cursor) {
634
+ const value = String(cursor || "").trim();
635
+ if (!value)
636
+ return 0;
637
+ try {
638
+ const parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
639
+ return typeof parsed.offset === "number" && parsed.offset >= 0
640
+ ? Math.floor(parsed.offset)
641
+ : 0;
642
+ }
643
+ catch {
644
+ throw new Error("Invalid Session Message cursor");
645
+ }
646
+ }
647
+ function resolve_import_id(map, source_id, prefix) {
648
+ const existing = map.get(source_id);
649
+ if (existing)
650
+ return existing;
651
+ const created = `${prefix}:${generateId()}`;
652
+ map.set(source_id, created);
653
+ return created;
654
+ }
655
+ //# sourceMappingURL=SessionRecorder.js.map