@convex-dev/agent 0.2.3-alpha.0 → 0.2.4-alpha.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.
Files changed (59) hide show
  1. package/dist/client/definePlaygroundAPI.d.ts +59 -5
  2. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  3. package/dist/client/files.d.ts +1 -1
  4. package/dist/client/files.d.ts.map +1 -1
  5. package/dist/client/files.js.map +1 -1
  6. package/dist/client/index.d.ts +46 -4
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +14 -13
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/messages.d.ts +14 -2
  11. package/dist/client/messages.d.ts.map +1 -1
  12. package/dist/client/search.d.ts +14 -2
  13. package/dist/client/search.d.ts.map +1 -1
  14. package/dist/client/search.js +2 -4
  15. package/dist/client/search.js.map +1 -1
  16. package/dist/client/streaming.d.ts +132 -23
  17. package/dist/client/streaming.d.ts.map +1 -1
  18. package/dist/component/_generated/api.d.ts +189 -0
  19. package/dist/component/messages.d.ts +149 -20
  20. package/dist/component/messages.d.ts.map +1 -1
  21. package/dist/component/messages.js +4 -4
  22. package/dist/component/messages.js.map +1 -1
  23. package/dist/component/schema.d.ts +342 -63
  24. package/dist/component/schema.d.ts.map +1 -1
  25. package/dist/mapping.d.ts +4 -4
  26. package/dist/mapping.d.ts.map +1 -1
  27. package/dist/mapping.js +67 -36
  28. package/dist/mapping.js.map +1 -1
  29. package/dist/react/deltas.d.ts +3 -3
  30. package/dist/react/deltas.d.ts.map +1 -1
  31. package/dist/react/deltas.js +302 -30
  32. package/dist/react/deltas.js.map +1 -1
  33. package/dist/react/toUIMessages.d.ts.map +1 -1
  34. package/dist/react/toUIMessages.js +289 -180
  35. package/dist/react/toUIMessages.js.map +1 -1
  36. package/dist/react/useSmoothText.d.ts.map +1 -1
  37. package/dist/react/useSmoothText.js +15 -6
  38. package/dist/react/useSmoothText.js.map +1 -1
  39. package/dist/shared.d.ts +4 -0
  40. package/dist/shared.d.ts.map +1 -1
  41. package/dist/shared.js +8 -0
  42. package/dist/shared.js.map +1 -1
  43. package/dist/validators.d.ts +662 -104
  44. package/dist/validators.d.ts.map +1 -1
  45. package/dist/validators.js +8 -0
  46. package/dist/validators.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/client/files.ts +4 -4
  49. package/src/client/index.ts +21 -20
  50. package/src/client/search.ts +2 -6
  51. package/src/component/_generated/api.d.ts +189 -0
  52. package/src/component/messages.ts +4 -3
  53. package/src/mapping.ts +99 -51
  54. package/src/react/deltas.ts +368 -48
  55. package/src/react/toUIMessages.test.ts +186 -0
  56. package/src/react/toUIMessages.ts +366 -192
  57. package/src/react/useSmoothText.ts +25 -13
  58. package/src/shared.ts +14 -0
  59. package/src/validators.ts +8 -0
@@ -1,16 +1,26 @@
1
- import type { ProviderMetadata, TextStreamPart, ToolSet } from "ai";
2
- import type { MessageDoc } from "../client/index.js";
3
1
  import type {
4
- Message,
5
- MessageStatus,
6
- StreamDelta,
7
- StreamMessage,
8
- vReasoningPart,
9
- vTextPart,
10
- vToolCallPart,
11
- vToolResultPart,
2
+ ProviderMetadata,
3
+ TextStreamPart,
4
+ ToolSet,
5
+ UIMessageChunk,
6
+ } from "ai";
7
+ import type { MessageDoc } from "../client/index.js";
8
+ import {
9
+ vSource,
10
+ type Message,
11
+ type MessageStatus,
12
+ type StreamDelta,
13
+ type StreamMessage,
14
+ type vFilePart,
15
+ type vReasoningPart,
16
+ type vTextPart,
17
+ type vToolCallPart,
18
+ type vToolResultPart,
12
19
  } from "../validators.js";
