kernl 0.1.4 → 0.2.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +21 -0
- package/dist/agent.d.ts +20 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +61 -41
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/kernl.d.ts +27 -1
- package/dist/kernl.d.ts.map +1 -1
- package/dist/kernl.js +36 -2
- package/dist/mcp/__tests__/integration.test.js +16 -0
- package/dist/thread/__tests__/fixtures/mock-model.d.ts +7 -0
- package/dist/thread/__tests__/fixtures/mock-model.d.ts.map +1 -0
- package/dist/thread/__tests__/fixtures/mock-model.js +59 -0
- package/dist/thread/__tests__/integration.test.d.ts +2 -0
- package/dist/thread/__tests__/integration.test.d.ts.map +1 -0
- package/dist/thread/__tests__/integration.test.js +315 -0
- package/dist/thread/__tests__/stream.test.d.ts +2 -0
- package/dist/thread/__tests__/stream.test.d.ts.map +1 -0
- package/dist/thread/__tests__/stream.test.js +244 -0
- package/dist/thread/__tests__/thread.test.js +612 -763
- package/dist/thread/thread.d.ts +30 -25
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/thread/thread.js +114 -314
- package/dist/thread/utils.d.ts +16 -1
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/utils.js +30 -0
- package/dist/tool/index.d.ts +1 -1
- package/dist/tool/index.d.ts.map +1 -1
- package/dist/tool/index.js +1 -1
- package/dist/tool/tool.d.ts.map +1 -1
- package/dist/tool/tool.js +6 -2
- package/dist/tool/toolkit.d.ts +7 -3
- package/dist/tool/toolkit.d.ts.map +1 -1
- package/dist/tool/toolkit.js +7 -3
- package/dist/types/agent.d.ts +5 -5
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/thread.d.ts +10 -16
- package/dist/types/thread.d.ts.map +1 -1
- package/package.json +7 -5
- package/src/agent.ts +99 -86
- package/src/index.ts +1 -1
- package/src/kernl.ts +51 -2
- package/src/mcp/__tests__/integration.test.ts +17 -0
- package/src/thread/__tests__/fixtures/mock-model.ts +71 -0
- package/src/thread/__tests__/integration.test.ts +449 -0
- package/src/thread/__tests__/thread.test.ts +625 -775
- package/src/thread/thread.ts +134 -381
- package/src/thread/utils.ts +36 -1
- package/src/tool/index.ts +1 -1
- package/src/tool/tool.ts +6 -2
- package/src/tool/toolkit.ts +10 -3
- package/src/types/agent.ts +9 -6
- package/src/types/thread.ts +25 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { LanguageModelRequest } from "@kernl-sdk/protocol";
|
|
5
5
|
import { IN_PROGRESS, COMPLETED, FAILED } from "@kernl-sdk/protocol";
|
|
6
6
|
|
|
7
7
|
import { Thread } from "../thread";
|
|
@@ -12,37 +12,42 @@ import { tool, FunctionToolkit } from "@/tool";
|
|
|
12
12
|
import { ModelBehaviorError } from "@/lib/error";
|
|
13
13
|
|
|
14
14
|
import type { ThreadEvent } from "@/types/thread";
|
|
15
|
+
import { createMockModel } from "./fixtures/mock-model";
|
|
16
|
+
|
|
17
|
+
// Helper to create user message input
|
|
18
|
+
function userMessage(text: string): ThreadEvent[] {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
kind: "message" as const,
|
|
22
|
+
id: "msg-test",
|
|
23
|
+
role: "user" as const,
|
|
24
|
+
content: [{ kind: "text" as const, text }],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
}
|
|
15
28
|
|
|
16
29
|
describe("Thread", () => {
|
|
17
30
|
describe("Basic Execution", () => {
|
|
18
31
|
it("should execute single turn and terminate with exact history", async () => {
|
|
19
|
-
const model:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
kind: "message" as const,
|
|
28
|
-
id: "msg_1",
|
|
29
|
-
role: "assistant" as const,
|
|
30
|
-
content: [{ kind: "text" as const, text: "Hello world" }],
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
finishReason: "stop",
|
|
34
|
-
usage: {
|
|
35
|
-
inputTokens: 2,
|
|
36
|
-
outputTokens: 2,
|
|
37
|
-
totalTokens: 4,
|
|
32
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
kind: "message" as const,
|
|
37
|
+
id: "msg_1",
|
|
38
|
+
role: "assistant" as const,
|
|
39
|
+
content: [{ kind: "text" as const, text: "Hello world" }],
|
|
38
40
|
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
],
|
|
42
|
+
finishReason: "stop",
|
|
43
|
+
usage: {
|
|
44
|
+
inputTokens: 2,
|
|
45
|
+
outputTokens: 2,
|
|
46
|
+
totalTokens: 4,
|
|
47
|
+
},
|
|
48
|
+
warnings: [],
|
|
49
|
+
};
|
|
50
|
+
});
|
|
46
51
|
|
|
47
52
|
const agent = new Agent({
|
|
48
53
|
id: "test",
|
|
@@ -52,7 +57,7 @@ describe("Thread", () => {
|
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
const kernl = new Kernl();
|
|
55
|
-
const thread = new Thread(kernl, agent, "Hello world");
|
|
60
|
+
const thread = new Thread(kernl, agent, userMessage("Hello world"));
|
|
56
61
|
|
|
57
62
|
const result = await thread.execute();
|
|
58
63
|
|
|
@@ -74,38 +79,29 @@ describe("Thread", () => {
|
|
|
74
79
|
},
|
|
75
80
|
]);
|
|
76
81
|
|
|
77
|
-
expect(
|
|
78
|
-
expect(result.state.modelResponses).toHaveLength(1);
|
|
82
|
+
expect(thread._tick).toBe(1);
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
it("should convert string input to UserMessage", async () => {
|
|
82
|
-
const model:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
{
|
|
90
|
-
kind: "message" as const,
|
|
91
|
-
id: "msg_1",
|
|
92
|
-
role: "assistant" as const,
|
|
93
|
-
content: [{ kind: "text" as const, text: "Response" }],
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
finishReason: "stop",
|
|
97
|
-
usage: {
|
|
98
|
-
inputTokens: 2,
|
|
99
|
-
outputTokens: 2,
|
|
100
|
-
totalTokens: 4,
|
|
86
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
kind: "message" as const,
|
|
91
|
+
id: "msg_1",
|
|
92
|
+
role: "assistant" as const,
|
|
93
|
+
content: [{ kind: "text" as const, text: "Response" }],
|
|
101
94
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
95
|
+
],
|
|
96
|
+
finishReason: "stop",
|
|
97
|
+
usage: {
|
|
98
|
+
inputTokens: 2,
|
|
99
|
+
outputTokens: 2,
|
|
100
|
+
totalTokens: 4,
|
|
101
|
+
},
|
|
102
|
+
warnings: [],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
109
105
|
|
|
110
106
|
const agent = new Agent({
|
|
111
107
|
id: "test",
|
|
@@ -115,7 +111,7 @@ describe("Thread", () => {
|
|
|
115
111
|
});
|
|
116
112
|
|
|
117
113
|
const kernl = new Kernl();
|
|
118
|
-
const thread = new Thread(kernl, agent, "Test input");
|
|
114
|
+
const thread = new Thread(kernl, agent, userMessage("Test input"));
|
|
119
115
|
|
|
120
116
|
await thread.execute();
|
|
121
117
|
|
|
@@ -131,33 +127,25 @@ describe("Thread", () => {
|
|
|
131
127
|
});
|
|
132
128
|
|
|
133
129
|
it("should use array input as-is", async () => {
|
|
134
|
-
const model:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
kind: "message" as const,
|
|
143
|
-
id: "msg_1",
|
|
144
|
-
role: "assistant" as const,
|
|
145
|
-
content: [{ kind: "text" as const, text: "Response" }],
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
finishReason: "stop",
|
|
149
|
-
usage: {
|
|
150
|
-
inputTokens: 2,
|
|
151
|
-
outputTokens: 2,
|
|
152
|
-
totalTokens: 4,
|
|
130
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
kind: "message" as const,
|
|
135
|
+
id: "msg_1",
|
|
136
|
+
role: "assistant" as const,
|
|
137
|
+
content: [{ kind: "text" as const, text: "Response" }],
|
|
153
138
|
},
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
139
|
+
],
|
|
140
|
+
finishReason: "stop",
|
|
141
|
+
usage: {
|
|
142
|
+
inputTokens: 2,
|
|
143
|
+
outputTokens: 2,
|
|
144
|
+
totalTokens: 4,
|
|
145
|
+
},
|
|
146
|
+
warnings: [],
|
|
147
|
+
};
|
|
148
|
+
});
|
|
161
149
|
|
|
162
150
|
const agent = new Agent({
|
|
163
151
|
id: "test",
|
|
@@ -191,64 +179,56 @@ describe("Thread", () => {
|
|
|
191
179
|
it("should execute multi-turn with tool call and exact history", async () => {
|
|
192
180
|
let callCount = 0;
|
|
193
181
|
|
|
194
|
-
const model:
|
|
195
|
-
|
|
196
|
-
provider: "test",
|
|
197
|
-
modelId: "test-model",
|
|
198
|
-
async generate(req: LanguageModelRequest) {
|
|
199
|
-
callCount++;
|
|
200
|
-
|
|
201
|
-
// First call: return tool call
|
|
202
|
-
if (callCount === 1) {
|
|
203
|
-
return {
|
|
204
|
-
content: [
|
|
205
|
-
{
|
|
206
|
-
kind: "message" as const,
|
|
207
|
-
id: "msg_1",
|
|
208
|
-
role: "assistant" as const,
|
|
209
|
-
content: [],
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
kind: "tool-call" as const,
|
|
213
|
-
toolId: "echo",
|
|
214
|
-
state: IN_PROGRESS,
|
|
215
|
-
callId: "call_1",
|
|
216
|
-
arguments: JSON.stringify({ text: "test" }),
|
|
217
|
-
},
|
|
218
|
-
],
|
|
219
|
-
finishReason: "stop",
|
|
220
|
-
usage: {
|
|
221
|
-
inputTokens: 2,
|
|
222
|
-
outputTokens: 2,
|
|
223
|
-
totalTokens: 4,
|
|
224
|
-
},
|
|
225
|
-
warnings: [],
|
|
226
|
-
};
|
|
227
|
-
}
|
|
182
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
183
|
+
callCount++;
|
|
228
184
|
|
|
229
|
-
|
|
185
|
+
// First call: return tool call
|
|
186
|
+
if (callCount === 1) {
|
|
230
187
|
return {
|
|
231
188
|
content: [
|
|
232
189
|
{
|
|
233
190
|
kind: "message" as const,
|
|
234
|
-
id: "
|
|
191
|
+
id: "msg_1",
|
|
235
192
|
role: "assistant" as const,
|
|
236
|
-
content: [
|
|
193
|
+
content: [],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
kind: "tool-call" as const,
|
|
197
|
+
toolId: "echo",
|
|
198
|
+
state: IN_PROGRESS,
|
|
199
|
+
callId: "call_1",
|
|
200
|
+
arguments: JSON.stringify({ text: "test" }),
|
|
237
201
|
},
|
|
238
202
|
],
|
|
239
203
|
finishReason: "stop",
|
|
240
204
|
usage: {
|
|
241
|
-
inputTokens:
|
|
205
|
+
inputTokens: 2,
|
|
242
206
|
outputTokens: 2,
|
|
243
|
-
totalTokens:
|
|
207
|
+
totalTokens: 4,
|
|
244
208
|
},
|
|
245
209
|
warnings: [],
|
|
246
210
|
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Second call: return final message
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
kind: "message" as const,
|
|
218
|
+
id: "msg_2",
|
|
219
|
+
role: "assistant" as const,
|
|
220
|
+
content: [{ kind: "text" as const, text: "Done!" }],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
finishReason: "stop",
|
|
224
|
+
usage: {
|
|
225
|
+
inputTokens: 4,
|
|
226
|
+
outputTokens: 2,
|
|
227
|
+
totalTokens: 6,
|
|
228
|
+
},
|
|
229
|
+
warnings: [],
|
|
230
|
+
};
|
|
231
|
+
});
|
|
252
232
|
|
|
253
233
|
const echoTool = tool({
|
|
254
234
|
id: "echo",
|
|
@@ -268,7 +248,7 @@ describe("Thread", () => {
|
|
|
268
248
|
});
|
|
269
249
|
|
|
270
250
|
const kernl = new Kernl();
|
|
271
|
-
const thread = new Thread(kernl, agent, "Use the echo tool");
|
|
251
|
+
const thread = new Thread(kernl, agent, userMessage("Use the echo tool"));
|
|
272
252
|
|
|
273
253
|
const result = await thread.execute();
|
|
274
254
|
|
|
@@ -292,16 +272,16 @@ describe("Thread", () => {
|
|
|
292
272
|
// Tool call (tick 1)
|
|
293
273
|
{
|
|
294
274
|
kind: "tool-call",
|
|
295
|
-
|
|
275
|
+
toolId: "echo",
|
|
296
276
|
callId: "call_1",
|
|
297
|
-
|
|
277
|
+
state: IN_PROGRESS,
|
|
298
278
|
arguments: JSON.stringify({ text: "test" }),
|
|
299
279
|
},
|
|
300
280
|
// Tool result (executed after tick 1)
|
|
301
281
|
{
|
|
302
282
|
kind: "tool-result",
|
|
303
283
|
callId: "call_1",
|
|
304
|
-
|
|
284
|
+
toolId: "echo",
|
|
305
285
|
state: COMPLETED,
|
|
306
286
|
result: "Echo: test",
|
|
307
287
|
error: null,
|
|
@@ -315,96 +295,87 @@ describe("Thread", () => {
|
|
|
315
295
|
},
|
|
316
296
|
]);
|
|
317
297
|
|
|
318
|
-
expect(
|
|
319
|
-
expect(result.state.modelResponses).toHaveLength(2);
|
|
298
|
+
expect(thread._tick).toBe(2);
|
|
320
299
|
});
|
|
321
300
|
|
|
322
301
|
it("should accumulate history across multiple turns", async () => {
|
|
323
302
|
let callCount = 0;
|
|
324
303
|
|
|
325
|
-
const model:
|
|
326
|
-
|
|
327
|
-
provider: "test",
|
|
328
|
-
modelId: "test-model",
|
|
329
|
-
async generate(req: LanguageModelRequest) {
|
|
330
|
-
callCount++;
|
|
304
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
305
|
+
callCount++;
|
|
331
306
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
kind: "tool-call" as const,
|
|
343
|
-
toolId: "simple",
|
|
344
|
-
state: IN_PROGRESS,
|
|
345
|
-
callId: "call_1",
|
|
346
|
-
arguments: "first",
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
finishReason: "stop",
|
|
350
|
-
usage: {
|
|
351
|
-
inputTokens: 2,
|
|
352
|
-
outputTokens: 2,
|
|
353
|
-
totalTokens: 4,
|
|
307
|
+
if (callCount === 1) {
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
kind: "message" as const,
|
|
312
|
+
id: "msg_1",
|
|
313
|
+
role: "assistant" as const,
|
|
314
|
+
content: [],
|
|
354
315
|
},
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
content: [
|
|
362
|
-
{
|
|
363
|
-
kind: "message" as const,
|
|
364
|
-
id: "msg_2",
|
|
365
|
-
role: "assistant" as const,
|
|
366
|
-
content: [],
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
kind: "tool-call" as const,
|
|
370
|
-
toolId: "simple",
|
|
371
|
-
state: IN_PROGRESS,
|
|
372
|
-
callId: "call_2",
|
|
373
|
-
arguments: "second",
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
finishReason: "stop",
|
|
377
|
-
usage: {
|
|
378
|
-
inputTokens: 3,
|
|
379
|
-
outputTokens: 2,
|
|
380
|
-
totalTokens: 5,
|
|
316
|
+
{
|
|
317
|
+
kind: "tool-call" as const,
|
|
318
|
+
toolId: "simple",
|
|
319
|
+
state: IN_PROGRESS,
|
|
320
|
+
callId: "call_1",
|
|
321
|
+
arguments: "first",
|
|
381
322
|
},
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
323
|
+
],
|
|
324
|
+
finishReason: "stop",
|
|
325
|
+
usage: {
|
|
326
|
+
inputTokens: 2,
|
|
327
|
+
outputTokens: 2,
|
|
328
|
+
totalTokens: 4,
|
|
329
|
+
},
|
|
330
|
+
warnings: [],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
385
333
|
|
|
334
|
+
if (callCount === 2) {
|
|
386
335
|
return {
|
|
387
336
|
content: [
|
|
388
337
|
{
|
|
389
338
|
kind: "message" as const,
|
|
390
|
-
id: "
|
|
339
|
+
id: "msg_2",
|
|
391
340
|
role: "assistant" as const,
|
|
392
|
-
content: [
|
|
341
|
+
content: [],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
kind: "tool-call" as const,
|
|
345
|
+
toolId: "simple",
|
|
346
|
+
state: IN_PROGRESS,
|
|
347
|
+
callId: "call_2",
|
|
348
|
+
arguments: "second",
|
|
393
349
|
},
|
|
394
350
|
],
|
|
395
351
|
finishReason: "stop",
|
|
396
352
|
usage: {
|
|
397
|
-
inputTokens:
|
|
353
|
+
inputTokens: 3,
|
|
398
354
|
outputTokens: 2,
|
|
399
|
-
totalTokens:
|
|
355
|
+
totalTokens: 5,
|
|
400
356
|
},
|
|
401
357
|
warnings: [],
|
|
402
358
|
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
content: [
|
|
363
|
+
{
|
|
364
|
+
kind: "message" as const,
|
|
365
|
+
id: "msg_3",
|
|
366
|
+
role: "assistant" as const,
|
|
367
|
+
content: [{ kind: "text" as const, text: "All done" }],
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
finishReason: "stop",
|
|
371
|
+
usage: {
|
|
372
|
+
inputTokens: 4,
|
|
373
|
+
outputTokens: 2,
|
|
374
|
+
totalTokens: 6,
|
|
375
|
+
},
|
|
376
|
+
warnings: [],
|
|
377
|
+
};
|
|
378
|
+
});
|
|
408
379
|
|
|
409
380
|
const simpleTool = tool({
|
|
410
381
|
id: "simple",
|
|
@@ -424,7 +395,7 @@ describe("Thread", () => {
|
|
|
424
395
|
});
|
|
425
396
|
|
|
426
397
|
const kernl = new Kernl();
|
|
427
|
-
const thread = new Thread(kernl, agent, "Start");
|
|
398
|
+
const thread = new Thread(kernl, agent, userMessage("Start"));
|
|
428
399
|
|
|
429
400
|
const result = await thread.execute();
|
|
430
401
|
|
|
@@ -432,7 +403,7 @@ describe("Thread", () => {
|
|
|
432
403
|
|
|
433
404
|
// Should have: 1 user msg + 3 assistant msgs + 2 tool calls + 2 tool results = 8 events
|
|
434
405
|
expect(history).toHaveLength(8);
|
|
435
|
-
expect(
|
|
406
|
+
expect(thread._tick).toBe(3);
|
|
436
407
|
});
|
|
437
408
|
});
|
|
438
409
|
|
|
@@ -440,49 +411,25 @@ describe("Thread", () => {
|
|
|
440
411
|
it("should handle tool not found with exact error in history", async () => {
|
|
441
412
|
let callCount = 0;
|
|
442
413
|
|
|
443
|
-
const model:
|
|
444
|
-
|
|
445
|
-
provider: "test",
|
|
446
|
-
modelId: "test-model",
|
|
447
|
-
async generate(req: LanguageModelRequest) {
|
|
448
|
-
callCount++;
|
|
414
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
415
|
+
callCount++;
|
|
449
416
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return {
|
|
453
|
-
content: [
|
|
454
|
-
{
|
|
455
|
-
kind: "message" as const,
|
|
456
|
-
id: "msg_1",
|
|
457
|
-
role: "assistant" as const,
|
|
458
|
-
content: [],
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
kind: "tool-call" as const,
|
|
462
|
-
toolId: "nonexistent",
|
|
463
|
-
state: IN_PROGRESS,
|
|
464
|
-
callId: "call_1",
|
|
465
|
-
arguments: "{}",
|
|
466
|
-
},
|
|
467
|
-
],
|
|
468
|
-
finishReason: "stop",
|
|
469
|
-
usage: {
|
|
470
|
-
inputTokens: 2,
|
|
471
|
-
outputTokens: 2,
|
|
472
|
-
totalTokens: 4,
|
|
473
|
-
},
|
|
474
|
-
warnings: [],
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Second call: return terminal message
|
|
417
|
+
// First call: return tool call
|
|
418
|
+
if (callCount === 1) {
|
|
479
419
|
return {
|
|
480
420
|
content: [
|
|
481
421
|
{
|
|
482
422
|
kind: "message" as const,
|
|
483
|
-
id: "
|
|
423
|
+
id: "msg_1",
|
|
484
424
|
role: "assistant" as const,
|
|
485
|
-
content: [
|
|
425
|
+
content: [],
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
kind: "tool-call" as const,
|
|
429
|
+
toolId: "nonexistent",
|
|
430
|
+
state: IN_PROGRESS,
|
|
431
|
+
callId: "call_1",
|
|
432
|
+
arguments: "{}",
|
|
486
433
|
},
|
|
487
434
|
],
|
|
488
435
|
finishReason: "stop",
|
|
@@ -493,11 +440,27 @@ describe("Thread", () => {
|
|
|
493
440
|
},
|
|
494
441
|
warnings: [],
|
|
495
442
|
};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Second call: return terminal message
|
|
446
|
+
return {
|
|
447
|
+
content: [
|
|
448
|
+
{
|
|
449
|
+
kind: "message" as const,
|
|
450
|
+
id: "msg_2",
|
|
451
|
+
role: "assistant" as const,
|
|
452
|
+
content: [{ kind: "text" as const, text: "Done" }],
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
finishReason: "stop",
|
|
456
|
+
usage: {
|
|
457
|
+
inputTokens: 2,
|
|
458
|
+
outputTokens: 2,
|
|
459
|
+
totalTokens: 4,
|
|
460
|
+
},
|
|
461
|
+
warnings: [],
|
|
462
|
+
};
|
|
463
|
+
});
|
|
501
464
|
|
|
502
465
|
const agent = new Agent({
|
|
503
466
|
id: "test",
|
|
@@ -508,7 +471,7 @@ describe("Thread", () => {
|
|
|
508
471
|
});
|
|
509
472
|
|
|
510
473
|
const kernl = new Kernl();
|
|
511
|
-
const thread = new Thread(kernl, agent, "test");
|
|
474
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
512
475
|
|
|
513
476
|
await thread.execute();
|
|
514
477
|
|
|
@@ -529,49 +492,25 @@ describe("Thread", () => {
|
|
|
529
492
|
it("should handle tool execution error", async () => {
|
|
530
493
|
let callCount = 0;
|
|
531
494
|
|
|
532
|
-
const model:
|
|
533
|
-
|
|
534
|
-
provider: "test",
|
|
535
|
-
modelId: "test-model",
|
|
536
|
-
async generate(req: LanguageModelRequest) {
|
|
537
|
-
callCount++;
|
|
495
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
496
|
+
callCount++;
|
|
538
497
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return {
|
|
542
|
-
content: [
|
|
543
|
-
{
|
|
544
|
-
kind: "message" as const,
|
|
545
|
-
id: "msg_1",
|
|
546
|
-
role: "assistant" as const,
|
|
547
|
-
content: [],
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
kind: "tool-call" as const,
|
|
551
|
-
toolId: "failing",
|
|
552
|
-
state: IN_PROGRESS,
|
|
553
|
-
callId: "call_1",
|
|
554
|
-
arguments: "{}",
|
|
555
|
-
},
|
|
556
|
-
],
|
|
557
|
-
finishReason: "stop",
|
|
558
|
-
usage: {
|
|
559
|
-
inputTokens: 2,
|
|
560
|
-
outputTokens: 2,
|
|
561
|
-
totalTokens: 4,
|
|
562
|
-
},
|
|
563
|
-
warnings: [],
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Second call: return terminal message
|
|
498
|
+
// First call: return tool call
|
|
499
|
+
if (callCount === 1) {
|
|
568
500
|
return {
|
|
569
501
|
content: [
|
|
570
502
|
{
|
|
571
503
|
kind: "message" as const,
|
|
572
|
-
id: "
|
|
504
|
+
id: "msg_1",
|
|
573
505
|
role: "assistant" as const,
|
|
574
|
-
content: [
|
|
506
|
+
content: [],
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
kind: "tool-call" as const,
|
|
510
|
+
toolId: "failing",
|
|
511
|
+
state: IN_PROGRESS,
|
|
512
|
+
callId: "call_1",
|
|
513
|
+
arguments: "{}",
|
|
575
514
|
},
|
|
576
515
|
],
|
|
577
516
|
finishReason: "stop",
|
|
@@ -582,11 +521,27 @@ describe("Thread", () => {
|
|
|
582
521
|
},
|
|
583
522
|
warnings: [],
|
|
584
523
|
};
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Second call: return terminal message
|
|
527
|
+
return {
|
|
528
|
+
content: [
|
|
529
|
+
{
|
|
530
|
+
kind: "message" as const,
|
|
531
|
+
id: "msg_2",
|
|
532
|
+
role: "assistant" as const,
|
|
533
|
+
content: [{ kind: "text" as const, text: "Done" }],
|
|
534
|
+
},
|
|
535
|
+
],
|
|
536
|
+
finishReason: "stop",
|
|
537
|
+
usage: {
|
|
538
|
+
inputTokens: 2,
|
|
539
|
+
outputTokens: 2,
|
|
540
|
+
totalTokens: 4,
|
|
541
|
+
},
|
|
542
|
+
warnings: [],
|
|
543
|
+
};
|
|
544
|
+
});
|
|
590
545
|
|
|
591
546
|
const failingTool = tool({
|
|
592
547
|
id: "failing",
|
|
@@ -608,7 +563,7 @@ describe("Thread", () => {
|
|
|
608
563
|
});
|
|
609
564
|
|
|
610
565
|
const kernl = new Kernl();
|
|
611
|
-
const thread = new Thread(kernl, agent, "test");
|
|
566
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
612
567
|
|
|
613
568
|
await thread.execute();
|
|
614
569
|
|
|
@@ -628,49 +583,25 @@ describe("Thread", () => {
|
|
|
628
583
|
it("should execute tool successfully with result in history", async () => {
|
|
629
584
|
let callCount = 0;
|
|
630
585
|
|
|
631
|
-
const model:
|
|
632
|
-
|
|
633
|
-
provider: "test",
|
|
634
|
-
modelId: "test-model",
|
|
635
|
-
async generate(req: LanguageModelRequest) {
|
|
636
|
-
callCount++;
|
|
637
|
-
|
|
638
|
-
// First call: return tool call
|
|
639
|
-
if (callCount === 1) {
|
|
640
|
-
return {
|
|
641
|
-
content: [
|
|
642
|
-
{
|
|
643
|
-
kind: "message" as const,
|
|
644
|
-
id: "msg_1",
|
|
645
|
-
role: "assistant" as const,
|
|
646
|
-
content: [],
|
|
647
|
-
},
|
|
648
|
-
{
|
|
649
|
-
kind: "tool-call" as const,
|
|
650
|
-
toolId: "add",
|
|
651
|
-
state: IN_PROGRESS,
|
|
652
|
-
callId: "call_1",
|
|
653
|
-
arguments: JSON.stringify({ a: 5, b: 3 }),
|
|
654
|
-
},
|
|
655
|
-
],
|
|
656
|
-
finishReason: "stop",
|
|
657
|
-
usage: {
|
|
658
|
-
inputTokens: 2,
|
|
659
|
-
outputTokens: 2,
|
|
660
|
-
totalTokens: 4,
|
|
661
|
-
},
|
|
662
|
-
warnings: [],
|
|
663
|
-
};
|
|
664
|
-
}
|
|
586
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
587
|
+
callCount++;
|
|
665
588
|
|
|
666
|
-
|
|
589
|
+
// First call: return tool call
|
|
590
|
+
if (callCount === 1) {
|
|
667
591
|
return {
|
|
668
592
|
content: [
|
|
669
593
|
{
|
|
670
594
|
kind: "message" as const,
|
|
671
|
-
id: "
|
|
595
|
+
id: "msg_1",
|
|
672
596
|
role: "assistant" as const,
|
|
673
|
-
content: [
|
|
597
|
+
content: [],
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
kind: "tool-call" as const,
|
|
601
|
+
toolId: "add",
|
|
602
|
+
state: IN_PROGRESS,
|
|
603
|
+
callId: "call_1",
|
|
604
|
+
arguments: JSON.stringify({ a: 5, b: 3 }),
|
|
674
605
|
},
|
|
675
606
|
],
|
|
676
607
|
finishReason: "stop",
|
|
@@ -681,11 +612,27 @@ describe("Thread", () => {
|
|
|
681
612
|
},
|
|
682
613
|
warnings: [],
|
|
683
614
|
};
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Second call: return terminal message
|
|
618
|
+
return {
|
|
619
|
+
content: [
|
|
620
|
+
{
|
|
621
|
+
kind: "message" as const,
|
|
622
|
+
id: "msg_2",
|
|
623
|
+
role: "assistant" as const,
|
|
624
|
+
content: [{ kind: "text" as const, text: "Done" }],
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
finishReason: "stop",
|
|
628
|
+
usage: {
|
|
629
|
+
inputTokens: 2,
|
|
630
|
+
outputTokens: 2,
|
|
631
|
+
totalTokens: 4,
|
|
632
|
+
},
|
|
633
|
+
warnings: [],
|
|
634
|
+
};
|
|
635
|
+
});
|
|
689
636
|
|
|
690
637
|
const addTool = tool({
|
|
691
638
|
id: "add",
|
|
@@ -703,11 +650,12 @@ describe("Thread", () => {
|
|
|
703
650
|
});
|
|
704
651
|
|
|
705
652
|
const kernl = new Kernl();
|
|
706
|
-
const thread = new Thread(kernl, agent, "Add 5 and 3");
|
|
653
|
+
const thread = new Thread(kernl, agent, userMessage("Add 5 and 3"));
|
|
707
654
|
|
|
708
655
|
await thread.execute();
|
|
709
656
|
|
|
710
|
-
|
|
657
|
+
// @ts-expect-error
|
|
658
|
+
const history = thread.history as ThreadEvent[];
|
|
711
659
|
|
|
712
660
|
const toolResult = history.find((e) => e.kind === "tool-result");
|
|
713
661
|
expect(toolResult).toEqual({
|
|
@@ -725,56 +673,32 @@ describe("Thread", () => {
|
|
|
725
673
|
it("should execute multiple tools in parallel with exact history", async () => {
|
|
726
674
|
let callCount = 0;
|
|
727
675
|
|
|
728
|
-
const model:
|
|
729
|
-
|
|
730
|
-
provider: "test",
|
|
731
|
-
modelId: "test-model",
|
|
732
|
-
async generate(req: LanguageModelRequest) {
|
|
733
|
-
callCount++;
|
|
676
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
677
|
+
callCount++;
|
|
734
678
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return {
|
|
738
|
-
content: [
|
|
739
|
-
{
|
|
740
|
-
kind: "message" as const,
|
|
741
|
-
id: "msg_1",
|
|
742
|
-
role: "assistant" as const,
|
|
743
|
-
content: [],
|
|
744
|
-
},
|
|
745
|
-
{
|
|
746
|
-
kind: "tool-call" as const,
|
|
747
|
-
toolId: "tool1",
|
|
748
|
-
state: IN_PROGRESS,
|
|
749
|
-
callId: "call_1",
|
|
750
|
-
arguments: JSON.stringify({ value: "a" }),
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
kind: "tool-call" as const,
|
|
754
|
-
toolId: "tool2",
|
|
755
|
-
state: IN_PROGRESS,
|
|
756
|
-
callId: "call_2",
|
|
757
|
-
arguments: JSON.stringify({ value: "b" }),
|
|
758
|
-
},
|
|
759
|
-
],
|
|
760
|
-
finishReason: "stop",
|
|
761
|
-
usage: {
|
|
762
|
-
inputTokens: 2,
|
|
763
|
-
outputTokens: 2,
|
|
764
|
-
totalTokens: 4,
|
|
765
|
-
},
|
|
766
|
-
warnings: [],
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Second call: return terminal message
|
|
679
|
+
// First call: return multiple tool calls
|
|
680
|
+
if (callCount === 1) {
|
|
771
681
|
return {
|
|
772
682
|
content: [
|
|
773
683
|
{
|
|
774
684
|
kind: "message" as const,
|
|
775
|
-
id: "
|
|
685
|
+
id: "msg_1",
|
|
776
686
|
role: "assistant" as const,
|
|
777
|
-
content: [
|
|
687
|
+
content: [],
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
kind: "tool-call" as const,
|
|
691
|
+
toolId: "tool1",
|
|
692
|
+
state: IN_PROGRESS,
|
|
693
|
+
callId: "call_1",
|
|
694
|
+
arguments: JSON.stringify({ value: "a" }),
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
kind: "tool-call" as const,
|
|
698
|
+
toolId: "tool2",
|
|
699
|
+
state: IN_PROGRESS,
|
|
700
|
+
callId: "call_2",
|
|
701
|
+
arguments: JSON.stringify({ value: "b" }),
|
|
778
702
|
},
|
|
779
703
|
],
|
|
780
704
|
finishReason: "stop",
|
|
@@ -785,11 +709,27 @@ describe("Thread", () => {
|
|
|
785
709
|
},
|
|
786
710
|
warnings: [],
|
|
787
711
|
};
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Second call: return terminal message
|
|
715
|
+
return {
|
|
716
|
+
content: [
|
|
717
|
+
{
|
|
718
|
+
kind: "message" as const,
|
|
719
|
+
id: "msg_2",
|
|
720
|
+
role: "assistant" as const,
|
|
721
|
+
content: [{ kind: "text" as const, text: "Done" }],
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
finishReason: "stop",
|
|
725
|
+
usage: {
|
|
726
|
+
inputTokens: 2,
|
|
727
|
+
outputTokens: 2,
|
|
728
|
+
totalTokens: 4,
|
|
729
|
+
},
|
|
730
|
+
warnings: [],
|
|
731
|
+
};
|
|
732
|
+
});
|
|
793
733
|
|
|
794
734
|
const tool1 = tool({
|
|
795
735
|
id: "tool1",
|
|
@@ -816,7 +756,7 @@ describe("Thread", () => {
|
|
|
816
756
|
});
|
|
817
757
|
|
|
818
758
|
const kernl = new Kernl();
|
|
819
|
-
const thread = new Thread(kernl, agent, "test");
|
|
759
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
820
760
|
|
|
821
761
|
await thread.execute();
|
|
822
762
|
|
|
@@ -852,47 +792,24 @@ describe("Thread", () => {
|
|
|
852
792
|
it("should track tick counter correctly", async () => {
|
|
853
793
|
let callCount = 0;
|
|
854
794
|
|
|
855
|
-
const model:
|
|
856
|
-
|
|
857
|
-
provider: "test",
|
|
858
|
-
modelId: "test-model",
|
|
859
|
-
async generate(req: LanguageModelRequest) {
|
|
860
|
-
callCount++;
|
|
861
|
-
|
|
862
|
-
if (callCount < 3) {
|
|
863
|
-
return {
|
|
864
|
-
content: [
|
|
865
|
-
{
|
|
866
|
-
kind: "message" as const,
|
|
867
|
-
id: `msg_${callCount}`,
|
|
868
|
-
role: "assistant" as const,
|
|
869
|
-
content: [],
|
|
870
|
-
},
|
|
871
|
-
{
|
|
872
|
-
kind: "tool-call" as const,
|
|
873
|
-
toolId: "simple",
|
|
874
|
-
state: IN_PROGRESS,
|
|
875
|
-
callId: `call_${callCount}`,
|
|
876
|
-
arguments: "{}",
|
|
877
|
-
},
|
|
878
|
-
],
|
|
879
|
-
finishReason: "stop",
|
|
880
|
-
usage: {
|
|
881
|
-
inputTokens: 2,
|
|
882
|
-
outputTokens: 2,
|
|
883
|
-
totalTokens: 4,
|
|
884
|
-
},
|
|
885
|
-
warnings: [],
|
|
886
|
-
};
|
|
887
|
-
}
|
|
795
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
796
|
+
callCount++;
|
|
888
797
|
|
|
798
|
+
if (callCount < 3) {
|
|
889
799
|
return {
|
|
890
800
|
content: [
|
|
891
801
|
{
|
|
892
802
|
kind: "message" as const,
|
|
893
|
-
id:
|
|
803
|
+
id: `msg_${callCount}`,
|
|
894
804
|
role: "assistant" as const,
|
|
895
|
-
content: [
|
|
805
|
+
content: [],
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
kind: "tool-call" as const,
|
|
809
|
+
toolId: "simple",
|
|
810
|
+
state: IN_PROGRESS,
|
|
811
|
+
callId: `call_${callCount}`,
|
|
812
|
+
arguments: "{}",
|
|
896
813
|
},
|
|
897
814
|
],
|
|
898
815
|
finishReason: "stop",
|
|
@@ -903,11 +820,26 @@ describe("Thread", () => {
|
|
|
903
820
|
},
|
|
904
821
|
warnings: [],
|
|
905
822
|
};
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return {
|
|
826
|
+
content: [
|
|
827
|
+
{
|
|
828
|
+
kind: "message" as const,
|
|
829
|
+
id: "msg_final",
|
|
830
|
+
role: "assistant" as const,
|
|
831
|
+
content: [{ kind: "text" as const, text: "Done" }],
|
|
832
|
+
},
|
|
833
|
+
],
|
|
834
|
+
finishReason: "stop",
|
|
835
|
+
usage: {
|
|
836
|
+
inputTokens: 2,
|
|
837
|
+
outputTokens: 2,
|
|
838
|
+
totalTokens: 4,
|
|
839
|
+
},
|
|
840
|
+
warnings: [],
|
|
841
|
+
};
|
|
842
|
+
});
|
|
911
843
|
|
|
912
844
|
const simpleTool = tool({
|
|
913
845
|
id: "simple",
|
|
@@ -927,72 +859,64 @@ describe("Thread", () => {
|
|
|
927
859
|
});
|
|
928
860
|
|
|
929
861
|
const kernl = new Kernl();
|
|
930
|
-
const thread = new Thread(kernl, agent, "test");
|
|
862
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
931
863
|
|
|
932
864
|
const result = await thread.execute();
|
|
933
865
|
|
|
934
|
-
expect(
|
|
866
|
+
expect(thread._tick).toBe(3);
|
|
935
867
|
});
|
|
936
868
|
|
|
937
869
|
it("should accumulate model responses", async () => {
|
|
938
870
|
let callCount = 0;
|
|
939
871
|
|
|
940
|
-
const model:
|
|
941
|
-
|
|
942
|
-
provider: "test",
|
|
943
|
-
modelId: "test-model",
|
|
944
|
-
async generate(req: LanguageModelRequest) {
|
|
945
|
-
callCount++;
|
|
946
|
-
|
|
947
|
-
if (callCount === 1) {
|
|
948
|
-
return {
|
|
949
|
-
content: [
|
|
950
|
-
{
|
|
951
|
-
kind: "message" as const,
|
|
952
|
-
id: "msg_1",
|
|
953
|
-
role: "assistant" as const,
|
|
954
|
-
content: [],
|
|
955
|
-
},
|
|
956
|
-
{
|
|
957
|
-
kind: "tool-call" as const,
|
|
958
|
-
toolId: "simple",
|
|
959
|
-
state: IN_PROGRESS,
|
|
960
|
-
callId: "call_1",
|
|
961
|
-
arguments: "{}",
|
|
962
|
-
},
|
|
963
|
-
],
|
|
964
|
-
finishReason: "stop",
|
|
965
|
-
usage: {
|
|
966
|
-
inputTokens: 10,
|
|
967
|
-
outputTokens: 5,
|
|
968
|
-
totalTokens: 15,
|
|
969
|
-
},
|
|
970
|
-
warnings: [],
|
|
971
|
-
};
|
|
972
|
-
}
|
|
872
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
873
|
+
callCount++;
|
|
973
874
|
|
|
875
|
+
if (callCount === 1) {
|
|
974
876
|
return {
|
|
975
877
|
content: [
|
|
976
878
|
{
|
|
977
879
|
kind: "message" as const,
|
|
978
|
-
id: "
|
|
880
|
+
id: "msg_1",
|
|
979
881
|
role: "assistant" as const,
|
|
980
|
-
content: [
|
|
882
|
+
content: [],
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
kind: "tool-call" as const,
|
|
886
|
+
toolId: "simple",
|
|
887
|
+
state: IN_PROGRESS,
|
|
888
|
+
callId: "call_1",
|
|
889
|
+
arguments: "{}",
|
|
981
890
|
},
|
|
982
891
|
],
|
|
983
892
|
finishReason: "stop",
|
|
984
893
|
usage: {
|
|
985
|
-
inputTokens:
|
|
986
|
-
outputTokens:
|
|
987
|
-
totalTokens:
|
|
894
|
+
inputTokens: 10,
|
|
895
|
+
outputTokens: 5,
|
|
896
|
+
totalTokens: 15,
|
|
988
897
|
},
|
|
989
898
|
warnings: [],
|
|
990
899
|
};
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return {
|
|
903
|
+
content: [
|
|
904
|
+
{
|
|
905
|
+
kind: "message" as const,
|
|
906
|
+
id: "msg_2",
|
|
907
|
+
role: "assistant" as const,
|
|
908
|
+
content: [{ kind: "text" as const, text: "Done" }],
|
|
909
|
+
},
|
|
910
|
+
],
|
|
911
|
+
finishReason: "stop",
|
|
912
|
+
usage: {
|
|
913
|
+
inputTokens: 20,
|
|
914
|
+
outputTokens: 10,
|
|
915
|
+
totalTokens: 30,
|
|
916
|
+
},
|
|
917
|
+
warnings: [],
|
|
918
|
+
};
|
|
919
|
+
});
|
|
996
920
|
|
|
997
921
|
const simpleTool = tool({
|
|
998
922
|
id: "simple",
|
|
@@ -1012,45 +936,37 @@ describe("Thread", () => {
|
|
|
1012
936
|
});
|
|
1013
937
|
|
|
1014
938
|
const kernl = new Kernl();
|
|
1015
|
-
const thread = new Thread(kernl, agent, "test");
|
|
939
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1016
940
|
|
|
1017
941
|
const result = await thread.execute();
|
|
1018
942
|
|
|
1019
|
-
|
|
1020
|
-
expect(
|
|
1021
|
-
expect(result.
|
|
943
|
+
// Verify the thread executed both turns
|
|
944
|
+
expect(thread._tick).toBe(2);
|
|
945
|
+
expect(result.response).toBe("Done");
|
|
1022
946
|
});
|
|
1023
947
|
});
|
|
1024
948
|
|
|
1025
949
|
describe("Terminal State Detection", () => {
|
|
1026
950
|
it("should terminate when assistant message has no tool calls", async () => {
|
|
1027
|
-
const model:
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
{
|
|
1035
|
-
kind: "message" as const,
|
|
1036
|
-
id: "msg_1",
|
|
1037
|
-
role: "assistant" as const,
|
|
1038
|
-
content: [{ kind: "text" as const, text: "Final response" }],
|
|
1039
|
-
},
|
|
1040
|
-
],
|
|
1041
|
-
finishReason: "stop",
|
|
1042
|
-
usage: {
|
|
1043
|
-
inputTokens: 2,
|
|
1044
|
-
outputTokens: 2,
|
|
1045
|
-
totalTokens: 4,
|
|
951
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
952
|
+
return {
|
|
953
|
+
content: [
|
|
954
|
+
{
|
|
955
|
+
kind: "message" as const,
|
|
956
|
+
id: "msg_1",
|
|
957
|
+
role: "assistant" as const,
|
|
958
|
+
content: [{ kind: "text" as const, text: "Final response" }],
|
|
1046
959
|
},
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
960
|
+
],
|
|
961
|
+
finishReason: "stop",
|
|
962
|
+
usage: {
|
|
963
|
+
inputTokens: 2,
|
|
964
|
+
outputTokens: 2,
|
|
965
|
+
totalTokens: 4,
|
|
966
|
+
},
|
|
967
|
+
warnings: [],
|
|
968
|
+
};
|
|
969
|
+
});
|
|
1054
970
|
|
|
1055
971
|
const agent = new Agent({
|
|
1056
972
|
id: "test",
|
|
@@ -1060,74 +976,64 @@ describe("Thread", () => {
|
|
|
1060
976
|
});
|
|
1061
977
|
|
|
1062
978
|
const kernl = new Kernl();
|
|
1063
|
-
const thread = new Thread(kernl, agent, "test");
|
|
979
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1064
980
|
|
|
1065
981
|
const result = await thread.execute();
|
|
1066
982
|
|
|
1067
|
-
expect(
|
|
983
|
+
expect(thread._tick).toBe(1);
|
|
1068
984
|
});
|
|
1069
985
|
|
|
1070
986
|
it("should continue when assistant message has tool calls", async () => {
|
|
1071
987
|
let callCount = 0;
|
|
1072
988
|
|
|
1073
|
-
const model:
|
|
1074
|
-
|
|
1075
|
-
provider: "test",
|
|
1076
|
-
modelId: "test-model",
|
|
1077
|
-
async generate(req: LanguageModelRequest) {
|
|
1078
|
-
callCount++;
|
|
1079
|
-
|
|
1080
|
-
if (callCount === 1) {
|
|
1081
|
-
return {
|
|
1082
|
-
content: [
|
|
1083
|
-
{
|
|
1084
|
-
kind: "message" as const,
|
|
1085
|
-
id: "msg_1",
|
|
1086
|
-
role: "assistant" as const,
|
|
1087
|
-
content: [
|
|
1088
|
-
{ kind: "text" as const, text: "Let me use a tool" },
|
|
1089
|
-
],
|
|
1090
|
-
},
|
|
1091
|
-
{
|
|
1092
|
-
kind: "tool-call" as const,
|
|
1093
|
-
toolId: "simple",
|
|
1094
|
-
state: IN_PROGRESS,
|
|
1095
|
-
callId: "call_1",
|
|
1096
|
-
arguments: "{}",
|
|
1097
|
-
},
|
|
1098
|
-
],
|
|
1099
|
-
finishReason: "stop",
|
|
1100
|
-
usage: {
|
|
1101
|
-
inputTokens: 2,
|
|
1102
|
-
outputTokens: 2,
|
|
1103
|
-
totalTokens: 4,
|
|
1104
|
-
},
|
|
1105
|
-
warnings: [],
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
989
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
990
|
+
callCount++;
|
|
1108
991
|
|
|
992
|
+
if (callCount === 1) {
|
|
1109
993
|
return {
|
|
1110
994
|
content: [
|
|
1111
995
|
{
|
|
1112
996
|
kind: "message" as const,
|
|
1113
|
-
id: "
|
|
997
|
+
id: "msg_1",
|
|
1114
998
|
role: "assistant" as const,
|
|
1115
|
-
content: [{ kind: "text" as const, text: "
|
|
999
|
+
content: [{ kind: "text" as const, text: "Let me use a tool" }],
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
kind: "tool-call" as const,
|
|
1003
|
+
toolId: "simple",
|
|
1004
|
+
state: IN_PROGRESS,
|
|
1005
|
+
callId: "call_1",
|
|
1006
|
+
arguments: "{}",
|
|
1116
1007
|
},
|
|
1117
1008
|
],
|
|
1118
1009
|
finishReason: "stop",
|
|
1119
1010
|
usage: {
|
|
1120
|
-
inputTokens:
|
|
1011
|
+
inputTokens: 2,
|
|
1121
1012
|
outputTokens: 2,
|
|
1122
|
-
totalTokens:
|
|
1013
|
+
totalTokens: 4,
|
|
1123
1014
|
},
|
|
1124
1015
|
warnings: [],
|
|
1125
1016
|
};
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
content: [
|
|
1021
|
+
{
|
|
1022
|
+
kind: "message" as const,
|
|
1023
|
+
id: "msg_2",
|
|
1024
|
+
role: "assistant" as const,
|
|
1025
|
+
content: [{ kind: "text" as const, text: "Done now" }],
|
|
1026
|
+
},
|
|
1027
|
+
],
|
|
1028
|
+
finishReason: "stop",
|
|
1029
|
+
usage: {
|
|
1030
|
+
inputTokens: 3,
|
|
1031
|
+
outputTokens: 2,
|
|
1032
|
+
totalTokens: 5,
|
|
1033
|
+
},
|
|
1034
|
+
warnings: [],
|
|
1035
|
+
};
|
|
1036
|
+
});
|
|
1131
1037
|
|
|
1132
1038
|
const simpleTool = tool({
|
|
1133
1039
|
id: "simple",
|
|
@@ -1147,44 +1053,36 @@ describe("Thread", () => {
|
|
|
1147
1053
|
});
|
|
1148
1054
|
|
|
1149
1055
|
const kernl = new Kernl();
|
|
1150
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1056
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1151
1057
|
|
|
1152
1058
|
const result = await thread.execute();
|
|
1153
1059
|
|
|
1154
1060
|
// Should have made 2 calls - first with tool, second without
|
|
1155
|
-
expect(
|
|
1061
|
+
expect(thread._tick).toBe(2);
|
|
1156
1062
|
});
|
|
1157
1063
|
});
|
|
1158
1064
|
|
|
1159
1065
|
describe("Final Output Parsing", () => {
|
|
1160
1066
|
it("should return text output when responseType is 'text'", async () => {
|
|
1161
|
-
const model:
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
{
|
|
1169
|
-
kind: "message" as const,
|
|
1170
|
-
id: "msg_1",
|
|
1171
|
-
role: "assistant" as const,
|
|
1172
|
-
content: [{ kind: "text" as const, text: "Hello, world!" }],
|
|
1173
|
-
},
|
|
1174
|
-
],
|
|
1175
|
-
finishReason: "stop",
|
|
1176
|
-
usage: {
|
|
1177
|
-
inputTokens: 2,
|
|
1178
|
-
outputTokens: 2,
|
|
1179
|
-
totalTokens: 4,
|
|
1067
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1068
|
+
return {
|
|
1069
|
+
content: [
|
|
1070
|
+
{
|
|
1071
|
+
kind: "message" as const,
|
|
1072
|
+
id: "msg_1",
|
|
1073
|
+
role: "assistant" as const,
|
|
1074
|
+
content: [{ kind: "text" as const, text: "Hello, world!" }],
|
|
1180
1075
|
},
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1076
|
+
],
|
|
1077
|
+
finishReason: "stop",
|
|
1078
|
+
usage: {
|
|
1079
|
+
inputTokens: 2,
|
|
1080
|
+
outputTokens: 2,
|
|
1081
|
+
totalTokens: 4,
|
|
1082
|
+
},
|
|
1083
|
+
warnings: [],
|
|
1084
|
+
};
|
|
1085
|
+
});
|
|
1188
1086
|
|
|
1189
1087
|
const agent = new Agent({
|
|
1190
1088
|
id: "test",
|
|
@@ -1195,12 +1093,12 @@ describe("Thread", () => {
|
|
|
1195
1093
|
});
|
|
1196
1094
|
|
|
1197
1095
|
const kernl = new Kernl();
|
|
1198
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1096
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1199
1097
|
|
|
1200
1098
|
const result = await thread.execute();
|
|
1201
1099
|
|
|
1202
1100
|
expect(result.response).toBe("Hello, world!");
|
|
1203
|
-
expect(
|
|
1101
|
+
expect(thread._tick).toBe(1);
|
|
1204
1102
|
});
|
|
1205
1103
|
|
|
1206
1104
|
it("should parse and validate structured output with valid JSON", async () => {
|
|
@@ -1210,38 +1108,30 @@ describe("Thread", () => {
|
|
|
1210
1108
|
email: z.string().email(),
|
|
1211
1109
|
});
|
|
1212
1110
|
|
|
1213
|
-
const model:
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
kind: "text" as const,
|
|
1227
|
-
text: '{"name": "Alice", "age": 30, "email": "alice@example.com"}',
|
|
1228
|
-
},
|
|
1229
|
-
],
|
|
1230
|
-
},
|
|
1231
|
-
],
|
|
1232
|
-
finishReason: "stop",
|
|
1233
|
-
usage: {
|
|
1234
|
-
inputTokens: 2,
|
|
1235
|
-
outputTokens: 2,
|
|
1236
|
-
totalTokens: 4,
|
|
1111
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1112
|
+
return {
|
|
1113
|
+
content: [
|
|
1114
|
+
{
|
|
1115
|
+
kind: "message" as const,
|
|
1116
|
+
id: "msg_1",
|
|
1117
|
+
role: "assistant" as const,
|
|
1118
|
+
content: [
|
|
1119
|
+
{
|
|
1120
|
+
kind: "text" as const,
|
|
1121
|
+
text: '{"name": "Alice", "age": 30, "email": "alice@example.com"}',
|
|
1122
|
+
},
|
|
1123
|
+
],
|
|
1237
1124
|
},
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1125
|
+
],
|
|
1126
|
+
finishReason: "stop",
|
|
1127
|
+
usage: {
|
|
1128
|
+
inputTokens: 2,
|
|
1129
|
+
outputTokens: 2,
|
|
1130
|
+
totalTokens: 4,
|
|
1131
|
+
},
|
|
1132
|
+
warnings: [],
|
|
1133
|
+
};
|
|
1134
|
+
});
|
|
1245
1135
|
|
|
1246
1136
|
const agent = new Agent({
|
|
1247
1137
|
id: "test",
|
|
@@ -1252,7 +1142,7 @@ describe("Thread", () => {
|
|
|
1252
1142
|
});
|
|
1253
1143
|
|
|
1254
1144
|
const kernl = new Kernl();
|
|
1255
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1145
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1256
1146
|
|
|
1257
1147
|
const result = await thread.execute();
|
|
1258
1148
|
|
|
@@ -1268,38 +1158,30 @@ describe("Thread", () => {
|
|
|
1268
1158
|
name: z.string(),
|
|
1269
1159
|
});
|
|
1270
1160
|
|
|
1271
|
-
const model:
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
kind: "text" as const,
|
|
1285
|
-
text: '{"name": "Alice"', // Invalid JSON - missing closing brace
|
|
1286
|
-
},
|
|
1287
|
-
],
|
|
1288
|
-
},
|
|
1289
|
-
],
|
|
1290
|
-
finishReason: "stop",
|
|
1291
|
-
usage: {
|
|
1292
|
-
inputTokens: 2,
|
|
1293
|
-
outputTokens: 2,
|
|
1294
|
-
totalTokens: 4,
|
|
1161
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1162
|
+
return {
|
|
1163
|
+
content: [
|
|
1164
|
+
{
|
|
1165
|
+
kind: "message" as const,
|
|
1166
|
+
id: "msg_1",
|
|
1167
|
+
role: "assistant" as const,
|
|
1168
|
+
content: [
|
|
1169
|
+
{
|
|
1170
|
+
kind: "text" as const,
|
|
1171
|
+
text: '{"name": "Alice"', // Invalid JSON - missing closing brace
|
|
1172
|
+
},
|
|
1173
|
+
],
|
|
1295
1174
|
},
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1175
|
+
],
|
|
1176
|
+
finishReason: "stop",
|
|
1177
|
+
usage: {
|
|
1178
|
+
inputTokens: 2,
|
|
1179
|
+
outputTokens: 2,
|
|
1180
|
+
totalTokens: 4,
|
|
1181
|
+
},
|
|
1182
|
+
warnings: [],
|
|
1183
|
+
};
|
|
1184
|
+
});
|
|
1303
1185
|
|
|
1304
1186
|
const agent = new Agent({
|
|
1305
1187
|
id: "test",
|
|
@@ -1310,7 +1192,7 @@ describe("Thread", () => {
|
|
|
1310
1192
|
});
|
|
1311
1193
|
|
|
1312
1194
|
const kernl = new Kernl();
|
|
1313
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1195
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1314
1196
|
|
|
1315
1197
|
await expect(thread.execute()).rejects.toThrow(ModelBehaviorError);
|
|
1316
1198
|
});
|
|
@@ -1321,38 +1203,30 @@ describe("Thread", () => {
|
|
|
1321
1203
|
age: z.number(),
|
|
1322
1204
|
});
|
|
1323
1205
|
|
|
1324
|
-
const model:
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
kind: "text" as const,
|
|
1338
|
-
text: '{"name": "Alice", "age": "thirty"}', // age is string instead of number
|
|
1339
|
-
},
|
|
1340
|
-
],
|
|
1341
|
-
},
|
|
1342
|
-
],
|
|
1343
|
-
finishReason: "stop",
|
|
1344
|
-
usage: {
|
|
1345
|
-
inputTokens: 2,
|
|
1346
|
-
outputTokens: 2,
|
|
1347
|
-
totalTokens: 4,
|
|
1206
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1207
|
+
return {
|
|
1208
|
+
content: [
|
|
1209
|
+
{
|
|
1210
|
+
kind: "message" as const,
|
|
1211
|
+
id: "msg_1",
|
|
1212
|
+
role: "assistant" as const,
|
|
1213
|
+
content: [
|
|
1214
|
+
{
|
|
1215
|
+
kind: "text" as const,
|
|
1216
|
+
text: '{"name": "Alice", "age": "thirty"}', // age is string instead of number
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1348
1219
|
},
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1220
|
+
],
|
|
1221
|
+
finishReason: "stop",
|
|
1222
|
+
usage: {
|
|
1223
|
+
inputTokens: 2,
|
|
1224
|
+
outputTokens: 2,
|
|
1225
|
+
totalTokens: 4,
|
|
1226
|
+
},
|
|
1227
|
+
warnings: [],
|
|
1228
|
+
};
|
|
1229
|
+
});
|
|
1356
1230
|
|
|
1357
1231
|
const agent = new Agent({
|
|
1358
1232
|
id: "test",
|
|
@@ -1363,7 +1237,7 @@ describe("Thread", () => {
|
|
|
1363
1237
|
});
|
|
1364
1238
|
|
|
1365
1239
|
const kernl = new Kernl();
|
|
1366
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1240
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1367
1241
|
|
|
1368
1242
|
await expect(thread.execute()).rejects.toThrow(ModelBehaviorError);
|
|
1369
1243
|
});
|
|
@@ -1375,38 +1249,30 @@ describe("Thread", () => {
|
|
|
1375
1249
|
email: z.string(),
|
|
1376
1250
|
});
|
|
1377
1251
|
|
|
1378
|
-
const model:
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
kind: "text" as const,
|
|
1392
|
-
text: '{"name": "Alice", "age": 30}', // missing email
|
|
1393
|
-
},
|
|
1394
|
-
],
|
|
1395
|
-
},
|
|
1396
|
-
],
|
|
1397
|
-
finishReason: "stop",
|
|
1398
|
-
usage: {
|
|
1399
|
-
inputTokens: 2,
|
|
1400
|
-
outputTokens: 2,
|
|
1401
|
-
totalTokens: 4,
|
|
1252
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1253
|
+
return {
|
|
1254
|
+
content: [
|
|
1255
|
+
{
|
|
1256
|
+
kind: "message" as const,
|
|
1257
|
+
id: "msg_1",
|
|
1258
|
+
role: "assistant" as const,
|
|
1259
|
+
content: [
|
|
1260
|
+
{
|
|
1261
|
+
kind: "text" as const,
|
|
1262
|
+
text: '{"name": "Alice", "age": 30}', // missing email
|
|
1263
|
+
},
|
|
1264
|
+
],
|
|
1402
1265
|
},
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1266
|
+
],
|
|
1267
|
+
finishReason: "stop",
|
|
1268
|
+
usage: {
|
|
1269
|
+
inputTokens: 2,
|
|
1270
|
+
outputTokens: 2,
|
|
1271
|
+
totalTokens: 4,
|
|
1272
|
+
},
|
|
1273
|
+
warnings: [],
|
|
1274
|
+
};
|
|
1275
|
+
});
|
|
1410
1276
|
|
|
1411
1277
|
const agent = new Agent({
|
|
1412
1278
|
id: "test",
|
|
@@ -1417,7 +1283,7 @@ describe("Thread", () => {
|
|
|
1417
1283
|
});
|
|
1418
1284
|
|
|
1419
1285
|
const kernl = new Kernl();
|
|
1420
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1286
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1421
1287
|
|
|
1422
1288
|
await expect(thread.execute()).rejects.toThrow(ModelBehaviorError);
|
|
1423
1289
|
});
|
|
@@ -1436,44 +1302,36 @@ describe("Thread", () => {
|
|
|
1436
1302
|
}),
|
|
1437
1303
|
});
|
|
1438
1304
|
|
|
1439
|
-
const model:
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
metadata: { timestamp: "2024-01-01" },
|
|
1459
|
-
}),
|
|
1460
|
-
},
|
|
1461
|
-
],
|
|
1462
|
-
},
|
|
1463
|
-
],
|
|
1464
|
-
finishReason: "stop",
|
|
1465
|
-
usage: {
|
|
1466
|
-
inputTokens: 2,
|
|
1467
|
-
outputTokens: 2,
|
|
1468
|
-
totalTokens: 4,
|
|
1305
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1306
|
+
return {
|
|
1307
|
+
content: [
|
|
1308
|
+
{
|
|
1309
|
+
kind: "message" as const,
|
|
1310
|
+
id: "msg_1",
|
|
1311
|
+
role: "assistant" as const,
|
|
1312
|
+
content: [
|
|
1313
|
+
{
|
|
1314
|
+
kind: "text" as const,
|
|
1315
|
+
text: JSON.stringify({
|
|
1316
|
+
user: {
|
|
1317
|
+
name: "Bob",
|
|
1318
|
+
profile: { bio: "Engineer", age: 25 },
|
|
1319
|
+
},
|
|
1320
|
+
metadata: { timestamp: "2024-01-01" },
|
|
1321
|
+
}),
|
|
1322
|
+
},
|
|
1323
|
+
],
|
|
1469
1324
|
},
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1325
|
+
],
|
|
1326
|
+
finishReason: "stop",
|
|
1327
|
+
usage: {
|
|
1328
|
+
inputTokens: 2,
|
|
1329
|
+
outputTokens: 2,
|
|
1330
|
+
totalTokens: 4,
|
|
1331
|
+
},
|
|
1332
|
+
warnings: [],
|
|
1333
|
+
};
|
|
1334
|
+
});
|
|
1477
1335
|
|
|
1478
1336
|
const agent = new Agent({
|
|
1479
1337
|
id: "test",
|
|
@@ -1484,7 +1342,7 @@ describe("Thread", () => {
|
|
|
1484
1342
|
});
|
|
1485
1343
|
|
|
1486
1344
|
const kernl = new Kernl();
|
|
1487
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1345
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1488
1346
|
|
|
1489
1347
|
const result = await thread.execute();
|
|
1490
1348
|
|
|
@@ -1500,42 +1358,18 @@ describe("Thread", () => {
|
|
|
1500
1358
|
it("should continue loop when no text in assistant message", async () => {
|
|
1501
1359
|
let callCount = 0;
|
|
1502
1360
|
|
|
1503
|
-
const model:
|
|
1504
|
-
|
|
1505
|
-
provider: "test",
|
|
1506
|
-
modelId: "test-model",
|
|
1507
|
-
async generate(req: LanguageModelRequest) {
|
|
1508
|
-
callCount++;
|
|
1361
|
+
const model = createMockModel(async (req: LanguageModelRequest) => {
|
|
1362
|
+
callCount++;
|
|
1509
1363
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
return {
|
|
1513
|
-
content: [
|
|
1514
|
-
{
|
|
1515
|
-
kind: "message" as const,
|
|
1516
|
-
id: "msg_1",
|
|
1517
|
-
role: "assistant" as const,
|
|
1518
|
-
content: [], // No content
|
|
1519
|
-
},
|
|
1520
|
-
],
|
|
1521
|
-
finishReason: "stop",
|
|
1522
|
-
usage: {
|
|
1523
|
-
inputTokens: 2,
|
|
1524
|
-
outputTokens: 2,
|
|
1525
|
-
totalTokens: 4,
|
|
1526
|
-
},
|
|
1527
|
-
warnings: [],
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
// Second call: return message with text
|
|
1364
|
+
// First call: return empty message (no text)
|
|
1365
|
+
if (callCount === 1) {
|
|
1532
1366
|
return {
|
|
1533
1367
|
content: [
|
|
1534
1368
|
{
|
|
1535
1369
|
kind: "message" as const,
|
|
1536
|
-
id: "
|
|
1370
|
+
id: "msg_1",
|
|
1537
1371
|
role: "assistant" as const,
|
|
1538
|
-
content: [
|
|
1372
|
+
content: [], // No content
|
|
1539
1373
|
},
|
|
1540
1374
|
],
|
|
1541
1375
|
finishReason: "stop",
|
|
@@ -1546,11 +1380,27 @@ describe("Thread", () => {
|
|
|
1546
1380
|
},
|
|
1547
1381
|
warnings: [],
|
|
1548
1382
|
};
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// Second call: return message with text
|
|
1386
|
+
return {
|
|
1387
|
+
content: [
|
|
1388
|
+
{
|
|
1389
|
+
kind: "message" as const,
|
|
1390
|
+
id: "msg_2",
|
|
1391
|
+
role: "assistant" as const,
|
|
1392
|
+
content: [{ kind: "text" as const, text: "Now I have text" }],
|
|
1393
|
+
},
|
|
1394
|
+
],
|
|
1395
|
+
finishReason: "stop",
|
|
1396
|
+
usage: {
|
|
1397
|
+
inputTokens: 2,
|
|
1398
|
+
outputTokens: 2,
|
|
1399
|
+
totalTokens: 4,
|
|
1400
|
+
},
|
|
1401
|
+
warnings: [],
|
|
1402
|
+
};
|
|
1403
|
+
});
|
|
1554
1404
|
|
|
1555
1405
|
const agent = new Agent({
|
|
1556
1406
|
id: "test",
|
|
@@ -1561,14 +1411,14 @@ describe("Thread", () => {
|
|
|
1561
1411
|
});
|
|
1562
1412
|
|
|
1563
1413
|
const kernl = new Kernl();
|
|
1564
|
-
const thread = new Thread(kernl, agent, "test");
|
|
1414
|
+
const thread = new Thread(kernl, agent, userMessage("test"));
|
|
1565
1415
|
|
|
1566
1416
|
const result = await thread.execute();
|
|
1567
1417
|
|
|
1568
1418
|
// Should have made 2 calls
|
|
1569
1419
|
expect(callCount).toBe(2);
|
|
1570
1420
|
expect(result.response).toBe("Now I have text");
|
|
1571
|
-
expect(
|
|
1421
|
+
expect(thread._tick).toBe(2);
|
|
1572
1422
|
});
|
|
1573
1423
|
});
|
|
1574
1424
|
});
|