@convex-dev/agent 0.6.4 → 0.7.0-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 (225) hide show
  1. package/MIGRATION.md +60 -116
  2. package/dist/client/messages.d.ts +1 -398
  3. package/dist/client/messages.d.ts.map +1 -1
  4. package/dist/client/messages.js +3 -47
  5. package/dist/client/messages.js.map +1 -1
  6. package/dist/client/types.d.ts +1 -455
  7. package/dist/client/types.d.ts.map +1 -1
  8. package/dist/component/_generated/component.d.ts +805 -45
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/messages.d.ts +1252 -201
  11. package/dist/component/messages.d.ts.map +1 -1
  12. package/dist/component/messages.js +49 -23
  13. package/dist/component/messages.js.map +1 -1
  14. package/dist/component/schema.d.ts +2593 -332
  15. package/dist/component/schema.d.ts.map +1 -1
  16. package/dist/component/streams.d.ts +7 -1
  17. package/dist/component/streams.d.ts.map +1 -1
  18. package/dist/component/streams.js +24 -30
  19. package/dist/component/streams.js.map +1 -1
  20. package/dist/component/threads.d.ts.map +1 -1
  21. package/dist/component/threads.js +1 -2
  22. package/dist/component/threads.js.map +1 -1
  23. package/dist/react/index.d.ts +5 -5
  24. package/dist/react/index.d.ts.map +1 -1
  25. package/dist/react/index.js +5 -5
  26. package/dist/react/index.js.map +1 -1
  27. package/dist/shared.d.ts +19 -7
  28. package/dist/shared.d.ts.map +1 -1
  29. package/dist/shared.js +4 -4
  30. package/dist/shared.js.map +1 -1
  31. package/dist/streaming/materializePersistedUIMessageChunks.d.ts +16 -0
  32. package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -0
  33. package/dist/streaming/materializePersistedUIMessageChunks.js +302 -0
  34. package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -0
  35. package/dist/streaming/persistedUIMessageChunks.d.ts +106 -0
  36. package/dist/streaming/persistedUIMessageChunks.d.ts.map +1 -0
  37. package/dist/streaming/persistedUIMessageChunks.js +475 -0
  38. package/dist/streaming/persistedUIMessageChunks.js.map +1 -0
  39. package/dist/validators.d.ts +10026 -1055
  40. package/dist/validators.d.ts.map +1 -1
  41. package/dist/validators.js +71 -9
  42. package/dist/validators.js.map +1 -1
  43. package/dist/{UIMessages.d.ts → vercel/UIMessages.d.ts} +1 -1
  44. package/dist/vercel/UIMessages.d.ts.map +1 -0
  45. package/dist/{UIMessages.js → vercel/UIMessages.js} +63 -1
  46. package/dist/vercel/UIMessages.js.map +1 -0
  47. package/dist/{client → vercel/client}/createTool.d.ts +23 -16
  48. package/dist/vercel/client/createTool.d.ts.map +1 -0
  49. package/dist/{client → vercel/client}/createTool.js +2 -0
  50. package/dist/vercel/client/createTool.js.map +1 -0
  51. package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts +533 -79
  52. package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts.map +1 -1
  53. package/dist/{client → vercel/client}/definePlaygroundAPI.js +8 -7
  54. package/dist/vercel/client/definePlaygroundAPI.js.map +1 -0
  55. package/dist/{client → vercel/client}/files.d.ts +2 -2
  56. package/dist/vercel/client/files.d.ts.map +1 -0
  57. package/dist/vercel/client/files.js.map +1 -0
  58. package/dist/vercel/client/messages.d.ts +542 -0
  59. package/dist/vercel/client/messages.d.ts.map +1 -0
  60. package/dist/vercel/client/messages.js +66 -0
  61. package/dist/vercel/client/messages.js.map +1 -0
  62. package/dist/{client → vercel/client}/mockModel.d.ts +17 -17
  63. package/dist/vercel/client/mockModel.d.ts.map +1 -0
  64. package/dist/{client → vercel/client}/mockModel.js +12 -13
  65. package/dist/vercel/client/mockModel.js.map +1 -0
  66. package/dist/{client → vercel/client}/saveInputMessages.d.ts +2 -2
  67. package/dist/vercel/client/saveInputMessages.d.ts.map +1 -0
  68. package/dist/vercel/client/saveInputMessages.js.map +1 -0
  69. package/dist/{client → vercel/client}/search.d.ts +119 -22
  70. package/dist/vercel/client/search.d.ts.map +1 -0
  71. package/dist/{client → vercel/client}/search.js +2 -2
  72. package/dist/vercel/client/search.js.map +1 -0
  73. package/dist/{client → vercel/client}/start.d.ts +26 -11
  74. package/dist/vercel/client/start.d.ts.map +1 -0
  75. package/dist/{client → vercel/client}/start.js +64 -22
  76. package/dist/vercel/client/start.js.map +1 -0
  77. package/dist/{client → vercel/client}/streamText.d.ts +13 -12
  78. package/dist/vercel/client/streamText.d.ts.map +1 -0
  79. package/dist/{client → vercel/client}/streamText.js +43 -9
  80. package/dist/vercel/client/streamText.js.map +1 -0
  81. package/dist/{client → vercel/client}/streaming.d.ts +1410 -183
  82. package/dist/vercel/client/streaming.d.ts.map +1 -0
  83. package/dist/{client → vercel/client}/streaming.js +120 -52
  84. package/dist/vercel/client/streaming.js.map +1 -0
  85. package/dist/vercel/client/types.d.ts +456 -0
  86. package/dist/vercel/client/types.d.ts.map +1 -0
  87. package/dist/vercel/client/types.js +3 -0
  88. package/dist/vercel/client/types.js.map +1 -0
  89. package/dist/{client → vercel/client}/utils.d.ts +3 -2
  90. package/dist/vercel/client/utils.d.ts.map +1 -0
  91. package/dist/{client → vercel/client}/utils.js +2 -2
  92. package/dist/vercel/client/utils.js.map +1 -0
  93. package/dist/{deltas.d.ts → vercel/deltas.d.ts} +3 -4
  94. package/dist/vercel/deltas.d.ts.map +1 -0
  95. package/dist/{deltas.js → vercel/deltas.js} +195 -99
  96. package/dist/vercel/deltas.js.map +1 -0
  97. package/dist/{client → vercel}/index.d.ts +482 -106
  98. package/dist/vercel/index.d.ts.map +1 -0
  99. package/dist/{client → vercel}/index.js +64 -46
  100. package/dist/vercel/index.js.map +1 -0
  101. package/dist/{mapping.d.ts → vercel/mapping.d.ts} +5 -19
  102. package/dist/vercel/mapping.d.ts.map +1 -0
  103. package/dist/{mapping.js → vercel/mapping.js} +411 -114
  104. package/dist/vercel/mapping.js.map +1 -0
  105. package/dist/{react → vercel/react}/optimisticallySendMessage.d.ts +2 -2
  106. package/dist/vercel/react/optimisticallySendMessage.d.ts.map +1 -0
  107. package/dist/vercel/react/optimisticallySendMessage.js.map +1 -0
  108. package/dist/{react → vercel/react}/types.d.ts +1 -1
  109. package/dist/vercel/react/types.d.ts.map +1 -0
  110. package/dist/{react → vercel/react}/types.js.map +1 -1
  111. package/dist/{react → vercel/react}/useDeltaStreams.d.ts +1 -1
  112. package/dist/vercel/react/useDeltaStreams.d.ts.map +1 -0
  113. package/dist/{react → vercel/react}/useDeltaStreams.js +1 -1
  114. package/dist/vercel/react/useDeltaStreams.js.map +1 -0
  115. package/dist/{react → vercel/react}/useStreamingUIMessages.d.ts +1 -1
  116. package/dist/vercel/react/useStreamingUIMessages.d.ts.map +1 -0
  117. package/dist/{react → vercel/react}/useStreamingUIMessages.js +7 -3
  118. package/dist/vercel/react/useStreamingUIMessages.js.map +1 -0
  119. package/dist/{react → vercel/react}/useThreadMessages.d.ts +1 -1
  120. package/dist/vercel/react/useThreadMessages.d.ts.map +1 -0
  121. package/dist/{react → vercel/react}/useThreadMessages.js +1 -1
  122. package/dist/vercel/react/useThreadMessages.js.map +1 -0
  123. package/dist/{react → vercel/react}/useUIMessages.d.ts +1 -1
  124. package/dist/vercel/react/useUIMessages.d.ts.map +1 -0
  125. package/dist/{react → vercel/react}/useUIMessages.js +1 -1
  126. package/dist/vercel/react/useUIMessages.js.map +1 -0
  127. package/package.json +29 -22
  128. package/src/client/messages.ts +5 -107
  129. package/src/client/types.ts +0 -608
  130. package/src/component/_generated/component.ts +1089 -48
  131. package/src/component/messages.test.ts +273 -1
  132. package/src/component/messages.ts +62 -32
  133. package/src/component/streams.ts +47 -47
  134. package/src/component/threads.ts +5 -2
  135. package/src/eslintConfig.test.ts +44 -0
  136. package/src/react/index.ts +8 -5
  137. package/src/shared.ts +26 -36
  138. package/src/streaming/materializePersistedUIMessageChunks.test.ts +513 -0
  139. package/src/streaming/materializePersistedUIMessageChunks.ts +398 -0
  140. package/src/streaming/persistedUIMessageChunks.test.ts +205 -0
  141. package/src/streaming/persistedUIMessageChunks.ts +658 -0
  142. package/src/validators.test.ts +4 -1
  143. package/src/validators.ts +89 -8
  144. package/src/{UIMessages.combineUIMessages.test.ts → vercel/UIMessages.combineUIMessages.test.ts} +4 -1
  145. package/src/{UIMessages.ts → vercel/UIMessages.ts} +95 -3
  146. package/src/{client → vercel/client}/approval.test.ts +23 -13
  147. package/src/{client → vercel/client}/createTool.ts +62 -22
  148. package/src/{client → vercel/client}/definePlaygroundAPI.ts +15 -16
  149. package/src/{client → vercel/client}/files.ts +2 -2
  150. package/src/{client → vercel/client}/index.test.ts +99 -31
  151. package/src/vercel/client/messages.ts +182 -0
  152. package/src/{client → vercel/client}/mockModel.ts +38 -39
  153. package/src/{client → vercel/client}/saveInputMessages.test.ts +1 -1
  154. package/src/{client → vercel/client}/saveInputMessages.ts +3 -3
  155. package/src/{client → vercel/client}/search.test.ts +3 -3
  156. package/src/{client → vercel/client}/search.ts +4 -4
  157. package/src/{client → vercel/client}/setup.test.ts +6 -3
  158. package/src/vercel/client/start.test.ts +14 -0
  159. package/src/{client → vercel/client}/start.ts +147 -36
  160. package/src/vercel/client/streamText.test.ts +223 -0
  161. package/src/{client → vercel/client}/streamText.ts +102 -31
  162. package/src/{client → vercel/client}/streaming.integration.test.ts +6 -3
  163. package/src/{client → vercel/client}/streaming.test.ts +178 -4
  164. package/src/{client → vercel/client}/streaming.ts +114 -52
  165. package/src/vercel/client/types.test.ts +13 -0
  166. package/src/vercel/client/types.ts +679 -0
  167. package/src/{client → vercel/client}/utils.ts +13 -7
  168. package/src/{deltas.test.ts → vercel/deltas.test.ts} +384 -93
  169. package/src/{deltas.ts → vercel/deltas.ts} +218 -115
  170. package/src/{fromUIMessages.test.ts → vercel/fromUIMessages.test.ts} +17 -1
  171. package/src/{client → vercel}/index.ts +167 -84
  172. package/src/{mapping.test.ts → vercel/mapping.test.ts} +377 -92
  173. package/src/{mapping.ts → vercel/mapping.ts} +501 -137
  174. package/src/{react → vercel/react}/optimisticallySendMessage.ts +2 -2
  175. package/src/{react → vercel/react}/types.ts +1 -1
  176. package/src/{react → vercel/react}/useDeltaStreams.ts +6 -2
  177. package/src/{react → vercel/react}/useStreamingUIMessages.ts +7 -3
  178. package/src/{react → vercel/react}/useThreadMessages.ts +2 -2
  179. package/src/{react → vercel/react}/useUIMessages.ts +2 -2
  180. package/src/{toUIMessages.test.ts → vercel/toUIMessages.test.ts} +49 -2
  181. package/dist/UIMessages.d.ts.map +0 -1
  182. package/dist/UIMessages.js.map +0 -1
  183. package/dist/client/createTool.d.ts.map +0 -1
  184. package/dist/client/createTool.js.map +0 -1
  185. package/dist/client/definePlaygroundAPI.js.map +0 -1
  186. package/dist/client/files.d.ts.map +0 -1
  187. package/dist/client/files.js.map +0 -1
  188. package/dist/client/index.d.ts.map +0 -1
  189. package/dist/client/index.js.map +0 -1
  190. package/dist/client/mockModel.d.ts.map +0 -1
  191. package/dist/client/mockModel.js.map +0 -1
  192. package/dist/client/saveInputMessages.d.ts.map +0 -1
  193. package/dist/client/saveInputMessages.js.map +0 -1
  194. package/dist/client/search.d.ts.map +0 -1
  195. package/dist/client/search.js.map +0 -1
  196. package/dist/client/start.d.ts.map +0 -1
  197. package/dist/client/start.js.map +0 -1
  198. package/dist/client/streamText.d.ts.map +0 -1
  199. package/dist/client/streamText.js.map +0 -1
  200. package/dist/client/streaming.d.ts.map +0 -1
  201. package/dist/client/streaming.js.map +0 -1
  202. package/dist/client/utils.d.ts.map +0 -1
  203. package/dist/client/utils.js.map +0 -1
  204. package/dist/deltas.d.ts.map +0 -1
  205. package/dist/deltas.js.map +0 -1
  206. package/dist/mapping.d.ts.map +0 -1
  207. package/dist/mapping.js.map +0 -1
  208. package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
  209. package/dist/react/optimisticallySendMessage.js.map +0 -1
  210. package/dist/react/types.d.ts.map +0 -1
  211. package/dist/react/useDeltaStreams.d.ts.map +0 -1
  212. package/dist/react/useDeltaStreams.js.map +0 -1
  213. package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
  214. package/dist/react/useStreamingUIMessages.js.map +0 -1
  215. package/dist/react/useThreadMessages.d.ts.map +0 -1
  216. package/dist/react/useThreadMessages.js.map +0 -1
  217. package/dist/react/useUIMessages.d.ts.map +0 -1
  218. package/dist/react/useUIMessages.js.map +0 -1
  219. package/src/client/streamText.test.ts +0 -102
  220. /package/dist/{client → vercel/client}/files.js +0 -0
  221. /package/dist/{client → vercel/client}/saveInputMessages.js +0 -0
  222. /package/dist/{react → vercel/react}/optimisticallySendMessage.js +0 -0
  223. /package/dist/{react → vercel/react}/types.js +0 -0
  224. /package/src/{UIMessages.test.ts → vercel/UIMessages.test.ts} +0 -0
  225. /package/src/{react → vercel/react}/useUIMessages.test.ts +0 -0
