@convex-dev/agent 0.2.2 → 0.2.3

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 (62) hide show
  1. package/dist/client/definePlaygroundAPI.d.ts +144 -13
  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 +130 -10
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +17 -7
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/messages.d.ts +32 -5
  11. package/dist/client/messages.d.ts.map +1 -1
  12. package/dist/client/messages.js +7 -4
  13. package/dist/client/messages.js.map +1 -1
  14. package/dist/client/search.d.ts +31 -4
  15. package/dist/client/search.d.ts.map +1 -1
  16. package/dist/client/search.js +2 -4
  17. package/dist/client/search.js.map +1 -1
  18. package/dist/client/streaming.d.ts +322 -39
  19. package/dist/client/streaming.d.ts.map +1 -1
  20. package/dist/component/_generated/api.d.ts +301 -15
  21. package/dist/component/messages.d.ts +336 -41
  22. package/dist/component/messages.d.ts.map +1 -1
  23. package/dist/component/messages.js +4 -4
  24. package/dist/component/messages.js.map +1 -1
  25. package/dist/component/schema.d.ts +874 -133
  26. package/dist/component/schema.d.ts.map +1 -1
  27. package/dist/mapping.d.ts +4 -4
  28. package/dist/mapping.d.ts.map +1 -1
  29. package/dist/mapping.js +68 -38
  30. package/dist/mapping.js.map +1 -1
  31. package/dist/react/deltas.d.ts +3 -3
  32. package/dist/react/deltas.d.ts.map +1 -1
  33. package/dist/react/deltas.js +321 -28
  34. package/dist/react/deltas.js.map +1 -1
  35. package/dist/react/toUIMessages.d.ts.map +1 -1
  36. package/dist/react/toUIMessages.js +289 -180
  37. package/dist/react/toUIMessages.js.map +1 -1
  38. package/dist/react/useSmoothText.d.ts.map +1 -1
  39. package/dist/react/useSmoothText.js +15 -6
  40. package/dist/react/useSmoothText.js.map +1 -1
  41. package/dist/shared.d.ts +4 -0
  42. package/dist/shared.d.ts.map +1 -1
  43. package/dist/shared.js +8 -0
  44. package/dist/shared.js.map +1 -1
  45. package/dist/validators.d.ts +1887 -249
  46. package/dist/validators.d.ts.map +1 -1
  47. package/dist/validators.js +10 -3
  48. package/dist/validators.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/client/files.ts +4 -4
  51. package/src/client/index.ts +41 -15
  52. package/src/client/messages.ts +12 -4
  53. package/src/client/search.ts +2 -6
  54. package/src/component/_generated/api.d.ts +301 -15
  55. package/src/component/messages.ts +4 -3
  56. package/src/mapping.ts +105 -56
  57. package/src/react/deltas.ts +391 -46
  58. package/src/react/toUIMessages.test.ts +234 -4
  59. package/src/react/toUIMessages.ts +366 -192
  60. package/src/react/useSmoothText.ts +25 -13
  61. package/src/shared.ts +14 -0
  62. package/src/validators.ts +23 -15
@@ -10,6 +10,7 @@ import {
10
10
  DEFAULT_RECENT_MESSAGES,
11
11
  extractText,
12
12
  isTool,
13
+ sorted,
13
14
  } from "../shared.js";
