@convex-dev/agent 0.5.0-alpha.1 → 0.6.0-alpha.1

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 (235) hide show
  1. package/MIGRATION.md +153 -0
  2. package/README.md +32 -27
  3. package/dist/UIMessages.d.ts +46 -0
  4. package/dist/UIMessages.d.ts.map +1 -0
  5. package/dist/UIMessages.js +546 -0
  6. package/dist/UIMessages.js.map +1 -0
  7. package/dist/client/createTool.d.ts +126 -27
  8. package/dist/client/createTool.d.ts.map +1 -1
  9. package/dist/client/createTool.js +67 -12
  10. package/dist/client/createTool.js.map +1 -1
  11. package/dist/client/defaultComponent.d.ts +11 -0
  12. package/dist/client/defaultComponent.d.ts.map +1 -0
  13. package/dist/client/defaultComponent.js +7 -0
  14. package/dist/client/defaultComponent.js.map +1 -0
  15. package/dist/client/definePlaygroundAPI.d.ts +1335 -204
  16. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  17. package/dist/client/definePlaygroundAPI.js +52 -28
  18. package/dist/client/definePlaygroundAPI.js.map +1 -1
  19. package/dist/client/files.d.ts +20 -7
  20. package/dist/client/files.d.ts.map +1 -1
  21. package/dist/client/files.js +68 -11
  22. package/dist/client/files.js.map +1 -1
  23. package/dist/client/index.d.ts +1116 -978
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +332 -747
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/messages.d.ts +461 -0
  28. package/dist/client/messages.d.ts.map +1 -0
  29. package/dist/client/messages.js +106 -0
  30. package/dist/client/messages.js.map +1 -0
  31. package/dist/client/mockModel.d.ts +42 -0
  32. package/dist/client/mockModel.d.ts.map +1 -0
  33. package/dist/client/mockModel.js +182 -0
  34. package/dist/client/mockModel.js.map +1 -0
  35. package/dist/client/saveInputMessages.d.ts +20 -0
  36. package/dist/client/saveInputMessages.d.ts.map +1 -0
  37. package/dist/client/saveInputMessages.js +58 -0
  38. package/dist/client/saveInputMessages.js.map +1 -0
  39. package/dist/client/search.d.ts +350 -39
  40. package/dist/client/search.d.ts.map +1 -1
  41. package/dist/client/search.js +350 -39
  42. package/dist/client/search.js.map +1 -1
  43. package/dist/client/start.d.ts +84 -0
  44. package/dist/client/start.d.ts.map +1 -0
  45. package/dist/client/start.js +185 -0
  46. package/dist/client/start.js.map +1 -0
  47. package/dist/client/streamText.d.ts +46 -0
  48. package/dist/client/streamText.d.ts.map +1 -0
  49. package/dist/client/streamText.js +117 -0
  50. package/dist/client/streamText.js.map +1 -0
  51. package/dist/client/streaming.d.ts +3716 -32
  52. package/dist/client/streaming.d.ts.map +1 -1
  53. package/dist/client/streaming.js +161 -59
  54. package/dist/client/streaming.js.map +1 -1
  55. package/dist/client/threads.d.ts +46 -0
  56. package/dist/client/threads.d.ts.map +1 -0
  57. package/dist/client/threads.js +49 -0
  58. package/dist/client/threads.js.map +1 -0
  59. package/dist/client/types.d.ts +266 -128
  60. package/dist/client/types.d.ts.map +1 -1
  61. package/dist/client/utils.d.ts +4 -0
  62. package/dist/client/utils.d.ts.map +1 -0
  63. package/dist/client/utils.js +21 -0
  64. package/dist/client/utils.js.map +1 -0
  65. package/dist/component/_generated/api.d.ts +24 -2178
  66. package/dist/component/_generated/api.d.ts.map +1 -1
  67. package/dist/component/_generated/api.js +10 -1
  68. package/dist/component/_generated/api.js.map +1 -1
  69. package/dist/component/_generated/component.d.ts +3120 -0
  70. package/dist/component/_generated/component.d.ts.map +1 -0
  71. package/dist/component/_generated/component.js +11 -0
  72. package/dist/component/_generated/component.js.map +1 -0
  73. package/dist/component/_generated/dataModel.d.ts +4 -18
  74. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  75. package/dist/component/_generated/dataModel.js +11 -0
  76. package/dist/component/_generated/dataModel.js.map +1 -0
  77. package/dist/component/_generated/server.d.ts +10 -38
  78. package/dist/component/_generated/server.d.ts.map +1 -1
  79. package/dist/component/_generated/server.js +9 -5
  80. package/dist/component/_generated/server.js.map +1 -1
  81. package/dist/component/files.d.ts +16 -10
  82. package/dist/component/files.d.ts.map +1 -1
  83. package/dist/component/files.js +10 -2
  84. package/dist/component/files.js.map +1 -1
  85. package/dist/component/messages.d.ts +2578 -366
  86. package/dist/component/messages.d.ts.map +1 -1
  87. package/dist/component/messages.js +397 -154
  88. package/dist/component/messages.js.map +1 -1
  89. package/dist/component/schema.d.ts +5697 -3584
  90. package/dist/component/schema.d.ts.map +1 -1
  91. package/dist/component/schema.js +18 -41
  92. package/dist/component/schema.js.map +1 -1
  93. package/dist/component/streams.d.ts +39 -339
  94. package/dist/component/streams.d.ts.map +1 -1
  95. package/dist/component/streams.js +114 -73
  96. package/dist/component/streams.js.map +1 -1
  97. package/dist/component/threads.d.ts +13 -13
  98. package/dist/component/users.d.ts +7 -7
  99. package/dist/component/vector/index.d.ts +1 -1
  100. package/dist/component/vector/index.d.ts.map +1 -1
  101. package/dist/component/vector/index.js +1 -3
  102. package/dist/component/vector/index.js.map +1 -1
  103. package/dist/deltas.d.ts +43 -0
  104. package/dist/deltas.d.ts.map +1 -0
  105. package/dist/deltas.js +446 -0
  106. package/dist/deltas.js.map +1 -0
  107. package/dist/mapping.d.ts +38 -20
  108. package/dist/mapping.d.ts.map +1 -1
  109. package/dist/mapping.js +365 -97
  110. package/dist/mapping.js.map +1 -1
  111. package/dist/react/SmoothText.d.ts +5 -0
  112. package/dist/react/SmoothText.d.ts.map +1 -0
  113. package/dist/react/SmoothText.js +6 -0
  114. package/dist/react/SmoothText.js.map +1 -0
  115. package/dist/react/index.d.ts +5 -77
  116. package/dist/react/index.d.ts.map +1 -1
  117. package/dist/react/index.js +6 -160
  118. package/dist/react/index.js.map +1 -1
  119. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  120. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  121. package/dist/react/optimisticallySendMessage.js +35 -9
  122. package/dist/react/optimisticallySendMessage.js.map +1 -1
  123. package/dist/react/types.d.ts +4 -18
  124. package/dist/react/types.d.ts.map +1 -1
  125. package/dist/react/useDeltaStreams.d.ts +10 -0
  126. package/dist/react/useDeltaStreams.d.ts.map +1 -0
  127. package/dist/react/useDeltaStreams.js +106 -0
  128. package/dist/react/useDeltaStreams.js.map +1 -0
  129. package/dist/react/useSmoothText.d.ts +13 -12
  130. package/dist/react/useSmoothText.d.ts.map +1 -1
  131. package/dist/react/useSmoothText.js +32 -15
  132. package/dist/react/useSmoothText.js.map +1 -1
  133. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  134. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  135. package/dist/react/useStreamingUIMessages.js +92 -0
  136. package/dist/react/useStreamingUIMessages.js.map +1 -0
  137. package/dist/react/useThreadMessages.d.ts +104 -0
  138. package/dist/react/useThreadMessages.d.ts.map +1 -0
  139. package/dist/react/useThreadMessages.js +148 -0
  140. package/dist/react/useThreadMessages.js.map +1 -0
  141. package/dist/react/useUIMessages.d.ts +96 -0
  142. package/dist/react/useUIMessages.d.ts.map +1 -0
  143. package/dist/react/useUIMessages.js +108 -0
  144. package/dist/react/useUIMessages.js.map +1 -0
  145. package/dist/shared.d.ts +20 -4
  146. package/dist/shared.d.ts.map +1 -1
  147. package/dist/shared.js +45 -8
  148. package/dist/shared.js.map +1 -1
  149. package/dist/validators.d.ts +22981 -5666
  150. package/dist/validators.d.ts.map +1 -1
  151. package/dist/validators.js +245 -137
  152. package/dist/validators.js.map +1 -1
  153. package/package.json +101 -51
  154. package/src/UIMessages.combineUIMessages.test.ts +239 -0
  155. package/src/UIMessages.test.ts +273 -0
  156. package/src/UIMessages.ts +739 -0
  157. package/src/client/approval.test.ts +350 -0
  158. package/src/client/createTool.ts +291 -76
  159. package/src/client/defaultComponent.ts +17 -0
  160. package/src/client/definePlaygroundAPI.ts +67 -31
  161. package/src/client/files.ts +100 -20
  162. package/src/client/index.test.ts +40 -85
  163. package/src/client/index.ts +638 -1289
  164. package/src/client/messages.ts +237 -0
  165. package/src/client/mockModel.ts +252 -0
  166. package/src/client/saveInputMessages.test.ts +583 -0
  167. package/src/client/saveInputMessages.ts +101 -0
  168. package/src/client/search.test.ts +1207 -0
  169. package/src/client/search.ts +581 -70
  170. package/src/client/start.ts +327 -0
  171. package/src/client/streamText.ts +187 -0
  172. package/src/client/streaming.test.ts +186 -0
  173. package/src/client/streaming.ts +241 -97
  174. package/src/client/threads.ts +83 -0
  175. package/src/client/types.ts +370 -219
  176. package/src/client/utils.ts +27 -0
  177. package/src/component/_generated/api.ts +64 -0
  178. package/src/component/_generated/component.ts +4902 -0
  179. package/src/component/_generated/{server.d.ts → server.ts} +33 -21
  180. package/src/component/files.ts +11 -2
  181. package/src/component/messages.test.ts +195 -51
  182. package/src/component/messages.ts +500 -201
  183. package/src/component/schema.ts +20 -46
  184. package/src/component/setup.test.ts +7 -0
  185. package/src/component/streams.ts +184 -83
  186. package/src/component/users.test.ts +0 -1
  187. package/src/component/vector/index.ts +1 -3
  188. package/src/deltas.test.ts +626 -0
  189. package/src/deltas.ts +569 -0
  190. package/src/fromUIMessages.test.ts +497 -0
  191. package/src/mapping.test.ts +180 -6
  192. package/src/mapping.ts +479 -162
  193. package/src/react/SmoothText.tsx +9 -0
  194. package/src/react/index.ts +10 -230
  195. package/src/react/optimisticallySendMessage.ts +55 -12
  196. package/src/react/types.ts +6 -39
  197. package/src/react/useDeltaStreams.ts +160 -0
  198. package/src/react/useSmoothText.ts +56 -36
  199. package/src/react/useStreamingUIMessages.ts +143 -0
  200. package/src/react/useThreadMessages.ts +262 -0
  201. package/src/react/useUIMessages.test.ts +255 -0
  202. package/src/react/useUIMessages.ts +195 -0
  203. package/src/shared.ts +88 -12
  204. package/src/test.ts +18 -0
  205. package/src/toUIMessages.test.ts +1269 -0
  206. package/src/validators.test.ts +18 -19
  207. package/src/validators.ts +325 -185
  208. package/dist/client/_generated/_ignore.d.ts +0 -1
  209. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  210. package/dist/client/_generated/_ignore.js +0 -3
  211. package/dist/client/_generated/_ignore.js.map +0 -1
  212. package/dist/client/listMessages.d.ts +0 -22
  213. package/dist/client/listMessages.d.ts.map +0 -1
  214. package/dist/client/listMessages.js +0 -25
  215. package/dist/client/listMessages.js.map +0 -1
  216. package/dist/package.json +0 -3
  217. package/dist/react/deltas.d.ts +0 -26
  218. package/dist/react/deltas.d.ts.map +0 -1
  219. package/dist/react/deltas.js +0 -384
  220. package/dist/react/deltas.js.map +0 -1
  221. package/dist/react/toUIMessages.d.ts +0 -15
  222. package/dist/react/toUIMessages.d.ts.map +0 -1
  223. package/dist/react/toUIMessages.js +0 -211
  224. package/dist/react/toUIMessages.js.map +0 -1
  225. package/src/client/listMessages.ts +0 -38
  226. package/src/component/_generated/api.d.ts +0 -2202
  227. package/src/component/_generated/api.js +0 -23
  228. package/src/component/_generated/server.js +0 -90
  229. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  230. package/src/react/deltas.test.ts +0 -315
  231. package/src/react/deltas.ts +0 -478
  232. package/src/react/toUIMessages.test.ts +0 -420
  233. package/src/react/toUIMessages.ts +0 -253
  234. package/src/vitest.config.ts +0 -7
  235. /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