@@ -26,7 +26,9 @@ import {
26
26
  type MessageWithMetadata,
27
27
  type Usage,
28
28
  type vFilePart,
29
+ type vCustomContentPart,
29
30
  type vImagePart,
31
+ type vReasoningFilePart,
30
32
  type vReasoningPart,
31
33
  type vRedactedReasoningPart,
32
34
  type vTextPart,
@@ -37,22 +39,27 @@ import {
37
39
  type MessageDoc,
38
40
  vToolApprovalRequest,
39
41
  vToolApprovalResponse,
40
- } from "./validators.js";
42
+ vProviderReference,
43
+ } from "../validators.js";
41
44
  import type { ActionCtx, AgentComponent } from "./client/types.js";
42
45
  import type { MutationCtx } from "./client/types.js";
43
46
  import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
44
47
  import type { Infer } from "convex/values";
45
48
  import {
46
49
  convertUint8ArrayToBase64,
50
+ type FileData,
47
51
  type ProviderOptions,
52
+ type ProviderReference,
53
+ type ReasoningFilePart,
48
54
  type ReasoningPart,
55
+ type ToolResultOutput,
49
56
  } from "@ai-sdk/provider-utils";
50
57
  import { parse, validate } from "convex-helpers/validators";
51
58
  import {
52
59
  getModelName,
53
60
  getProviderName,
54
61
  type ModelOrMetadata,
55
- } from "./shared.js";
62
+ } from "../shared.js";
56
63
  export type AIMessageWithoutId = Omit<AIMessage, "id">;
