@agentxjs/core 1.9.1-dev

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 (77) hide show
  1. package/package.json +31 -0
  2. package/src/agent/AgentStateMachine.ts +151 -0
  3. package/src/agent/README.md +296 -0
  4. package/src/agent/__tests__/AgentStateMachine.test.ts +346 -0
  5. package/src/agent/__tests__/createAgent.test.ts +728 -0
  6. package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +567 -0
  7. package/src/agent/__tests__/engine/internal/stateEventProcessor.test.ts +315 -0
  8. package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +340 -0
  9. package/src/agent/__tests__/engine/mealy/Mealy.test.ts +370 -0
  10. package/src/agent/__tests__/engine/mealy/Store.test.ts +123 -0
  11. package/src/agent/__tests__/engine/mealy/combinators.test.ts +322 -0
  12. package/src/agent/createAgent.ts +467 -0
  13. package/src/agent/engine/AgentProcessor.ts +106 -0
  14. package/src/agent/engine/MealyMachine.ts +184 -0
  15. package/src/agent/engine/internal/index.ts +35 -0
  16. package/src/agent/engine/internal/messageAssemblerProcessor.ts +550 -0
  17. package/src/agent/engine/internal/stateEventProcessor.ts +313 -0
  18. package/src/agent/engine/internal/turnTrackerProcessor.ts +239 -0
  19. package/src/agent/engine/mealy/Mealy.ts +308 -0
  20. package/src/agent/engine/mealy/Processor.ts +70 -0
  21. package/src/agent/engine/mealy/Sink.ts +56 -0
  22. package/src/agent/engine/mealy/Source.ts +51 -0
  23. package/src/agent/engine/mealy/Store.ts +98 -0
  24. package/src/agent/engine/mealy/combinators.ts +176 -0
  25. package/src/agent/engine/mealy/index.ts +45 -0
  26. package/src/agent/index.ts +106 -0
  27. package/src/agent/types/engine.ts +395 -0
  28. package/src/agent/types/event.ts +478 -0
  29. package/src/agent/types/index.ts +197 -0
  30. package/src/agent/types/message.ts +387 -0
  31. package/src/common/index.ts +8 -0
  32. package/src/common/logger/ConsoleLogger.ts +137 -0
  33. package/src/common/logger/LoggerFactoryImpl.ts +123 -0
  34. package/src/common/logger/index.ts +26 -0
  35. package/src/common/logger/types.ts +98 -0
  36. package/src/container/Container.ts +185 -0
  37. package/src/container/index.ts +44 -0
  38. package/src/container/types.ts +71 -0
  39. package/src/driver/index.ts +42 -0
  40. package/src/driver/types.ts +363 -0
  41. package/src/event/EventBus.ts +260 -0
  42. package/src/event/README.md +237 -0
  43. package/src/event/__tests__/EventBus.test.ts +251 -0
  44. package/src/event/index.ts +46 -0
  45. package/src/event/types/agent.ts +512 -0
  46. package/src/event/types/base.ts +241 -0
  47. package/src/event/types/bus.ts +429 -0
  48. package/src/event/types/command.ts +749 -0
  49. package/src/event/types/container.ts +471 -0
  50. package/src/event/types/driver.ts +452 -0
  51. package/src/event/types/index.ts +26 -0
  52. package/src/event/types/session.ts +314 -0
  53. package/src/image/Image.ts +203 -0
  54. package/src/image/index.ts +36 -0
  55. package/src/image/types.ts +77 -0
  56. package/src/index.ts +20 -0
  57. package/src/mq/OffsetGenerator.ts +48 -0
  58. package/src/mq/README.md +166 -0
  59. package/src/mq/__tests__/OffsetGenerator.test.ts +121 -0
  60. package/src/mq/index.ts +18 -0
  61. package/src/mq/types.ts +172 -0
  62. package/src/network/RpcClient.ts +455 -0
  63. package/src/network/index.ts +76 -0
  64. package/src/network/jsonrpc.ts +336 -0
  65. package/src/network/protocol.ts +90 -0
  66. package/src/network/types.ts +284 -0
  67. package/src/persistence/index.ts +27 -0
  68. package/src/persistence/types.ts +226 -0
  69. package/src/runtime/AgentXRuntime.ts +501 -0
  70. package/src/runtime/index.ts +56 -0
  71. package/src/runtime/types.ts +236 -0
  72. package/src/session/Session.ts +71 -0
  73. package/src/session/index.ts +25 -0
  74. package/src/session/types.ts +77 -0
  75. package/src/workspace/index.ts +27 -0
  76. package/src/workspace/types.ts +131 -0
  77. package/tsconfig.json +10 -0