13
20
  import type { Infer } from "convex/values";
21
+ import { serializeWarnings } from "../mapping.js";
22
+ import { parse } from "convex-helpers/validators";
23
+ import { sorted } from "../shared.js";
14
24
 
15
25
  export function mergeDeltas(
16
26
  threadId: string,
@@ -55,10 +65,7 @@ export function mergeDeltas(
55
65
  changed = true;
56
66
  }
57
67
  }
58
- const messages = newStreams
59
- .map((s) => s.messages)
60
- .flat()
61
- .sort((a, b) => a.order - b.order || a.stepOrder - b.stepOrder);
68
+ const messages = sorted(newStreams.map((s) => s.messages).flat());
62
69
  return [messages, newStreams, changed];
63
70
  }
64
71
 
@@ -73,7 +80,7 @@ export function applyDeltasToStreamMessage(
73
80
  ): [{ streamId: string; cursor: number; messages: MessageDoc[] }, boolean] {
74
81
  let changed = false;
75
82
  let cursor = existing?.cursor ?? 0;
76
- let parts: TextStreamPart<ToolSet>[] = [];
83
+ let parts: (UIMessageChunk<ToolSet> | TextStreamPart<ToolSet>)[] = [];
77
84
  for (const delta of deltas.sort((a, b) => a.start - b.start)) {
78
85
  if (delta.parts.length === 0) {
79
86
  console.warn(`Got delta with no parts: ${JSON.stringify(delta)}`);
@@ -146,6 +153,7 @@ export function applyDeltasToStreamMessage(
146
153
  | Infer<typeof vToolCallPart>
147
154
  | Infer<typeof vToolResultPart>
148
155
  | Infer<typeof vReasoningPart>
156
+ | Infer<typeof vFilePart>
149
157
  | undefined;
150
158
  const isToolRole = part.type === "source" || part.type === "tool-result";
151
159
  if (isToolRole !== (currentMessage.message!.role === "tool")) {
@@ -161,25 +169,52 @@ export function applyDeltasToStreamMessage(
161
169
  }
162
170
  switch (part.type) {
163
171
  case "text-delta": {
164
- const text = part.text;
172
+ const text = "text" in part ? part.text : part.delta;
173
+ if (!text) {
174
+ console.warn("Got text delta with no text", part);
175
+ }
165
176
  currentMessage.text = (currentMessage.text ?? "") + text;
166
177
  if (lastContent?.type === "text") {
167
178
  lastContent.text = (lastContent.text ?? "") + text;
179
+ lastContent.providerMetadata = mergeProviderMetadata(
180
+ lastContent.providerMetadata,
181
+ part.providerMetadata,
182
+ );
183
+ } else {
184
+ contentToAdd = {
185
+ type: "text",
186
+ providerMetadata: part.providerMetadata,
187
+ text,
188
+ } satisfies Infer<typeof vTextPart>;
189
+ }
190
+ break;
191
+ }
192
+ case "tool-input-available": {
193
+ if (
194
+ lastContent?.type === "tool-call" &&
195
+ lastContent.toolCallId === part.toolCallId
196
+ ) {
197
+ lastContent.args = part.input;
198
+ lastContent.providerExecuted ??= part.providerExecuted;
199
+ lastContent.providerMetadata = mergeProviderMetadata(
200
+ lastContent.providerMetadata,
201
+ part.providerMetadata,
202
+ );
168
203
  } else {
169
- contentToAdd = { type: "text", text } satisfies Infer<
170
- typeof vTextPart
171
- >;
204
+ contentToAdd = toolCallContent(part);
172
205
  }
173
206
  break;
174
207
  }
175
208
  case "tool-input-start": {
176
- const toolCallId = part.id;
209
+ const toolCallId = "id" in part ? part.id : part.toolCallId;
177
210
  currentMessage.tool = true;
178
211
  contentToAdd = {
179
212
  type: "tool-call",
180
213
  toolCallId,
181
214
  toolName: part.toolName,
182
215
  args: "",
216
+ providerMetadata:
217
+ "providerMetadata" in part ? part.providerMetadata : undefined,
183
218
  providerExecuted:
184
219
  "providerExecuted" in part ? part.providerExecuted : undefined,
185
220
  } satisfies Infer<typeof vToolCallPart>;
@@ -192,11 +227,22 @@ export function applyDeltasToStreamMessage(
192
227
  throw new Error("Expected last content to be a tool call");
193
228
  }
194
229
  if (typeof lastContent.args !== "string") {
195
- throw new Error("Expected args to be a string");
230
+ lastContent.args = lastContent.args?.toString() ?? "";
196
231
  }
197
232
  const delta =
198
- "argsTextDelta" in part ? part.argsTextDelta : part.delta;
199
- lastContent.args = (lastContent.args ?? "") + delta;
233
+ "inputTextDelta" in part
234
+ ? part.inputTextDelta
235
+ : "delta" in part
236
+ ? part.delta
237
+ : "";
238
+ lastContent.args = lastContent.args + delta;
239
+ lastContent.providerMetadata =
240
+ "providerMetadata" in part
241
+ ? mergeProviderMetadata(
242
+ lastContent.providerMetadata,
243
+ part.providerMetadata,
244
+ )
245
+ : undefined;
200
246
  }
201
247
  break;
202
248
  case "tool-call": {
@@ -204,18 +250,50 @@ export function applyDeltasToStreamMessage(
204
250
  contentToAdd = toolCallContent(part);
205
251
  break;
206
252
  }
253
+ case "tool-output-available":
254
+ if (
255
+ lastContent?.type === "tool-call" &&
256
+ lastContent.toolCallId === part.toolCallId
257
+ ) {
258
+ contentToAdd = {
259
+ type: "tool-result",
260
+ toolCallId: part.toolCallId,
261
+ toolName: lastContent.toolName,
262
+ args: lastContent.args,
263
+ result: part.output,
264
+ providerExecuted: part.providerExecuted,
265
+ } satisfies Infer<typeof vToolResultPart>;
266
+ } else if (
267
+ lastContent?.type === "tool-result" &&
268
+ lastContent.toolCallId === part.toolCallId
269
+ ) {
270
+ lastContent.result = part.output;
271
+ lastContent.providerExecuted = part.providerExecuted;
272
+ } else {
273
+ console.warn(
274
+ "Got tool output available part for unknown tool call",
275
+ part,
276
+ );
277
+ }
278
+ break;
207
279
  case "tool-result": {
208
280
  contentToAdd = toolResultContent(part);
209
281
  break;
210
282
  }
211
283
  case "reasoning-delta": {
212
- currentMessage.reasoning = (currentMessage.reasoning ?? "") + part.text;
284
+ const text = "text" in part ? part.text : part.delta;
285
+ currentMessage.reasoning = (currentMessage.reasoning ?? "") + text;
213
286
  if (lastContent?.type === "reasoning") {
214
- lastContent.text = (lastContent.text ?? "") + part.text;
287
+ lastContent.text = (lastContent.text ?? "") + text;
288
+ lastContent.providerMetadata = mergeProviderMetadata(
289
+ lastContent.providerMetadata,
290
+ part.providerMetadata,
291
+ );
215
292
  } else {
216
293
  contentToAdd = {
217
294
  type: "reasoning",
218
- text: part.text,
295
+ text,
296
+ providerMetadata: part.providerMetadata,
219
297
  } satisfies Infer<typeof vReasoningPart>;
220
298
  }
221
299
  break;
@@ -224,20 +302,158 @@ export function applyDeltasToStreamMessage(
224
302
  if (!currentMessage.sources) {
225
303
  currentMessage.sources = [];
226
304
  }
227
- currentMessage.sources.push(part);
305
+ currentMessage.sources.push(parse(vSource, part));
306
+ console.warn("Got source part with unknown source type", part);
307
+ break;
308
+ case "abort":
309
+ currentMessage.status = "failed";
310
+ currentMessage.error = "abort";
311
+ break;
312
+ case "error":
313
+ currentMessage.status = "failed";
314
+ currentMessage.error =
315
+ "error" in part ? part.error?.toString() : part.errorText;
316
+ break;
317
+ case "message-metadata":
318
+ currentMessage.providerMetadata ??= {};
319
+ currentMessage.providerMetadata["metadata"] = {
320
+ parts: [part.messageMetadata],
321
+ };
322
+ console.warn(
323
+ "Putting message metadata part in providerMetadata. Use useUIMessages or useStreamingUIMessages instead.",
324
+ part,
325
+ );
326
+ break;
327
+ case "source-document":
328
+ contentToAdd = {
329
+ type: "file",
330
+ data: part.sourceId,
331
+ mimeType: part.mediaType,
332
+ filename: part.title ?? part.filename,
333
+ providerMetadata: part.providerMetadata,
334
+ } satisfies Infer<typeof vFilePart>;
335
+ break;
336
+ case "source-url":
337
+ contentToAdd = {
338
+ type: "file",
339
+ data: part.url,
340
+ filename: part.title,
341
+ mimeType: "text/plain", // What do we do here?
342
+ providerMetadata: part.providerMetadata,
343
+ } satisfies Infer<typeof vFilePart>;
344
+ break;
345
+ case "tool-input-error":
346
+ if (
347
+ lastContent?.type === "tool-call" &&
348
+ lastContent.toolCallId === part.toolCallId
349
+ ) {
350
+ lastContent.args ||= part.input;
351
+ lastContent.providerExecuted ??= part.providerExecuted;
352
+ lastContent.providerMetadata = mergeProviderMetadata(
353
+ lastContent.providerMetadata,
354
+ part.providerMetadata,
355
+ );
356
+ } else {
357
+ if (
358
+ lastContent?.type === "tool-result" &&
359
+ lastContent.toolCallId === part.toolCallId
360
+ ) {
361
+ lastContent.isError = true;
362
+ lastContent.providerExecuted ??= part.providerExecuted;
363
+ lastContent.providerMetadata = mergeProviderMetadata(
364
+ lastContent.providerMetadata,
365
+ part.providerMetadata,
366
+ );
367
+ } else {
368
+ contentToAdd = toolCallContent(part);
369
+ }
370
+ }
371
+ currentMessage.error = part.errorText;
372
+ break;
373
+ case "tool-output-error":
374
+ if (
375
+ lastContent?.type === "tool-result" &&
376
+ lastContent.toolCallId === part.toolCallId
377
+ ) {
378
+ lastContent.isError = true;
379
+ lastContent.result = part.errorText;
380
+ lastContent.providerExecuted = part.providerExecuted;
381
+ } else if (
382
+ lastContent?.type === "tool-call" &&
383
+ lastContent.toolCallId === part.toolCallId
384
+ ) {
385
+ console.warn(
386
+ "Got tool output error part for unknown tool result",
387
+ part,
388
+ );
389
+ contentToAdd = {
390
+ type: "tool-result",
391
+ toolCallId: part.toolCallId,
392
+ toolName: lastContent.toolName,
393
+ args: lastContent.args,
394
+ result: part.errorText,
395
+ providerExecuted: part.providerExecuted,
396
+ isError: true,
397
+ } satisfies Infer<typeof vToolResultPart>;
398
+ } else {
399
+ console.warn(
400
+ "Got tool output error part for unknown tool call",
401
+ part,
402
+ );
403
+ }
404
+ break;
405
+ case "tool-error":
406
+ if (
407
+ lastContent?.type === "tool-result" &&
408
+ lastContent.toolCallId === part.toolCallId
409
+ ) {
410
+ lastContent.isError = true;
411
+ lastContent.result = part.error;
412
+ lastContent.providerExecuted = part.providerExecuted;
413
+ } else {
414
+ if (
415
+ lastContent?.type === "tool-call" &&
416
+ lastContent.toolCallId === part.toolCallId
417
+ ) {
418
+ lastContent.args ||= part.input;
419
+ lastContent.providerExecuted ??= part.providerExecuted;
420
+ }
421
+ currentMessage.error = part.error?.toString();
422
+ contentToAdd = {
423
+ type: "tool-result",
424
+ toolCallId: part.toolCallId,
425
+ toolName: part.toolName,
426
+ result: part.error,
427
+ providerExecuted: part.providerExecuted,
428
+ isError: true,
429
+ args: part.input,
430
+ } satisfies Infer<typeof vToolResultPart>;
431
+ }
228
432
  break;
433
+ case "tool-input-end":
434
+ case "text-end":
435
+ case "reasoning-end":
436
+ case "finish-step":
437
+ case "finish":
438
+ case "file":
229
439
  case "raw":
440
+ case "start-step":
441
+ case "text-start":
442
+ case "reasoning-start":
443
+ case "start":
230
444
  // ignore
231
445
  break;
232
- default:
233
- console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
446
+ default: {
447
+ if (!part.type.startsWith("data-")) {
448
+ console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
449
+ } else {
450
+ console.warn(
451
+ "Dropping a data part. Use useUIMessages or useStreamingUIMessages instead for full UIMessage streaming support.",
452
+ part,
453
+ );
454
+ }
234
455
  break;
235
- }
236
- if ("providerMetadata" in part) {
237
- currentMessage.providerMetadata = mergeProviderMetadata(
238
- currentMessage.providerMetadata,
239
- part.providerMetadata,
240
- );
456
+ }
241
457
  }
242
458
  if (contentToAdd) {
243
459
  if (!currentMessage.message!.content) {
@@ -278,7 +494,10 @@ function mergeProviderMetadata(
278
494
  }
279
495
 
280
496
  function toolCallContent(
281
- part: Extract<TextStreamPart<ToolSet>, { type: "tool-call" }>,
497
+ part:
498
+ | Extract<TextStreamPart<ToolSet>, { type: "tool-call" }>
499
+ | Extract<UIMessageChunk<ToolSet>, { type: "tool-input-error" }>
500
+ | Extract<UIMessageChunk<ToolSet>, { type: "tool-input-available" }>,
282
501
  ): Infer<typeof vToolCallPart> {
283
502
  const args = "args" in part ? part.args : part.input;
284
503
  return {
@@ -286,6 +505,7 @@ function toolCallContent(
286
505
  toolCallId: part.toolCallId,
287
506
  toolName: part.toolName,
288
507
  args,
508
+ providerMetadata: part.providerMetadata,
289
509
  providerExecuted: part.providerExecuted,
290
510
  } satisfies Infer<typeof vToolCallPart>;
291
511
  }
@@ -342,7 +562,7 @@ function statusFromStreamStatus(
342
562
  export function createStreamingMessage(
343
563
  threadId: string,
344
564
  message: StreamMessage,
345
- part: TextStreamPart<ToolSet>,
565
+ part: UIMessageChunk<ToolSet> | TextStreamPart<ToolSet>,
346
566
  index: number,
347
567
  ): MessageDoc {
348
568
  const { streamId, ...rest } = message;
@@ -354,15 +574,19 @@ export function createStreamingMessage(
354
574
  threadId,
355
575
  tool: false,
356
576
  };
357
- if ("providerMetadata" in part) {
358
- metadata.providerMetadata = part.providerMetadata;
359
- }
577
+ const providerMetadata =
578
+ "providerMetadata" in part ? part.providerMetadata : undefined;
579
+ metadata.providerMetadata = providerMetadata;
580
+
360
581
  switch (part.type) {
361
582
  case "text-delta": {
362
- const text = part.text || "";
583
+ const text = "text" in part ? part.text : part.delta;
363
584
  return {
364
585
  ...metadata,
365
- message: { role: "assistant", content: [{ type: "text", text }] },
586
+ message: {
587
+ role: "assistant",
588
+ content: [{ type: "text", text, providerMetadata }],
589
+ },
366
590
  text,
367
591
  };
368
592
  }
@@ -376,10 +600,11 @@ export function createStreamingMessage(
376
600
  {
377
601
  type: "tool-call",
378
602
  toolName: part.toolName,
379
- toolCallId: part.id,
603
+ toolCallId: "id" in part ? part.id : part.toolCallId,
380
604
  args: "", // when it's a string, it's a partial call
381
605
  providerExecuted:
382
606
  "providerExecuted" in part ? part.providerExecuted : undefined,
607
+ providerMetadata,
383
608
  },
384
609
  ],
385
610
  },
@@ -387,15 +612,23 @@ export function createStreamingMessage(
387
612
  }
388
613
  case "tool-input-delta": {
389
614
  console.warn("Received tool call delta part first??");
390
- const delta = part.delta;
391
- const toolCallId = part.id;
615
+ const delta = "delta" in part ? part.delta : part.inputTextDelta;
616
+ const toolCallId = "id" in part ? part.id : part.toolCallId;
392
617
  const toolName = part.type.slice("tool-".length);
393
618
  return {
394
619
  ...metadata,
395
620
  tool: true,
396
621
  message: {
397
622
  role: "assistant",
398
- content: [{ type: "tool-call", toolCallId, toolName, args: delta }],
623
+ content: [
624
+ {
625
+ type: "tool-call",
626
+ toolCallId,
627
+ toolName,
628
+ args: delta,
629
+ providerMetadata,
630
+ },
631
+ ],
399
632
  },
400
633
  };
401
634
  }
@@ -413,13 +646,20 @@ export function createStreamingMessage(
413
646
  message: { role: "tool", content: [toolResultContent(part)] },
414
647
  };
415
648
  case "reasoning-delta": {
649
+ const text = "text" in part ? part.text : part.delta;
416
650
  return {
417
651
  ...metadata,
418
652
  message: {
419
653
  role: "assistant",
420
- content: [{ type: "reasoning", text: part.text }],
654
+ content: [
655
+ {
656
+ type: "reasoning",
657
+ text,
658
+ providerMetadata,
659
+ },
660
+ ],
421
661
  },
422
- reasoning: part.text,
662
+ reasoning: text,
423
663
  };
424
664
  }
425
665
  case "source":
@@ -430,6 +670,86 @@ export function createStreamingMessage(
430
670
  message: { role: "tool", content: [] },
431
671
  sources: [part],
432
672
  };
673
+ case "raw":
674
+ case "start":
675
+ return {
676
+ ...metadata,
677
+ tool: false,
678
+ message: { role: "assistant", content: [] },
679
+ };
680
+ case "start-step":
681
+ return {
682
+ ...metadata,
683
+ message: { role: "assistant", content: [] },
684
+ ...("warnings" in part && part.warnings?.length > 0
685
+ ? { warnings: serializeWarnings(part.warnings) }
686
+ : {}),
687
+ };
688
+ case "reasoning-start":
689
+ return {
690
+ ...metadata,
691
+ message: { role: "assistant", content: [] },
692
+ reasoning: "",
693
+ };
694
+ case "abort":
695
+ return {
696
+ ...metadata,
697
+ message: { role: "assistant", content: [] },
698
+ status: "failed",
699
+ error: "abort",
700
+ };
701
+ case "tool-error":
702
+ return {
703
+ ...metadata,
704
+ message: {
705
+ role: "assistant",
706
+ content: [
707
+ {
708
+ type: "tool-call",
709
+ toolCallId: part.toolCallId,
710
+ args: part.input,
711
+ toolName: part.toolName,
712
+ providerExecuted: part.providerExecuted,
713
+ providerMetadata,
714
+ },
715
+ {
716
+ type: "tool-result",
717
+ result: part.error,
718
+ toolCallId: part.toolCallId,
719
+ isError: true,
720
+ args: part.input,
721
+ toolName: part.toolName,
722
+ providerExecuted: part.providerExecuted,
723
+ },
724
+ ],
725
+ },
726
+ error: part.error?.toString(),
727
+ };
728
+ case "text-start":
729
+ return {
730
+ ...metadata,
731
+ message: { role: "assistant", content: [] },
732
+ providerMetadata,
733
+ id: part.id,
734
+ _id: part.id,
735
+ };
736
+ case "error": {
737
+ const errorMessage: MessageDoc = {
738
+ ...metadata,
739
+ message: {
740
+ role: "assistant",
741
+ content: [],
742
+ },
743
+ };
744
+ if ("error" in part) {
745
+ errorMessage.error = part.error?.toString();
746
+ } else if (part.errorText) {
747
+ errorMessage.error = part.errorText;
748
+ } else {
749
+ console.warn("Got an error delta with no error", part);
750
+ }
751
+ return errorMessage;
752
+ }
433
753
  // case "raw":
434
754
  // return {
435
755
  // ...metadata,