@convex-dev/agent 0.1.18-alpha.1 → 0.5.0-alpha.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/dist/client/createTool.d.ts +30 -9
- package/dist/client/createTool.d.ts.map +1 -1
- package/dist/client/createTool.js +24 -9
- package/dist/client/createTool.js.map +1 -1
- package/dist/client/definePlaygroundAPI.d.ts +174 -199
- package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/client/definePlaygroundAPI.js +12 -19
- package/dist/client/definePlaygroundAPI.js.map +1 -1
- package/dist/client/files.js +4 -4
- package/dist/client/files.js.map +1 -1
- package/dist/client/index.d.ts +166 -63
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +75 -45
- package/dist/client/index.js.map +1 -1
- package/dist/client/search.d.ts +27 -6
- package/dist/client/search.d.ts.map +1 -1
- package/dist/client/search.js.map +1 -1
- package/dist/client/streaming.d.ts +3 -2
- package/dist/client/streaming.d.ts.map +1 -1
- package/dist/client/streaming.js.map +1 -1
- package/dist/client/types.d.ts +27 -41
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/api.d.ts +436 -73
- package/dist/component/messages.d.ts +246 -43
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +2 -2
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +1453 -152
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +2 -2
- package/dist/component/schema.js.map +1 -1
- package/dist/component/streams.d.ts +180 -6
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/mapping.d.ts +12 -14
- package/dist/mapping.d.ts.map +1 -1
- package/dist/mapping.js +187 -47
- package/dist/mapping.js.map +1 -1
- package/dist/react/deltas.d.ts +0 -3
- package/dist/react/deltas.d.ts.map +1 -1
- package/dist/react/deltas.js +140 -44
- package/dist/react/deltas.js.map +1 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
- package/dist/react/optimisticallySendMessage.js +2 -1
- package/dist/react/optimisticallySendMessage.js.map +1 -1
- package/dist/react/toUIMessages.d.ts +5 -4
- package/dist/react/toUIMessages.d.ts.map +1 -1
- package/dist/react/toUIMessages.js +103 -40
- package/dist/react/toUIMessages.js.map +1 -1
- package/dist/validators.d.ts +1798 -259
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +79 -15
- package/dist/validators.js.map +1 -1
- package/package.json +2 -2
- package/src/client/createTool.ts +68 -37
- package/src/client/definePlaygroundAPI.ts +24 -25
- package/src/client/files.ts +4 -4
- package/src/client/index.test.ts +14 -12
- package/src/client/index.ts +134 -94
- package/src/client/search.ts +3 -2
- package/src/client/streaming.ts +4 -3
- package/src/client/types.ts +34 -70
- package/src/component/_generated/api.d.ts +436 -73
- package/src/component/messages.ts +2 -2
- package/src/component/schema.ts +2 -2
- package/src/mapping.ts +228 -75
- package/src/react/deltas.ts +165 -52
- package/src/react/optimisticallySendMessage.ts +4 -1
- package/src/react/toUIMessages.test.ts +154 -36
- package/src/react/toUIMessages.ts +136 -57
- package/src/validators.test.ts +2 -99
- package/src/validators.ts +95 -17
package/src/react/deltas.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
|
|
2
1
|
import type { MessageDoc } from "../client/index.js";
|
|
3
2
|
import type {
|
|
4
3
|
Message,
|
|
@@ -6,11 +5,13 @@ import type {
|
|
|
6
5
|
StreamDelta,
|
|
7
6
|
StreamMessage,
|
|
8
7
|
TextStreamPart,
|
|
8
|
+
vReasoningPart,
|
|
9
|
+
vSource,
|
|
10
|
+
vTextPart,
|
|
11
|
+
vToolCallPart,
|
|
12
|
+
vToolResultPart,
|
|
9
13
|
} from "../validators.js";
|
|
10
|
-
import type {
|
|
11
|
-
import { toUIMessages } from "./toUIMessages.js";
|
|
12
|
-
|
|
13
|
-
export { toUIMessages, type UIMessage };
|
|
14
|
+
import type { Infer } from "convex/values";
|
|
14
15
|
|
|
15
16
|
export function mergeDeltas(
|
|
16
17
|
threadId: string,
|
|
@@ -142,10 +143,10 @@ export function applyDeltasToStreamMessage(
|
|
|
142
143
|
let lastContent = getLastContent(currentMessage);
|
|
143
144
|
for (const part of parts) {
|
|
144
145
|
let contentToAdd:
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
146
|
+
| Infer<typeof vTextPart>
|
|
147
|
+
| Infer<typeof vToolCallPart>
|
|
148
|
+
| Infer<typeof vToolResultPart>
|
|
149
|
+
| Infer<typeof vReasoningPart>
|
|
149
150
|
| undefined;
|
|
150
151
|
const isToolRole = part.type === "source" || part.type === "tool-result";
|
|
151
152
|
if (isToolRole !== (currentMessage.message!.role === "tool")) {
|
|
@@ -160,27 +161,36 @@ export function applyDeltasToStreamMessage(
|
|
|
160
161
|
continue;
|
|
161
162
|
}
|
|
162
163
|
switch (part.type) {
|
|
163
|
-
case "text-delta":
|
|
164
|
-
|
|
164
|
+
case "text-delta": {
|
|
165
|
+
const text = "text" in part ? part.text : part.textDelta;
|
|
166
|
+
currentMessage.text = (currentMessage.text ?? "") + text;
|
|
165
167
|
if (lastContent?.type === "text") {
|
|
166
|
-
lastContent.text = (lastContent.text ?? "") +
|
|
168
|
+
lastContent.text = (lastContent.text ?? "") + text;
|
|
167
169
|
} else {
|
|
168
|
-
contentToAdd = {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
};
|
|
170
|
+
contentToAdd = { type: "text", text } satisfies Infer<
|
|
171
|
+
typeof vTextPart
|
|
172
|
+
>;
|
|
172
173
|
}
|
|
173
174
|
break;
|
|
174
|
-
|
|
175
|
+
}
|
|
176
|
+
case "tool-input-start":
|
|
177
|
+
case "tool-call-streaming-start": {
|
|
178
|
+
const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
|
|
175
179
|
currentMessage.tool = true;
|
|
176
180
|
contentToAdd = {
|
|
177
181
|
type: "tool-call",
|
|
178
|
-
toolCallId
|
|
182
|
+
toolCallId,
|
|
179
183
|
toolName: part.toolName,
|
|
180
184
|
args: "",
|
|
181
|
-
|
|
185
|
+
providerExecuted:
|
|
186
|
+
"providerExecuted" in part ? part.providerExecuted : undefined,
|
|
187
|
+
providerOptions:
|
|
188
|
+
"providerMetadata" in part ? part.providerMetadata : undefined,
|
|
189
|
+
} satisfies Infer<typeof vToolCallPart>;
|
|
182
190
|
break;
|
|
191
|
+
}
|
|
183
192
|
case "tool-call-delta":
|
|
193
|
+
case "tool-input-delta":
|
|
184
194
|
{
|
|
185
195
|
currentMessage.tool = true;
|
|
186
196
|
if (lastContent?.type !== "tool-call") {
|
|
@@ -189,13 +199,20 @@ export function applyDeltasToStreamMessage(
|
|
|
189
199
|
if (typeof lastContent.args !== "string") {
|
|
190
200
|
throw new Error("Expected args to be a string");
|
|
191
201
|
}
|
|
192
|
-
|
|
202
|
+
const delta =
|
|
203
|
+
"argsTextDelta" in part ? part.argsTextDelta : part.delta;
|
|
204
|
+
lastContent.args = (lastContent.args ?? "") + delta;
|
|
193
205
|
}
|
|
194
206
|
break;
|
|
195
|
-
case "tool-call":
|
|
207
|
+
case "tool-call": {
|
|
196
208
|
currentMessage.tool = true;
|
|
197
|
-
contentToAdd = part;
|
|
209
|
+
contentToAdd = toolCallContent(part);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "tool-result": {
|
|
213
|
+
contentToAdd = toolResultContent(part);
|
|
198
214
|
break;
|
|
215
|
+
}
|
|
199
216
|
case "reasoning":
|
|
200
217
|
currentMessage.reasoning =
|
|
201
218
|
(currentMessage.reasoning ?? "") + part.textDelta;
|
|
@@ -208,14 +225,36 @@ export function applyDeltasToStreamMessage(
|
|
|
208
225
|
};
|
|
209
226
|
}
|
|
210
227
|
break;
|
|
228
|
+
case "reasoning-delta": {
|
|
229
|
+
currentMessage.reasoning = (currentMessage.reasoning ?? "") + part.text;
|
|
230
|
+
if (lastContent?.type === "reasoning") {
|
|
231
|
+
lastContent.text = (lastContent.text ?? "") + part.text;
|
|
232
|
+
} else {
|
|
233
|
+
contentToAdd = {
|
|
234
|
+
type: "reasoning",
|
|
235
|
+
text: part.text,
|
|
236
|
+
providerOptions:
|
|
237
|
+
"providerMetadata" in part ? part.providerMetadata : undefined,
|
|
238
|
+
state: "streaming",
|
|
239
|
+
} satisfies Infer<typeof vReasoningPart>;
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
211
243
|
case "source":
|
|
212
244
|
if (!currentMessage.sources) {
|
|
213
245
|
currentMessage.sources = [];
|
|
214
246
|
}
|
|
215
|
-
|
|
247
|
+
if ("source" in part) {
|
|
248
|
+
currentMessage.sources.push({
|
|
249
|
+
type: "source",
|
|
250
|
+
...part.source,
|
|
251
|
+
} satisfies Infer<typeof vSource>);
|
|
252
|
+
} else {
|
|
253
|
+
currentMessage.sources.push(part);
|
|
254
|
+
}
|
|
216
255
|
break;
|
|
217
|
-
case "
|
|
218
|
-
contentToAdd = part;
|
|
256
|
+
case "raw":
|
|
257
|
+
contentToAdd = part.rawValue;
|
|
219
258
|
break;
|
|
220
259
|
default:
|
|
221
260
|
console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
|
|
@@ -236,6 +275,35 @@ export function applyDeltasToStreamMessage(
|
|
|
236
275
|
return [newStream, true];
|
|
237
276
|
}
|
|
238
277
|
|
|
278
|
+
function toolCallContent(
|
|
279
|
+
part: Extract<TextStreamPart, { type: "tool-call" }>,
|
|
280
|
+
): Infer<typeof vToolCallPart> {
|
|
281
|
+
const args = "args" in part ? part.args : part.input;
|
|
282
|
+
return {
|
|
283
|
+
type: "tool-call",
|
|
284
|
+
toolCallId: part.toolCallId,
|
|
285
|
+
toolName: part.toolName,
|
|
286
|
+
args,
|
|
287
|
+
providerExecuted: part.providerExecuted,
|
|
288
|
+
} satisfies Infer<typeof vToolCallPart>;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function toolResultContent(
|
|
292
|
+
part: Extract<TextStreamPart, { type: "tool-result" }>,
|
|
293
|
+
): Infer<typeof vToolResultPart> {
|
|
294
|
+
const result =
|
|
295
|
+
"output" in part ? part.output : "result" in part ? part.result : undefined;
|
|
296
|
+
const args =
|
|
297
|
+
"input" in part ? part.input : "args" in part ? part.args : undefined;
|
|
298
|
+
return {
|
|
299
|
+
type: "tool-result",
|
|
300
|
+
toolCallId: part.toolCallId,
|
|
301
|
+
toolName: part.toolName,
|
|
302
|
+
result,
|
|
303
|
+
args,
|
|
304
|
+
providerExecuted: part.providerExecuted,
|
|
305
|
+
} satisfies Infer<typeof vToolResultPart>;
|
|
306
|
+
}
|
|
239
307
|
function cloneMessageAndContent(
|
|
240
308
|
message: Message | undefined,
|
|
241
309
|
): Message | undefined {
|
|
@@ -272,6 +340,7 @@ function statusFromStreamStatus(
|
|
|
272
340
|
}
|
|
273
341
|
}
|
|
274
342
|
|
|
343
|
+
// TODO: share more code with applyDeltasToStreamMessage
|
|
275
344
|
export function createStreamingMessage(
|
|
276
345
|
threadId: string,
|
|
277
346
|
message: StreamMessage,
|
|
@@ -288,16 +357,20 @@ export function createStreamingMessage(
|
|
|
288
357
|
tool: false,
|
|
289
358
|
};
|
|
290
359
|
switch (part.type) {
|
|
291
|
-
case "text-delta":
|
|
360
|
+
case "text-delta": {
|
|
361
|
+
const text = "text" in part ? part.text : part.textDelta;
|
|
292
362
|
return {
|
|
293
363
|
...metadata,
|
|
294
364
|
message: {
|
|
295
365
|
role: "assistant",
|
|
296
|
-
content: [{ type: "text", text
|
|
366
|
+
content: [{ type: "text", text }],
|
|
297
367
|
},
|
|
298
|
-
text
|
|
368
|
+
text,
|
|
299
369
|
};
|
|
300
|
-
|
|
370
|
+
}
|
|
371
|
+
case "tool-input-start":
|
|
372
|
+
case "tool-call-streaming-start": {
|
|
373
|
+
const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
|
|
301
374
|
return {
|
|
302
375
|
...metadata,
|
|
303
376
|
tool: true,
|
|
@@ -307,58 +380,98 @@ export function createStreamingMessage(
|
|
|
307
380
|
{
|
|
308
381
|
type: "tool-call",
|
|
309
382
|
toolName: part.toolName,
|
|
310
|
-
toolCallId
|
|
383
|
+
toolCallId,
|
|
311
384
|
args: "", // when it's a string, it's a partial call
|
|
385
|
+
providerExecuted:
|
|
386
|
+
"providerExecuted" in part ? part.providerExecuted : undefined,
|
|
387
|
+
providerOptions:
|
|
388
|
+
"providerMetadata" in part ? part.providerMetadata : undefined,
|
|
312
389
|
},
|
|
313
390
|
],
|
|
314
391
|
},
|
|
315
392
|
};
|
|
316
|
-
|
|
393
|
+
}
|
|
394
|
+
case "tool-input-delta":
|
|
395
|
+
case "tool-call-delta": {
|
|
396
|
+
console.warn("Received tool call delta part first??");
|
|
397
|
+
const delta = "argsTextDelta" in part ? part.argsTextDelta : part.delta;
|
|
398
|
+
const toolCallId = "toolCallId" in part ? part.toolCallId : part.id;
|
|
399
|
+
const toolName =
|
|
400
|
+
"toolName" in part ? part.toolName : part.type.slice("tool-".length);
|
|
317
401
|
return {
|
|
318
402
|
...metadata,
|
|
403
|
+
tool: true,
|
|
319
404
|
message: {
|
|
320
405
|
role: "assistant",
|
|
321
|
-
content: [
|
|
406
|
+
content: [
|
|
407
|
+
{
|
|
408
|
+
type: "tool-call",
|
|
409
|
+
toolCallId,
|
|
410
|
+
toolName,
|
|
411
|
+
args: delta,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
322
414
|
},
|
|
323
|
-
reasoning: part.textDelta,
|
|
324
415
|
};
|
|
325
|
-
|
|
326
|
-
|
|
416
|
+
}
|
|
417
|
+
case "tool-call": {
|
|
327
418
|
return {
|
|
328
419
|
...metadata,
|
|
329
420
|
tool: true,
|
|
330
|
-
message: {
|
|
331
|
-
|
|
421
|
+
message: {
|
|
422
|
+
role: "assistant",
|
|
423
|
+
content: [toolCallContent(part)],
|
|
424
|
+
},
|
|
332
425
|
};
|
|
333
|
-
|
|
426
|
+
}
|
|
427
|
+
case "tool-result":
|
|
334
428
|
return {
|
|
335
429
|
...metadata,
|
|
336
430
|
tool: true,
|
|
337
|
-
message: {
|
|
431
|
+
message: {
|
|
432
|
+
role: "tool",
|
|
433
|
+
content: [toolResultContent(part)],
|
|
434
|
+
},
|
|
338
435
|
};
|
|
339
|
-
case "
|
|
340
|
-
console.warn("Received tool call delta part first??");
|
|
436
|
+
case "reasoning":
|
|
341
437
|
return {
|
|
342
438
|
...metadata,
|
|
343
|
-
tool: true,
|
|
344
439
|
message: {
|
|
345
440
|
role: "assistant",
|
|
346
|
-
content: [
|
|
347
|
-
{
|
|
348
|
-
type: "tool-call",
|
|
349
|
-
toolCallId: part.toolCallId,
|
|
350
|
-
toolName: part.toolName,
|
|
351
|
-
args: part.argsTextDelta,
|
|
352
|
-
},
|
|
353
|
-
],
|
|
441
|
+
content: [{ type: "reasoning", text: part.textDelta }],
|
|
354
442
|
},
|
|
443
|
+
reasoning: part.textDelta,
|
|
355
444
|
};
|
|
356
|
-
case "
|
|
445
|
+
case "reasoning-delta": {
|
|
446
|
+
return {
|
|
447
|
+
...metadata,
|
|
448
|
+
message: {
|
|
449
|
+
role: "assistant",
|
|
450
|
+
content: [{ type: "reasoning", text: part.text }],
|
|
451
|
+
},
|
|
452
|
+
reasoning: part.text,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
case "source":
|
|
456
|
+
console.warn("Received source part first??");
|
|
357
457
|
return {
|
|
358
458
|
...metadata,
|
|
359
459
|
tool: true,
|
|
360
|
-
message: { role: "tool", content: [
|
|
460
|
+
message: { role: "tool", content: [] },
|
|
461
|
+
sources: [
|
|
462
|
+
"source" in part
|
|
463
|
+
? {
|
|
464
|
+
...part.source,
|
|
465
|
+
type: "source",
|
|
466
|
+
}
|
|
467
|
+
: part,
|
|
468
|
+
],
|
|
361
469
|
};
|
|
470
|
+
// case "raw":
|
|
471
|
+
// return {
|
|
472
|
+
// ...metadata,
|
|
473
|
+
// message: { role: "assistant", content: [part.rawValue] },
|
|
474
|
+
// };
|
|
362
475
|
default:
|
|
363
476
|
throw new Error(`Unexpected part type: ${JSON.stringify(part)}`);
|
|
364
477
|
}
|
|
@@ -46,7 +46,10 @@ export function optimisticallySendMessage(
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export function randomUUID() {
|
|
49
|
-
if (
|
|
49
|
+
if (
|
|
50
|
+
typeof crypto !== "undefined" &&
|
|
51
|
+
typeof crypto.randomUUID === "function"
|
|
52
|
+
) {
|
|
50
53
|
return crypto.randomUUID();
|
|
51
54
|
}
|
|
52
55
|
return (
|
|
@@ -31,7 +31,7 @@ describe("toUIMessages", () => {
|
|
|
31
31
|
const uiMessages = toUIMessages(messages);
|
|
32
32
|
expect(uiMessages).toHaveLength(1);
|
|
33
33
|
expect(uiMessages[0].role).toBe("user");
|
|
34
|
-
expect(uiMessages[0].
|
|
34
|
+
expect(uiMessages[0].text).toBe("Hello!");
|
|
35
35
|
expect(uiMessages[0].parts[0]).toEqual({ type: "text", text: "Hello!" });
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -48,10 +48,11 @@ describe("toUIMessages", () => {
|
|
|
48
48
|
const uiMessages = toUIMessages(messages);
|
|
49
49
|
expect(uiMessages).toHaveLength(1);
|
|
50
50
|
expect(uiMessages[0].role).toBe("assistant");
|
|
51
|
-
expect(uiMessages[0].
|
|
51
|
+
expect(uiMessages[0].text).toBe("Hi, how can I help?");
|
|
52
52
|
expect(uiMessages[0].parts[0]).toEqual({
|
|
53
53
|
type: "text",
|
|
54
54
|
text: "Hi, how can I help?",
|
|
55
|
+
state: "done",
|
|
55
56
|
});
|
|
56
57
|
});
|
|
57
58
|
|
|
@@ -83,13 +84,13 @@ describe("toUIMessages", () => {
|
|
|
83
84
|
{
|
|
84
85
|
type: "file",
|
|
85
86
|
mimeType: "text/plain",
|
|
86
|
-
data: "
|
|
87
|
+
data: "https://example.com/file.txt",
|
|
87
88
|
},
|
|
88
89
|
{
|
|
89
90
|
type: "tool-call",
|
|
90
91
|
toolName: "myTool",
|
|
91
92
|
toolCallId: "call1",
|
|
92
|
-
args: "",
|
|
93
|
+
args: "an arg",
|
|
93
94
|
},
|
|
94
95
|
],
|
|
95
96
|
},
|
|
@@ -109,7 +110,6 @@ describe("toUIMessages", () => {
|
|
|
109
110
|
},
|
|
110
111
|
],
|
|
111
112
|
},
|
|
112
|
-
text: "42",
|
|
113
113
|
tool: true,
|
|
114
114
|
}),
|
|
115
115
|
];
|
|
@@ -121,18 +121,15 @@ describe("toUIMessages", () => {
|
|
|
121
121
|
);
|
|
122
122
|
expect(uiMessages[1].role).toBe("assistant");
|
|
123
123
|
expect(
|
|
124
|
-
uiMessages[1].parts.filter((p) => p.type === "tool-
|
|
124
|
+
uiMessages[1].parts.filter((p) => p.type === "tool-myTool"),
|
|
125
125
|
).toHaveLength(1);
|
|
126
126
|
expect(
|
|
127
|
-
uiMessages[1].parts.filter((p) => p.type === "tool-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
toolName: "myTool",
|
|
127
|
+
uiMessages[1].parts.filter((p) => p.type === "tool-myTool")[0],
|
|
128
|
+
).toMatchObject({
|
|
129
|
+
type: "tool-myTool",
|
|
131
130
|
toolCallId: "call1",
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
result: "42",
|
|
135
|
-
step: 0,
|
|
131
|
+
state: "output-available",
|
|
132
|
+
output: "42",
|
|
136
133
|
});
|
|
137
134
|
});
|
|
138
135
|
|
|
@@ -167,21 +164,39 @@ describe("toUIMessages", () => {
|
|
|
167
164
|
const uiMessages = toUIMessages(messages);
|
|
168
165
|
expect(uiMessages).toHaveLength(1);
|
|
169
166
|
expect(uiMessages[0].role).toBe("assistant");
|
|
170
|
-
expect(uiMessages[0].
|
|
171
|
-
expect(
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
expect(uiMessages[0].text).toBe("Here's one idea. Here's another idea.");
|
|
168
|
+
expect(uiMessages[0].parts.filter((p) => p.type === "reasoning")).toEqual([
|
|
169
|
+
{
|
|
170
|
+
providerMetadata: undefined,
|
|
171
|
+
state: undefined,
|
|
172
|
+
text: "I'm thinking...",
|
|
173
|
+
type: "reasoning",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
providerMetadata: undefined,
|
|
177
|
+
state: undefined,
|
|
178
|
+
text: "I'm thinking...",
|
|
179
|
+
type: "reasoning",
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
174
182
|
expect(uiMessages[0].parts[0].type).toBe("reasoning");
|
|
175
183
|
assert(uiMessages[0].parts[0].type === "reasoning");
|
|
176
|
-
expect(uiMessages[0].parts[0].
|
|
177
|
-
|
|
178
|
-
);
|
|
184
|
+
expect(uiMessages[0].parts[0].text).toBe("I'm thinking...");
|
|
185
|
+
expect(uiMessages[0].parts[1].type).toBe("text");
|
|
186
|
+
assert(uiMessages[0].parts[1].type === "text");
|
|
187
|
+
expect(uiMessages[0].parts[1].text).toBe("Here's one idea.");
|
|
188
|
+
expect(uiMessages[0].parts[2].type).toBe("reasoning");
|
|
189
|
+
assert(uiMessages[0].parts[2].type === "reasoning");
|
|
190
|
+
expect(uiMessages[0].parts[2].text).toBe("I'm thinking...");
|
|
179
191
|
|
|
180
192
|
expect(uiMessages[0].parts.filter((p) => p.type === "text")).toHaveLength(
|
|
181
|
-
|
|
193
|
+
2,
|
|
182
194
|
);
|
|
183
195
|
expect(uiMessages[0].parts.filter((p) => p.type === "text")[0].text).toBe(
|
|
184
|
-
"Here's one idea.
|
|
196
|
+
"Here's one idea.",
|
|
197
|
+
);
|
|
198
|
+
expect(uiMessages[0].parts.filter((p) => p.type === "text")[1].text).toBe(
|
|
199
|
+
"Here's another idea.",
|
|
185
200
|
);
|
|
186
201
|
});
|
|
187
202
|
|
|
@@ -198,10 +213,58 @@ describe("toUIMessages", () => {
|
|
|
198
213
|
const uiMessages = toUIMessages(messages);
|
|
199
214
|
expect(uiMessages).toHaveLength(1);
|
|
200
215
|
expect(uiMessages[0].role).toBe("system");
|
|
201
|
-
expect(uiMessages[0].
|
|
216
|
+
expect(uiMessages[0].text).toBe("System message here");
|
|
202
217
|
expect(uiMessages[0].parts[0]).toEqual({
|
|
203
218
|
type: "text",
|
|
204
219
|
text: "System message here",
|
|
220
|
+
state: "done",
|
|
221
|
+
providerMetadata: undefined,
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("handles wrapped JSON tool output", () => {
|
|
226
|
+
const messages = [
|
|
227
|
+
baseMessageDoc({
|
|
228
|
+
message: {
|
|
229
|
+
role: "assistant",
|
|
230
|
+
content: [
|
|
231
|
+
{
|
|
232
|
+
type: "tool-call",
|
|
233
|
+
toolName: "myTool",
|
|
234
|
+
toolCallId: "call1",
|
|
235
|
+
args: { query: "test" },
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
tool: true,
|
|
240
|
+
}),
|
|
241
|
+
baseMessageDoc({
|
|
242
|
+
message: {
|
|
243
|
+
role: "tool",
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "tool-result",
|
|
247
|
+
toolName: "myTool",
|
|
248
|
+
toolCallId: "call1",
|
|
249
|
+
result: {
|
|
250
|
+
type: "json",
|
|
251
|
+
value: { data: "wrapped result", success: true },
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
tool: true,
|
|
257
|
+
}),
|
|
258
|
+
];
|
|
259
|
+
const uiMessages = toUIMessages(messages);
|
|
260
|
+
expect(uiMessages).toHaveLength(1);
|
|
261
|
+
const toolPart = uiMessages[0].parts.find((p) => p.type === "tool-myTool");
|
|
262
|
+
expect(toolPart).toMatchObject({
|
|
263
|
+
type: "tool-myTool",
|
|
264
|
+
toolCallId: "call1",
|
|
265
|
+
state: "output-available",
|
|
266
|
+
input: { query: "test" },
|
|
267
|
+
output: { data: "wrapped result", success: true }, // Should be unwrapped
|
|
205
268
|
});
|
|
206
269
|
});
|
|
207
270
|
|
|
@@ -215,7 +278,7 @@ describe("toUIMessages", () => {
|
|
|
215
278
|
type: "tool-call",
|
|
216
279
|
toolName: "myTool",
|
|
217
280
|
toolCallId: "call1",
|
|
218
|
-
args: "",
|
|
281
|
+
args: "hi",
|
|
219
282
|
},
|
|
220
283
|
],
|
|
221
284
|
},
|
|
@@ -226,17 +289,12 @@ describe("toUIMessages", () => {
|
|
|
226
289
|
expect(uiMessages).toHaveLength(1);
|
|
227
290
|
expect(uiMessages[0].role).toBe("assistant");
|
|
228
291
|
expect(
|
|
229
|
-
uiMessages[0].parts.filter((p) => p.type === "tool-
|
|
230
|
-
).
|
|
231
|
-
|
|
232
|
-
uiMessages[0].parts.filter((p) => p.type === "tool-invocation")[0]
|
|
233
|
-
.toolInvocation,
|
|
234
|
-
).toEqual({
|
|
235
|
-
toolName: "myTool",
|
|
292
|
+
uiMessages[0].parts.filter((p) => p.type === "tool-myTool")[0],
|
|
293
|
+
).toMatchObject({
|
|
294
|
+
type: "tool-myTool",
|
|
236
295
|
toolCallId: "call1",
|
|
237
|
-
|
|
238
|
-
state: "
|
|
239
|
-
step: 0,
|
|
296
|
+
input: "hi",
|
|
297
|
+
state: "input-available",
|
|
240
298
|
});
|
|
241
299
|
});
|
|
242
300
|
|
|
@@ -276,7 +334,7 @@ describe("toUIMessages", () => {
|
|
|
276
334
|
expect(uiMessages).toHaveLength(1);
|
|
277
335
|
expect(uiMessages[0].role).toBe("assistant");
|
|
278
336
|
// Should have a tool-invocation part
|
|
279
|
-
expect(uiMessages[0].parts.some((p) => p.type === "tool-
|
|
337
|
+
expect(uiMessages[0].parts.some((p) => p.type === "tool-myTool")).toBe(
|
|
280
338
|
true,
|
|
281
339
|
);
|
|
282
340
|
});
|
|
@@ -299,4 +357,64 @@ describe("toUIMessages", () => {
|
|
|
299
357
|
});
|
|
300
358
|
|
|
301
359
|
// Add more tests for array content, tool calls, etc. as needed
|
|
360
|
+
|
|
361
|
+
it("should update tool call state from input-available to output-available", () => {
|
|
362
|
+
const messages = [
|
|
363
|
+
baseMessageDoc({
|
|
364
|
+
message: {
|
|
365
|
+
role: "assistant",
|
|
366
|
+
content: [
|
|
367
|
+
{
|
|
368
|
+
type: "tool-call",
|
|
369
|
+
toolName: "calculator",
|
|
370
|
+
toolCallId: "call1",
|
|
371
|
+
args: { operation: "add", a: 1, b: 2 },
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
tool: true,
|
|
376
|
+
}),
|
|
377
|
+
baseMessageDoc({
|
|
378
|
+
message: {
|
|
379
|
+
role: "tool",
|
|
380
|
+
content: [
|
|
381
|
+
{
|
|
382
|
+
type: "tool-result",
|
|
383
|
+
toolCallId: "call1",
|
|
384
|
+
toolName: "calculator",
|
|
385
|
+
result: { sum: 3 },
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
tool: true,
|
|
390
|
+
}),
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const uiMessages = toUIMessages(messages);
|
|
394
|
+
|
|
395
|
+
// Should have one assistant message
|
|
396
|
+
expect(uiMessages).toHaveLength(1);
|
|
397
|
+
expect(uiMessages[0].role).toBe("assistant");
|
|
398
|
+
|
|
399
|
+
// Should have a single tool-calculator part (not separate tool-call and tool-result parts)
|
|
400
|
+
const toolParts = uiMessages[0].parts.filter(
|
|
401
|
+
(p) => p.type === "tool-calculator",
|
|
402
|
+
);
|
|
403
|
+
expect(toolParts).toHaveLength(1);
|
|
404
|
+
|
|
405
|
+
const toolPart = toolParts[0];
|
|
406
|
+
expect(toolPart).toMatchObject({
|
|
407
|
+
type: "tool-calculator",
|
|
408
|
+
toolCallId: "call1",
|
|
409
|
+
state: "output-available",
|
|
410
|
+
input: { operation: "add", a: 1, b: 2 },
|
|
411
|
+
output: { sum: 3 },
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Should NOT have a tool-call part (which is what currently happens)
|
|
415
|
+
const toolCallParts = uiMessages[0].parts.filter(
|
|
416
|
+
(p) => p.type === "tool-call",
|
|
417
|
+
);
|
|
418
|
+
expect(toolCallParts).toHaveLength(0);
|
|
419
|
+
});
|
|
302
420
|
});
|