@agentscope-ai/agentscope 0.0.2 → 0.0.4

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 (90) hide show
  1. package/LICENSE +202 -0
  2. package/dist/agent/index.d.mts +10 -10
  3. package/dist/agent/index.d.ts +10 -10
  4. package/dist/agent/index.js +104 -93
  5. package/dist/agent/index.js.map +1 -1
  6. package/dist/agent/index.mjs +104 -93
  7. package/dist/agent/index.mjs.map +1 -1
  8. package/dist/{base-BOx3UzOl.d.mts → base-1YVBgB4n.d.mts} +2 -2
  9. package/dist/{base-DYlBMCy_.d.mts → base-B_MQMHWr.d.mts} +3 -3
  10. package/dist/{base-Cwi4bjze.d.ts → base-BherSLRs.d.ts} +3 -3
  11. package/dist/{base-NX-knWOv.d.ts → base-CY4DMBH1.d.ts} +1 -1
  12. package/dist/{base-BoIps2RL.d.ts → base-ChWjyzPL.d.ts} +2 -2
  13. package/dist/{base-C7jwyH4Z.d.mts → base-ClilytRZ.d.mts} +1 -1
  14. package/dist/{block-VsnHrllL.d.mts → block-B72uPF1H.d.mts} +7 -5
  15. package/dist/{block-VsnHrllL.d.ts → block-B72uPF1H.d.ts} +7 -5
  16. package/dist/event/index.d.mts +105 -89
  17. package/dist/event/index.d.ts +105 -89
  18. package/dist/event/index.js +8 -8
  19. package/dist/event/index.js.map +1 -1
  20. package/dist/event/index.mjs +8 -8
  21. package/dist/event/index.mjs.map +1 -1
  22. package/dist/formatter/index.d.mts +4 -3
  23. package/dist/formatter/index.d.ts +4 -3
  24. package/dist/formatter/index.js +17 -17
  25. package/dist/formatter/index.js.map +1 -1
  26. package/dist/formatter/index.mjs +17 -17
  27. package/dist/formatter/index.mjs.map +1 -1
  28. package/dist/{index-BcatlwXQ.d.ts → index-BNfyKbQN.d.ts} +1 -1
  29. package/dist/{index-BTJDlKvQ.d.mts → index-UQCwdfet.d.mts} +1 -1
  30. package/dist/mcp/index.d.mts +2 -2
  31. package/dist/mcp/index.d.ts +2 -2
  32. package/dist/mcp/index.js +1 -1
  33. package/dist/mcp/index.js.map +1 -1
  34. package/dist/mcp/index.mjs +1 -1
  35. package/dist/mcp/index.mjs.map +1 -1
  36. package/dist/message/index.d.mts +3 -2
  37. package/dist/message/index.d.ts +3 -2
  38. package/dist/message/index.js +204 -5
  39. package/dist/message/index.js.map +1 -1
  40. package/dist/message/index.mjs +200 -5
  41. package/dist/message/index.mjs.map +1 -1
  42. package/dist/message-CPZd0NIc.d.ts +133 -0
  43. package/dist/message-DgpfAaHK.d.mts +133 -0
  44. package/dist/model/index.d.mts +6 -5
  45. package/dist/model/index.d.ts +6 -5
  46. package/dist/model/index.js +39 -28
  47. package/dist/model/index.js.map +1 -1
  48. package/dist/model/index.mjs +39 -28
  49. package/dist/model/index.mjs.map +1 -1
  50. package/dist/storage/index.d.mts +4 -3
  51. package/dist/storage/index.d.ts +4 -3
  52. package/dist/storage/index.js +4 -4
  53. package/dist/storage/index.js.map +1 -1
  54. package/dist/storage/index.mjs +4 -4
  55. package/dist/storage/index.mjs.map +1 -1
  56. package/dist/tool/index.d.mts +4 -4
  57. package/dist/tool/index.d.ts +4 -4
  58. package/dist/{toolkit-CEpulFi0.d.ts → toolkit-DeOlul5Y.d.ts} +2 -2
  59. package/dist/{toolkit-CGEZSZPa.d.mts → toolkit-jwe7NmVJ.d.mts} +2 -2
  60. package/package.json +87 -87
  61. package/src/agent/agent.test.ts +104 -71
  62. package/src/agent/agent.ts +112 -104
  63. package/src/agent/test-compression.ts +1 -1
  64. package/src/event/index.ts +96 -98
  65. package/src/formatter/base.ts +3 -3
  66. package/src/formatter/dashscope-chat-formatter.test.ts +11 -8
  67. package/src/formatter/dashscope-chat-formatter.ts +3 -3
  68. package/src/formatter/openai-chat-formatter.test.ts +13 -5
  69. package/src/formatter/openai-chat-formatter.ts +6 -6
  70. package/src/mcp/base.ts +1 -1
  71. package/src/mcp/http.test.ts +2 -0
  72. package/src/mcp/stdio.test.ts +1 -0
  73. package/src/message/append-event.test.ts +783 -0
  74. package/src/message/block.ts +8 -4
  75. package/src/message/index.ts +12 -1
  76. package/src/message/message.test.ts +3 -1
  77. package/src/message/message.ts +310 -47
  78. package/src/model/dashscope-model.test.ts +4 -0
  79. package/src/model/dashscope-model.ts +3 -0
  80. package/src/model/deepseek-model.test.ts +2 -0
  81. package/src/model/deepseek-model.ts +3 -0
  82. package/src/model/ollama-model.test.ts +1 -0
  83. package/src/model/ollama-model.ts +2 -0
  84. package/src/model/openai-model.ts +3 -0
  85. package/src/permission/index.ts +13 -0
  86. package/src/storage/file-system.test.ts +4 -3
  87. package/src/storage/file-system.ts +4 -4
  88. package/src/tool/toolkit.test.ts +12 -0
  89. package/dist/message-CkN21KaY.d.mts +0 -99
  90. package/dist/message-CzLeTlua.d.ts +0 -99