14
15
  import {
15
16
  vMessageEmbeddingsWithDimension,
@@ -631,11 +632,11 @@ export const _fetchSearchMessages = internalQuery({
631
632
  .map(publicMessage);
632
633
  messages.push(...(args.textSearchMessages ?? []));
633
634
  // TODO: prioritize more recent messages
634
- messages.sort((a, b) => a.order! - b.order!);
635
+ messages = sorted(messages);
635
636
  messages = messages.slice(0, args.limit);
636
637
  // Fetch the surrounding messages
637
638
  if (!threadId) {
638
- return messages.sort((a, b) => a.order - b.order);
639
+ return messages;
639
640
  }
640
641
  const included: Record<string, Set<number>> = {};
641
642
  for (const m of messages) {
@@ -688,7 +689,7 @@ export const _fetchSearchMessages = internalQuery({
688
689
  messages.push(publicMessage(r));
689
690
  }
690
691
  }
691
- return messages.sort((a, b) => a.order - b.order);
692
+ return sorted(messages);
692
693
  },
693
694
  });
694
695
 
package/src/mapping.ts CHANGED
@@ -16,26 +16,32 @@ import {
16
16
  type TextPart,
17
17
  type ToolCallPart,
18
18
  type ToolResultPart,
19
- type ReasoningUIPart,
19
+ type ProviderMetadata,
20
20
  } from "ai";
21
- import type {
22
- Message,
23
- MessageWithMetadata,
24
- Usage,
25
- vContent,
26
- vFilePart,
27
- vImagePart,
28
- vReasoningPart,
29
- vRedactedReasoningPart,
30
- vTextPart,
31
- vToolCallPart,
32
- vToolResultPart,
21
+ import {
22
+ vMessageWithMetadata,
23
+ type Message,
24
+ type MessageWithMetadata,
25
+ type Usage,
26
+ type vContent,
27
+ type vFilePart,
28
+ type vImagePart,
29
+ type vReasoningPart,
30
+ type vRedactedReasoningPart,
31
+ type vTextPart,
32
+ type vToolCallPart,
33
+ type vToolResultPart,
33
34
  } from "./validators.js";
34
35
  import type { ActionCtx, AgentComponent } from "./client/types.js";
35
36
  import type { RunMutationCtx } from "./client/types.js";
36
37
  import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
37
38
  import type { Infer } from "convex/values";
38
- import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils";
39
+ import {
40
+ convertUint8ArrayToBase64,
41
+ type ProviderOptions,
42
+ type ReasoningPart,
43
+ } from "@ai-sdk/provider-utils";
44
+ import { parse } from "convex-helpers/validators";
39
45
  export type AIMessageWithoutId = Omit<AIMessage, "id">;
40
46
 
41
47
  export type SerializeUrlsAndUint8Arrays<T> = T extends URL
@@ -95,7 +101,9 @@ export async function serializeOrThrow(
95
101
  } as SerializedMessage;
96
102
  }
97
103
 
98
- export function deserializeMessage(message: SerializedMessage): ModelMessage {
104
+ export function deserializeMessage(
105
+ message: SerializedMessage | ModelMessage,
106
+ ): ModelMessage {
99
107
  return {
100
108
  ...message,
101
109
  content: deserializeContent(message.content),
@@ -169,12 +177,12 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
169
177
  : step.response.messages.slice(-1)
170
178
  ).map(async (msg): Promise<MessageWithMetadata> => {
171
179
  const { message, fileIds } = await serializeMessage(ctx, component, msg);
172
- return {
180
+ return parse(vMessageWithMetadata, {
173
181
  message,
174
182
  ...(message.role === "tool" ? toolFields : assistantFields),
175
183
  text: step.text,
176
184
  fileIds,
177
- };
185
+ });
178
186
  }),
179
187
  );
180
188
  // TODO: capture step.files separately?
@@ -210,6 +218,16 @@ export async function serializeObjectResult(
210
218
  };
211
219
  }
212
220
 
221
+ function getMimeOrMediaType(part: { mediaType?: string; mimeType?: string }) {
222
+ if ("mediaType" in part) {
223
+ return part.mediaType;
224
+ }
225
+ if ("mimeType" in part) {
226
+ return part.mimeType;
227
+ }
228
+ return undefined;
229
+ }
230
+
213
231
  export async function serializeContent(
214
232
  ctx: ActionCtx | RunMutationCtx,
215
233
  component: AgentComponent,
@@ -219,14 +237,15 @@ export async function serializeContent(
219
237
  return { content };
220
238
  }
221
239
  const fileIds: string[] = [];
222
- function getMimeType(part: { mediaType?: string; mimeType?: string }) {
223
- if ("mediaType" in part) {
224
- return part.mediaType;
225
- }
226
- if ("mimeType" in part) {
227
- return part.mimeType;
228
- }
229
- return undefined;
240
+ const metadata: {
241
+ providerOptions?: ProviderOptions;
242
+ providerMetadata?: ProviderMetadata;
243
+ } = {};
244
+ if ("providerOptions" in content) {
245
+ metadata.providerOptions = content.providerOptions as ProviderOptions;
246
+ }
247
+ if ("providerMetadata" in content) {
248
+ metadata.providerMetadata = content.providerMetadata as ProviderMetadata;
230
249
  }
231
250
  const serialized = await Promise.all(
232
251
  content.map(async (part) => {
@@ -235,7 +254,7 @@ export async function serializeContent(
235
254
  return {
236
255
  type: part.type,
237
256
  text: part.text,
238
- providerOptions: part.providerOptions,
257
+ ...metadata,
239
258
  } satisfies Infer<typeof vTextPart>;
240
259
  }
241
260
  case "image": {
@@ -248,7 +267,7 @@ export async function serializeContent(
248
267
  ctx,
249
268
  component,
250
269
  new Blob([image], {
251
- type: getMimeType(part) || guessMimeType(image),
270
+ type: getMimeOrMediaType(part) || guessMimeType(image),
252
271
  }),
253
272
  );
254
273
  image = file.url;
@@ -256,8 +275,8 @@ export async function serializeContent(
256
275
  }
257
276
  return {
258
277
  type: part.type,
259
- mimeType: getMimeType(part),
260
- providerOptions: part.providerOptions,
278
+ mimeType: getMimeOrMediaType(part),
279
+ ...metadata,
261
280
  image,
262
281
  } satisfies Infer<typeof vImagePart>;
263
282
  }
@@ -267,7 +286,7 @@ export async function serializeContent(
267
286
  const { file } = await storeFile(
268
287
  ctx,
269
288
  component,
270
- new Blob([data], { type: getMimeType(part) }),
289
+ new Blob([data], { type: getMimeOrMediaType(part) }),
271
290
  );
272
291
  data = file.url;
273
292
  fileIds.push(file.fileId);
@@ -276,8 +295,8 @@ export async function serializeContent(
276
295
  type: part.type,
277
296
  data,
278
297
  filename: part.filename,
279
- mimeType: getMimeType(part)!,
280
- providerOptions: part.providerOptions,
298
+ mimeType: getMimeOrMediaType(part)!,
299
+ ...metadata,
281
300
  } satisfies Infer<typeof vFilePart>;
282
301
  }
283
302
  case "tool-call": {
@@ -287,8 +306,8 @@ export async function serializeContent(
287
306
  args: args ?? null,
288
307
  toolCallId: part.toolCallId,
289
308
  toolName: part.toolName,
290
- providerOptions: part.providerOptions,
291
309
  providerExecuted: part.providerExecuted,
310
+ ...metadata,
292
311
  } satisfies Infer<typeof vToolCallPart>;
293
312
  }
294
313
  case "tool-result": {
@@ -298,14 +317,14 @@ export async function serializeContent(
298
317
  result: result ?? null,
299
318
  toolCallId: part.toolCallId,
300
319
  toolName: part.toolName,
301
- providerOptions: part.providerOptions,
320
+ ...metadata,
302
321
  } satisfies Infer<typeof vToolResultPart>;
303
322
  }
304
323
  case "reasoning": {
305
324
  return {
306
325
  type: part.type,
307
326
  text: part.text,
308
- providerOptions: part.providerOptions,
327
+ ...metadata,
309
328
  } satisfies Infer<typeof vReasoningPart>;
310
329
  }
311
330
  // Not in current generation output, but could be in historical messages
@@ -313,7 +332,7 @@ export async function serializeContent(
313
332
  return {
314
333
  type: part.type,
315
334
  data: part.data,
316
- providerOptions: part.providerOptions,
335
+ ...metadata,
317
336
  } satisfies Infer<typeof vRedactedReasoningPart>;
318
337
  }
319
338
  default:
@@ -327,64 +346,91 @@ export async function serializeContent(
327
346
  };
328
347
  }
329
348
 
330
- export function deserializeContent(content: SerializedContent): Content {
349
+ export function deserializeContent(
350
+ content: SerializedContent | ModelMessage["content"],
351
+ ): Content {
331
352
  if (typeof content === "string") {
332
353
  return content;
333
354
  }
334
355
  return content.map((part) => {
356
+ const metadata: {
357
+ providerOptions?: ProviderOptions;
358
+ providerMetadata?: ProviderMetadata;
359
+ } = {};
360
+ if ("providerOptions" in part) {
361
+ metadata.providerOptions = part.providerOptions;
362
+ }
363
+ if ("providerMetadata" in part) {
364
+ metadata.providerMetadata = part.providerMetadata;
365
+ }
335
366
  switch (part.type) {
336
367
  case "text":
337
368
  return {
338
369
  type: part.type,
339
370
  text: part.text,
340
- providerOptions: part.providerOptions,
371
+ ...metadata,
341
372
  } satisfies TextPart;
342
373
  case "image":
343
374
  return {
344
375
  type: part.type,
345
376
  image: deserializeUrl(part.image),
346
- mediaType: part.mimeType,
347
- providerOptions: part.providerOptions,
377
+ mediaType: getMimeOrMediaType(part),
378
+ ...metadata,
348
379
  } satisfies ImagePart;
349
380
  case "file":
350
381
  return {
351
382
  type: part.type,
352
383
  data: deserializeUrl(part.data),
353
384
  filename: part.filename,
354
- mediaType: part.mimeType,
355
- providerOptions: part.providerOptions,
385
+ mediaType: getMimeOrMediaType(part)!,
386
+ ...metadata,
356
387
  } satisfies FilePart;
357
- case "tool-call":
388
+ case "tool-call": {
389
+ const input = "input" in part ? part.input : part.args;
358
390
  return {
359
391
  type: part.type,
360
- input: part.args ?? null,
361
- providerExecuted: part.providerExecuted,
392
+ input: input ?? null,
362
393
  toolCallId: part.toolCallId,
363
394
  toolName: part.toolName,
364
- providerOptions: part.providerOptions,
395
+ providerExecuted: part.providerExecuted,
396
+ ...metadata,
365
397
  } satisfies ToolCallPart;
366
- case "tool-result":
398
+ }
399
+ case "tool-result": {
400
+ const result = "output" in part ? part.output : part.result;
367
401
  return {
368
402
  type: part.type,
369
- output: part.result ?? null,
403
+ output: result ?? null,
370
404
  toolCallId: part.toolCallId,
371
405
  toolName: part.toolName,
372
- providerOptions: part.providerOptions,
406
+ ...metadata,
373
407
  } satisfies ToolResultPart;
408
+ }
374
409
  case "reasoning":
375
410
  return {
376
411
  type: part.type,
377
412
  text: part.text,
378
- providerMetadata: part.providerOptions,
379
- state: part.state,
380
- } satisfies ReasoningUIPart;
413
+ ...metadata,
414
+ } satisfies ReasoningPart;
381
415
  case "redacted-reasoning":
382
416
  // TODO: should we just drop this?
383
417
  return {
384
418
  type: "reasoning",
385
- text: part.data,
386
- providerMetadata: part.providerOptions,
387
- } satisfies ReasoningUIPart;
419
+ text: "",
420
+ ...metadata,
421
+ providerOptions: metadata.providerOptions
422
+ ? {
423
+ ...Object.fromEntries(
424
+ Object.entries(metadata.providerOptions ?? {}).map(
425
+ ([key, value]) => [
426
+ key,
427
+ { ...value, redactedData: part.data },
428
+ ],
429
+ ),
430
+ ),
431
+ }
432
+ : undefined,
433
+ } satisfies ReasoningPart;
388
434
  default:
389
435
  return part satisfies Content;
390
436
  }
@@ -482,8 +528,11 @@ export function serializeDataOrUrl(
482
528
  }
483
529
 
484
530
  export function deserializeUrl(
485
- urlOrString: string | ArrayBuffer,
531
+ urlOrString: string | ArrayBuffer | URL | DataContent,
486
532
  ): URL | DataContent {
533
+ if (urlOrString instanceof URL) {
534
+ return urlOrString;
535
+ }
487
536
  if (typeof urlOrString === "string") {
488
537
  if (
489
538
  urlOrString.startsWith("http://") ||