57
64
 
58
65
  export type SerializeUrlsAndUint8Arrays<T> = T extends URL
@@ -232,32 +239,15 @@ export function autoDenyUnresolvedApprovals(
232
239
 
233
240
  export function serializeUsage(usage: LanguageModelUsage): Usage {
234
241
  return {
235
- promptTokens: usage.inputTokens ?? 0,
236
- completionTokens: usage.outputTokens ?? 0,
237
- totalTokens: usage.totalTokens ?? 0,
238
- reasoningTokens: usage.reasoningTokens,
239
- cachedInputTokens: usage.cachedInputTokens,
240
- };
241
- }
242
-
243
- export function toModelMessageUsage(usage: Usage): LanguageModelUsage {
244
- return {
245
- inputTokens: usage.promptTokens,
246
- outputTokens: usage.completionTokens,
242
+ promptTokens: usage.inputTokens,
243
+ completionTokens: usage.outputTokens,
247
244
  totalTokens: usage.totalTokens,
248
- reasoningTokens: usage.reasoningTokens,
249
- cachedInputTokens: usage.cachedInputTokens,
250
- // These detail fields are required by LanguageModelUsage type but we don't
251
- // have the granular data, so we provide empty objects with undefined values.
252
- inputTokenDetails: {
253
- cacheReadTokens: undefined,
254
- cacheWriteTokens: undefined,
255
- noCacheTokens: undefined,
256
- },
257
- outputTokenDetails: {
258
- textTokens: undefined,
259
- reasoningTokens: undefined,
260
- },
245
+ reasoningTokens: usage.outputTokenDetails?.reasoningTokens,
246
+ cachedInputTokens: usage.inputTokenDetails?.cacheReadTokens,
247
+ nonCachedInputTokens: usage.inputTokenDetails?.noCacheTokens,
248
+ cacheWriteInputTokens: usage.inputTokenDetails?.cacheWriteTokens,
249
+ textOutputTokens: usage.outputTokenDetails?.textTokens,
250
+ raw: usage.raw,
261
251
  };
262
252
  }
263
253
 
@@ -268,33 +258,24 @@ export function serializeWarnings(
268
258
  return undefined;
269
259
  }
270
260
  return warnings.map((warning) => {
271
- if (warning.type === "compatibility") {
272
- return {
273
- type: "unsupported-setting",
274
- setting: warning.feature,
275
- details: warning.details,
276
- };
261
+ switch (warning.type) {
262
+ case "unsupported":
263
+ case "compatibility":
264
+ return {
265
+ type: warning.type,
266
+ feature: warning.feature,
267
+ details: warning.details,
268
+ };
269
+ case "deprecated":
270
+ return {
271
+ type: warning.type,
272
+ setting: warning.setting,
273
+ message: warning.message,
274
+ };
275
+ case "other":
276
+ return { type: warning.type, message: warning.message };
277
277
  }
278
- return warning;
279
- }) as any;
280
- }
281
-
282
- export function toModelMessageWarnings(
283
- warnings: MessageWithMetadata["warnings"],
284
- ): CallWarning[] | undefined {
285
- if (!warnings) {
286
- return undefined;
287
- }
288
- return warnings.map((warning) => {
289
- if (warning.type === "unsupported-setting") {
290
- return {
291
- type: "compatibility",
292
- feature: warning.setting,
293
- details: warning.details,
294
- };
295
- }
296
- return warning;
297
- }) as any;
278
+ });
298
279
  }