@@ -0,0 +1,783 @@
1
+ import { Msg, createMsg, appendEvent } from './message';
2
+ import { EventType, AgentEvent } from '../event';
3
+ import {
4
+ ContentBlock,
5
+ DataBlock,
6
+ TextBlock,
7
+ ThinkingBlock,
8
+ ToolCallBlock,
9
+ ToolResultBlock,
10
+ } from './block';
11
+
12
+ // Fixed IDs used throughout
13
+ const REPLY_ID = 'reply_001';
14
+ const SESSION_ID = 'session_001';
15
+
16
+ const B_TEXT = 'b_text_001';
17
+ const B_THINK = 'b_think_001';
18
+ const B_DATA = 'b_data_001';
19
+
20
+ const TC_ALLOW = 'tc_allow_001';
21
+ const TC_DENY = 'tc_deny_001';
22
+ const TC_EXT = 'tc_ext_001';
23
+ const TC_IMG = 'tc_img_001';
24
+
25
+ const RES_DATA_B = 'res_data_001';
26
+ const RES_URL_B = 'res_url_001';
27
+
28
+ const FIXED_END_TS = '2026-01-01T12:00:00';
29
+
30
+ // Block-dict helpers
31
+ /**
32
+ * Creates a text block dict for testing.
33
+ * @param blockId
34
+ * @param text
35
+ * @returns A text block object.
36
+ */
37
+ function tb(blockId: string, text: string): TextBlock {
38
+ return { type: 'text', id: blockId, text };
39
+ }
40
+
41
+ /**
42
+ * Creates a thinking block dict for testing.
43
+ * @param blockId
44
+ * @param thinking
45
+ * @returns A thinking block object.
46
+ */
47
+ function thb(blockId: string, thinking: string): ThinkingBlock {
48
+ return { type: 'thinking', id: blockId, thinking };
49
+ }
50
+
51
+ /**
52
+ * Creates a base64 data block dict for testing.
53
+ * @param blockId
54
+ * @param data
55
+ * @param mediaType
56
+ * @returns A base64 data block object.
57
+ */
58
+ function dbB64(blockId: string, data: string, mediaType: string): DataBlock {
59
+ return {
60
+ type: 'data',
61
+ id: blockId,
62
+ source: { type: 'base64', data, media_type: mediaType },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Creates a URL data block dict for testing.
68
+ * @param blockId
69
+ * @param url
70
+ * @param mediaType
71
+ * @returns A URL data block object.
72
+ */
73
+ function dbUrl(blockId: string, url: string, mediaType: string): DataBlock {
74
+ return {
75
+ type: 'data',
76
+ id: blockId,
77
+ source: { type: 'url', url, media_type: mediaType },
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Creates a tool call block dict for testing.
83
+ * @param tcId
84
+ * @param name
85
+ * @param inp
86
+ * @param state
87
+ * @returns A tool call block object.
88
+ */
89
+ function tcb(
90
+ tcId: string,
91
+ name: string,
92
+ inp: string,
93
+ state: ToolCallBlock['state']
94
+ ): ToolCallBlock {
95
+ return { type: 'tool_call', id: tcId, name, input: inp, state };
96
+ }
97
+
98
+ /**
99
+ * Creates a tool result block dict for testing.
100
+ * @param tcId
101
+ * @param name
102
+ * @param output
103
+ * @param state
104
+ * @returns A tool result block object.
105
+ */
106
+ function trb(
107
+ tcId: string,
108
+ name: string,
109
+ output: ToolResultBlock['output'],
110
+ state: ToolResultBlock['state']
111
+ ): ToolResultBlock {
112
+ return { type: 'tool_result', id: tcId, name, output, state };
113
+ }
114
+
115
+ describe('appendEvent', () => {
116
+ let msg: Msg;
117
+ let createdAt: string;
118
+
119
+ beforeEach(() => {
120
+ msg = createMsg({
121
+ id: REPLY_ID,
122
+ name: 'TestAgent',
123
+ role: 'assistant',
124
+ content: [],
125
+ });
126
+ createdAt = msg.created_at;
127
+ });
128
+
129
+ /**
130
+ * Creates a base message object for comparison in tests.
131
+ * @param content
132
+ * @param finishedAt
133
+ * @returns A plain message object for comparison.
134
+ */
135
+ function base(content: ContentBlock[], finishedAt: string | null = null) {
136
+ return {
137
+ id: REPLY_ID,
138
+ name: 'TestAgent',
139
+ role: 'assistant',
140
+ metadata: {},
141
+ created_at: createdAt,
142
+ finished_at: finishedAt,
143
+ content,
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Extracts a plain object representation of a Msg for comparison.
149
+ * @param m
150
+ * @returns A plain object with message fields.
151
+ */
152
+ function msgDump(m: Msg) {
153
+ return {
154
+ id: m.id,
155
+ name: m.name,
156
+ role: m.role,
157
+ metadata: m.metadata,
158
+ created_at: m.created_at,
159
+ finished_at: m.finished_at ?? null,
160
+ content: m.content,
161
+ };
162
+ }
163
+
164
+ test('full streaming event sequence', () => {
165
+ const events: AgentEvent[] = [];
166
+ const groundTruths: ReturnType<typeof base>[] = [];
167
+
168
+ // Stage 1: Text block streaming
169
+ events.push({
170
+ id: '1',
171
+ created_at: '',
172
+ type: EventType.TEXT_BLOCK_START,
173
+ reply_id: REPLY_ID,
174
+ block_id: B_TEXT,
175
+ });
176
+ groundTruths.push(base([tb(B_TEXT, '')]));
177
+
178
+ events.push({
179
+ id: '2',
180
+ created_at: '',
181
+ type: EventType.TEXT_BLOCK_DELTA,
182
+ reply_id: REPLY_ID,
183
+ block_id: B_TEXT,
184
+ delta: 'Hello',
185
+ });
186
+ groundTruths.push(base([tb(B_TEXT, 'Hello')]));
187
+
188
+ events.push({
189
+ id: '3',
190
+ created_at: '',
191
+ type: EventType.TEXT_BLOCK_DELTA,
192
+ reply_id: REPLY_ID,
193
+ block_id: B_TEXT,
194
+ delta: ' World',
195
+ });
196
+ groundTruths.push(base([tb(B_TEXT, 'Hello World')]));
197
+
198
+ events.push({
199
+ id: '4',
200
+ created_at: '',
201
+ type: EventType.TEXT_BLOCK_END,
202
+ reply_id: REPLY_ID,
203
+ block_id: B_TEXT,
204
+ });
205
+ groundTruths.push(base([tb(B_TEXT, 'Hello World')]));
206
+
207
+ // Stage 2: Thinking block streaming
208
+ events.push({
209
+ id: '5',
210
+ created_at: '',
211
+ type: EventType.THINKING_BLOCK_START,
212
+ reply_id: REPLY_ID,
213
+ block_id: B_THINK,
214
+ });
215
+ groundTruths.push(base([tb(B_TEXT, 'Hello World'), thb(B_THINK, '')]));
216
+
217
+ events.push({
218
+ id: '6',
219
+ created_at: '',
220
+ type: EventType.THINKING_BLOCK_DELTA,
221
+ reply_id: REPLY_ID,
222
+ block_id: B_THINK,
223
+ delta: 'Let me',
224
+ });
225
+ groundTruths.push(base([tb(B_TEXT, 'Hello World'), thb(B_THINK, 'Let me')]));
226
+
227
+ events.push({
228
+ id: '7',
229
+ created_at: '',
230
+ type: EventType.THINKING_BLOCK_DELTA,
231
+ reply_id: REPLY_ID,
232
+ block_id: B_THINK,
233
+ delta: ' think',
234
+ });
235
+ groundTruths.push(base([tb(B_TEXT, 'Hello World'), thb(B_THINK, 'Let me think')]));
236
+
237
+ events.push({
238
+ id: '8',
239
+ created_at: '',
240
+ type: EventType.THINKING_BLOCK_END,
241
+ reply_id: REPLY_ID,
242
+ block_id: B_THINK,
243
+ });
244
+ groundTruths.push(base([tb(B_TEXT, 'Hello World'), thb(B_THINK, 'Let me think')]));
245
+
246
+ // Stage 3: Data block streaming (base64)
247
+ events.push({
248
+ id: '9',
249
+ created_at: '',
250
+ type: EventType.DATA_BLOCK_START,
251
+ reply_id: REPLY_ID,
252
+ block_id: B_DATA,
253
+ media_type: 'image/png',
254
+ });
255
+ groundTruths.push(
256
+ base([
257
+ tb(B_TEXT, 'Hello World'),
258
+ thb(B_THINK, 'Let me think'),
259
+ dbB64(B_DATA, '', 'image/png'),
260
+ ])
261
+ );
262
+
263
+ events.push({
264
+ id: '10',
265
+ created_at: '',
266
+ type: EventType.DATA_BLOCK_DELTA,
267
+ reply_id: REPLY_ID,
268
+ block_id: B_DATA,
269
+ data: 'abc',
270
+ media_type: 'image/png',
271
+ });
272
+ groundTruths.push(
273
+ base([
274
+ tb(B_TEXT, 'Hello World'),
275
+ thb(B_THINK, 'Let me think'),
276
+ dbB64(B_DATA, 'abc', 'image/png'),
277
+ ])
278
+ );
279
+
280
+ events.push({
281
+ id: '11',
282
+ created_at: '',
283
+ type: EventType.DATA_BLOCK_DELTA,
284
+ reply_id: REPLY_ID,
285
+ block_id: B_DATA,
286
+ data: 'def',
287
+ media_type: 'image/png',
288
+ });
289
+ groundTruths.push(
290
+ base([
291
+ tb(B_TEXT, 'Hello World'),
292
+ thb(B_THINK, 'Let me think'),
293
+ dbB64(B_DATA, 'abcdef', 'image/png'),
294
+ ])
295
+ );
296
+
297
+ events.push({
298
+ id: '12',
299
+ created_at: '',
300
+ type: EventType.DATA_BLOCK_END,
301
+ reply_id: REPLY_ID,
302
+ block_id: B_DATA,
303
+ });
304
+ groundTruths.push(
305
+ base([
306
+ tb(B_TEXT, 'Hello World'),
307
+ thb(B_THINK, 'Let me think'),
308
+ dbB64(B_DATA, 'abcdef', 'image/png'),
309
+ ])
310
+ );
311
+
312
+ // Stage 4: ToolCall → confirm (allowed) + text result (success)
313
+ const s4Prefix = [
314
+ tb(B_TEXT, 'Hello World'),
315
+ thb(B_THINK, 'Let me think'),
316
+ dbB64(B_DATA, 'abcdef', 'image/png'),
317
+ ];
318
+
319
+ events.push({
320
+ id: '13',
321
+ created_at: '',
322
+ type: EventType.TOOL_CALL_START,
323
+ reply_id: REPLY_ID,
324
+ tool_call_id: TC_ALLOW,
325
+ tool_call_name: 'search',
326
+ });
327
+ groundTruths.push(base([...s4Prefix, tcb(TC_ALLOW, 'search', '', 'pending')]));
328
+
329
+ events.push({
330
+ id: '14',
331
+ created_at: '',
332
+ type: EventType.TOOL_CALL_DELTA,
333
+ reply_id: REPLY_ID,
334
+ tool_call_id: TC_ALLOW,
335
+ delta: '{"q"',
336
+ });
337
+ groundTruths.push(base([...s4Prefix, tcb(TC_ALLOW, 'search', '{"q"', 'pending')]));
338
+
339
+ events.push({
340
+ id: '15',
341
+ created_at: '',
342
+ type: EventType.TOOL_CALL_DELTA,
343
+ reply_id: REPLY_ID,
344
+ tool_call_id: TC_ALLOW,
345
+ delta: ': "hi"}',
346
+ });
347
+ groundTruths.push(base([...s4Prefix, tcb(TC_ALLOW, 'search', '{"q": "hi"}', 'pending')]));
348
+
349
+ events.push({
350
+ id: '16',
351
+ created_at: '',
352
+ type: EventType.TOOL_CALL_END,
353
+ reply_id: REPLY_ID,
354
+ tool_call_id: TC_ALLOW,
355
+ });
356
+ groundTruths.push(base([...s4Prefix, tcb(TC_ALLOW, 'search', '{"q": "hi"}', 'pending')]));
357
+
358
+ // RequireUserConfirmEvent → state: pending → asking
359
+ const tcAllowBlock: ToolCallBlock = {
360
+ type: 'tool_call',
361
+ id: TC_ALLOW,
362
+ name: 'search',
363
+ input: '{"q": "hi"}',
364
+ state: 'pending',
365
+ };
366
+ events.push({
367
+ id: '17',
368
+ created_at: '',
369
+ type: EventType.REQUIRE_USER_CONFIRM,
370
+ reply_id: REPLY_ID,
371
+ tool_calls: [tcAllowBlock],
372
+ });
373
+ groundTruths.push(base([...s4Prefix, tcb(TC_ALLOW, 'search', '{"q": "hi"}', 'asking')]));
374
+
375
+ // UserConfirmResultEvent (confirmed=true) → state: asking → allowed
376
+ events.push({
377
+ id: '18',
378
+ created_at: '',
379
+ type: EventType.USER_CONFIRM_RESULT,
380
+ reply_id: REPLY_ID,
381
+ confirm_results: [{ confirmed: true, tool_call: tcAllowBlock }],
382
+ });
383
+ groundTruths.push(base([...s4Prefix, tcb(TC_ALLOW, 'search', '{"q": "hi"}', 'allowed')]));
384
+
385
+ // ToolResult for TC_ALLOW - text output
386
+ events.push({
387
+ id: '19',
388
+ created_at: '',
389
+ type: EventType.TOOL_RESULT_START,
390
+ reply_id: REPLY_ID,
391
+ tool_call_id: TC_ALLOW,
392
+ tool_call_name: 'search',
393
+ });
394
+ const s4bPrefix = [...s4Prefix, tcb(TC_ALLOW, 'search', '{"q": "hi"}', 'allowed')];
395
+ groundTruths.push(base([...s4bPrefix, trb(TC_ALLOW, 'search', [], 'running')]));
396
+
397
+ // ToolResult text deltas
398
+ events.push({
399
+ id: '20',
400
+ created_at: '',
401
+ type: EventType.TOOL_RESULT_TEXT_DELTA,
402
+ reply_id: REPLY_ID,
403
+ tool_call_id: TC_ALLOW,
404
+ block_id: 'auto_1',
405
+ delta: 'Found:',
406
+ });
407
+ groundTruths.push(
408
+ base([
409
+ ...s4bPrefix,
410
+ trb(
411
+ TC_ALLOW,
412
+ 'search',
413
+ [{ type: 'text', id: expect.any(String), text: 'Found:' }],
414
+ 'running'
415
+ ),
416
+ ])
417
+ );
418
+
419
+ events.push({
420
+ id: '21',
421
+ created_at: '',
422
+ type: EventType.TOOL_RESULT_TEXT_DELTA,
423
+ reply_id: REPLY_ID,
424
+ tool_call_id: TC_ALLOW,
425
+ block_id: 'auto_1',
426
+ delta: ' 3 items',
427
+ });
428
+ groundTruths.push(
429
+ base([
430
+ ...s4bPrefix,
431
+ trb(
432
+ TC_ALLOW,
433
+ 'search',
434
+ [{ type: 'text', id: expect.any(String), text: 'Found: 3 items' }],
435
+ 'running'
436
+ ),
437
+ ])
438
+ );
439
+
440
+ events.push({
441
+ id: '22',
442
+ created_at: '',
443
+ type: EventType.TOOL_RESULT_END,
444
+ reply_id: REPLY_ID,
445
+ tool_call_id: TC_ALLOW,
446
+ state: 'success',
447
+ });
448
+ groundTruths.push(
449
+ base([
450
+ ...s4bPrefix,
451
+ trb(
452
+ TC_ALLOW,
453
+ 'search',
454
+ [{ type: 'text', id: expect.any(String), text: 'Found: 3 items' }],
455
+ 'success'
456
+ ),
457
+ ])
458
+ );
459
+
460
+ // Stage 5: ToolCall (TC_DENY) → confirm → denied (finished)
461
+ const s5Prefix = [
462
+ ...s4bPrefix,
463
+ trb(
464
+ TC_ALLOW,
465
+ 'search',
466
+ [{ type: 'text', id: expect.any(String), text: 'Found: 3 items' }],
467
+ 'success'
468
+ ),
469
+ ];
470
+
471
+ events.push({
472
+ id: '23',
473
+ created_at: '',
474
+ type: EventType.TOOL_CALL_START,
475
+ reply_id: REPLY_ID,
476
+ tool_call_id: TC_DENY,
477
+ tool_call_name: 'delete',
478
+ });
479
+ groundTruths.push(base([...s5Prefix, tcb(TC_DENY, 'delete', '', 'pending')]));
480
+
481
+ events.push({
482
+ id: '24',
483
+ created_at: '',
484
+ type: EventType.TOOL_CALL_END,
485
+ reply_id: REPLY_ID,
486
+ tool_call_id: TC_DENY,
487
+ });
488
+ groundTruths.push(base([...s5Prefix, tcb(TC_DENY, 'delete', '', 'pending')]));
489
+
490
+ const tcDenyBlock: ToolCallBlock = {
491
+ type: 'tool_call',
492
+ id: TC_DENY,
493
+ name: 'delete',
494
+ input: '',
495
+ state: 'pending',
496
+ };
497
+ events.push({
498
+ id: '25',
499
+ created_at: '',
500
+ type: EventType.REQUIRE_USER_CONFIRM,
501
+ reply_id: REPLY_ID,
502
+ tool_calls: [tcDenyBlock],
503
+ });
504
+ groundTruths.push(base([...s5Prefix, tcb(TC_DENY, 'delete', '', 'asking')]));
505
+
506
+ events.push({
507
+ id: '26',
508
+ created_at: '',
509
+ type: EventType.USER_CONFIRM_RESULT,
510
+ reply_id: REPLY_ID,
511
+ confirm_results: [{ confirmed: false, tool_call: tcDenyBlock }],
512
+ });
513
+ groundTruths.push(base([...s5Prefix, tcb(TC_DENY, 'delete', '', 'finished')]));
514
+
515
+ // Stage 6: ToolCall (TC_EXT) → external execution
516
+ const s6Prefix = [...s5Prefix, tcb(TC_DENY, 'delete', '', 'finished')];
517
+
518
+ events.push({
519
+ id: '27',
520
+ created_at: '',
521
+ type: EventType.TOOL_CALL_START,
522
+ reply_id: REPLY_ID,
523
+ tool_call_id: TC_EXT,
524
+ tool_call_name: 'run_code',
525
+ });
526
+ groundTruths.push(base([...s6Prefix, tcb(TC_EXT, 'run_code', '', 'pending')]));
527
+
528
+ events.push({
529
+ id: '28',
530
+ created_at: '',
531
+ type: EventType.TOOL_CALL_END,
532
+ reply_id: REPLY_ID,
533
+ tool_call_id: TC_EXT,
534
+ });
535
+ groundTruths.push(base([...s6Prefix, tcb(TC_EXT, 'run_code', '', 'pending')]));
536
+
537
+ const tcExtBlock: ToolCallBlock = {
538
+ type: 'tool_call',
539
+ id: TC_EXT,
540
+ name: 'run_code',
541
+ input: '',
542
+ state: 'pending',
543
+ };
544
+ events.push({
545
+ id: '29',
546
+ created_at: '',
547
+ type: EventType.REQUIRE_EXTERNAL_EXECUTION,
548
+ reply_id: REPLY_ID,
549
+ tool_calls: [tcExtBlock],
550
+ });
551
+ groundTruths.push(base([...s6Prefix, tcb(TC_EXT, 'run_code', '', 'submitted')]));
552
+
553
+ // ExternalExecutionResultEvent
554
+ const extResultBlock: ToolResultBlock = {
555
+ type: 'tool_result',
556
+ id: TC_EXT,
557
+ name: 'run_code',
558
+ output: 'output: hello',
559
+ state: 'success',
560
+ };
561
+ events.push({
562
+ id: '30',
563
+ created_at: '',
564
+ type: EventType.EXTERNAL_EXECUTION_RESULT,
565
+ reply_id: REPLY_ID,
566
+ execution_results: [extResultBlock],
567
+ });
568
+ const s6bPrefix = [...s6Prefix, tcb(TC_EXT, 'run_code', '', 'submitted')];
569
+ groundTruths.push(
570
+ base([...s6bPrefix, trb(TC_EXT, 'run_code', 'output: hello', 'success')])
571
+ );
572
+
573
+ // Stage 7: ToolResult with data output (base64 + URL)
574
+ const s7Prefix = [...s6bPrefix, trb(TC_EXT, 'run_code', 'output: hello', 'success')];
575
+
576
+ events.push({
577
+ id: '31',
578
+ created_at: '',
579
+ type: EventType.TOOL_CALL_START,
580
+ reply_id: REPLY_ID,
581
+ tool_call_id: TC_IMG,
582
+ tool_call_name: 'screenshot',
583
+ });
584
+ groundTruths.push(base([...s7Prefix, tcb(TC_IMG, 'screenshot', '', 'pending')]));
585
+
586
+ events.push({
587
+ id: '32',
588
+ created_at: '',
589
+ type: EventType.TOOL_CALL_END,
590
+ reply_id: REPLY_ID,
591
+ tool_call_id: TC_IMG,
592
+ });
593
+ groundTruths.push(base([...s7Prefix, tcb(TC_IMG, 'screenshot', '', 'pending')]));
594
+
595
+ events.push({
596
+ id: '33',
597
+ created_at: '',
598
+ type: EventType.TOOL_RESULT_START,
599
+ reply_id: REPLY_ID,
600
+ tool_call_id: TC_IMG,
601
+ tool_call_name: 'screenshot',
602
+ });
603
+ const s7bPrefix = [...s7Prefix, tcb(TC_IMG, 'screenshot', '', 'pending')];
604
+ groundTruths.push(base([...s7bPrefix, trb(TC_IMG, 'screenshot', [], 'running')]));
605
+
606
+ events.push({
607
+ id: '34',
608
+ created_at: '',
609
+ type: EventType.TOOL_RESULT_DATA_DELTA,
610
+ reply_id: REPLY_ID,
611
+ tool_call_id: TC_IMG,
612
+ block_id: RES_DATA_B,
613
+ media_type: 'image/png',
614
+ data: 'iVBOR==',
615
+ });
616
+ groundTruths.push(
617
+ base([
618
+ ...s7bPrefix,
619
+ trb(TC_IMG, 'screenshot', [dbB64(RES_DATA_B, 'iVBOR==', 'image/png')], 'running'),
620
+ ])
621
+ );
622
+
623
+ events.push({
624
+ id: '35',
625
+ created_at: '',
626
+ type: EventType.TOOL_RESULT_DATA_DELTA,
627
+ reply_id: REPLY_ID,
628
+ tool_call_id: TC_IMG,
629
+ block_id: RES_URL_B,
630
+ media_type: 'image/jpeg',
631
+ url: 'https://example.com/img.jpg',
632
+ });
633
+ groundTruths.push(
634
+ base([
635
+ ...s7bPrefix,
636
+ trb(
637
+ TC_IMG,
638
+ 'screenshot',
639
+ [
640
+ dbB64(RES_DATA_B, 'iVBOR==', 'image/png'),
641
+ dbUrl(RES_URL_B, 'https://example.com/img.jpg', 'image/jpeg'),
642
+ ],
643
+ 'running'
644
+ ),
645
+ ])
646
+ );
647
+
648
+ events.push({
649
+ id: '36',
650
+ created_at: '',
651
+ type: EventType.TOOL_RESULT_END,
652
+ reply_id: REPLY_ID,
653
+ tool_call_id: TC_IMG,
654
+ state: 'error',
655
+ });
656
+ groundTruths.push(
657
+ base([
658
+ ...s7bPrefix,
659
+ trb(
660
+ TC_IMG,
661
+ 'screenshot',
662
+ [
663
+ dbB64(RES_DATA_B, 'iVBOR==', 'image/png'),
664
+ dbUrl(RES_URL_B, 'https://example.com/img.jpg', 'image/jpeg'),
665
+ ],
666
+ 'error'
667
+ ),
668
+ ])
669
+ );
670
+
671
+ // Stage 8: ReplyEnd
672
+ events.push({
673
+ id: '37',
674
+ created_at: FIXED_END_TS,
675
+ type: EventType.REPLY_END,
676
+ reply_id: REPLY_ID,
677
+ session_id: SESSION_ID,
678
+ });
679
+ const finalContent = [
680
+ ...s7bPrefix,
681
+ trb(
682
+ TC_IMG,
683
+ 'screenshot',
684
+ [
685
+ dbB64(RES_DATA_B, 'iVBOR==', 'image/png'),
686
+ dbUrl(RES_URL_B, 'https://example.com/img.jpg', 'image/jpeg'),
687
+ ],
688
+ 'error'
689
+ ),
690
+ ];
691
+ groundTruths.push(base(finalContent, FIXED_END_TS));
692
+
693
+ // Apply all events and check ground truths
694
+ expect(events.length).toBe(groundTruths.length);
695
+ for (let i = 0; i < events.length; i++) {
696
+ appendEvent(msg, events[i]);
697
+ expect(msgDump(msg)).toEqual(groundTruths[i]);
698
+ }
699
+ });
700
+
701
+ test('wrong reply_id is skipped', () => {
702
+ const original = msgDump(msg);
703
+ const wrongEvent: AgentEvent = {
704
+ id: 'x',
705
+ created_at: '',
706
+ type: EventType.TEXT_BLOCK_START,
707
+ reply_id: 'totally_wrong_id',
708
+ block_id: 'should_not_appear',
709
+ };
710
+ appendEvent(msg, wrongEvent);
711
+ expect(msgDump(msg)).toEqual(original);
712
+ });
713
+
714
+ test('missing block does not crash', () => {
715
+ const original = msgDump(msg);
716
+ const ghostEvents: AgentEvent[] = [
717
+ {
718
+ id: 'g1',
719
+ created_at: '',
720
+ type: EventType.TEXT_BLOCK_DELTA,
721
+ reply_id: REPLY_ID,
722
+ block_id: 'ghost',
723
+ delta: 'x',
724
+ },
725
+ {
726
+ id: 'g2',
727
+ created_at: '',
728
+ type: EventType.THINKING_BLOCK_DELTA,
729
+ reply_id: REPLY_ID,
730
+ block_id: 'ghost',
731
+ delta: 'x',
732
+ },
733
+ {
734
+ id: 'g3',
735
+ created_at: '',
736
+ type: EventType.DATA_BLOCK_DELTA,
737
+ reply_id: REPLY_ID,
738
+ block_id: 'ghost',
739
+ data: 'x',
740
+ media_type: 'image/png',
741
+ },
742
+ {
743
+ id: 'g4',
744
+ created_at: '',
745
+ type: EventType.TOOL_CALL_DELTA,
746
+ reply_id: REPLY_ID,
747
+ tool_call_id: 'ghost',
748
+ delta: 'x',
749
+ },
750
+ {
751
+ id: 'g5',
752
+ created_at: '',
753
+ type: EventType.TOOL_RESULT_TEXT_DELTA,
754
+ reply_id: REPLY_ID,
755
+ tool_call_id: 'ghost',
756
+ block_id: 'b',
757
+ delta: 'x',
758
+ },
759
+ {
760
+ id: 'g6',
761
+ created_at: '',
762
+ type: EventType.TOOL_RESULT_DATA_DELTA,
763
+ reply_id: REPLY_ID,
764
+ tool_call_id: 'ghost',
765
+ block_id: 'b',
766
+ media_type: 'image/png',
767
+ data: 'x',
768
+ },
769
+ {
770
+ id: 'g7',
771
+ created_at: '',
772
+ type: EventType.TOOL_RESULT_END,
773
+ reply_id: REPLY_ID,
774
+ tool_call_id: 'ghost',
775
+ state: 'success',
776
+ },
777
+ ];
778
+ for (const ev of ghostEvents) {
779
+ appendEvent(msg, ev);
780
+ }
781
+ expect(msgDump(msg)).toEqual(original);
782
+ });
783
+ });