@agentxjs/core 1.9.9-dev → 2.0.0
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.
- package/README.md +342 -0
- package/dist/RpcClient-BcJ_zAGu.d.ts +304 -0
- package/dist/agent/engine/internal/index.d.ts +20 -15
- package/dist/agent/engine/internal/index.js +1 -2
- package/dist/agent/engine/mealy/index.js +0 -1
- package/dist/agent/index.d.ts +4 -4
- package/dist/agent/index.js +6 -6
- package/dist/agent/types/index.d.ts +4 -4
- package/dist/agent/types/index.js +1 -2
- package/dist/bash/index.d.ts +29 -0
- package/dist/bash/index.js +7 -0
- package/dist/{bus-uF1DM2ox.d.ts → bus-C9FLWIu8.d.ts} +3 -1
- package/dist/{chunk-K6WXQ2RW.js → chunk-23UUBQXR.js} +1 -2
- package/dist/chunk-23UUBQXR.js.map +1 -0
- package/dist/chunk-BHOD5PKR.js +55 -0
- package/dist/chunk-BHOD5PKR.js.map +1 -0
- package/dist/{chunk-I7GYR3MN.js → chunk-DEAR6N3O.js} +77 -91
- package/dist/chunk-DEAR6N3O.js.map +1 -0
- package/dist/chunk-FI7WQFGV.js +37 -0
- package/dist/chunk-FI7WQFGV.js.map +1 -0
- package/dist/{chunk-TBU7FFZT.js → chunk-JTKCV7IS.js} +4 -4
- package/dist/chunk-JTKCV7IS.js.map +1 -0
- package/dist/{chunk-E5FPOAPO.js → chunk-LTVNPHST.js} +1 -1
- package/dist/chunk-LTVNPHST.js.map +1 -0
- package/dist/chunk-SKS7S2RY.js +1 -0
- package/dist/common/logger/index.js +0 -2
- package/dist/common/logger/index.js.map +1 -1
- package/dist/container/index.d.ts +3 -4
- package/dist/container/index.js +0 -2
- package/dist/container/index.js.map +1 -1
- package/dist/driver/index.d.ts +2 -310
- package/dist/event/index.d.ts +4 -4
- package/dist/event/index.js +1 -2
- package/dist/event/types/index.d.ts +4 -10
- package/dist/event/types/index.js +1 -2
- package/dist/{event-CDuTzs__.d.ts → event-DNWOBSBO.d.ts} +3 -4
- package/dist/image/index.d.ts +9 -5
- package/dist/image/index.js +5 -2
- package/dist/image/index.js.map +1 -1
- package/dist/index-CuS1i5V-.d.ts +609 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/{message-BMrMm1pq.d.ts → message-03TJzvIX.d.ts} +10 -33
- package/dist/mq/index.js +0 -2
- package/dist/mq/index.js.map +1 -1
- package/dist/network/index.d.ts +3 -291
- package/dist/network/index.js +3 -14
- package/dist/network/index.js.map +1 -1
- package/dist/persistence/index.d.ts +2 -155
- package/dist/platform/index.d.ts +76 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/runtime/index.d.ts +26 -59
- package/dist/runtime/index.js +117 -33
- package/dist/runtime/index.js.map +1 -1
- package/dist/session/index.d.ts +4 -52
- package/dist/session/index.js +4 -51
- package/dist/session/index.js.map +1 -1
- package/dist/types-aE74Eo6G.d.ts +90 -0
- package/package.json +10 -5
- package/src/agent/__tests__/engine/internal/messageAssemblerProcessor.test.ts +291 -87
- package/src/agent/__tests__/engine/internal/turnTrackerProcessor.test.ts +56 -75
- package/src/agent/engine/MealyMachine.ts +1 -1
- package/src/agent/engine/internal/messageAssemblerProcessor.ts +99 -114
- package/src/agent/engine/internal/turnTrackerProcessor.ts +23 -27
- package/src/agent/types/event.ts +0 -4
- package/src/agent/types/index.ts +1 -3
- package/src/agent/types/message.ts +9 -43
- package/src/bash/index.ts +21 -0
- package/src/bash/tool.ts +57 -0
- package/src/bash/types.ts +108 -0
- package/src/driver/index.ts +1 -0
- package/src/driver/types.ts +122 -4
- package/src/event/__tests__/EventBus.test.ts +1 -1
- package/src/event/types/agent.ts +0 -11
- package/src/event/types/command.ts +3 -1
- package/src/image/Image.ts +11 -1
- package/src/image/types.ts +8 -2
- package/src/network/RpcClient.ts +21 -20
- package/src/network/index.ts +1 -1
- package/src/persistence/types.ts +5 -2
- package/src/platform/index.ts +21 -0
- package/src/platform/types.ts +84 -0
- package/src/runtime/AgentXRuntime.ts +184 -57
- package/src/runtime/__tests__/AgentXRuntime.test.ts +343 -0
- package/src/runtime/index.ts +7 -19
- package/src/runtime/types.ts +10 -62
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-E5FPOAPO.js.map +0 -1
- package/dist/chunk-I7GYR3MN.js.map +0 -1
- package/dist/chunk-K6WXQ2RW.js.map +0 -1
- package/dist/chunk-TBU7FFZT.js.map +0 -1
- package/dist/workspace/index.d.ts +0 -111
- package/dist/wrapper-Y3UTVU2E.js +0 -3635
- package/dist/wrapper-Y3UTVU2E.js.map +0 -1
- package/src/workspace/index.ts +0 -27
- package/src/workspace/types.ts +0 -131
- /package/dist/{workspace → bash}/index.js.map +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-SKS7S2RY.js.map} +0 -0
- /package/dist/{workspace → platform}/index.js +0 -0
|
@@ -36,7 +36,7 @@ describe("messageAssemblerProcessor", () => {
|
|
|
36
36
|
|
|
37
37
|
expect(initialState.currentMessageId).toBeNull();
|
|
38
38
|
expect(initialState.messageStartTime).toBeNull();
|
|
39
|
-
expect(initialState.pendingContents).toEqual(
|
|
39
|
+
expect(initialState.pendingContents).toEqual([]);
|
|
40
40
|
expect(initialState.pendingToolCalls).toEqual({});
|
|
41
41
|
});
|
|
42
42
|
});
|
|
@@ -49,19 +49,19 @@ describe("messageAssemblerProcessor", () => {
|
|
|
49
49
|
|
|
50
50
|
expect(newState.currentMessageId).toBe("msg_123");
|
|
51
51
|
expect(newState.messageStartTime).toBe(1000);
|
|
52
|
-
expect(newState.pendingContents).toEqual(
|
|
52
|
+
expect(newState.pendingContents).toEqual([]);
|
|
53
53
|
expect(outputs).toHaveLength(0);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it("should reset pendingContents on new message", () => {
|
|
57
57
|
// First message with some content
|
|
58
|
-
state.pendingContents = {
|
|
58
|
+
state.pendingContents = [{ type: "text", textDeltas: ["old"] }];
|
|
59
59
|
|
|
60
60
|
const event = createStreamEvent("message_start", { messageId: "msg_new" });
|
|
61
61
|
|
|
62
62
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
63
63
|
|
|
64
|
-
expect(newState.pendingContents).toEqual(
|
|
64
|
+
expect(newState.pendingContents).toEqual([]);
|
|
65
65
|
expect(outputs).toHaveLength(0);
|
|
66
66
|
});
|
|
67
67
|
});
|
|
@@ -72,7 +72,7 @@ describe("messageAssemblerProcessor", () => {
|
|
|
72
72
|
|
|
73
73
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
74
74
|
|
|
75
|
-
expect(newState.pendingContents
|
|
75
|
+
expect(newState.pendingContents).toHaveLength(1);
|
|
76
76
|
expect(newState.pendingContents[0].type).toBe("text");
|
|
77
77
|
expect(newState.pendingContents[0].textDeltas).toContain("Hello");
|
|
78
78
|
expect(outputs).toHaveLength(0);
|
|
@@ -104,6 +104,32 @@ describe("messageAssemblerProcessor", () => {
|
|
|
104
104
|
expect(finalState.pendingContents[0].textDeltas).toEqual(["Hello", " World", "!"]);
|
|
105
105
|
expect(outputs).toHaveLength(0);
|
|
106
106
|
});
|
|
107
|
+
|
|
108
|
+
it("should create new text block after a tool_use block", () => {
|
|
109
|
+
// Setup: text then tool_use already in pendingContents
|
|
110
|
+
state.pendingContents = [
|
|
111
|
+
{ type: "text", textDeltas: ["Before tool"] },
|
|
112
|
+
{
|
|
113
|
+
type: "tool_use",
|
|
114
|
+
toolId: "t1",
|
|
115
|
+
toolName: "test",
|
|
116
|
+
toolInputJson: "{}",
|
|
117
|
+
assembled: true,
|
|
118
|
+
parsedInput: {},
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
const event = createStreamEvent("text_delta", { text: "After tool" });
|
|
123
|
+
const [newState] = messageAssemblerProcessor(state, event);
|
|
124
|
+
|
|
125
|
+
// Should create a NEW text block (not append to the first one)
|
|
126
|
+
expect(newState.pendingContents).toHaveLength(3);
|
|
127
|
+
expect(newState.pendingContents[0].type).toBe("text");
|
|
128
|
+
expect(newState.pendingContents[0].textDeltas).toEqual(["Before tool"]);
|
|
129
|
+
expect(newState.pendingContents[1].type).toBe("tool_use");
|
|
130
|
+
expect(newState.pendingContents[2].type).toBe("text");
|
|
131
|
+
expect(newState.pendingContents[2].textDeltas).toEqual(["After tool"]);
|
|
132
|
+
});
|
|
107
133
|
});
|
|
108
134
|
|
|
109
135
|
describe("tool_use_start event", () => {
|
|
@@ -115,43 +141,60 @@ describe("messageAssemblerProcessor", () => {
|
|
|
115
141
|
|
|
116
142
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
117
143
|
|
|
118
|
-
expect(newState.pendingContents
|
|
119
|
-
expect(newState.pendingContents[
|
|
120
|
-
expect(newState.pendingContents[
|
|
121
|
-
expect(newState.pendingContents[
|
|
122
|
-
expect(newState.pendingContents[
|
|
144
|
+
expect(newState.pendingContents).toHaveLength(1);
|
|
145
|
+
expect(newState.pendingContents[0].type).toBe("tool_use");
|
|
146
|
+
expect(newState.pendingContents[0].toolId).toBe("tool_123");
|
|
147
|
+
expect(newState.pendingContents[0].toolName).toBe("calculate");
|
|
148
|
+
expect(newState.pendingContents[0].toolInputJson).toBe("");
|
|
123
149
|
expect(outputs).toHaveLength(0);
|
|
124
150
|
});
|
|
151
|
+
|
|
152
|
+
it("should append tool_use after existing text", () => {
|
|
153
|
+
state.pendingContents = [{ type: "text", textDeltas: ["Some text"] }];
|
|
154
|
+
|
|
155
|
+
const event = createStreamEvent("tool_use_start", {
|
|
156
|
+
toolCallId: "tool_123",
|
|
157
|
+
toolName: "calculate",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const [newState] = messageAssemblerProcessor(state, event);
|
|
161
|
+
|
|
162
|
+
expect(newState.pendingContents).toHaveLength(2);
|
|
163
|
+
expect(newState.pendingContents[0].type).toBe("text");
|
|
164
|
+
expect(newState.pendingContents[1].type).toBe("tool_use");
|
|
165
|
+
});
|
|
125
166
|
});
|
|
126
167
|
|
|
127
168
|
describe("input_json_delta event", () => {
|
|
128
169
|
it("should accumulate JSON input for tool use", () => {
|
|
129
170
|
// Setup: start a tool use
|
|
130
|
-
state.pendingContents
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
state.pendingContents = [
|
|
172
|
+
{
|
|
173
|
+
type: "tool_use",
|
|
174
|
+
toolId: "tool_123",
|
|
175
|
+
toolName: "calculate",
|
|
176
|
+
toolInputJson: "",
|
|
177
|
+
},
|
|
178
|
+
];
|
|
137
179
|
|
|
138
180
|
const event = createStreamEvent("input_json_delta", { partialJson: '{"value":' });
|
|
139
181
|
|
|
140
182
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
141
183
|
|
|
142
|
-
expect(newState.pendingContents[
|
|
184
|
+
expect(newState.pendingContents[0].toolInputJson).toBe('{"value":');
|
|
143
185
|
expect(outputs).toHaveLength(0);
|
|
144
186
|
});
|
|
145
187
|
|
|
146
188
|
it("should append multiple JSON deltas", () => {
|
|
147
189
|
// Setup
|
|
148
|
-
state.pendingContents
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
190
|
+
state.pendingContents = [
|
|
191
|
+
{
|
|
192
|
+
type: "tool_use",
|
|
193
|
+
toolId: "tool_123",
|
|
194
|
+
toolName: "calculate",
|
|
195
|
+
toolInputJson: "",
|
|
196
|
+
},
|
|
197
|
+
];
|
|
155
198
|
|
|
156
199
|
let currentState = state;
|
|
157
200
|
|
|
@@ -168,7 +211,7 @@ describe("messageAssemblerProcessor", () => {
|
|
|
168
211
|
createStreamEvent("input_json_delta", { partialJson: "42}" })
|
|
169
212
|
);
|
|
170
213
|
|
|
171
|
-
expect(finalState.pendingContents[
|
|
214
|
+
expect(finalState.pendingContents[0].toolInputJson).toBe('{"value":42}');
|
|
172
215
|
});
|
|
173
216
|
|
|
174
217
|
it("should ignore input_json_delta without pending tool use", () => {
|
|
@@ -182,57 +225,55 @@ describe("messageAssemblerProcessor", () => {
|
|
|
182
225
|
});
|
|
183
226
|
|
|
184
227
|
describe("tool_use_stop event", () => {
|
|
185
|
-
it("should
|
|
228
|
+
it("should mark tool_use as assembled without emitting event", () => {
|
|
186
229
|
// Setup: complete tool use sequence
|
|
187
230
|
state.currentMessageId = "parent_msg";
|
|
188
|
-
state.pendingContents
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
231
|
+
state.pendingContents = [
|
|
232
|
+
{
|
|
233
|
+
type: "tool_use",
|
|
234
|
+
toolId: "tool_123",
|
|
235
|
+
toolName: "calculate",
|
|
236
|
+
toolInputJson: '{"x":10,"y":20}',
|
|
237
|
+
},
|
|
238
|
+
];
|
|
195
239
|
|
|
196
240
|
const event = createStreamEvent("tool_use_stop", {});
|
|
197
241
|
|
|
198
242
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
199
243
|
|
|
200
|
-
|
|
201
|
-
expect(outputs
|
|
244
|
+
// No event emitted — tool calls are part of the assistant message
|
|
245
|
+
expect(outputs).toHaveLength(0);
|
|
202
246
|
|
|
203
|
-
|
|
204
|
-
expect(
|
|
205
|
-
expect(
|
|
206
|
-
expect(
|
|
207
|
-
expect(
|
|
208
|
-
expect(toolCallMessage.toolCall.input).toEqual({ x: 10, y: 20 });
|
|
209
|
-
expect(toolCallMessage.parentId).toBe("parent_msg");
|
|
247
|
+
// Should mark as assembled with parsed input (stays in pendingContents)
|
|
248
|
+
expect(newState.pendingContents).toHaveLength(1);
|
|
249
|
+
expect(newState.pendingContents[0].type).toBe("tool_use");
|
|
250
|
+
expect(newState.pendingContents[0].assembled).toBe(true);
|
|
251
|
+
expect(newState.pendingContents[0].parsedInput).toEqual({ x: 10, y: 20 });
|
|
210
252
|
|
|
211
253
|
// Should add to pending tool calls
|
|
212
254
|
expect(newState.pendingToolCalls["tool_123"]).toEqual({
|
|
213
255
|
id: "tool_123",
|
|
214
256
|
name: "calculate",
|
|
215
257
|
});
|
|
216
|
-
|
|
217
|
-
// Should remove from pending contents
|
|
218
|
-
expect(newState.pendingContents[1]).toBeUndefined();
|
|
219
258
|
});
|
|
220
259
|
|
|
221
260
|
it("should handle invalid JSON input gracefully", () => {
|
|
222
|
-
state.pendingContents
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
261
|
+
state.pendingContents = [
|
|
262
|
+
{
|
|
263
|
+
type: "tool_use",
|
|
264
|
+
toolId: "tool_123",
|
|
265
|
+
toolName: "test",
|
|
266
|
+
toolInputJson: "invalid json",
|
|
267
|
+
},
|
|
268
|
+
];
|
|
229
269
|
|
|
230
270
|
const event = createStreamEvent("tool_use_stop", {});
|
|
231
271
|
|
|
232
272
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
233
273
|
|
|
234
|
-
expect(outputs).toHaveLength(
|
|
235
|
-
expect(
|
|
274
|
+
expect(outputs).toHaveLength(0);
|
|
275
|
+
expect(newState.pendingContents[0].assembled).toBe(true);
|
|
276
|
+
expect(newState.pendingContents[0].parsedInput).toEqual({});
|
|
236
277
|
});
|
|
237
278
|
|
|
238
279
|
it("should handle missing pending tool use", () => {
|
|
@@ -313,11 +354,12 @@ describe("messageAssemblerProcessor", () => {
|
|
|
313
354
|
// Setup: complete message with text
|
|
314
355
|
state.currentMessageId = "msg_123";
|
|
315
356
|
state.messageStartTime = 1000;
|
|
316
|
-
state.pendingContents
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
357
|
+
state.pendingContents = [
|
|
358
|
+
{
|
|
359
|
+
type: "text",
|
|
360
|
+
textDeltas: ["Hello", " ", "World!"],
|
|
361
|
+
},
|
|
362
|
+
];
|
|
321
363
|
|
|
322
364
|
const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
|
|
323
365
|
|
|
@@ -336,7 +378,7 @@ describe("messageAssemblerProcessor", () => {
|
|
|
336
378
|
|
|
337
379
|
// Should reset state
|
|
338
380
|
expect(newState.currentMessageId).toBeNull();
|
|
339
|
-
expect(newState.pendingContents).toEqual(
|
|
381
|
+
expect(newState.pendingContents).toEqual([]);
|
|
340
382
|
});
|
|
341
383
|
|
|
342
384
|
it("should skip empty messages", () => {
|
|
@@ -352,11 +394,12 @@ describe("messageAssemblerProcessor", () => {
|
|
|
352
394
|
|
|
353
395
|
it("should skip whitespace-only messages", () => {
|
|
354
396
|
state.currentMessageId = "msg_123";
|
|
355
|
-
state.pendingContents
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
397
|
+
state.pendingContents = [
|
|
398
|
+
{
|
|
399
|
+
type: "text",
|
|
400
|
+
textDeltas: [" ", "\n", "\t"],
|
|
401
|
+
},
|
|
402
|
+
];
|
|
360
403
|
|
|
361
404
|
const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
|
|
362
405
|
|
|
@@ -368,28 +411,41 @@ describe("messageAssemblerProcessor", () => {
|
|
|
368
411
|
it("should preserve pending tool calls when stopReason is tool_use", () => {
|
|
369
412
|
state.currentMessageId = "msg_123";
|
|
370
413
|
state.pendingToolCalls["tool_123"] = { id: "tool_123", name: "test" };
|
|
371
|
-
state.pendingContents
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
414
|
+
state.pendingContents = [
|
|
415
|
+
{
|
|
416
|
+
type: "tool_use",
|
|
417
|
+
toolId: "tool_123",
|
|
418
|
+
toolName: "test",
|
|
419
|
+
toolInputJson: '{"q":"hello"}',
|
|
420
|
+
assembled: true,
|
|
421
|
+
parsedInput: { q: "hello" },
|
|
422
|
+
},
|
|
423
|
+
];
|
|
376
424
|
|
|
377
425
|
const event = createStreamEvent("message_stop", { stopReason: "tool_use" });
|
|
378
426
|
|
|
379
427
|
const [newState, outputs] = messageAssemblerProcessor(state, event);
|
|
380
428
|
|
|
381
|
-
|
|
429
|
+
// Emits assistant_message with tool call in content
|
|
430
|
+
expect(outputs).toHaveLength(1);
|
|
431
|
+
expect(outputs[0].type).toBe("assistant_message");
|
|
432
|
+
const content = outputs[0].data.content;
|
|
433
|
+
expect(content).toHaveLength(1);
|
|
434
|
+
expect(content[0].type).toBe("tool-call");
|
|
435
|
+
expect(content[0].id).toBe("tool_123");
|
|
436
|
+
|
|
382
437
|
expect(newState.pendingToolCalls["tool_123"]).toBeDefined();
|
|
383
438
|
});
|
|
384
439
|
|
|
385
440
|
it("should clear pending tool calls for non-tool_use stop reason", () => {
|
|
386
441
|
state.currentMessageId = "msg_123";
|
|
387
442
|
state.pendingToolCalls["tool_123"] = { id: "tool_123", name: "test" };
|
|
388
|
-
state.pendingContents
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
443
|
+
state.pendingContents = [
|
|
444
|
+
{
|
|
445
|
+
type: "text",
|
|
446
|
+
textDeltas: ["Done"],
|
|
447
|
+
},
|
|
448
|
+
];
|
|
393
449
|
|
|
394
450
|
const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
|
|
395
451
|
|
|
@@ -399,11 +455,12 @@ describe("messageAssemblerProcessor", () => {
|
|
|
399
455
|
});
|
|
400
456
|
|
|
401
457
|
it("should handle missing currentMessageId", () => {
|
|
402
|
-
state.pendingContents
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
458
|
+
state.pendingContents = [
|
|
459
|
+
{
|
|
460
|
+
type: "text",
|
|
461
|
+
textDeltas: ["Some text"],
|
|
462
|
+
},
|
|
463
|
+
];
|
|
407
464
|
|
|
408
465
|
const event = createStreamEvent("message_stop", { stopReason: "end_turn" });
|
|
409
466
|
|
|
@@ -433,7 +490,7 @@ describe("messageAssemblerProcessor", () => {
|
|
|
433
490
|
|
|
434
491
|
// Should reset state
|
|
435
492
|
expect(newState.currentMessageId).toBeNull();
|
|
436
|
-
expect(newState.pendingContents).toEqual(
|
|
493
|
+
expect(newState.pendingContents).toEqual([]);
|
|
437
494
|
});
|
|
438
495
|
|
|
439
496
|
it("should handle missing errorCode", () => {
|
|
@@ -529,15 +586,16 @@ describe("messageAssemblerProcessor", () => {
|
|
|
529
586
|
currentState = s3;
|
|
530
587
|
allOutputs.push(...o3);
|
|
531
588
|
|
|
532
|
-
// tool_use_stop
|
|
589
|
+
// tool_use_stop — no event emitted, marks as assembled
|
|
533
590
|
const [s4, o4] = messageAssemblerProcessor(
|
|
534
591
|
currentState,
|
|
535
592
|
createStreamEvent("tool_use_stop", {})
|
|
536
593
|
);
|
|
537
594
|
currentState = s4;
|
|
538
595
|
allOutputs.push(...o4);
|
|
596
|
+
expect(o4).toHaveLength(0);
|
|
539
597
|
|
|
540
|
-
// message_stop with tool_use
|
|
598
|
+
// message_stop with tool_use — emits assistant_message with tool call in content
|
|
541
599
|
const [s5, o5] = messageAssemblerProcessor(
|
|
542
600
|
currentState,
|
|
543
601
|
createStreamEvent("message_stop", { stopReason: "tool_use" })
|
|
@@ -557,11 +615,157 @@ describe("messageAssemblerProcessor", () => {
|
|
|
557
615
|
currentState = s6;
|
|
558
616
|
allOutputs.push(...o6);
|
|
559
617
|
|
|
618
|
+
// assistant_message (with tool call) + tool_result_message
|
|
560
619
|
expect(allOutputs).toHaveLength(2);
|
|
561
|
-
expect(allOutputs[0].type).toBe("
|
|
562
|
-
|
|
620
|
+
expect(allOutputs[0].type).toBe("assistant_message");
|
|
621
|
+
// Verify tool call is inside assistant message content
|
|
622
|
+
const content = allOutputs[0].data.content;
|
|
623
|
+
expect(content).toHaveLength(1);
|
|
624
|
+
expect(content[0].type).toBe("tool-call");
|
|
625
|
+
expect(content[0].name).toBe("search");
|
|
626
|
+
expect(content[0].input).toEqual({ query: "test" });
|
|
627
|
+
|
|
563
628
|
expect(allOutputs[1].type).toBe("tool_result_message");
|
|
564
629
|
expect(allOutputs[1].data.toolResult.output.value).toBe("Found it!");
|
|
565
630
|
});
|
|
631
|
+
|
|
632
|
+
it("should preserve interleaved text and tool call order", () => {
|
|
633
|
+
let currentState = createInitialMessageAssemblerState();
|
|
634
|
+
const allOutputs: MessageAssemblerOutput[] = [];
|
|
635
|
+
|
|
636
|
+
// message_start
|
|
637
|
+
const [s1, o1] = messageAssemblerProcessor(
|
|
638
|
+
currentState,
|
|
639
|
+
createStreamEvent("message_start", { messageId: "msg_1" }, 1000)
|
|
640
|
+
);
|
|
641
|
+
currentState = s1;
|
|
642
|
+
allOutputs.push(...o1);
|
|
643
|
+
|
|
644
|
+
// Text before tool
|
|
645
|
+
const [s2] = messageAssemblerProcessor(
|
|
646
|
+
currentState,
|
|
647
|
+
createStreamEvent("text_delta", { text: "Let me search for that." })
|
|
648
|
+
);
|
|
649
|
+
currentState = s2;
|
|
650
|
+
|
|
651
|
+
// tool_use_start
|
|
652
|
+
const [s3] = messageAssemblerProcessor(
|
|
653
|
+
currentState,
|
|
654
|
+
createStreamEvent("tool_use_start", { toolCallId: "tool_1", toolName: "search" })
|
|
655
|
+
);
|
|
656
|
+
currentState = s3;
|
|
657
|
+
|
|
658
|
+
// input_json_delta
|
|
659
|
+
const [s4] = messageAssemblerProcessor(
|
|
660
|
+
currentState,
|
|
661
|
+
createStreamEvent("input_json_delta", { partialJson: '{"q":"test"}' })
|
|
662
|
+
);
|
|
663
|
+
currentState = s4;
|
|
664
|
+
|
|
665
|
+
// tool_use_stop
|
|
666
|
+
const [s5] = messageAssemblerProcessor(currentState, createStreamEvent("tool_use_stop", {}));
|
|
667
|
+
currentState = s5;
|
|
668
|
+
|
|
669
|
+
// Text after tool
|
|
670
|
+
const [s6] = messageAssemblerProcessor(
|
|
671
|
+
currentState,
|
|
672
|
+
createStreamEvent("text_delta", { text: "Here are the results." })
|
|
673
|
+
);
|
|
674
|
+
currentState = s6;
|
|
675
|
+
|
|
676
|
+
// message_stop
|
|
677
|
+
const [, o7] = messageAssemblerProcessor(
|
|
678
|
+
currentState,
|
|
679
|
+
createStreamEvent("message_stop", { stopReason: "end_turn" })
|
|
680
|
+
);
|
|
681
|
+
allOutputs.push(...o7);
|
|
682
|
+
|
|
683
|
+
expect(allOutputs).toHaveLength(1);
|
|
684
|
+
expect(allOutputs[0].type).toBe("assistant_message");
|
|
685
|
+
|
|
686
|
+
const content = allOutputs[0].data.content;
|
|
687
|
+
// Order must be: text, tool-call, text (preserving stream order)
|
|
688
|
+
expect(content).toHaveLength(3);
|
|
689
|
+
expect(content[0].type).toBe("text");
|
|
690
|
+
expect(content[0].text).toBe("Let me search for that.");
|
|
691
|
+
expect(content[1].type).toBe("tool-call");
|
|
692
|
+
expect(content[1].name).toBe("search");
|
|
693
|
+
expect(content[1].input).toEqual({ q: "test" });
|
|
694
|
+
expect(content[2].type).toBe("text");
|
|
695
|
+
expect(content[2].text).toBe("Here are the results.");
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("should handle text + tool + text + tool interleaving", () => {
|
|
699
|
+
let currentState = createInitialMessageAssemblerState();
|
|
700
|
+
|
|
701
|
+
// message_start
|
|
702
|
+
const [s1] = messageAssemblerProcessor(
|
|
703
|
+
currentState,
|
|
704
|
+
createStreamEvent("message_start", { messageId: "msg_1" }, 1000)
|
|
705
|
+
);
|
|
706
|
+
currentState = s1;
|
|
707
|
+
|
|
708
|
+
// Text 1
|
|
709
|
+
const [s2] = messageAssemblerProcessor(
|
|
710
|
+
currentState,
|
|
711
|
+
createStreamEvent("text_delta", { text: "First " })
|
|
712
|
+
);
|
|
713
|
+
currentState = s2;
|
|
714
|
+
|
|
715
|
+
// Tool 1
|
|
716
|
+
const [s3] = messageAssemblerProcessor(
|
|
717
|
+
currentState,
|
|
718
|
+
createStreamEvent("tool_use_start", { toolCallId: "t1", toolName: "toolA" })
|
|
719
|
+
);
|
|
720
|
+
currentState = s3;
|
|
721
|
+
const [s4] = messageAssemblerProcessor(
|
|
722
|
+
currentState,
|
|
723
|
+
createStreamEvent("input_json_delta", { partialJson: '{"a":1}' })
|
|
724
|
+
);
|
|
725
|
+
currentState = s4;
|
|
726
|
+
const [s5] = messageAssemblerProcessor(currentState, createStreamEvent("tool_use_stop", {}));
|
|
727
|
+
currentState = s5;
|
|
728
|
+
|
|
729
|
+
// Text 2
|
|
730
|
+
const [s6] = messageAssemblerProcessor(
|
|
731
|
+
currentState,
|
|
732
|
+
createStreamEvent("text_delta", { text: "Second " })
|
|
733
|
+
);
|
|
734
|
+
currentState = s6;
|
|
735
|
+
|
|
736
|
+
// Tool 2
|
|
737
|
+
const [s7] = messageAssemblerProcessor(
|
|
738
|
+
currentState,
|
|
739
|
+
createStreamEvent("tool_use_start", { toolCallId: "t2", toolName: "toolB" })
|
|
740
|
+
);
|
|
741
|
+
currentState = s7;
|
|
742
|
+
const [s8] = messageAssemblerProcessor(
|
|
743
|
+
currentState,
|
|
744
|
+
createStreamEvent("input_json_delta", { partialJson: '{"b":2}' })
|
|
745
|
+
);
|
|
746
|
+
currentState = s8;
|
|
747
|
+
const [s9] = messageAssemblerProcessor(currentState, createStreamEvent("tool_use_stop", {}));
|
|
748
|
+
currentState = s9;
|
|
749
|
+
|
|
750
|
+
// message_stop
|
|
751
|
+
const [, outputs] = messageAssemblerProcessor(
|
|
752
|
+
currentState,
|
|
753
|
+
createStreamEvent("message_stop", { stopReason: "tool_use" })
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
expect(outputs).toHaveLength(1);
|
|
757
|
+
const content = outputs[0].data.content;
|
|
758
|
+
|
|
759
|
+
// Must preserve: text, tool, text, tool
|
|
760
|
+
expect(content).toHaveLength(4);
|
|
761
|
+
expect(content[0]).toEqual({ type: "text", text: "First " });
|
|
762
|
+
expect(content[1].type).toBe("tool-call");
|
|
763
|
+
expect(content[1].name).toBe("toolA");
|
|
764
|
+
expect(content[1].input).toEqual({ a: 1 });
|
|
765
|
+
expect(content[2]).toEqual({ type: "text", text: "Second " });
|
|
766
|
+
expect(content[3].type).toBe("tool-call");
|
|
767
|
+
expect(content[3].name).toBe("toolB");
|
|
768
|
+
expect(content[3].input).toEqual({ b: 2 });
|
|
769
|
+
});
|
|
566
770
|
});
|
|
567
771
|
});
|