@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
@@ -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,19 +16,21 @@ import {
16
16
  type TextPart,
17
17
  type ToolCallPart,
18
18
  type ToolResultPart,
19
+ type ProviderMetadata,
19
20
  } from "ai";
20
- import type {
21
- Message,
22
- MessageWithMetadata,
23
- Usage,
24
- vContent,
25
- vFilePart,
26
- vImagePart,
27
- vReasoningPart,
28
- vRedactedReasoningPart,
29
- vTextPart,
30
- vToolCallPart,
31
- 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,
32
34
  } from "./validators.js";
33
35
  import type { ActionCtx, AgentComponent } from "./client/types.js";
34
36
  import type { RunMutationCtx } from "./client/types.js";
@@ -36,8 +38,10 @@ import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
36
38
  import type { Infer } from "convex/values";
37
39
  import {
38
40
  convertUint8ArrayToBase64,
41
+ type ProviderOptions,
39
42
  type ReasoningPart,
40
43
  } from "@ai-sdk/provider-utils";
44
+ import { parse } from "convex-helpers/validators";
41
45
  export type AIMessageWithoutId = Omit<AIMessage, "id">;
42
46
 
43
47
  export type SerializeUrlsAndUint8Arrays<T> = T extends URL
@@ -97,7 +101,9 @@ export async function serializeOrThrow(
97
101
  } as SerializedMessage;
98
102
  }
99
103
 
100
- export function deserializeMessage(message: SerializedMessage): ModelMessage {
104
+ export function deserializeMessage(
105
+ message: SerializedMessage | ModelMessage,
106
+ ): ModelMessage {
101
107
  return {
102
108
  ...message,
103
109
  content: deserializeContent(message.content),
@@ -171,12 +177,12 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
171
177
  : step.response.messages.slice(-1)
172
178
  ).map(async (msg): Promise<MessageWithMetadata> => {
173
179
  const { message, fileIds } = await serializeMessage(ctx, component, msg);
174
- return {
180
+ return parse(vMessageWithMetadata, {
175
181
  message,
176
182
  ...(message.role === "tool" ? toolFields : assistantFields),
177
183
  text: step.text,
178
184
  fileIds,
179
- };
185
+ });
180
186
  }),
181
187
  );
182
188
  // TODO: capture step.files separately?
@@ -212,6 +218,16 @@ export async function serializeObjectResult(
212
218
  };
213
219
  }
