@convex-dev/agent 0.2.8-alpha.6 → 0.2.9

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 (40) hide show
  1. package/dist/UIMessages.d.ts.map +1 -1
  2. package/dist/UIMessages.js +4 -4
  3. package/dist/UIMessages.js.map +1 -1
  4. package/dist/client/definePlaygroundAPI.js +3 -3
  5. package/dist/client/definePlaygroundAPI.js.map +1 -1
  6. package/dist/client/index.d.ts +4 -2
  7. package/dist/client/index.d.ts.map +1 -1
  8. package/dist/client/index.js +13 -12
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/search.d.ts.map +1 -1
  11. package/dist/client/search.js +15 -13
  12. package/dist/client/search.js.map +1 -1
  13. package/dist/client/start.d.ts +1 -1
  14. package/dist/client/start.d.ts.map +1 -1
  15. package/dist/client/start.js +1 -1
  16. package/dist/client/start.js.map +1 -1
  17. package/dist/client/streaming.d.ts +1 -0
  18. package/dist/client/streaming.d.ts.map +1 -1
  19. package/dist/client/streaming.js +13 -3
  20. package/dist/client/streaming.js.map +1 -1
  21. package/dist/client/types.d.ts +4 -0
  22. package/dist/client/types.d.ts.map +1 -1
  23. package/dist/mapping.d.ts +5 -5
  24. package/dist/mapping.d.ts.map +1 -1
  25. package/dist/mapping.js +22 -22
  26. package/dist/mapping.js.map +1 -1
  27. package/package.json +2 -2
  28. package/src/UIMessages.ts +4 -8
  29. package/src/client/definePlaygroundAPI.ts +3 -3
  30. package/src/client/index.ts +14 -11
  31. package/src/client/search.ts +15 -13
  32. package/src/client/start.ts +1 -1
  33. package/src/client/streaming.ts +18 -7
  34. package/src/client/types.ts +4 -0
  35. package/src/deltas.test.ts +262 -0
  36. package/src/mapping.test.ts +8 -8
  37. package/src/mapping.ts +28 -28
  38. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  39. package/src/client/mock.json +0 -68
  40. package/src/client/stream.json +0 -48
@@ -36,7 +36,7 @@ import { convexToJson, v, type Value } from "convex/values";
36
36
  import type { threadFieldsSupportingPatch } from "../component/threads.js";
37
37
  import { type VectorDimension } from "../component/vector/tables.js";