package/dist/mapping.js CHANGED
@@ -1,38 +1,115 @@
1
1
  import {} from "ai";
2
+ import { vMessageWithMetadata, vToolResultOutput, vToolApprovalRequest, vToolApprovalResponse, } from "./validators.js";
2
3
  import { MAX_FILE_SIZE, storeFile } from "./client/files.js";
3
- import { omit } from "convex-helpers";
4
- export async function serializeMessage(ctx, component, messageWithId) {
5
- const message = "id" in messageWithId ? omit(messageWithId, ["id"]) : messageWithId;
4
+ import { convertUint8ArrayToBase64, } from "@ai-sdk/provider-utils";
5
+ import { parse, validate } from "convex-helpers/validators";
6
+ import { getModelName, getProviderName, } from "./shared.js";
7
+ export async function serializeMessage(ctx, component, message) {
6
8
  const { content, fileIds } = await serializeContent(ctx, component, message.content);
7
9
  return {
8
10
  message: {
9
- ...message,
11
+ role: message.role,
10
12
  content,
13
+ ...(message.providerOptions
14
+ ? { providerOptions: message.providerOptions }
15
+ : {}),
11
16
  },
12
17
  fileIds,
13
18
  };
14
19
  }
15
- export function deserializeMessage(message) {
20
+ // Similar to serializeMessage, but doesn't save any files and is looser
21
+ // For use on the frontend / in synchronous environments.
22
+ export function fromModelMessage(message) {
23
+ const content = fromModelMessageContent(message.content);
24
+ return {
25
+ role: message.role,
26
+ content,
27
+ ...(message.providerOptions
28
+ ? { providerOptions: message.providerOptions }
29
+ : {}),
30
+ };
31
+ }
32
+ export async function serializeOrThrow(message) {
33
+ const { content } = await serializeContent({}, {}, message.content);
34
+ return {
35
+ role: message.role,
36
+ content,
37
+ ...(message.providerOptions
38
+ ? { providerOptions: message.providerOptions }
39
+ : {}),
40
+ };
41
+ }
42
+ export function toModelMessage(message) {
16
43
  return {
17
44
  ...message,
18
- content: deserializeContent(message.content),
45
+ content: toModelMessageContent(message.content),
19
46
  };
20
47
  }
48
+ export function docsToModelMessages(messages) {
49
+ return messages
50
+ .map((m) => m.message)
51
+ .filter((m) => !!m)
52
+ .filter((m) => !!m.content.length)
53
+ .map(toModelMessage);
54
+ }
55
+ /**
56
+ * Merge consecutive tool messages that contain `tool-approval-response` parts
57
+ * into a single tool message. The AI SDK's `collectToolApprovals` only examines
58
+ * the last tool message, so when multiple approvals are saved as separate
59
+ * messages (e.g. approve tool A, then deny tool B), they must be combined
60
+ * for the SDK to process them all.
61
+ */
62
+ export function mergeApprovalResponseMessages(messages) {
63
+ const result = [];
64
+ for (const msg of messages) {
65
+ const prev = result.at(-1);
66
+ if (msg.role === "tool" &&
67
+ prev?.role === "tool" &&
68
+ Array.isArray(msg.content) &&
69
+ Array.isArray(prev.content) &&
70
+ hasApprovalResponse(msg.content) &&
71
+ hasApprovalResponse(prev.content)) {
72
+ // Clone before merging to avoid mutating the original message's content array
73
+ const cloned = { ...prev, content: [...prev.content] };
74
+ result[result.length - 1] = cloned;
75
+ cloned.content.push(...msg.content);
76
+ }
77
+ else {
78
+ result.push(msg);
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ function hasApprovalResponse(content) {
84
+ return content.some((p) => p.type === "tool-approval-response");
85
+ }
21
86
  export function serializeUsage(usage) {
22
87
  return {
23
88
  promptTokens: usage.inputTokens ?? 0,
24
89
  completionTokens: usage.outputTokens ?? 0,
25
90
  totalTokens: usage.totalTokens ?? 0,
26
91
  reasoningTokens: usage.reasoningTokens,
92
+ cachedInputTokens: usage.cachedInputTokens,
27
93
  };
28
94
  }
29
- export function deserializeUsage(usage) {
95
+ export function toModelMessageUsage(usage) {
30
96
  return {
31
97
  inputTokens: usage.promptTokens,
32
98
  outputTokens: usage.completionTokens,
33
99
  totalTokens: usage.totalTokens,
34
100
  reasoningTokens: usage.reasoningTokens,
35
101
  cachedInputTokens: usage.cachedInputTokens,
102
+ // These detail fields are required by LanguageModelUsage type but we don't
103
+ // have the granular data, so we provide empty objects with undefined values.
104
+ inputTokenDetails: {
105
+ cacheReadTokens: undefined,
106
+ cacheWriteTokens: undefined,
107
+ noCacheTokens: undefined,
108
+ },
109
+ outputTokenDetails: {
110
+ textTokens: undefined,
111
+ reasoningTokens: undefined,
112
+ },
36
113
  };
37
114
  }
38
115
  export function serializeWarnings(warnings) {
@@ -40,25 +117,48 @@ export function serializeWarnings(warnings) {
40
117
  return undefined;
41
118
  }
42
119
  return warnings.map((warning) => {
43
- if (warning.type !== "unsupported-setting") {
44
- return warning;
120
+ if (warning.type === "compatibility") {
121
+ return {
122
+ type: "unsupported-setting",
123
+ setting: warning.feature,
124
+ details: warning.details,
125
+ };
45
126
  }
46
- return {
47
- ...warning,
48
- setting: warning.setting.toString(),
49
- };
127
+ return warning;
50
128
  });
51
129
  }
52
- export function deserializeWarnings(warnings) {
53
- // We don't need to do anythign here for now
54
- return warnings;
130
+ export function toModelMessageWarnings(warnings) {
131
+ if (!warnings) {
132
+ return undefined;
133
+ }
134
+ return warnings.map((warning) => {
135
+ if (warning.type === "unsupported-setting") {
136
+ return {
137
+ type: "compatibility",
138
+ feature: warning.setting,
139
+ details: warning.details,
140
+ };
141
+ }
142
+ return warning;
143
+ });
55
144
  }
56
- export async function serializeNewMessagesInStep(ctx, component, step, metadata) {
145
+ export async function serializeNewMessagesInStep(ctx, component, step, model,
146
+ /**
147
+ * If provided, these are the new response messages for this step
148
+ * (pre-sliced by the caller). When not provided, falls back to the
149
+ * existing heuristic of slicing the last 1-2 messages.
150
+ *
151
+ * This is needed for tool approval flows where the SDK adds extra
152
+ * messages (e.g. approval tool-results) at the beginning of
153
+ * responseMessages that the old slice(-1/-2) logic would miss.
154
+ */
155
+ newResponseMessages) {
57
156
  // If there are tool results, there's another message with the tool results
58
- // ref: https://github.com/vercel/ai/blob/main/packages/ai/core/generate-text/to-response-messages.ts
157
+ // ref: https://github.com/vercel/ai/blob/main/packages/ai/src/generate-text/to-response-messages.ts#L120
158
+ const hasToolMessage = step.response.messages.at(-1)?.role === "tool";
59
159
  const assistantFields = {
60
- model: metadata.model,
61
- provider: metadata.provider,
160
+ model: model ? getModelName(model) : undefined,
161
+ provider: model ? getProviderName(model) : undefined,
62
162
  providerMetadata: step.providerMetadata,
63
163
  reasoning: step.reasoningText,
64
164
  reasoningDetails: step.reasoning,
@@ -66,28 +166,36 @@ export async function serializeNewMessagesInStep(ctx, component, step, metadata)
66
166
  warnings: serializeWarnings(step.warnings),
67
167
  finishReason: step.finishReason,
68
168
  // Only store the sources on one message
69
- sources: step.toolResults.length === 0 ? step.sources : undefined,
169
+ sources: hasToolMessage ? undefined : step.sources,
70
170
  };
71
- const toolFields = {
72
- sources: step.sources,
73
- };
74
- const messages = await Promise.all((step.toolResults.length > 0
75
- ? step.response.messages.slice(-2)
76
- : step.response.messages.slice(-1)).map(async (messageWithId) => {
77
- const { message, fileIds } = await serializeMessage(ctx, component, messageWithId);
78
- return {
171
+ const toolFields = { sources: step.sources };
172
+ // Determine which messages to serialize for this step
173
+ let messagesToSerialize;
174
+ if (newResponseMessages) {
175
+ messagesToSerialize = newResponseMessages;
176
+ }
177
+ else if (hasToolMessage) {
178
+ messagesToSerialize = step.response.messages.slice(-2);
179
+ }
180
+ else if (step.content.length) {
181
+ messagesToSerialize = step.response.messages.slice(-1);
182
+ }
183
+ else {
184
+ messagesToSerialize = [{ role: "assistant", content: [] }];
185
+ }
186
+ const messages = await Promise.all(messagesToSerialize.map(async (msg) => {
187
+ const { message, fileIds } = await serializeMessage(ctx, component, msg);
188
+ return parse(vMessageWithMetadata, {
79
189
  message,
80
- // Let's not store the ID by default here. It's being generated internally
81
- // and not referenced elsewhere that we know of.
82
- // id: message.id,
83
190
  ...(message.role === "tool" ? toolFields : assistantFields),
84
191
  text: step.text,
85
192
  fileIds,
86
- };
193
+ });
87
194
  }));
88
- return messages;
195
+ // TODO: capture step.files separately?
196
+ return { messages };
89
197
  }
90
- export async function serializeObjectResult(ctx, component, result, metadata) {
198
+ export async function serializeObjectResult(ctx, component, result, model) {
91
199
  const text = JSON.stringify(result.object);
92
200
  const { message, fileIds } = await serializeMessage(ctx, component, {
93
201
  role: "assistant",
@@ -97,9 +205,8 @@ export async function serializeObjectResult(ctx, component, result, metadata) {
97
205
  messages: [
98
206
  {
99
207
  message,
100
- id: result.response.id,
101
- model: metadata.model,
102
- provider: metadata.provider,
208
+ model: model ? getModelName(model) : undefined,
209
+ provider: model ? getProviderName(model) : undefined,
103
210
  providerMetadata: result.providerMetadata,
104
211
  finishReason: result.finishReason,
105
212
  text,
@@ -110,27 +217,34 @@ export async function serializeObjectResult(ctx, component, result, metadata) {
110
217
  ],
111
218
  };
112
219
  }
220
+ function getMimeOrMediaType(part) {
221
+ if ("mediaType" in part) {
222
+ return part.mediaType;
223
+ }
224
+ if ("mimeType" in part) {
225
+ return part.mimeType;
226
+ }
227
+ return undefined;
228
+ }
113
229
  export async function serializeContent(ctx, component, content) {
114
230
  if (typeof content === "string") {
115
231
  return { content };
116
232
  }
117
233
  const fileIds = [];
118
- function getMimeType(part) {
119
- if ("mediaType" in part) {
120
- return part.mediaType;
234
+ const serialized = await Promise.all(content.map(async (part) => {
235
+ const metadata = {};
236
+ if ("providerOptions" in part) {
237
+ metadata.providerOptions = part.providerOptions;
121
238
  }
122
- if ("mimeType" in part) {
123
- return part.mimeType;
239
+ if ("providerMetadata" in part) {
240
+ metadata.providerMetadata = part.providerMetadata;
124
241
  }
125
- return undefined;
126
- }
127
- const serialized = await Promise.all(content.map(async (part) => {
128
242
  switch (part.type) {
129
243
  case "text": {
130
244
  return {
131
245
  type: part.type,
132
246
  text: part.text,
133
- providerOptions: part.providerOptions,
247
+ ...metadata,
134
248
  };
135
249
  }
136
250
  case "image": {
@@ -138,22 +252,22 @@ export async function serializeContent(ctx, component, content) {
138
252
  if (image instanceof ArrayBuffer &&
139
253
  image.byteLength > MAX_FILE_SIZE) {
140
254
  const { file } = await storeFile(ctx, component, new Blob([image], {
141
- type: getMimeType(part) || guessMimeType(image),
255
+ type: getMimeOrMediaType(part) || guessMimeType(image),
142
256
  }));
143
257
  image = file.url;
144
258
  fileIds.push(file.fileId);
145
259
  }
146
260
  return {
147
261
  type: part.type,
148
- mimeType: getMimeType(part),
149
- providerOptions: part.providerOptions,
262
+ mediaType: getMimeOrMediaType(part),
263
+ ...metadata,
150
264
  image,
151
265
  };
152
266
  }
153
267
  case "file": {
154
268
  let data = serializeDataOrUrl(part.data);
155
269
  if (data instanceof ArrayBuffer && data.byteLength > MAX_FILE_SIZE) {
156
- const { file } = await storeFile(ctx, component, new Blob([data], { type: getMimeType(part) }));
270
+ const { file } = await storeFile(ctx, component, new Blob([data], { type: getMimeOrMediaType(part) }));
157
271
  data = file.url;
158
272
  fileIds.push(file.fileId);
159
273
  }
@@ -161,116 +275,270 @@ export async function serializeContent(ctx, component, content) {
161
275
  type: part.type,
162
276
  data,
163
277
  filename: part.filename,
164
- mimeType: getMimeType(part),
165
- providerOptions: part.providerOptions,
278
+ mediaType: getMimeOrMediaType(part),
279
+ ...metadata,
166
280
  };
167
281
  }
168
282
  case "tool-call": {
169
- const args = "input" in part ? part.input : part.args;
283
+ // Handle legacy data where only args field exists
284
+ const input = part.input ?? part?.args ?? {};
170
285
  return {
171
286
  type: part.type,
172
- args: args ?? null,
287
+ input,
288
+ /** @deprecated Use `input` instead. */
289
+ args: input,
173
290
  toolCallId: part.toolCallId,
174
291
  toolName: part.toolName,
175
- providerOptions: part.providerOptions,
176
292
  providerExecuted: part.providerExecuted,
293
+ ...metadata,
177
294
  };
178
295
  }
179
296
  case "tool-result": {
180
- const result = "output" in part ? part.output : part.result;
181
- return {
182
- type: part.type,
183
- result: result ?? null,
184
- toolCallId: part.toolCallId,
185
- toolName: part.toolName,
186
- providerOptions: part.providerOptions,
187
- };
297
+ return normalizeToolResult(part, metadata);
188
298
  }
189
299
  case "reasoning": {
190
300
  return {
191
301
  type: part.type,
192
302
  text: part.text,
193
- providerOptions: part.providerOptions,
303
+ ...metadata,
194
304
  };
195
305
  }
306
+ // Not in current generation output, but could be in historical messages
196
307
  case "redacted-reasoning": {
197
308
  return {
198
309
  type: part.type,
199
310
  data: part.data,
200
- providerOptions: part.providerOptions,
311
+ ...metadata,
201
312
  };
202
313
  }
203
- default:
314
+ case "source": {
204
315
  return part;
316
+ }
317
+ case "tool-approval-request": {
318
+ return {
319
+ type: part.type,
320
+ approvalId: part.approvalId,
321
+ toolCallId: part.toolCallId,
322
+ ...metadata,
323
+ };
324
+ }
325
+ case "tool-approval-response": {
326
+ return {
327
+ type: part.type,
328
+ approvalId: part.approvalId,
329
+ approved: part.approved,
330
+ reason: part.reason,
331
+ providerExecuted: part.providerExecuted,
332
+ ...metadata,
333
+ };
334
+ }
335
+ default:
336
+ return null;
205
337
  }
206
338
  }));
207
339
  return {
208
- content: serialized,
340
+ content: serialized.filter((p) => p !== null),
209
341
  fileIds: fileIds.length > 0 ? fileIds : undefined,
210
342
  };
211
343
  }
212
- export function deserializeContent(content) {
344
+ export function fromModelMessageContent(content) {
213
345
  if (typeof content === "string") {
214
346
  return content;
215
347
  }
216
- return content.map((part) => {
348
+ return content
349
+ .map((part) => {
350
+ const metadata = {};
351
+ if ("providerOptions" in part) {
352
+ metadata.providerOptions = part.providerOptions;
353
+ }
354
+ if ("providerMetadata" in part) {
355
+ metadata.providerMetadata = part.providerMetadata;
356
+ }
217
357
  switch (part.type) {
218
358
  case "text":
219
- return {
220
- type: part.type,
221
- text: part.text,
222
- providerOptions: part.providerOptions,
223
- };
359
+ return part;
224
360
  case "image":
225
361
  return {
226
362
  type: part.type,
227
- image: deserializeUrl(part.image),
228
- mediaType: part.mimeType,
229
- providerOptions: part.providerOptions,
363
+ mediaType: getMimeOrMediaType(part),
364
+ ...metadata,
365
+ image: serializeDataOrUrl(part.image),
230
366
  };
231
367
  case "file":
232
368
  return {
233
369
  type: part.type,
234
- data: deserializeUrl(part.data),
370
+ data: serializeDataOrUrl(part.data),
235
371
  filename: part.filename,
236
- mediaType: part.mimeType,
237
- providerOptions: part.providerOptions,
372
+ mediaType: getMimeOrMediaType(part),
373
+ ...metadata,
238
374
  };
239
375
  case "tool-call":
376
+ // Handle legacy data where only args field exists
240
377
  return {
241
378
  type: part.type,
242
- input: part.args ?? null,
243
- providerExecuted: part.providerExecuted,
379
+ input: part.input ?? part?.args ?? {},
380
+ /** @deprecated Use `input` instead. */
381
+ args: part.input ?? part?.args ?? {},
244
382
  toolCallId: part.toolCallId,
245
383
  toolName: part.toolName,
246
- providerOptions: part.providerOptions,
384
+ providerExecuted: part.providerExecuted,
385
+ ...metadata,
247
386
  };
248
387
  case "tool-result":
388
+ return normalizeToolResult(part, metadata);
389
+ case "reasoning":
249
390
  return {
250
391
  type: part.type,
251
- output: part.result ?? null,
392
+ text: part.text,
393
+ ...metadata,
394
+ };
395
+ case "tool-approval-request":
396
+ return {
397
+ type: part.type,
398
+ approvalId: part.approvalId,
399
+ toolCallId: part.toolCallId,
400
+ ...metadata,
401
+ };
402
+ case "tool-approval-response":
403
+ return {
404
+ type: part.type,
405
+ approvalId: part.approvalId,
406
+ approved: part.approved,
407
+ reason: part.reason,
408
+ providerExecuted: part.providerExecuted,
409
+ ...metadata,
410
+ };
411
+ // Not in current generation output, but could be in historical messages
412
+ default:
413
+ return null;
414
+ }
415
+ })
416
+ .filter((p) => p !== null);
417
+ }
418
+ export function toModelMessageContent(content) {
419
+ if (typeof content === "string") {
420
+ return content;
421
+ }
422
+ return content
423
+ .map((part) => {
424
+ const metadata = {};
425
+ if ("providerOptions" in part) {
426
+ metadata.providerOptions = part.providerOptions;
427
+ }
428
+ if ("providerMetadata" in part) {
429
+ metadata.providerMetadata = part.providerMetadata;
430
+ }
431
+ switch (part.type) {
432
+ case "text":
433
+ return {
434
+ type: part.type,
435
+ text: part.text,
436
+ ...metadata,
437
+ };
438
+ case "image":
439
+ return {
440
+ type: part.type,
441
+ image: toModelMessageDataOrUrl(part.image),
442
+ mediaType: getMimeOrMediaType(part),
443
+ ...metadata,
444
+ };
445
+ case "file":
446
+ return {
447
+ type: part.type,
448
+ data: toModelMessageDataOrUrl(part.data),
449
+ filename: part.filename,
450
+ mediaType: getMimeOrMediaType(part),
451
+ ...metadata,
452
+ };
453
+ case "tool-call": {
454
+ // Handle legacy data where only args field exists
455
+ const input = part.input ?? part?.args ?? {};
456
+ return {
457
+ type: part.type,
458
+ input,
252
459
  toolCallId: part.toolCallId,
253
460
  toolName: part.toolName,
254
- providerOptions: part.providerOptions,
461
+ providerExecuted: part.providerExecuted,
462
+ ...metadata,
255
463
  };
464
+ }
465
+ case "tool-result": {
466
+ return normalizeToolResult(part, metadata);
467
+ }
256
468
  case "reasoning":
257
469
  return {
258
470
  type: part.type,
259
471
  text: part.text,
260
- providerMetadata: part.providerOptions,
261
- state: part.state,
472
+ ...metadata,
262
473
  };
263
474
  case "redacted-reasoning":
264
475
  // TODO: should we just drop this?
265
476
  return {
266
477
  type: "reasoning",
267
- text: part.data,
268
- providerMetadata: part.providerOptions,
478
+ text: "",
479
+ ...metadata,
480
+ providerOptions: metadata.providerOptions
481
+ ? {
482
+ ...Object.fromEntries(Object.entries(metadata.providerOptions ?? {}).map(([key, value]) => [
483
+ key,
484
+ { ...value, redactedData: part.data },
485
+ ])),
486
+ }
487
+ : undefined,
269
488
  };
270
- default:
489
+ case "source":
271
490
  return part;
491
+ case "tool-approval-request":
492
+ return {
493
+ type: part.type,
494
+ approvalId: part.approvalId,
495
+ toolCallId: part.toolCallId,
496
+ ...metadata,
497
+ };
498
+ case "tool-approval-response":
499
+ return {
500
+ type: part.type,
501
+ approvalId: part.approvalId,
502
+ approved: part.approved,
503
+ reason: part.reason,
504
+ providerExecuted: part.providerExecuted,
505
+ ...metadata,
506
+ };
507
+ default:
508
+ return null;
272
509
  }
273
- });
510
+ })
511
+ .filter((p) => p !== null);
512
+ }
513
+ export function normalizeToolOutput(result) {
514
+ if (typeof result === "string") {
515
+ return {
516
+ type: "text",
517
+ value: result,
518
+ };
519
+ }
520
+ if (validate(vToolResultOutput, result)) {
521
+ return result;
522
+ }
523
+ return {
524
+ type: "json",
525
+ value: result ?? null,
526
+ };
527
+ }
528
+ function normalizeToolResult(part, metadata) {
529
+ return {
530
+ type: part.type,
531
+ output: part.output
532
+ ? validate(vToolResultOutput, part.output)
533
+ ? part.output
534
+ : normalizeToolOutput(JSON.stringify(part.output))
535
+ : normalizeToolOutput("result" in part ? part.result : undefined),
536
+ toolCallId: part.toolCallId,
537
+ toolName: part.toolName,
538
+ // Preserve isError flag for error reporting
539
+ ...("isError" in part && part.isError ? { isError: true } : {}),
540
+ ...metadata,
541
+ };
274
542
  }
275
543
  /**
276
544
  * Return a best-guess MIME type based on the magic-number signature
@@ -362,7 +630,10 @@ export function serializeDataOrUrl(dataOrUrl) {
362
630
  }
363
631
  return dataOrUrl.buffer.slice(dataOrUrl.byteOffset, dataOrUrl.byteOffset + dataOrUrl.byteLength);
364
632
  }
365
- export function deserializeUrl(urlOrString) {
633
+ export function toModelMessageDataOrUrl(urlOrString) {
634
+ if (urlOrString instanceof URL) {
635
+ return urlOrString;
636
+ }
366
637
  if (typeof urlOrString === "string") {
367
638
  if (urlOrString.startsWith("http://") ||
368
639
  urlOrString.startsWith("https://")) {
@@ -375,7 +646,7 @@ export function deserializeUrl(urlOrString) {
375
646
  export function toUIFilePart(part) {
376
647
  const dataOrUrl = part.type === "image" ? part.image : part.data;
377
648
  const url = dataOrUrl instanceof ArrayBuffer
378
- ? encodeBase64(dataOrUrl)
649
+ ? convertUint8ArrayToBase64(new Uint8Array(dataOrUrl))
379
650
  : dataOrUrl.toString();
380
651
  return {
381
652
  type: "file",
@@ -385,9 +656,6 @@ export function toUIFilePart(part) {
385
656
  providerMetadata: part.providerOptions,
386
657
  };
387
658
  }
388
- function encodeBase64(data) {
389
- return Buffer.from(data).toString("base64");
390
- }
391
659
  // Currently unused
392
660
  // export function toModelMessages(args: {
393
661
  // messages?: ModelMessage[] | AIMessageWithoutId[];