214
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
+
215
231
  export async function serializeContent(
216
232
  ctx: ActionCtx | RunMutationCtx,
217
233
  component: AgentComponent,
@@ -221,14 +237,15 @@ export async function serializeContent(
221
237
  return { content };
222
238
  }
223
239
  const fileIds: string[] = [];
224
- function getMimeType(part: { mediaType?: string; mimeType?: string }) {
225
- if ("mediaType" in part) {
226
- return part.mediaType;
227
- }
228
- if ("mimeType" in part) {
229
- return part.mimeType;
230
- }
231
- 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;
232
249
  }
233
250
  const serialized = await Promise.all(
234
251
  content.map(async (part) => {
@@ -237,7 +254,7 @@ export async function serializeContent(
237
254
  return {
238
255
  type: part.type,
239
256
  text: part.text,
240
- providerOptions: part.providerOptions,
257
+ ...metadata,
241
258
  } satisfies Infer<typeof vTextPart>;
242
259
  }
243
260
  case "image": {
@@ -250,7 +267,7 @@ export async function serializeContent(
250
267
  ctx,
251
268
  component,
252
269
  new Blob([image], {
253
- type: getMimeType(part) || guessMimeType(image),
270
+ type: getMimeOrMediaType(part) || guessMimeType(image),
254
271
  }),
255
272
  );
256
273
  image = file.url;
@@ -258,8 +275,8 @@ export async function serializeContent(
258
275
  }
259
276
  return {
260
277
  type: part.type,
261
- mimeType: getMimeType(part),
262
- providerOptions: part.providerOptions,
278
+ mimeType: getMimeOrMediaType(part),
279
+ ...metadata,
263
280
  image,
264
281
  } satisfies Infer<typeof vImagePart>;
265
282
  }
@@ -269,7 +286,7 @@ export async function serializeContent(
269
286
  const { file } = await storeFile(
270
287
  ctx,
271
288
  component,
272
- new Blob([data], { type: getMimeType(part) }),
289
+ new Blob([data], { type: getMimeOrMediaType(part) }),
273
290
  );
274
291
  data = file.url;
275
292
  fileIds.push(file.fileId);
@@ -278,8 +295,8 @@ export async function serializeContent(
278
295
  type: part.type,
279
296
  data,
280
297
  filename: part.filename,
281
- mimeType: getMimeType(part)!,
282
- providerOptions: part.providerOptions,
298
+ mimeType: getMimeOrMediaType(part)!,
299
+ ...metadata,
283
300
  } satisfies Infer<typeof vFilePart>;
284
301
  }
285
302
  case "tool-call": {
@@ -289,8 +306,8 @@ export async function serializeContent(
289
306
  args: args ?? null,
290
307
  toolCallId: part.toolCallId,
291
308
  toolName: part.toolName,
292
- providerOptions: part.providerOptions,
293
309
  providerExecuted: part.providerExecuted,
310
+ ...metadata,
294
311
  } satisfies Infer<typeof vToolCallPart>;
295
312
  }
296
313
  case "tool-result": {
@@ -300,14 +317,14 @@ export async function serializeContent(
300
317
  result: result ?? null,
301
318
  toolCallId: part.toolCallId,
302
319
  toolName: part.toolName,
303
- providerOptions: part.providerOptions,
320
+ ...metadata,
304
321
  } satisfies Infer<typeof vToolResultPart>;
305
322
  }
306
323
  case "reasoning": {
307
324
  return {
308
325
  type: part.type,
309
326
  text: part.text,
310
- providerOptions: part.providerOptions,
327
+ ...metadata,
311
328
  } satisfies Infer<typeof vReasoningPart>;
312
329
  }
313
330
  // Not in current generation output, but could be in historical messages
@@ -315,7 +332,7 @@ export async function serializeContent(
315
332
  return {
316
333
  type: part.type,
317
334
  data: part.data,
318
- providerOptions: part.providerOptions,
335
+ ...metadata,
319
336
  } satisfies Infer<typeof vRedactedReasoningPart>;
320
337
  }
321
338
  default:
@@ -329,62 +346,90 @@ export async function serializeContent(
329
346
  };
330
347
  }
331
348
 
332
- export function deserializeContent(content: SerializedContent): Content {
349
+ export function deserializeContent(
350
+ content: SerializedContent | ModelMessage["content"],
351
+ ): Content {
333
352
  if (typeof content === "string") {
334
353
  return content;
335
354
  }
336
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
+ }
337
366
  switch (part.type) {
338
367
  case "text":
339
368
  return {
340
369
  type: part.type,
341
370
  text: part.text,
342
- providerOptions: part.providerOptions,
371
+ ...metadata,
343
372
  } satisfies TextPart;
344
373
  case "image":
345
374
  return {
346
375
  type: part.type,
347
376
  image: deserializeUrl(part.image),
348
- mediaType: part.mimeType,
349
- providerOptions: part.providerOptions,
377
+ mediaType: getMimeOrMediaType(part),
378
+ ...metadata,
350
379
  } satisfies ImagePart;
351
380
  case "file":
352
381
  return {
353
382
  type: part.type,
354
383
  data: deserializeUrl(part.data),
355
384
  filename: part.filename,
356
- mediaType: part.mimeType,
357
- providerOptions: part.providerOptions,
385
+ mediaType: getMimeOrMediaType(part)!,
386
+ ...metadata,
358
387
  } satisfies FilePart;
359
- case "tool-call":
388
+ case "tool-call": {
389
+ const input = "input" in part ? part.input : part.args;
360
390
  return {
361
391
  type: part.type,
362
- input: part.args ?? null,
363
- providerExecuted: part.providerExecuted,
392
+ input: input ?? null,
364
393
  toolCallId: part.toolCallId,
365
394
  toolName: part.toolName,
366
- providerOptions: part.providerOptions,
395
+ providerExecuted: part.providerExecuted,
396
+ ...metadata,
367
397
  } satisfies ToolCallPart;
368
- case "tool-result":
398
+ }
399
+ case "tool-result": {
400
+ const result = "output" in part ? part.output : part.result;
369
401
  return {
370
402
  type: part.type,
371
- output: part.result ?? null,
403
+ output: result ?? null,
372
404
  toolCallId: part.toolCallId,
373
405
  toolName: part.toolName,
374
- providerOptions: part.providerOptions,
406
+ ...metadata,
375
407
  } satisfies ToolResultPart;
408
+ }
376
409
  case "reasoning":
377
410
  return {
378
411
  type: part.type,
379
412
  text: part.text,
380
- providerOptions: part.providerOptions,
413
+ ...metadata,
381
414
  } satisfies ReasoningPart;
382
415
  case "redacted-reasoning":
383
416
  // TODO: should we just drop this?
384
417
  return {
385
418
  type: "reasoning",
386
- text: part.data,
387
- providerOptions: part.providerOptions,
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,
388
433
  } satisfies ReasoningPart;
389
434
  default:
390
435
  return part satisfies Content;
@@ -483,8 +528,11 @@ export function serializeDataOrUrl(
483
528
  }
484
529
 
485
530
  export function deserializeUrl(
486
- urlOrString: string | ArrayBuffer,
531
+ urlOrString: string | ArrayBuffer | URL | DataContent,
487
532
  ): URL | DataContent {
533
+ if (urlOrString instanceof URL) {
534
+ return urlOrString;
535
+ }
488
536
  if (typeof urlOrString === "string") {
489
537
  if (
490
538
  urlOrString.startsWith("http://") ||