38
38
  import {
39
- deserializeMessage,
39
+ toModelMessage,
40
40
  serializeMessage,
41
41
  serializeNewMessagesInStep,
42
42
  serializeObjectResult,
@@ -67,7 +67,7 @@ import {
67
67
  fetchContextMessages,
68
68
  generateAndSaveEmbeddings,
69
69
  } from "./search.js";
70
- import { start } from "./start.js";
70
+ import { startGeneration } from "./start.js";
71
71
  import {
72
72
  compressUIMessageChunks,
73
73
  DeltaStreamer,
@@ -104,7 +104,9 @@ import type {
104
104
 
105
105
  export { stepCountIs } from "ai";
106
106
  export {
107
- deserializeMessage,
107
+ toModelMessage,
108
+ //** @deprecated use toModelMessage instead */
109
+ toModelMessage as deserializeMessage,
108
110
  guessMimeType,
109
111
  serializeDataOrUrl,
110
112
  serializeMessage,
@@ -159,10 +161,12 @@ export {
159
161
  embedMessages,
160
162
  embedMany,
161
163
  } from "./search.js";
164
+ export { startGeneration } from "./start.js";
162
165
  export {
163
166
  DEFAULT_STREAMING_OPTIONS,
164
167
  DeltaStreamer,
165
168
  abortStream,
169
+ compressUIMessageChunks,
166
170
  listStreams,
167
171
  syncStreams,
168
172
  vStreamMessagesReturnValue,
@@ -173,6 +177,7 @@ export {
173
177
  searchThreadTitles,
174
178
  updateThreadMetadata,
175
179
  } from "./threads.js";
180
+ export type { ContextHandler } from "./types.js";
176
181
  export { toUIMessages, fromUIMessages, type UIMessage } from "../UIMessages.js";
177
182
 
178
183
  export type {
@@ -449,7 +454,7 @@ export class Agent<
449
454
  getSavedMessages: () => MessageDoc[];
450
455
  }> {
451
456
  type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
452
- return start<T, Tools, CustomCtx>(
457
+ return startGeneration<T, Tools, CustomCtx>(
453
458
  ctx,
454
459
  this.component,
455
460
  {
@@ -1496,10 +1501,8 @@ export class Agent<
1496
1501
  stopWhen: spec?.stopWhen,
1497
1502
  ...overrides,
1498
1503
  ...omit(rest, ["storageOptions", "contextOptions", "stream"]),
1499
- messages: messages?.map(deserializeMessage),
1500
- prompt: Array.isArray(prompt)
1501
- ? prompt.map(deserializeMessage)
1502
- : prompt,
1504
+ messages: messages?.map(toModelMessage),
1505
+ prompt: Array.isArray(prompt) ? prompt.map(toModelMessage) : prompt,
1503
1506
  toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1504
1507
  } satisfies StreamingTextArgs<AgentTools>;
1505
1508
  if (maxSteps) {
@@ -1573,9 +1576,9 @@ export class Agent<
1573
1576
  ...objectArgs,
1574
1577
  ...callSettings,
1575
1578
  ...omit(rest, ["storageOptions", "contextOptions"]),
1576
- messages: args.messages?.map(deserializeMessage),
1579
+ messages: args.messages?.map(toModelMessage),
1577
1580
  prompt: Array.isArray(args.prompt)
1578
- ? args.prompt.map(deserializeMessage)
1581
+ ? args.prompt.map(toModelMessage)
1579
1582
  : args.prompt,
1580
1583
  } as GenerateObjectArgs<FlexibleSchema<T>>;
1581
1584
  const ctx = (
@@ -1633,7 +1636,7 @@ export class Agent<
1633
1636
  handler: async (ctx, args) => {
1634
1637
  const { messages } = await this.saveMessages(ctx, {
1635
1638
  ...args,
1636
- messages: args.messages.map((m) => deserializeMessage(m.message)),
1639
+ messages: args.messages.map((m) => toModelMessage(m.message)),
1637
1640
  metadata: args.messages.map(({ message: _, ...m }) => m),
1638
1641
  skipEmbeddings: true,
1639
1642
  });
@@ -28,7 +28,7 @@ import type {
28
28
  RunQueryCtx,
29
29
  } from "./types.js";
30
30
  import { inlineMessagesFiles } from "./files.js";
31
- import { deserializeMessage } from "../mapping.js";
31
+ import { toModelMessage } from "../mapping.js";
32
32
 
33
33
  const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
34
34
  // 10k characters should be more than enough for most cases, and stays under
@@ -525,20 +525,28 @@ export async function fetchContextWithPrompt(
525
525
  const search = searchMessages
526
526
  .map((m) => m.message)
527
527
  .filter((m) => !!m)
528
- .map(deserializeMessage);
528
+ .map(toModelMessage);
529
529
  const recent = prePromptDocs
530
530
  .map((m) => m.message)
531
531
  .filter((m) => !!m)
532
- .map(deserializeMessage);
533
- const inputMessages = messages.map(deserializeMessage);
534
- const inputPrompt = promptArray.map(deserializeMessage);
532
+ .map(toModelMessage);
533
+ const inputMessages = messages.map(toModelMessage);
534
+ const inputPrompt = promptArray.map(toModelMessage);
535
535
  const existingResponses = existingResponseDocs
536
536
  .map((m) => m.message)
537
537
  .filter((m) => !!m)
538
- .map(deserializeMessage);
538
+ .map(toModelMessage);
539
539
 
540
+ const allMessages = [
541
+ ...search,
542
+ ...recent,
543
+ ...inputMessages,
544
+ ...inputPrompt,
545
+ ...existingResponses,
546
+ ];
540
547
  let processedMessages = args.contextHandler
541
548
  ? await args.contextHandler(ctx, {
549
+ allMessages,
542
550
  search,
543
551
  recent,
544
552
  inputMessages,
@@ -547,13 +555,7 @@ export async function fetchContextWithPrompt(
547
555
  userId,
548
556
  threadId,
549
557
  })
550
- : [
551
- ...search,
552
- ...recent,
553
- ...inputMessages,
554
- ...inputPrompt,
555
- ...existingResponses,
556
- ];
558
+ : allMessages;
557
559
 
558
560
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
559
561
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
@@ -32,7 +32,7 @@ import type { Agent } from "./index.js";
32
32
  import { omit } from "convex-helpers";
33
33
  import { saveInputMessages } from "./saveInputMessages.js";
34
34
 
35
- export async function start<
35
+ export async function startGeneration<
36
36
  T,
37
37
  Tools extends ToolSet = ToolSet,
38
38
  CustomCtx extends object = object,
@@ -198,7 +198,7 @@ export function mergeTransforms<TOOLS extends ToolSet>(
198
198
  * optimize the data in transit.
199
199
  */
200
200
  export class DeltaStreamer<T> {
201
- public streamId: string | undefined;
201
+ streamId: string | undefined;
202
202
  public readonly config: {
203
203
  throttleMs: number;
204
204
  onAsyncAbort: (reason: string) => Promise<void>;
@@ -255,16 +255,27 @@ export class DeltaStreamer<T> {
255
255
  }
256
256
  }
257
257
 
258
+ // Avoid race conditions by only creating once
259
+ #creatingStreamIdPromise: Promise<string> | undefined;
260
+ public async getStreamId() {
261
+ if (this.streamId) {
262
+ return this.streamId;
263
+ }
264
+ if (this.#creatingStreamIdPromise) {
265
+ return this.#creatingStreamIdPromise;
266
+ }
267
+ this.#creatingStreamIdPromise = this.ctx.runMutation(
268
+ this.component.streams.create,
269
+ this.metadata,
270
+ );
271
+ this.streamId = await this.#creatingStreamIdPromise;
272
+ }
273
+
258
274
  public async addParts(parts: T[]) {
259
275
  if (this.abortController.signal.aborted) {
260
276
  return;
261
277
  }
262
- if (!this.streamId) {
263
- this.streamId = await this.ctx.runMutation(
264
- this.component.streams.create,
265
- this.metadata,
266
- );
267
- }
278
+ await this.getStreamId();
268
279
  this.#nextParts.push(...parts);
269
280
  if (
270
281
  !this.#ongoingWrite &&
@@ -244,6 +244,10 @@ export type UsageHandler = (
244
244
  export type ContextHandler = (
245
245
  ctx: RunActionCtx,
246
246
  args: {
247
+ /**
248
+ * All messages in the default order.
249
+ */
250
+ allMessages: ModelMessage[];
247
251
  /**
248
252
  * The messages fetched from search.
249
253
  */
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import {
3
3
  blankUIMessage,
4
+ combineUIMessages,
4
5
  deriveUIMessagesFromTextStreamParts,
5
6
  updateFromTextStreamParts,
6
7
  updateFromUIMessageChunks,
@@ -534,3 +535,264 @@ describe("mergeDeltas", () => {
534
535
  ]);
535
536
  });
536
537
  });
538
+
539
+ describe("combineUIMessages", () => {
540
+ it("combines messages spanning two pages correctly", () => {
541
+ const message1 = {
542
+ id: "msg1",
543
+ key: "msg1-key",
544
+ _creationTime: Date.now(),
545
+ order: 1,
546
+ stepOrder: 1,
547
+ status: "success" as const,
548
+ role: "assistant" as const,
549
+ text: "",
550
+ parts: [
551
+ {
552
+ type: "dynamic-tool" as const,
553
+ state: "input-available" as const,
554
+ toolCallId: "call_123",
555
+ toolName: "calculator",
556
+ input: { operation: "add", a: 2, b: 3 },
557
+ },
558
+ ],
559
+ };
560
+
561
+ const message2 = {
562
+ id: "msg2",
563
+ key: "msg2-key",
564
+ _creationTime: Date.now() + 1,
565
+ order: 1,
566
+ stepOrder: 2,
567
+ status: "success" as const,
568
+ role: "assistant" as const,
569
+ text: "The result is 5.",
570
+ parts: [
571
+ {
572
+ type: "tool-calculator" as const,
573
+ state: "output-available" as const,
574
+ toolCallId: "call_123",
575
+ input: { operation: "add", a: 2, b: 3 },
576
+ output: { result: 5 },
577
+ },
578
+ {
579
+ type: "text" as const,
580
+ text: "The result is 5.",
581
+ state: "done" as const,
582
+ },
583
+ ],
584
+ };
585
+
586
+ const combined = combineUIMessages([message1, message2]);
587
+
588
+ expect(combined).toHaveLength(1);
589
+ expect(combined[0].role).toBe("assistant");
590
+ expect(combined[0].text).toBe("The result is 5.");
591
+ expect(combined[0].parts).toHaveLength(2);
592
+
593
+ const toolPart = combined[0].parts.find(p => p.type === "tool-calculator");
594
+ expect(toolPart).toMatchObject({
595
+ type: "tool-calculator",
596
+ state: "output-available",
597
+ toolCallId: "call_123",
598
+ input: { operation: "add", a: 2, b: 3 },
599
+ output: { result: 5 },
600
+ });
601
+
602
+ const textPart = combined[0].parts.find(p => p.type === "text");
603
+ expect(textPart).toMatchObject({
604
+ type: "text",
605
+ text: "The result is 5.",
606
+ state: "done",
607
+ });
608
+ });
609
+
610
+ it("preserves separate messages with different roles", () => {
611
+ const userMessage = {
612
+ id: "user1",
613
+ key: "user1-key",
614
+ _creationTime: Date.now(),
615
+ order: 1,
616
+ stepOrder: 0,
617
+ status: "success" as const,
618
+ role: "user" as const,
619
+ text: "Calculate 2 + 3",
620
+ parts: [{ type: "text" as const, text: "Calculate 2 + 3" }],
621
+ };
622
+
623
+ const assistantMessage = {
624
+ id: "assistant1",
625
+ key: "assistant1-key",
626
+ _creationTime: Date.now() + 1,
627
+ order: 2,
628
+ stepOrder: 0,
629
+ status: "success" as const,
630
+ role: "assistant" as const,
631
+ text: "The result is 5.",
632
+ parts: [{ type: "text" as const, text: "The result is 5.", state: "done" as const }],
633
+ };
634
+
635
+ const combined = combineUIMessages([userMessage, assistantMessage]);
636
+
637
+ expect(combined).toHaveLength(2);
638
+ expect(combined[0]).toEqual(userMessage);
639
+ expect(combined[1]).toEqual(assistantMessage);
640
+ });
641
+
642
+ it("combines multiple tool calls across pages", () => {
643
+ const message1 = {
644
+ id: "msg1",
645
+ key: "msg1-key",
646
+ _creationTime: Date.now(),
647
+ order: 1,
648
+ stepOrder: 1,
649
+ status: "success" as const,
650
+ role: "assistant" as const,
651
+ text: "",
652
+ parts: [
653
+ {
654
+ type: "dynamic-tool" as const,
655
+ state: "input-available" as const,
656
+ toolCallId: "call_1",
657
+ toolName: "calculator",
658
+ input: { operation: "add", a: 2, b: 3 },
659
+ },
660
+ {
661
+ type: "dynamic-tool" as const,
662
+ state: "input-available" as const,
663
+ toolCallId: "call_2",
664
+ toolName: "formatter",
665
+ input: { text: "result" },
666
+ },
667
+ ],
668
+ };
669
+
670
+ const message2 = {
671
+ id: "msg2",
672
+ key: "msg2-key",
673
+ _creationTime: Date.now() + 1,
674
+ order: 1,
675
+ stepOrder: 2,
676
+ status: "success" as const,
677
+ role: "assistant" as const,
678
+ text: "The formatted result is: 5",
679
+ parts: [
680
+ {
681
+ type: "tool-calculator" as const,
682
+ state: "output-available" as const,
683
+ toolCallId: "call_1",
684
+ input: { operation: "add", a: 2, b: 3 },
685
+ output: { result: 5 },
686
+ },
687
+ {
688
+ type: "tool-formatter" as const,
689
+ state: "output-available" as const,
690
+ toolCallId: "call_2",
691
+ input: { text: "result" },
692
+ output: { formatted: "The formatted result is: 5" },
693
+ },
694
+ {
695
+ type: "text" as const,
696
+ text: "The formatted result is: 5",
697
+ state: "done" as const,
698
+ },
699
+ ],
700
+ };
701
+
702
+ const combined = combineUIMessages([message1, message2]);
703
+
704
+ expect(combined).toHaveLength(1);
705
+ expect(combined[0].role).toBe("assistant");
706
+ expect(combined[0].text).toBe("The formatted result is: 5");
707
+ expect(combined[0].parts).toHaveLength(3);
708
+
709
+ const calculatorPart = combined[0].parts.find(p =>
710
+ p.type === "tool-calculator" && "toolCallId" in p && p.toolCallId === "call_1"
711
+ );
712
+ expect(calculatorPart).toMatchObject({
713
+ type: "tool-calculator",
714
+ state: "output-available",
715
+ toolCallId: "call_1",
716
+ input: { operation: "add", a: 2, b: 3 },
717
+ output: { result: 5 },
718
+ });
719
+
720
+ const formatterPart = combined[0].parts.find(p =>
721
+ p.type === "tool-formatter" && "toolCallId" in p && p.toolCallId === "call_2"
722
+ );
723
+ expect(formatterPart).toMatchObject({
724
+ type: "tool-formatter",
725
+ state: "output-available",
726
+ toolCallId: "call_2",
727
+ input: { text: "result" },
728
+ output: { formatted: "The formatted result is: 5" },
729
+ });
730
+ });
731
+
732
+ it("handles tool call without corresponding output", () => {
733
+ const message1 = {
734
+ id: "msg1",
735
+ key: "msg1-key",
736
+ _creationTime: Date.now(),
737
+ order: 1,
738
+ stepOrder: 1,
739
+ status: "success" as const,
740
+ role: "assistant" as const,
741
+ text: "",
742
+ parts: [
743
+ {
744
+ type: "dynamic-tool" as const,
745
+ state: "input-available" as const,
746
+ toolCallId: "call_orphan",
747
+ toolName: "calculator",
748
+ input: { operation: "add", a: 2, b: 3 },
749
+ },
750
+ ],
751
+ };
752
+
753
+ const message2 = {
754
+ id: "msg2",
755
+ key: "msg2-key",
756
+ _creationTime: Date.now() + 1,
757
+ order: 1,
758
+ stepOrder: 2,
759
+ status: "success" as const,
760
+ role: "assistant" as const,
761
+ text: "Still processing...",
762
+ parts: [
763
+ {
764
+ type: "text" as const,
765
+ text: "Still processing...",
766
+ state: "done" as const,
767
+ },
768
+ ],
769
+ };
770
+
771
+ const combined = combineUIMessages([message1, message2]);
772
+
773
+ expect(combined).toHaveLength(1);
774
+ expect(combined[0].role).toBe("assistant");
775
+ expect(combined[0].text).toBe("Still processing...");
776
+ expect(combined[0].parts).toHaveLength(2);
777
+
778
+ const toolPart = combined[0].parts.find(p =>
779
+ p.type === "dynamic-tool"
780
+ );
781
+ expect(toolPart).toMatchObject({
782
+ type: "dynamic-tool",
783
+ state: "input-available",
784
+ toolCallId: "call_orphan",
785
+ toolName: "calculator",
786
+ input: { operation: "add", a: 2, b: 3 },
787
+ });
788
+
789
+ const textPart = combined[0].parts.find(p =>
790
+ p.type === "text"
791
+ );
792
+ expect(textPart).toMatchObject({
793
+ type: "text",
794
+ text: "Still processing...",
795
+ state: "done",
796
+ });
797
+ });
798
+ });
@@ -2,11 +2,11 @@ import { describe, test, expect } from "vitest";
2
2
  import {
3
3
  guessMimeType,
4
4
  serializeDataOrUrl,
5
- deserializeUrl,
5
+ toModelMessageDataOrUrl,
6
6
  serializeMessage,
7
- deserializeMessage,
7
+ toModelMessage,
8
8
  serializeContent,
9
- deserializeContent,
9
+ toModelMessageContent,
10
10
  } from "./mapping.js";
11
11
  import { api } from "./component/_generated/api.js";
12
12
  import type { AgentComponent, ActionCtx } from "./client/types.js";
@@ -57,8 +57,8 @@ describe("mapping", () => {
57
57
  const ser = serializeDataOrUrl(arr);
58
58
  expect(ser).toBeInstanceOf(ArrayBuffer);
59
59
  expect(new Uint8Array(ser as ArrayBuffer)).toEqual(arr);
60
- // deserializeUrl should return the same ArrayBuffer
61
- const deser = deserializeUrl(ser);
60
+ // toModelMessageDataOrUrl should return the same ArrayBuffer
61
+ const deser = toModelMessageDataOrUrl(ser);
62
62
  expect(deser).toBeInstanceOf(ArrayBuffer);
63
63
  expect(new Uint8Array(deser as ArrayBuffer)).toEqual(arr);
64
64
  });
@@ -83,7 +83,7 @@ describe("mapping", () => {
83
83
  const { message: ser } = await serializeMessage(ctx, component, message);
84
84
  // Use is for type validation
85
85
  expect(validate(vMessage, ser)).toBeTruthy();
86
- const round = deserializeMessage(ser);
86
+ const round = toModelMessage(ser);
87
87
  expect(round).toEqual(message);
88
88
  });
89
89
 
@@ -97,7 +97,7 @@ describe("mapping", () => {
97
97
  value: "hello world",
98
98
  },
99
99
  } satisfies ToolResultPart;
100
- const [result] = deserializeContent([toolResult]);
100
+ const [result] = toModelMessageContent([toolResult]);
101
101
  expect(result).toMatchObject(toolResult);
102
102
  const {
103
103
  content: [roundtrip],
@@ -123,7 +123,7 @@ describe("mapping", () => {
123
123
  value: "hello world",
124
124
  },
125
125
  };
126
- const [deserialized] = deserializeContent([toolResult]);
126
+ const [deserialized] = toModelMessageContent([toolResult]);
127
127
  expect(deserialized).toMatchObject(expected);
128
128
  const {
129
129
  content: [serialized],
package/src/mapping.ts CHANGED
@@ -125,12 +125,12 @@ export async function serializeOrThrow(
125
125
  } as SerializedMessage;
126
126
  }
127
127
 
128
- export function deserializeMessage(
128
+ export function toModelMessage(
129
129
  message: SerializedMessage | ModelMessage,
130
130
  ): ModelMessage {
131
131
  return {
132
132
  ...message,
133
- content: deserializeContent(message.content),
133
+ content: toModelMessageContent(message.content),
134
134
  } as ModelMessage;
135
135
  }
136
136
 
@@ -144,7 +144,7 @@ export function serializeUsage(usage: LanguageModelUsage): Usage {
144
144
  };
145
145
  }
146
146
 
147
- export function deserializeUsage(usage: Usage): LanguageModelUsage {
147
+ export function toModelMessageUsage(usage: Usage): LanguageModelUsage {
148
148
  return {
149
149
  inputTokens: usage.promptTokens,
150
150
  outputTokens: usage.completionTokens,
@@ -168,7 +168,7 @@ export function serializeWarnings(
168
168
  });
169
169
  }
170
170
 
171
- export function deserializeWarnings(
171
+ export function toModelMessageWarnings(
172
172
  warnings: MessageWithMetadata["warnings"],
173
173
  ): CallWarning[] | undefined {
174
174
  // We don't need to do anythign here for now
@@ -262,18 +262,18 @@ export async function serializeContent(
262
262
  return { content };
263
263
  }
264
264
  const fileIds: string[] = [];
265
- const metadata: {
266
- providerOptions?: ProviderOptions;
267
- providerMetadata?: ProviderMetadata;
268
- } = {};
269
- if ("providerOptions" in content) {
270
- metadata.providerOptions = content.providerOptions as ProviderOptions;
271
- }
272
- if ("providerMetadata" in content) {
273
- metadata.providerMetadata = content.providerMetadata as ProviderMetadata;
274
- }
275
265
  const serialized = await Promise.all(
276
266
  content.map(async (part) => {
267
+ const metadata: {
268
+ providerOptions?: ProviderOptions;
269
+ providerMetadata?: ProviderMetadata;
270
+ } = {};
271
+ if ("providerOptions" in part) {
272
+ metadata.providerOptions = part.providerOptions as ProviderOptions;
273
+ }
274
+ if ("providerMetadata" in part) {
275
+ metadata.providerMetadata = part.providerMetadata as ProviderMetadata;
276
+ }
277
277
  switch (part.type) {
278
278
  case "text": {
279
279
  return {
@@ -371,17 +371,17 @@ export function fromModelMessageContent(content: Content): Message["content"] {
371
371
  if (typeof content === "string") {
372
372
  return content;
373
373
  }
374
- const metadata: {
375
- providerOptions?: ProviderOptions;
376
- providerMetadata?: ProviderMetadata;
377
- } = {};
378
- if ("providerOptions" in content) {
379
- metadata.providerOptions = content.providerOptions as ProviderOptions;
380
- }
381
- if ("providerMetadata" in content) {
382
- metadata.providerMetadata = content.providerMetadata as ProviderMetadata;
383
- }
384
374
  return content.map((part) => {
375
+ const metadata: {
376
+ providerOptions?: ProviderOptions;
377
+ providerMetadata?: ProviderMetadata;
378
+ } = {};
379
+ if ("providerOptions" in part) {
380
+ metadata.providerOptions = part.providerOptions as ProviderOptions;
381
+ }
382
+ if ("providerMetadata" in part) {
383
+ metadata.providerMetadata = part.providerMetadata as ProviderMetadata;
384
+ }
385
385
  switch (part.type) {
386
386
  case "text":
387
387
  return part satisfies Infer<typeof vTextPart>;
@@ -424,7 +424,7 @@ export function fromModelMessageContent(content: Content): Message["content"] {
424
424
  }) as Message["content"];
425
425
  }
426
426
 
427
- export function deserializeContent(
427
+ export function toModelMessageContent(
428
428
  content: SerializedContent | ModelMessage["content"],
429
429
  ): Content {
430
430
  if (typeof content === "string") {
@@ -451,14 +451,14 @@ export function deserializeContent(
451
451
  case "image":
452
452
  return {
453
453
  type: part.type,
454
- image: deserializeUrl(part.image),
454
+ image: toModelMessageDataOrUrl(part.image),
455
455
  mediaType: getMimeOrMediaType(part),
456
456
  ...metadata,
457
457
  } satisfies ImagePart;
458
458
  case "file":
459
459
  return {
460
460
  type: part.type,
461
- data: deserializeUrl(part.data),
461
+ data: toModelMessageDataOrUrl(part.data),
462
462
  filename: part.filename,
463
463
  mediaType: getMimeOrMediaType(part)!,
464
464
  ...metadata,
@@ -636,7 +636,7 @@ export function serializeDataOrUrl(
636
636
  ) as ArrayBuffer;
637
637
  }
638
638
 
639
- export function deserializeUrl(
639
+ export function toModelMessageDataOrUrl(
640
640
  urlOrString: string | ArrayBuffer | URL | DataContent,
641
641
  ): URL | DataContent {
642
642
  if (urlOrString instanceof URL) {
@@ -1 +1 @@
1
- {"version":"3.2.4","results":[[":client/index.test.ts",{"duration":129.9694999999997,"failed":false}],[":component/messages.test.ts",{"duration":191.73391600000014,"failed":false}],[":component/users.test.ts",{"duration":545.7272500000001,"failed":false}],[":mapping.test.ts",{"duration":9.238791999999876,"failed":false}],[":react/deltas.test.ts",{"duration":5.8497500000000855,"failed":false}],[":react/toUIMessages.test.ts",{"duration":6.495208000000048,"failed":false}],[":validators.test.ts",{"duration":1.0037499999999682,"failed":false}],[":deltas.test.ts",{"duration":0,"failed":false}],[":client/streaming.test.ts",{"duration":177.87866699695587,"failed":false}]]}
1
+ {"version":"3.2.4","results":[[":client/index.test.ts",{"duration":129.9694999999997,"failed":false}],[":component/messages.test.ts",{"duration":191.73391600000014,"failed":false}],[":component/users.test.ts",{"duration":545.7272500000001,"failed":false}],[":mapping.test.ts",{"duration":28.127082999999402,"failed":false}],[":react/deltas.test.ts",{"duration":5.8497500000000855,"failed":false}],[":react/toUIMessages.test.ts",{"duration":6.495208000000048,"failed":false}],[":validators.test.ts",{"duration":1.0037499999999682,"failed":false}],[":deltas.test.ts",{"duration":0,"failed":false}],[":client/streaming.test.ts",{"duration":177.87866699695587,"failed":false}]]}