299
280
 
300
281
  /**
@@ -312,34 +293,6 @@ export async function serializeResponseMessages<TOOLS extends ToolSet>(
312
293
  return serializeStepMessages(ctx, component, step, model, responseMessages);
313
294
  }
314
295
 
315
- /**
316
- * Serialize the new response messages produced by this step.
317
- *
318
- * `step.response.messages` is cumulative across steps in AI SDK v6 — each
319
- * step's array contains all messages from prior steps too. Pass
320
- * `previousResponseMessageCount` (the prior step's `response.messages.length`,
321
- * or `0` for the first step) so we slice only the new tail. The parameter is
322
- * required: defaulting it would silently duplicate every prior message on
323
- * every multi-step save.
324
- */
325
- export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
326
- ctx: ActionCtx,
327
- component: AgentComponent,
328
- step: StepResult<TOOLS>,
329
- model: ModelOrMetadata | undefined,
330
- previousResponseMessageCount: number,
331
- ): Promise<{ messages: MessageWithMetadata[] }> {
332
- const newMessages = step.response.messages.slice(previousResponseMessageCount);
333
- // Keep at least one message in the output so the step still anchors an
334
- // order slot — downstream `addMessages` relies on each step contributing a
335
- // row even when AI SDK produced no response messages.
336
- const messagesToSerialize: ModelMessage[] =
337
- newMessages.length > 0
338
- ? newMessages
339
- : [{ role: "assistant" as const, content: [] }];
340
- return serializeStepMessages(ctx, component, step, model, messagesToSerialize);
341
- }
342
-
343
296
  async function serializeStepMessages<TOOLS extends ToolSet>(
344
297
  ctx: ActionCtx,
345
298
  component: AgentComponent,
@@ -350,12 +303,15 @@ async function serializeStepMessages<TOOLS extends ToolSet>(
350
303
  // If there are tool results, there's another message with the tool results
351
304
  // ref: https://github.com/vercel/ai/blob/main/packages/ai/src/generate-text/to-response-messages.ts#L120
352
305
  const hasToolMessage = step.response.messages.at(-1)?.role === "tool";
306
+ const resolvedModel = step.model ?? model;
353
307
  const assistantFields = {
354
- model: model ? getModelName(model) : undefined,
355
- provider: model ? getProviderName(model) : undefined,
308
+ model: resolvedModel ? getModelName(resolvedModel) : undefined,
309
+ provider: resolvedModel ? getProviderName(resolvedModel) : undefined,
356
310
  providerMetadata: step.providerMetadata,
357
311
  reasoning: step.reasoningText,
358
- reasoningDetails: step.reasoning,
312
+ reasoningDetails: step.reasoning.filter(
313
+ (part) => part.type !== "reasoning-file",
314
+ ),
359
315
  usage: serializeUsage(step.usage),
360
316
  warnings: serializeWarnings(step.warnings),
361
317
  finishReason: step.finishReason,
@@ -375,7 +331,6 @@ async function serializeStepMessages<TOOLS extends ToolSet>(
375
331
  });
376
332
  }),
377
333
  );
378
- // TODO: capture step.files separately?
379
334
  return { messages };
380
335
  }
381
336
 
@@ -448,7 +403,9 @@ export async function serializeContent(
448
403
  } satisfies Infer<typeof vTextPart>;
449
404
  }