@@ -0,0 +1,567 @@
1
+ /**
2
+ * messageAssemblerProcessor.test.ts - Unit tests for message assembly processor
3
+ *
4
+ * Tests the pure Mealy transition function that assembles complete Message Layer events
5
+ * from Stream Layer events.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach } from "bun:test";
9
+ import {
10
+ messageAssemblerProcessor,
11
+ createInitialMessageAssemblerState,
12
+ type MessageAssemblerState,
13
+ type MessageAssemblerInput,
14
+ type MessageAssemblerOutput,
15
+ } from "../../../engine/internal/messageAssemblerProcessor";
16
+
17
+ // Helper to create test events
18
+ function createStreamEvent(
19
+ type: string,
20
+ data: unknown,
21
+ timestamp = Date.now()
22
+ ): MessageAssemblerInput {
23
+ return { type, data, timestamp } as MessageAssemblerInput;
24
+ }
25
+
26
+ describe("messageAssemblerProcessor", () => {
27
+ let state: MessageAssemblerState;
28
+
29
+ beforeEach(() => {
30
+ state = createInitialMessageAssemblerState();
31
+ });
32
+
33
+ describe("initial state", () => {
34
+ it("should create correct initial state", () => {
35
+ const initialState = createInitialMessageAssemblerState();
36
+
37
+ expect(initialState.currentMessageId).toBeNull();
38
+ expect(initialState.messageStartTime).toBeNull();
39
+ expect(initialState.pendingContents).toEqual({});
40
+ expect(initialState.pendingToolCalls).toEqual({});
41
+ });
42
+ });
43
+
44
+ describe("message_start event", () => {
45
+ it("should set currentMessageId and messageStartTime", () => {
46
+ const event = createStreamEvent("message_start", { messageId: "msg_123" }, 1000);
47
+
48
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
49
+
50
+ expect(newState.currentMessageId).toBe("msg_123");
51
+ expect(newState.messageStartTime).toBe(1000);
52
+ expect(newState.pendingContents).toEqual({});
53
+ expect(outputs).toHaveLength(0);
54
+ });
55
+
56
+ it("should reset pendingContents on new message", () => {
57
+ // First message with some content
58
+ state.pendingContents = { 0: { type: "text", index: 0, textDeltas: ["old"] } };
59
+
60
+ const event = createStreamEvent("message_start", { messageId: "msg_new" });
61
+
62
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
63
+
64
+ expect(newState.pendingContents).toEqual({});
65
+ expect(outputs).toHaveLength(0);
66
+ });
67
+ });
68
+
69
+ describe("text_delta event", () => {
70
+ it("should accumulate text deltas", () => {
71
+ const event = createStreamEvent("text_delta", { text: "Hello" });
72
+
73
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
74
+
75
+ expect(newState.pendingContents[0]).toBeDefined();
76
+ expect(newState.pendingContents[0].type).toBe("text");
77
+ expect(newState.pendingContents[0].textDeltas).toContain("Hello");
78
+ expect(outputs).toHaveLength(0);
79
+ });
80
+
81
+ it("should append multiple text deltas", () => {
82
+ let currentState = state;
83
+
84
+ // First delta
85
+ const [state1] = messageAssemblerProcessor(
86
+ currentState,
87
+ createStreamEvent("text_delta", { text: "Hello" })
88
+ );
89
+ currentState = state1;
90
+
91
+ // Second delta
92
+ const [state2] = messageAssemblerProcessor(
93
+ currentState,
94
+ createStreamEvent("text_delta", { text: " World" })
95
+ );
96
+ currentState = state2;
97
+
98
+ // Third delta
99
+ const [finalState, outputs] = messageAssemblerProcessor(
100
+ currentState,
101
+ createStreamEvent("text_delta", { text: "!" })
102
+ );
103
+
104
+ expect(finalState.pendingContents[0].textDeltas).toEqual(["Hello", " World", "!"]);
105
+ expect(outputs).toHaveLength(0);
106
+ });
107
+ });
108
+
109
+ describe("tool_use_start event", () => {
110
+ it("should create pending tool use content", () => {
111
+ const event = createStreamEvent("tool_use_start", {
112
+ toolCallId: "tool_123",
113
+ toolName: "calculate",
114
+ });
115
+
116
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
117
+
118
+ expect(newState.pendingContents[1]).toBeDefined();
119
+ expect(newState.pendingContents[1].type).toBe("tool_use");
120
+ expect(newState.pendingContents[1].toolId).toBe("tool_123");
121
+ expect(newState.pendingContents[1].toolName).toBe("calculate");
122
+ expect(newState.pendingContents[1].toolInputJson).toBe("");
123
+ expect(outputs).toHaveLength(0);
124
+ });
125
+ });
126
+
127
+ describe("input_json_delta event", () => {
128
+ it("should accumulate JSON input for tool use", () => {
129
+ // Setup: start a tool use
130
+ state.pendingContents[1] = {
131
+ type: "tool_use",
132
+ index: 1,
133
+ toolId: "tool_123",
134
+ toolName: "calculate",
135
+ toolInputJson: "",
136
+ };
137
+
138
+ const event = createStreamEvent("input_json_delta", { partialJson: '{"value":' });
139
+
140
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
141
+
142
+ expect(newState.pendingContents[1].toolInputJson).toBe('{"value":');
143
+ expect(outputs).toHaveLength(0);
144
+ });
145
+
146
+ it("should append multiple JSON deltas", () => {
147
+ // Setup
148
+ state.pendingContents[1] = {
149
+ type: "tool_use",
150
+ index: 1,
151
+ toolId: "tool_123",
152
+ toolName: "calculate",
153
+ toolInputJson: "",
154
+ };
155
+
156
+ let currentState = state;
157
+
158
+ // First delta
159
+ const [state1] = messageAssemblerProcessor(
160
+ currentState,
161
+ createStreamEvent("input_json_delta", { partialJson: '{"value":' })
162
+ );
163
+ currentState = state1;
164
+
165
+ // Second delta
166
+ const [finalState] = messageAssemblerProcessor(
167
+ currentState,
168
+ createStreamEvent("input_json_delta", { partialJson: "42}" })
169
+ );
170
+
171
+ expect(finalState.pendingContents[1].toolInputJson).toBe('{"value":42}');
172
+ });
173
+
174
+ it("should ignore input_json_delta without pending tool use", () => {
175
+ const event = createStreamEvent("input_json_delta", { partialJson: "ignored" });
176
+
177
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
178
+
179
+ expect(newState).toEqual(state);
180
+ expect(outputs).toHaveLength(0);
181
+ });
182
+ });
183
+
184
+ describe("tool_use_stop event", () => {
185
+ it("should emit tool_call_message event", () => {
186
+ // Setup: complete tool use sequence
187
+ state.currentMessageId = "parent_msg";
188
+ state.pendingContents[1] = {
189
+ type: "tool_use",
190
+ index: 1,
191
+ toolId: "tool_123",
192
+ toolName: "calculate",
193
+ toolInputJson: '{"x":10,"y":20}',
194
+ };
195
+
196
+ const event = createStreamEvent("tool_use_stop", {});
197
+
198
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
199
+
200
+ expect(outputs).toHaveLength(1);
201
+ expect(outputs[0].type).toBe("tool_call_message");
202
+
203
+ const toolCallMessage = outputs[0].data;
204
+ expect(toolCallMessage.role).toBe("assistant");
205
+ expect(toolCallMessage.subtype).toBe("tool-call");
206
+ expect(toolCallMessage.toolCall.id).toBe("tool_123");
207
+ expect(toolCallMessage.toolCall.name).toBe("calculate");
208
+ expect(toolCallMessage.toolCall.input).toEqual({ x: 10, y: 20 });
209
+ expect(toolCallMessage.parentId).toBe("parent_msg");
210
+
211
+ // Should add to pending tool calls
212
+ expect(newState.pendingToolCalls["tool_123"]).toEqual({
213
+ id: "tool_123",
214
+ name: "calculate",
215
+ });
216
+
217
+ // Should remove from pending contents
218
+ expect(newState.pendingContents[1]).toBeUndefined();
219
+ });
220
+
221
+ it("should handle invalid JSON input gracefully", () => {
222
+ state.pendingContents[1] = {
223
+ type: "tool_use",
224
+ index: 1,
225
+ toolId: "tool_123",
226
+ toolName: "test",
227
+ toolInputJson: "invalid json",
228
+ };
229
+
230
+ const event = createStreamEvent("tool_use_stop", {});
231
+
232
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
233
+
234
+ expect(outputs).toHaveLength(1);
235
+ expect(outputs[0].data.toolCall.input).toEqual({});
236
+ });
237
+
238
+ it("should handle missing pending tool use", () => {
239
+ const event = createStreamEvent("tool_use_stop", {});
240
+
241
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
242
+
243
+ expect(newState).toEqual(state);
244
+ expect(outputs).toHaveLength(0);
245
+ });
246
+ });
247
+
248
+ describe("tool_result event", () => {
249
+ it("should emit tool_result_message event", () => {
250
+ // Setup: pending tool call
251
+ state.pendingToolCalls["tool_123"] = {
252
+ id: "tool_123",
253
+ name: "calculate",
254
+ };
255
+
256
+ const event = createStreamEvent("tool_result", {
257
+ toolCallId: "tool_123",
258
+ result: "42",
259
+ isError: false,
260
+ });
261
+
262
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
263
+
264
+ expect(outputs).toHaveLength(1);
265
+ expect(outputs[0].type).toBe("tool_result_message");
266
+
267
+ const toolResultMessage = outputs[0].data;
268
+ expect(toolResultMessage.role).toBe("tool");
269
+ expect(toolResultMessage.subtype).toBe("tool-result");
270
+ expect(toolResultMessage.toolCallId).toBe("tool_123");
271
+ expect(toolResultMessage.toolResult.id).toBe("tool_123");
272
+ expect(toolResultMessage.toolResult.name).toBe("calculate");
273
+ expect(toolResultMessage.toolResult.output.type).toBe("text");
274
+ expect(toolResultMessage.toolResult.output.value).toBe("42");
275
+
276
+ // Should remove from pending tool calls
277
+ expect(newState.pendingToolCalls["tool_123"]).toBeUndefined();
278
+ });
279
+
280
+ it("should handle error results", () => {
281
+ state.pendingToolCalls["tool_123"] = {
282
+ id: "tool_123",
283
+ name: "test",
284
+ };
285
+
286
+ const event = createStreamEvent("tool_result", {
287
+ toolCallId: "tool_123",
288
+ result: "Error: Something went wrong",
289
+ isError: true,
290
+ });
291
+
292
+ const [, outputs] = messageAssemblerProcessor(state, event);
293
+
294
+ expect(outputs[0].data.toolResult.output.type).toBe("error-text");
295
+ });
296
+
297
+ it("should handle unknown tool name", () => {
298
+ const event = createStreamEvent("tool_result", {
299
+ toolCallId: "unknown_tool",
300
+ result: "result",
301
+ isError: false,
302
+ });
303
+
304
+ const [, outputs] = messageAssemblerProcessor(state, event);
305
+
306
+ expect(outputs).toHaveLength(1);
307
+ expect(outputs[0].data.toolResult.name).toBe("unknown");
308
+ });
309
+ });
310
+
311
+ describe("message_stop event", () => {
312
+ it("should emit assistant_message event with assembled content", () => {
313
+ // Setup: complete message with text
314
+ state.currentMessageId = "msg_123";
315
+ state.messageStartTime = 1000;
316
+ state.pendingContents[0] = {
317
+ type: "text",
318
+ index: 0,
319
+ textDeltas: ["Hello", " ", "World!"],
320
+ };
321
+
322
+ const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
323
+
324
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
325
+
326
+ expect(outputs).toHaveLength(1);
327
+ expect(outputs[0].type).toBe("assistant_message");
328
+
329
+ const assistantMessage = outputs[0].data;
330
+ expect(assistantMessage.id).toBe("msg_123");
331
+ expect(assistantMessage.role).toBe("assistant");
332
+ expect(assistantMessage.subtype).toBe("assistant");
333
+ expect(assistantMessage.content).toHaveLength(1);
334
+ expect(assistantMessage.content[0].type).toBe("text");
335
+ expect(assistantMessage.content[0].text).toBe("Hello World!");
336
+
337
+ // Should reset state
338
+ expect(newState.currentMessageId).toBeNull();
339
+ expect(newState.pendingContents).toEqual({});
340
+ });
341
+
342
+ it("should skip empty messages", () => {
343
+ state.currentMessageId = "msg_123";
344
+
345
+ const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
346
+
347
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
348
+
349
+ expect(outputs).toHaveLength(0);
350
+ expect(newState.currentMessageId).toBeNull();
351
+ });
352
+
353
+ it("should skip whitespace-only messages", () => {
354
+ state.currentMessageId = "msg_123";
355
+ state.pendingContents[0] = {
356
+ type: "text",
357
+ index: 0,
358
+ textDeltas: [" ", "\n", "\t"],
359
+ };
360
+
361
+ const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
362
+
363
+ const [, outputs] = messageAssemblerProcessor(state, event);
364
+
365
+ expect(outputs).toHaveLength(0);
366
+ });
367
+
368
+ it("should preserve pending tool calls when stopReason is tool_use", () => {
369
+ state.currentMessageId = "msg_123";
370
+ state.pendingToolCalls["tool_123"] = { id: "tool_123", name: "test" };
371
+ state.pendingContents[0] = {
372
+ type: "text",
373
+ index: 0,
374
+ textDeltas: ["Calling tool..."],
375
+ };
376
+
377
+ const event = createStreamEvent("message_stop", { stopReason: "tool_use" });
378
+
379
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
380
+
381
+ expect(outputs).toHaveLength(1); // Still emits assistant message
382
+ expect(newState.pendingToolCalls["tool_123"]).toBeDefined();
383
+ });
384
+
385
+ it("should clear pending tool calls for non-tool_use stop reason", () => {
386
+ state.currentMessageId = "msg_123";
387
+ state.pendingToolCalls["tool_123"] = { id: "tool_123", name: "test" };
388
+ state.pendingContents[0] = {
389
+ type: "text",
390
+ index: 0,
391
+ textDeltas: ["Done"],
392
+ };
393
+
394
+ const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
395
+
396
+ const [newState] = messageAssemblerProcessor(state, event);
397
+
398
+ expect(newState.pendingToolCalls).toEqual({});
399
+ });
400
+
401
+ it("should handle missing currentMessageId", () => {
402
+ state.pendingContents[0] = {
403
+ type: "text",
404
+ index: 0,
405
+ textDeltas: ["Some text"],
406
+ };
407
+
408
+ const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
409
+
410
+ const [, outputs] = messageAssemblerProcessor(state, event);
411
+
412
+ expect(outputs).toHaveLength(0);
413
+ });
414
+ });
415
+
416
+ describe("error_received event", () => {
417
+ it("should emit error_message event", () => {
418
+ const event = createStreamEvent("error_received", {
419
+ message: "API error occurred",
420
+ errorCode: "api_error",
421
+ });
422
+
423
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
424
+
425
+ expect(outputs).toHaveLength(1);
426
+ expect(outputs[0].type).toBe("error_message");
427
+
428
+ const errorMessage = outputs[0].data;
429
+ expect(errorMessage.role).toBe("error");
430
+ expect(errorMessage.subtype).toBe("error");
431
+ expect(errorMessage.content).toBe("API error occurred");
432
+ expect(errorMessage.errorCode).toBe("api_error");
433
+
434
+ // Should reset state
435
+ expect(newState.currentMessageId).toBeNull();
436
+ expect(newState.pendingContents).toEqual({});
437
+ });
438
+
439
+ it("should handle missing errorCode", () => {
440
+ const event = createStreamEvent("error_received", {
441
+ message: "Unknown error",
442
+ });
443
+
444
+ const [, outputs] = messageAssemblerProcessor(state, event);
445
+
446
+ expect(outputs[0].data.errorCode).toBeUndefined();
447
+ });
448
+ });
449
+
450
+ describe("unhandled events", () => {
451
+ it("should pass through unhandled events without state change", () => {
452
+ const event = createStreamEvent("unknown_event_type", { data: "ignored" });
453
+
454
+ const [newState, outputs] = messageAssemblerProcessor(state, event);
455
+
456
+ expect(newState).toEqual(state);
457
+ expect(outputs).toHaveLength(0);
458
+ });
459
+ });
460
+
461
+ describe("complete message flow", () => {
462
+ it("should handle complete text message flow", () => {
463
+ let currentState = createInitialMessageAssemblerState();
464
+ const allOutputs: MessageAssemblerOutput[] = [];
465
+
466
+ // message_start
467
+ const [s1, o1] = messageAssemblerProcessor(
468
+ currentState,
469
+ createStreamEvent("message_start", { messageId: "msg_1" }, 1000)
470
+ );
471
+ currentState = s1;
472
+ allOutputs.push(...o1);
473
+
474
+ // text_delta
475
+ const [s2, o2] = messageAssemblerProcessor(
476
+ currentState,
477
+ createStreamEvent("text_delta", { text: "Hello" })
478
+ );
479
+ currentState = s2;
480
+ allOutputs.push(...o2);
481
+
482
+ // text_delta
483
+ const [s3, o3] = messageAssemblerProcessor(
484
+ currentState,
485
+ createStreamEvent("text_delta", { text: " World" })
486
+ );
487
+ currentState = s3;
488
+ allOutputs.push(...o3);
489
+
490
+ // message_stop
491
+ const [s4, o4] = messageAssemblerProcessor(
492
+ currentState,
493
+ createStreamEvent("message_stop", { stopReason: "end_turn" })
494
+ );
495
+ currentState = s4;
496
+ allOutputs.push(...o4);
497
+
498
+ expect(allOutputs).toHaveLength(1);
499
+ expect(allOutputs[0].type).toBe("assistant_message");
500
+ expect(allOutputs[0].data.content[0].text).toBe("Hello World");
501
+ expect(currentState).toEqual(createInitialMessageAssemblerState());
502
+ });
503
+
504
+ it("should handle tool call flow", () => {
505
+ let currentState = createInitialMessageAssemblerState();
506
+ const allOutputs: MessageAssemblerOutput[] = [];
507
+
508
+ // message_start
509
+ const [s1, o1] = messageAssemblerProcessor(
510
+ currentState,
511
+ createStreamEvent("message_start", { messageId: "msg_1" }, 1000)
512
+ );
513
+ currentState = s1;
514
+ allOutputs.push(...o1);
515
+
516
+ // tool_use_start
517
+ const [s2, o2] = messageAssemblerProcessor(
518
+ currentState,
519
+ createStreamEvent("tool_use_start", { toolCallId: "tool_1", toolName: "search" })
520
+ );
521
+ currentState = s2;
522
+ allOutputs.push(...o2);
523
+
524
+ // input_json_delta
525
+ const [s3, o3] = messageAssemblerProcessor(
526
+ currentState,
527
+ createStreamEvent("input_json_delta", { partialJson: '{"query":"test"}' })
528
+ );
529
+ currentState = s3;
530
+ allOutputs.push(...o3);
531
+
532
+ // tool_use_stop
533
+ const [s4, o4] = messageAssemblerProcessor(
534
+ currentState,
535
+ createStreamEvent("tool_use_stop", {})
536
+ );
537
+ currentState = s4;
538
+ allOutputs.push(...o4);
539
+
540
+ // message_stop with tool_use
541
+ const [s5, o5] = messageAssemblerProcessor(
542
+ currentState,
543
+ createStreamEvent("message_stop", { stopReason: "tool_use" })
544
+ );
545
+ currentState = s5;
546
+ allOutputs.push(...o5);
547
+
548
+ // tool_result
549
+ const [s6, o6] = messageAssemblerProcessor(
550
+ currentState,
551
+ createStreamEvent("tool_result", {
552
+ toolCallId: "tool_1",
553
+ result: "Found it!",
554
+ isError: false,
555
+ })
556
+ );
557
+ currentState = s6;
558
+ allOutputs.push(...o6);
559
+
560
+ expect(allOutputs).toHaveLength(2);
561
+ expect(allOutputs[0].type).toBe("tool_call_message");
562
+ expect(allOutputs[0].data.toolCall.name).toBe("search");
563
+ expect(allOutputs[1].type).toBe("tool_result_message");
564
+ expect(allOutputs[1].data.toolResult.output.value).toBe("Found it!");
565
+ });
566
+ });
567
+ });