450
405
  case "image": {
451
- let image = serializeDataOrUrl(part.image);
406
+ let image =
407
+ toStoredProviderReference(part.image) ??
408
+ serializeDataOrUrl(part.image);
452
409
  if (
453
410
  image instanceof ArrayBuffer &&
454
411
  image.byteLength > MAX_FILE_SIZE
@@ -471,7 +428,9 @@ export async function serializeContent(
471
428
  } satisfies Infer<typeof vImagePart>;
472
429
  }
473
430
  case "file": {
474
- let data = serializeDataOrUrl(part.data);
431
+ let data =
432
+ toStoredProviderReference(part.data) ??
433
+ serializeDataOrUrl(part.data);
475
434
  if (data instanceof ArrayBuffer && data.byteLength > MAX_FILE_SIZE) {
476
435
  const { file } = await storeFile(
477
436
  ctx,
@@ -500,19 +459,41 @@ export async function serializeContent(
500
459
  toolCallId: part.toolCallId,
501
460
  toolName: part.toolName,
502
461
  providerExecuted: part.providerExecuted,
462
+ title: "title" in part ? part.title : undefined,
463
+ toolMetadata:
464
+ "toolMetadata" in part ? part.toolMetadata : undefined,
503
465
  ...metadata,
504
466
  } satisfies Infer<typeof vToolCallPart>;
505
467
  }
506
468
  case "tool-result": {
507
- return normalizeToolResult(part, metadata);
469
+ return serializeToolResult(part, metadata);
508
470
  }
509
471
  case "reasoning": {
510
472
  return {
511
473
  type: part.type,
512
474
  text: part.text,
475
+ signature:
476
+ "signature" in part && typeof part.signature === "string"
477
+ ? part.signature
478
+ : undefined,
513
479
  ...metadata,
514
480
  } satisfies Infer<typeof vReasoningPart>;
515
481
  }
482
+ case "reasoning-file": {
483
+ return {
484
+ type: part.type,
485
+ ...serializeReasoningFile(part),
486
+ mediaType: part.mediaType,
487
+ ...metadata,
488
+ } satisfies Infer<typeof vReasoningFilePart>;
489
+ }
490
+ case "custom": {
491
+ return {
492
+ type: part.type,
493
+ kind: part.kind,
494
+ ...metadata,
495
+ } satisfies Infer<typeof vCustomContentPart>;
496
+ }
516
497
  // Not in current generation output, but could be in historical messages
517
498
  case "redacted-reasoning": {
518
499
  return {
@@ -529,6 +510,8 @@ export async function serializeContent(
529
510
  type: part.type,
530
511
  approvalId: part.approvalId,
531
512
  toolCallId: part.toolCallId,
513
+ isAutomatic: "isAutomatic" in part ? part.isAutomatic : undefined,
514
+ signature: "signature" in part ? part.signature : undefined,
532
515
  ...metadata,
533
516
  } satisfies Infer<typeof vToolApprovalRequest>;
534
517
  }
@@ -577,12 +560,16 @@ export function fromModelMessageContent(content: Content): Message["content"] {
577
560
  type: part.type,
578
561
  mediaType: getMimeOrMediaType(part),
579
562
  ...metadata,
580
- image: serializeDataOrUrl(part.image),
563
+ image:
564
+ toStoredProviderReference(part.image) ??
565
+ serializeDataOrUrl(part.image),
581
566
  } satisfies Infer<typeof vImagePart>;
582
567
  case "file":
583
568
  return {
584
569
  type: part.type,
585
- data: serializeDataOrUrl(part.data),
570
+ data:
571
+ toStoredProviderReference(part.data) ??
572
+ serializeDataOrUrl(part.data),
586
573
  filename: part.filename,
587
574
  mediaType: getMimeOrMediaType(part)!,
588
575
  ...metadata,
@@ -600,18 +587,38 @@ export function fromModelMessageContent(content: Content): Message["content"] {
600
587
  ...metadata,
601
588
  } satisfies Infer<typeof vToolCallPart>;
602
589
  case "tool-result":
603
- return normalizeToolResult(part, metadata);
590
+ return serializeToolResult(part, metadata);
604
591
  case "reasoning":
605
592
  return {
606
593
  type: part.type,
607
594
  text: part.text,
595
+ signature:
596
+ "signature" in part && typeof part.signature === "string"
597
+ ? part.signature
598
+ : undefined,
608
599
  ...metadata,
609
600
  } satisfies Infer<typeof vReasoningPart>;
601
+ case "reasoning-file": {
602
+ return {
603
+ type: part.type,
604
+ ...serializeReasoningFile(part),
605
+ mediaType: part.mediaType,
606
+ ...metadata,
607
+ } satisfies Infer<typeof vReasoningFilePart>;
608
+ }
609
+ case "custom":
610
+ return {
611
+ type: part.type,
612
+ kind: part.kind,
613
+ ...metadata,
614
+ } satisfies Infer<typeof vCustomContentPart>;
610
615
  case "tool-approval-request":
611
616
  return {
612
617
  type: part.type,
613
618
  approvalId: part.approvalId,
614
619
  toolCallId: part.toolCallId,
620
+ isAutomatic: "isAutomatic" in part ? part.isAutomatic : undefined,
621
+ signature: "signature" in part ? part.signature : undefined,
615
622
  ...metadata,
616
623
  } satisfies Infer<typeof vToolApprovalRequest>;
617
624
  case "tool-approval-response":
@@ -659,14 +666,18 @@ export function toModelMessageContent(
659
666
  case "image":
660
667
  return {
661
668
  type: part.type,
662
- image: toModelMessageDataOrUrl(part.image),
669
+ image: (isStoredProviderReference(part.image)
670
+ ? part.image.reference
671
+ : toModelMessageDataOrUrl(part.image)) as ImagePart["image"],
663
672
  mediaType: getMimeOrMediaType(part),
664
673
  ...metadata,
665
674
  } satisfies ImagePart;
666
675
  case "file":
667
676
  return {
668
677
  type: part.type,
669
- data: toModelMessageDataOrUrl(part.data),
678
+ data: (isStoredProviderReference(part.data)
679
+ ? { type: "reference" as const, reference: part.data.reference }
680
+ : toModelMessageDataOrUrl(part.data)) as FilePart["data"],
670
681
  filename: part.filename,
671
682
  mediaType: getMimeOrMediaType(part)!,
672
683
  ...metadata,
@@ -684,7 +695,7 @@ export function toModelMessageContent(
684
695
  } satisfies ToolCallPart;
685
696
  }
686
697
  case "tool-result": {
687
- return normalizeToolResult(part, metadata);
698
+ return deserializeToolResult(part, metadata);
688
699
  }
689
700
  case "reasoning":
690
701
  return {
@@ -692,23 +703,32 @@ export function toModelMessageContent(
692
703
  text: part.text,
693
704
  ...metadata,
694
705
  } satisfies ReasoningPart;
706
+ case "reasoning-file":
707
+ return {
708
+ type: "reasoning-file",
709
+ data: deserializeReasoningFile(
710
+ part as Infer<typeof vReasoningFilePart>,
711
+ ),
712
+ mediaType: part.mediaType,
713
+ ...metadata,
714
+ } satisfies ReasoningFilePart;
715
+ case "custom":
716
+ return { type: "custom", kind: part.kind, ...metadata };
695
717
  case "redacted-reasoning":
696
- // TODO: should we just drop this?
718
+ // Legacy v5 part: round-trip the redacted payload via providerOptions.
697
719
  return {
698
720
  type: "reasoning",
699
721
  text: "",
700
722
  ...metadata,
701
723
  providerOptions: metadata.providerOptions
702
- ? {
703
- ...Object.fromEntries(
704
- Object.entries(metadata.providerOptions ?? {}).map(
705
- ([key, value]) => [
706
- key,
707
- { ...value, redactedData: part.data },
708
- ],
709
- ),
724
+ ? Object.fromEntries(
725
+ Object.entries(metadata.providerOptions ?? {}).map(
726
+ ([key, value]) => [
727
+ key,
728
+ { ...value, redactedData: part.data },
729
+ ],
710
730
  ),
711
- }
731
+ )
712
732
  : undefined,
713
733
  } satisfies ReasoningPart;
714
734
  case "source":
@@ -718,6 +738,8 @@ export function toModelMessageContent(
718
738
  type: part.type,
719
739
  approvalId: part.approvalId,
720
740
  toolCallId: part.toolCallId,
741
+ isAutomatic: "isAutomatic" in part ? part.isAutomatic : undefined,
742
+ signature: "signature" in part ? part.signature : undefined,
721
743
  ...metadata,
722
744
  } satisfies Infer<typeof vToolApprovalRequest>;
723
745
  case "tool-approval-response":
@@ -746,7 +768,7 @@ export function normalizeToolOutput(
746
768
  };
747
769
  }
748
770
  if (validate(vToolResultOutput, result)) {
749
- return result;
771
+ return result as unknown as ToolResultOutput;
750
772
  }
751
773
  return {
752
774
  type: "json",
@@ -754,28 +776,277 @@ export function normalizeToolOutput(
754
776
  };
755
777
  }
756
778
 
757
- function normalizeToolResult(
779
+ function serializeToolResult(
758
780
  part: ToolResultPart | Infer<typeof vToolResultPart>,
759
781
  metadata: {
760
782
  providerOptions?: ProviderOptions;
761
783
  providerMetadata?: ProviderMetadata;
762
784
  },
763
- ): ToolResultPart & Infer<typeof vToolResultPart> {
785
+ ): Infer<typeof vToolResultPart> {
764
786
  return {
765
787
  type: part.type,
766
- output: part.output
767
- ? validate(vToolResultOutput, part.output)
768
- ? (part.output as any)
769
- : normalizeToolOutput(JSON.stringify(part.output))
770
- : normalizeToolOutput("result" in part ? part.result : undefined),
788
+ output:
789
+ "output" in part && part.output !== undefined
790
+ ? serializeToolResultOutput(part.output)
791
+ : (normalizeToolOutput(
792
+ "result" in part ? part.result : undefined,
793
+ ) as Infer<typeof vToolResultOutput>),
771
794
  toolCallId: part.toolCallId,
772
795
  toolName: part.toolName,
796
+ ...("providerExecuted" in part
797
+ ? { providerExecuted: part.providerExecuted }
798
+ : {}),
773
799
  // Preserve isError flag for error reporting
774
800
  ...("isError" in part && part.isError ? { isError: true } : {}),
775
801
  ...metadata,
802
+ } as Infer<typeof vToolResultPart>;
803
+ }
804
+
805
+ function deserializeToolResult(
806
+ part: ToolResultPart | Infer<typeof vToolResultPart>,
807
+ metadata: {
808
+ providerOptions?: ProviderOptions;
809
+ providerMetadata?: ProviderMetadata;
810
+ },
811
+ ): ToolResultPart {
812
+ return {
813
+ type: part.type,
814
+ output:
815
+ "output" in part && part.output !== undefined
816
+ ? deserializeToolResultOutput(part.output)
817
+ : normalizeToolOutput("result" in part ? part.result : undefined),
818
+ toolCallId: part.toolCallId,
819
+ toolName: part.toolName,
820
+ ...("isError" in part && part.isError ? { isError: true } : {}),
821
+ ...metadata,
776
822
  } satisfies ToolResultPart;
777
823
  }
778
824
 
825
+ function serializeToolResultOutput(
826
+ output: unknown,
827
+ ): Infer<typeof vToolResultOutput> {
828
+ if (output === undefined) {
829
+ return normalizeToolOutput(undefined) as Infer<typeof vToolResultOutput>;
830
+ }
831
+ if (!isRecord(output) || typeof output.type !== "string") {
832
+ return normalizeToolOutput(output as JSONValue) as Infer<
833
+ typeof vToolResultOutput
834
+ >;
835
+ }
836
+ if (output.type !== "content") {
837
+ if (validate(vToolResultOutput, output)) {
838
+ return output as Infer<typeof vToolResultOutput>;
839
+ }
840
+ throw new Error(`Invalid AI SDK 7 tool-result output: ${output.type}`);
841
+ }
842
+ if (!Array.isArray(output.value)) {
843
+ throw new Error("Invalid AI SDK 7 tool-result content output");
844
+ }
845
+ const serialized = {
846
+ ...output,
847
+ value: output.value.map(serializeToolResultContentPart),
848
+ };
849
+ if (!validate(vToolResultOutput, serialized)) {
850
+ throw new Error("Invalid AI SDK 7 tool-result content part");
851
+ }
852
+ return serialized as Infer<typeof vToolResultOutput>;
853
+ }
854
+
855
+ function serializeToolResultContentPart(part: unknown): unknown {
856
+ if (!isRecord(part) || typeof part.type !== "string") {
857
+ throw new Error("Invalid AI SDK 7 tool-result content part");
858
+ }
859
+ if (part.type !== "file") return part;
860
+ if (!isRecord(part.data) || typeof part.data.type !== "string") {
861
+ throw new Error("Invalid AI SDK 7 tool-result file data");
862
+ }
863
+
864
+ const common = {
865
+ type: "file" as const,
866
+ mediaType: part.mediaType,
867
+ ...(typeof part.filename === "string" ? { filename: part.filename } : {}),
868
+ ...(isRecord(part.providerOptions)
869
+ ? { providerOptions: part.providerOptions }
870
+ : {}),
871
+ };
872
+ switch (part.data.type) {
873
+ case "data":
874
+ return {
875
+ ...common,
876
+ data: {
877
+ type: "data" as const,
878
+ data: serializeDataOrUrl(part.data.data as DataContent),
879
+ },
880
+ };
881
+ case "url": {
882
+ const { url } = part.data;
883
+ if (typeof url !== "string" && !(url instanceof URL)) {
884
+ throw new Error("Invalid AI SDK 7 tool-result file URL");
885
+ }
886
+ return { ...common, data: { type: "url" as const, url: url.toString() } };
887
+ }
888
+ case "text":
889
+ if (typeof part.data.text !== "string") {
890
+ throw new Error("Invalid AI SDK 7 tool-result file text");
891
+ }
892
+ return {
893
+ ...common,
894
+ data: { type: "text" as const, text: part.data.text },
895
+ };
896
+ case "reference":
897
+ if (!isProviderReference(part.data.reference)) {
898
+ throw new Error("Invalid AI SDK 7 tool-result file reference");
899
+ }
900
+ return {
901
+ ...common,
902
+ data: { type: "reference" as const, reference: part.data.reference },
903
+ };
904
+ default:
905
+ throw new Error(
906
+ `Invalid AI SDK 7 tool-result file data: ${part.data.type}`,
907
+ );
908
+ }
909
+ }
910
+
911
+ function deserializeToolResultOutput(output: unknown): ToolResultOutput {
912
+ if (!validate(vToolResultOutput, output)) {
913
+ return normalizeToolOutput(output as JSONValue);
914
+ }
915
+ const stored = output as Infer<typeof vToolResultOutput>;
916
+ if (stored.type !== "content") return stored as unknown as ToolResultOutput;
917
+ return {
918
+ type: "content",
919
+ value: stored.value.map(deserializeToolResultContentPart),
920
+ } as ToolResultOutput;
921
+ }
922
+
923
+ function deserializeToolResultContentPart(
924
+ part: Extract<
925
+ Infer<typeof vToolResultOutput>,
926
+ { type: "content" }
927
+ >["value"][number],
928
+ ) {
929
+ if (part.type === "file") {
930
+ return {
931
+ ...part,
932
+ data:
933
+ part.data.type === "url"
934
+ ? { type: "url" as const, url: new URL(part.data.url) }
935
+ : part.data,
936
+ };
937
+ }
938
+ switch (part.type) {
939
+ case "media":
940
+ return {
941
+ type: "file" as const,
942
+ data: { type: "data" as const, data: part.data },
943
+ mediaType: part.mediaType,
944
+ };
945
+ case "file-data":
946
+ return {
947
+ type: "file" as const,
948
+ data: { type: "data" as const, data: part.data },
949
+ mediaType: part.mediaType,
950
+ ...(part.filename !== undefined ? { filename: part.filename } : {}),
951
+ ...(part.providerOptions !== undefined
952
+ ? { providerOptions: part.providerOptions }
953
+ : {}),
954
+ };
955
+ case "file-url":
956
+ return {
957
+ type: "file" as const,
958
+ data: { type: "url" as const, url: new URL(part.url) },
959
+ mediaType: part.mediaType ?? "application/octet-stream",
960
+ ...(part.providerOptions !== undefined
961
+ ? { providerOptions: part.providerOptions }
962
+ : {}),
963
+ };
964
+ case "file-id":
965
+ case "image-file-id":
966
+ if (typeof part.fileId === "string") return part;
967
+ return {
968
+ type: "file" as const,
969
+ data: {
970
+ type: "reference" as const,
971
+ reference: part.fileId,
972
+ },
973
+ mediaType:
974
+ part.type === "image-file-id" ? "image" : "application/octet-stream",
975
+ ...(part.providerOptions !== undefined
976
+ ? { providerOptions: part.providerOptions }
977
+ : {}),
978
+ };
979
+ case "file-reference":
980
+ case "image-file-reference":
981
+ return {
982
+ type: "file" as const,
983
+ data: { type: "reference" as const, reference: part.providerReference },
984
+ mediaType:
985
+ part.type === "image-file-reference"
986
+ ? "image"
987
+ : "application/octet-stream",
988
+ ...(part.providerOptions !== undefined
989
+ ? { providerOptions: part.providerOptions }
990
+ : {}),
991
+ };
992
+ case "image-data":
993
+ return {
994
+ type: "file" as const,
995
+ data: { type: "data" as const, data: part.data },
996
+ mediaType: part.mediaType,
997
+ ...(part.providerOptions !== undefined
998
+ ? { providerOptions: part.providerOptions }
999
+ : {}),
1000
+ };
1001
+ case "image-url":
1002
+ return {
1003
+ type: "file" as const,
1004
+ data: { type: "url" as const, url: new URL(part.url) },
1005
+ mediaType: "image",
1006
+ ...(part.providerOptions !== undefined
1007
+ ? { providerOptions: part.providerOptions }
1008
+ : {}),
1009
+ };
1010
+ default:
1011
+ return part;
1012
+ }
1013
+ }
1014
+
1015
+ function serializeReasoningFile(
1016
+ part: ReasoningFilePart | Infer<typeof vReasoningFilePart>,
1017
+ ): Pick<Infer<typeof vReasoningFilePart>, "url" | "data"> {
1018
+ if ("url" in part && part.url !== undefined) {
1019
+ return { url: part.url.toString() };
1020
+ }
1021
+ if (!("data" in part) || part.data === undefined) {
1022
+ throw new Error("reasoning-file requires data or url");
1023
+ }
1024
+ const { data } = part;
1025
+ if (data instanceof URL) return { url: data.toString() };
1026
+ if (isRecord(data) && data.type === "url" && data.url instanceof URL) {
1027
+ return { url: data.url.toString() };
1028
+ }
1029
+ if (isRecord(data) && data.type === "data") {
1030
+ return { data: serializeDataOrUrl(data.data as DataContent) };
1031
+ }
1032
+ if (isRecord(data) && "type" in data) {
1033
+ throw new Error("Invalid AI SDK 7 reasoning-file data");
1034
+ }
1035
+ return { data: serializeDataOrUrl(data) };
1036
+ }
1037
+
1038
+ function deserializeReasoningFile(
1039
+ part: Infer<typeof vReasoningFilePart>,
1040
+ ): Extract<ReasoningFilePart["data"], { type: string }> {
1041
+ if (part.url !== undefined) {
1042
+ return { type: "url", url: new URL(part.url) };
1043
+ }
1044
+ if (part.data !== undefined) {
1045
+ return { type: "data", data: part.data };
1046
+ }
1047
+ throw new Error("reasoning-file requires data or url");
1048
+ }
1049
+
779
1050
  /**
780
1051
  * Return a best-guess MIME type based on the magic-number signature
781
1052
  * found at the start of an ArrayBuffer.
@@ -849,7 +1120,7 @@ export function guessMimeType(buf: ArrayBuffer | string): string {
849
1120
  * @returns The serialized data as an ArrayBuffer or the URL as a string.
850
1121
  */
851
1122
  export function serializeDataOrUrl(
852
- dataOrUrl: DataContent | URL,
1123
+ dataOrUrl: DataContent | URL | ProviderReference | FileData,
853
1124
  ): ArrayBuffer | string {
854
1125
  if (typeof dataOrUrl === "string") {
855
1126
  return dataOrUrl;
@@ -860,6 +1131,23 @@ export function serializeDataOrUrl(
860
1131
  if (dataOrUrl instanceof URL) {
861
1132
  return dataOrUrl.toString();
862
1133
  }
1134
+ if ("type" in dataOrUrl) {
1135
+ switch (dataOrUrl.type) {
1136
+ case "data":
1137
+ return serializeDataOrUrl(dataOrUrl.data);
1138
+ case "url":
1139
+ return dataOrUrl.url.toString();
1140
+ case "text":
1141
+ return convertUint8ArrayToBase64(
1142
+ new TextEncoder().encode(dataOrUrl.text),
1143
+ );
1144
+ case "reference":
1145
+ throw new Error("Provider references must be stored as references");
1146
+ }
1147
+ }
1148
+ if (!(dataOrUrl instanceof Uint8Array)) {
1149
+ throw new Error("Unsupported provider reference");
1150
+ }
863
1151
  return dataOrUrl.buffer.slice(
864
1152
  dataOrUrl.byteOffset,
865
1153
  dataOrUrl.byteOffset + dataOrUrl.byteLength,
@@ -867,8 +1155,14 @@ export function serializeDataOrUrl(
867
1155
  }
868
1156
 
869
1157
  export function toModelMessageDataOrUrl(
870
- urlOrString: string | ArrayBuffer | URL | DataContent,
871
- ): URL | DataContent {
1158
+ urlOrString:
1159
+ | string
1160
+ | ArrayBuffer
1161
+ | URL
1162
+ | DataContent
1163
+ | ProviderReference
1164
+ | FileData,
1165
+ ): URL | DataContent | ProviderReference | FileData {
872
1166
  if (urlOrString instanceof URL) {
873
1167
  return urlOrString;
874
1168
  }
@@ -884,37 +1178,107 @@ export function toModelMessageDataOrUrl(
884
1178
  return urlOrString;
885
1179
  }
886
1180
 
1181
+ type StoredProviderReference = Infer<typeof vProviderReference>;
1182
+
1183
+ function isRecord(value: unknown): value is Record<string, unknown> {
1184
+ return typeof value === "object" && value !== null;
1185
+ }
1186
+
1187
+ function isProviderReference(value: unknown): value is ProviderReference {
1188
+ return (
1189
+ isRecord(value) &&
1190
+ !(value instanceof ArrayBuffer) &&
1191
+ !(value instanceof Uint8Array) &&
1192
+ !(value instanceof URL) &&
1193
+ !("type" in value) &&
1194
+ Object.values(value).every((id) => typeof id === "string")
1195
+ );
1196
+ }
1197
+
1198
+ function isStoredProviderReference(
1199
+ value: unknown,
1200
+ ): value is StoredProviderReference {
1201
+ return (
1202
+ isRecord(value) &&
1203
+ value.type === "reference" &&
1204
+ isProviderReference(value.reference)
1205
+ );
1206
+ }
1207
+
1208
+ function toStoredProviderReference(
1209
+ value: unknown,
1210
+ ): StoredProviderReference | undefined {
1211
+ if (isStoredProviderReference(value)) return value;
1212
+ if (isProviderReference(value))
1213
+ return { type: "reference", reference: value };
1214
+ if (
1215
+ isRecord(value) &&
1216
+ value.type === "reference" &&
1217
+ isProviderReference(value.reference)
1218
+ ) {
1219
+ return { type: "reference", reference: value.reference };
1220
+ }
1221
+ return undefined;
1222
+ }
1223
+
1224
+ function isFileReference(
1225
+ value: unknown,
1226
+ ): value is { type: "reference"; reference: ProviderReference } {
1227
+ return (
1228
+ isRecord(value) &&
1229
+ value.type === "reference" &&
1230
+ isProviderReference(value.reference)
1231
+ );
1232
+ }
1233
+
887
1234
  export function toUIFilePart(part: ImagePart | FilePart): FileUIPart {
888
1235
  const dataOrUrl = part.type === "image" ? part.image : part.data;
889
- const url =
890
- dataOrUrl instanceof ArrayBuffer
891
- ? convertUint8ArrayToBase64(new Uint8Array(dataOrUrl))
892
- : dataOrUrl.toString();
1236
+ const providerReference = isProviderReference(dataOrUrl)
1237
+ ? dataOrUrl
1238
+ : isFileReference(dataOrUrl)
1239
+ ? dataOrUrl.reference
1240
+ : undefined;
1241
+ const url = providerReference
1242
+ ? ""
1243
+ : toUIFileUrl(dataOrUrl, part.mediaType ?? "application/octet-stream");
893
1244
 
894
1245
  return {
895
1246
  type: "file",
896
1247
  mediaType: part.mediaType!,
897
1248
  filename: part.type === "file" ? part.filename : undefined,
898
1249
  url,
1250
+ ...(providerReference !== undefined ? { providerReference } : {}),
899
1251
  providerMetadata: part.providerOptions,
900
1252
  };
901
1253
  }
902
1254
 
903
- // Currently unused
904
- // export function toModelMessages(args: {
905
- // messages?: ModelMessage[] | AIMessageWithoutId[];
906
- // }): ModelMessage[] {
907
- // const messages: ModelMessage[] = [];
908
- // if (args.messages) {
909
- // if (
910
- // args.messages.every(
911
- // (m) => typeof m === "object" && m !== null && "parts" in m,
912
- // )
913
- // ) {
914
- // messages.push(...convertToModelMessages(args.messages));
915
- // } else {
916
- // messages.push(...modelMessageSchema.array().parse(args.messages));
917
- // }
918
- // }
919
- // return messages;
920
- // }
1255
+ function toUIFileUrl(data: unknown, mediaType: string): string {
1256
+ if (isFileReference(data)) return "";
1257
+ if (isRecord(data) && typeof data.type === "string") {
1258
+ switch (data.type) {
1259
+ case "url":
1260
+ return (data.url as URL).toString();
1261
+ case "data":
1262
+ return toUIFileDataUrl(data.data as DataContent, mediaType);
1263
+ case "text":
1264
+ return toUIFileDataUrl(
1265
+ new TextEncoder().encode(data.text as string),
1266
+ mediaType,
1267
+ );
1268
+ }
1269
+ }
1270
+ if (data instanceof URL) return data.toString();
1271
+ if (typeof data === "string") return data;
1272
+ return toUIFileDataUrl(data as DataContent, mediaType);
1273
+ }
1274
+
1275
+ function toUIFileDataUrl(data: DataContent, mediaType: string): string {
1276
+ if (typeof data === "string") {
1277
+ return data.startsWith("data:") ? data : `data:${mediaType};base64,${data}`;
1278
+ }
1279
+ const bytes =
1280
+ data instanceof ArrayBuffer
1281
+ ? new Uint8Array(data)
1282
+ : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1283
+ return `data:${mediaType};base64,${convertUint8ArrayToBase64(bytes)}`;
1284
+ }