@convex-dev/agent 0.6.4 → 0.7.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 +31 -24
  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
package/src/validators.ts CHANGED
@@ -15,6 +15,11 @@ export const vProviderMetadata = vProviderOptions;
15
15
  const providerMetadata = providerOptions;
16
16
  export type ProviderMetadata = Infer<typeof providerMetadata>;
17
17
 
18
+ export const vProviderReference = v.object({
19
+ type: v.literal("reference"),
20
+ reference: v.record(v.string(), v.string()),
21
+ });
22
+
18
23
  export const vThreadStatus = v.union(
19
24
  v.literal("active"),
20
25
  v.literal("archived"), // unused
@@ -42,7 +47,7 @@ export const vTextPart = v.object({
42
47
 
43
48
  export const vImagePart = v.object({
44
49
  type: v.literal("image"),
45
- image: v.union(v.string(), v.bytes()),
50
+ image: v.union(v.string(), v.bytes(), vProviderReference),
46
51
  mediaType: v.optional(v.string()),
47
52
  /** @deprecated Use `mediaType` instead. */
48
53
  mimeType: v.optional(v.string()),
@@ -51,7 +56,7 @@ export const vImagePart = v.object({
51
56
 
52
57
  export const vFilePart = v.object({
53
58
  type: v.literal("file"),
54
- data: v.union(v.string(), v.bytes()),
59
+ data: v.union(v.string(), v.bytes(), vProviderReference),
55
60
  filename: v.optional(v.string()),
56
61
  mediaType: v.optional(v.string()),
57
62
  /** @deprecated Use `mediaType` instead. */
@@ -73,6 +78,21 @@ export const vReasoningPart = v.object({
73
78
  providerMetadata,
74
79
  });
75
80
 
81
+ export const vReasoningFilePart = v.object({
82
+ type: v.literal("reasoning-file"),
83
+ url: v.optional(v.string()),
84
+ data: v.optional(v.union(v.string(), v.bytes())),
85
+ mediaType: v.string(),
86
+ providerOptions,
87
+ providerMetadata,
88
+ });
89
+
90
+ export const vCustomContentPart = v.object({
91
+ type: v.literal("custom"),
92
+ kind: v.string(),
93
+ providerOptions,
94
+ providerMetadata,
95
+ });
76
96
  export const vRedactedReasoningPart = v.object({
77
97
  type: v.literal("redacted-reasoning"),
78
98
  data: v.string(),
@@ -127,6 +147,8 @@ export const vToolCallPart = v.union(
127
147
  /** @deprecated Use `input` instead. */
128
148
  args: v.optional(v.any()),
129
149
  providerExecuted: v.optional(v.boolean()),
150
+ title: v.optional(v.string()),
151
+ toolMetadata: v.optional(v.record(v.string(), v.any())),
130
152
  providerOptions,
131
153
  providerMetadata,
132
154
  }),
@@ -139,6 +161,8 @@ export const vToolCallPart = v.union(
139
161
  args: v.any(),
140
162
  input: v.optional(v.any()),
141
163
  providerExecuted: v.optional(v.boolean()),
164
+ title: v.optional(v.string()),
165
+ toolMetadata: v.optional(v.record(v.string(), v.any())),
142
166
  providerOptions,
143
167
  providerMetadata,
144
168
  }),
@@ -178,6 +202,21 @@ export const vToolResultOutput = v.union(
178
202
  text: v.string(),
179
203
  providerOptions,
180
204
  }),
205
+ v.object({
206
+ type: v.literal("file"),
207
+ data: v.union(
208
+ v.object({ type: v.literal("url"), url: v.string() }),
209
+ vProviderReference,
210
+ v.object({
211
+ type: v.literal("data"),
212
+ data: v.union(v.string(), v.bytes()),
213
+ }),
214
+ v.object({ type: v.literal("text"), text: v.string() }),
215
+ ),
216
+ mediaType: v.string(),
217
+ filename: v.optional(v.string()),
218
+ providerOptions,
219
+ }),
181
220
  /** @deprecated Use `image-data` or `file-data` instead. */
182
221
  v.object({
183
222
  type: v.literal("media"),
@@ -199,6 +238,7 @@ export const vToolResultOutput = v.union(
199
238
  v.object({
200
239
  type: v.literal("file-url"),
201
240
  url: v.string(),
241
+ mediaType: v.optional(v.string()),
202
242
  providerOptions,
203
243
  }),
204
244
  v.object({
@@ -214,6 +254,11 @@ export const vToolResultOutput = v.union(
214
254
  fileId: v.union(v.string(), v.record(v.string(), v.string())),
215
255
  providerOptions,
216
256
  }),
257
+ v.object({
258
+ type: v.literal("file-reference"),
259
+ providerReference: v.record(v.string(), v.string()),
260
+ providerOptions,
261
+ }),
217
262
  v.object({
218
263
  type: v.literal("image-data"),
219
264
  data: v.string(),
@@ -224,6 +269,11 @@ export const vToolResultOutput = v.union(
224
269
  mediaType: v.string(),
225
270
  providerOptions,
226
271
  }),
272
+ v.object({
273
+ type: v.literal("image-file-reference"),
274
+ providerReference: v.record(v.string(), v.string()),
275
+ providerOptions,
276
+ }),
227
277
  v.object({
228
278
  type: v.literal("image-url"),
229
279
  url: v.string(),
@@ -271,6 +321,8 @@ export const vToolApprovalRequest = v.object({
271
321
  * ID of the tool call that the approval request is for.
272
322
  */
273
323
  toolCallId: v.string(),
324
+ isAutomatic: v.optional(v.boolean()),
325
+ signature: v.optional(v.string()),
274
326
  /** @todo Should we continue to include? */
275
327
  providerMetadata,
276
328
  /** @todo Should we continue to include? */
@@ -334,6 +386,8 @@ export const vAssistantContent = v.union(
334
386
  vTextPart,
335
387
  vFilePart,
336
388
  vReasoningPart,
389
+ vReasoningFilePart,
390
+ vCustomContentPart,
337
391
  vRedactedReasoningPart,
338
392
  vToolCallPart,
339
393
  vToolResultPart,
@@ -383,6 +437,8 @@ export type MessageContentParts =
383
437
  | Infer<typeof vImagePart>
384
438
  | Infer<typeof vFilePart>
385
439
  | Infer<typeof vReasoningPart>
440
+ | Infer<typeof vReasoningFilePart>
441
+ | Infer<typeof vCustomContentPart>
386
442
  | Infer<typeof vRedactedReasoningPart>
387
443
  | Infer<typeof vToolCallPart>
388
444
  | Infer<typeof vToolResultPart>
@@ -431,15 +487,34 @@ export const vFinishReason = v.union(
431
487
  );
432
488
 
433
489
  export const vUsage = v.object({
434
- promptTokens: v.number(),
435
- completionTokens: v.number(),
436
- totalTokens: v.number(),
490
+ promptTokens: v.optional(v.number()),
491
+ completionTokens: v.optional(v.number()),
492
+ totalTokens: v.optional(v.number()),
437
493
  reasoningTokens: v.optional(v.number()),
438
494
  cachedInputTokens: v.optional(v.number()),
495
+ nonCachedInputTokens: v.optional(v.number()),
496
+ cacheWriteInputTokens: v.optional(v.number()),
497
+ textOutputTokens: v.optional(v.number()),
498
+ raw: v.optional(v.record(v.string(), v.any())),
439
499
  });
440
500
  export type Usage = Infer<typeof vUsage>;
441
501
 
442
502
  export const vLanguageModelCallWarning = v.union(
503
+ v.object({
504
+ type: v.literal("unsupported"),
505
+ feature: v.string(),
506
+ details: v.optional(v.string()),
507
+ }),
508
+ v.object({
509
+ type: v.literal("compatibility"),
510
+ feature: v.string(),
511
+ details: v.optional(v.string()),
512
+ }),
513
+ v.object({
514
+ type: v.literal("deprecated"),
515
+ setting: v.string(),
516
+ message: v.string(),
517
+ }),
443
518
  v.object({
444
519
  type: v.literal("unsupported-setting"),
445
520
  setting: v.string(),
@@ -515,8 +590,13 @@ export const vStorageOptions = v.object({
515
590
  ),
516
591
  });
517
592
 
518
- const vPromptFields = {
519
- system: v.optional(v.string()),
593
+ // Generated Convex actions keep the established `system` wire field. The
594
+ // Vercel adapter translates it to AI SDK 7's `instructions` before invoking
595
+ // the model, so action payloads never carry both spellings.
596
+ const vActionPromptFields = {
597
+ system: v.optional(
598
+ v.union(v.string(), vSystemMessage, v.array(vSystemMessage)),
599
+ ),
520
600
  prompt: v.optional(v.string()),
521
601
  messages: v.optional(v.array(vMessage)),
522
602
  promptMessageId: v.optional(v.string()),
@@ -543,7 +623,8 @@ const vCommonArgs = {
543
623
  storageOptions: v.optional(vStorageOptions),
544
624
  providerOptions,
545
625
  callSettings: v.optional(vCallSettings),
546
- ...vPromptFields,
626
+ allowSystemInMessages: v.optional(v.boolean()),
627
+ ...vActionPromptFields,
547
628
  };
548
629
 
549
630
  export const vTextArgs = v.object({
@@ -1,5 +1,8 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { combineUIMessages, type UIMessage } from "./UIMessages.js";
2
+ import {
3
+ combineUIMessages,
4
+ type UIMessage,
5
+ } from "./UIMessages.js";
3
6
 
4
7
  describe("combineUIMessages", () => {
5
8
  it("should preserve all tool calls when combining messages", () => {
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  convertToModelMessages,
3
3
  type UIMessage as AIUIMessage,
4
+ type CustomContentUIPart,
4
5
  type DeepPartial,
5
6
  type DynamicToolUIPart,
7
+ type ReasoningFileUIPart,
6
8
  type ReasoningUIPart,
7
9
  type SourceDocumentUIPart,
8
10
  type SourceUrlUIPart,
@@ -13,6 +15,10 @@ import {
13
15
  type UIDataTypes,
14
16
  type UITools,
15
17
  } from "ai";
18
+ import {
19
+ convertUint8ArrayToBase64,
20
+ type ReasoningFilePart,
21
+ } from "@ai-sdk/provider-utils";
16
22
  import type { Infer } from "convex/values";
17
23
  import { toModelMessage, fromModelMessage, toUIFilePart } from "./mapping.js";
18
24
  import {
@@ -21,14 +27,14 @@ import {
21
27
  isTool,
22
28
  joinText,
23
29
  sorted,
24
- } from "./shared.js";
30
+ } from "../shared.js";
25
31
  import type {
26
32
  MessageDoc,
27
33
  MessageStatus,
28
34
  ProviderOptions,
29
35
  SourcePart,
30
36
  vSource,
31
- } from "./validators.js";
37
+ } from "../validators.js";
32
38
  import { omit, pick } from "convex-helpers";
33
39
 
34
40
  export type UIStatus = "streaming" | MessageStatus;
@@ -153,6 +159,7 @@ function fromSourceParts(parts: UIMessage["parts"]): Infer<typeof vSource>[] {
153
159
  id: part.sourceId,
154
160
  providerMetadata: part.providerMetadata,
155
161
  title: part.title,
162
+ filename: part.filename,
156
163
  } satisfies Infer<typeof vSource>;
157
164
  }
158
165
  return undefined;
@@ -398,7 +405,13 @@ function createAssistantUIMessage<
398
405
 
399
406
  // Extract approval parts from raw message content for UI rendering
400
407
  type ApprovalPart =
401
- | { type: "tool-approval-request"; approvalId: string; toolCallId: string }
408
+ | {
409
+ type: "tool-approval-request";
410
+ approvalId: string;
411
+ toolCallId: string;
412
+ isAutomatic?: boolean;
413
+ signature?: string;
414
+ }
402
415
  | {
403
416
  type: "tool-approval-response";
404
417
  approvalId: string;
@@ -496,6 +509,16 @@ function createAssistantUIMessage<
496
509
  ...contentPart,
497
510
  } satisfies ReasoningUIPart);
498
511
  break;
512
+ case "reasoning-file":
513
+ allParts.push(toUIReasoningFilePart(contentPart));
514
+ break;
515
+ case "custom":
516
+ allParts.push({
517
+ type: "custom",
518
+ kind: contentPart.kind,
519
+ providerMetadata: contentPart.providerOptions,
520
+ } satisfies CustomContentUIPart);
521
+ break;
499
522
  case "file":
500
523
  case "image":
501
524
  allParts.push(toUIFilePart(contentPart));
@@ -613,6 +636,8 @@ function createAssistantUIMessage<
613
636
  const typedPart = contentPart as {
614
637
  toolCallId: string;
615
638
  approvalId: string;
639
+ isAutomatic?: boolean;
640
+ signature?: string;
616
641
  };
617
642
  const toolCallPart = allParts.find(
618
643
  (part) =>
@@ -623,6 +648,12 @@ function createAssistantUIMessage<
623
648
  toolCallPart.state = "approval-requested";
624
649
  (toolCallPart as ToolUIPart & { approval?: object }).approval = {
625
650
  id: typedPart.approvalId,
651
+ ...(typedPart.isAutomatic !== undefined
652
+ ? { isAutomatic: typedPart.isAutomatic }
653
+ : {}),
654
+ ...(typedPart.signature !== undefined
655
+ ? { signature: typedPart.signature }
656
+ : {}),
626
657
  };
627
658
  } else {
628
659
  console.warn(
@@ -647,9 +678,15 @@ function createAssistantUIMessage<
647
678
  ) as ToolUIPart | undefined;
648
679
 
649
680
  if (toolCallPart) {
681
+ const approval = (
682
+ toolCallPart as ToolUIPart & {
683
+ approval?: { isAutomatic?: boolean; signature?: string };
684
+ }
685
+ ).approval;
650
686
  if (typedPart.approved) {
651
687
  toolCallPart.state = "approval-responded";
652
688
  (toolCallPart as ToolUIPart & { approval?: object }).approval = {
689
+ ...approval,
653
690
  id: typedPart.approvalId,
654
691
  approved: true,
655
692
  reason: typedPart.reason,
@@ -657,6 +694,7 @@ function createAssistantUIMessage<
657
694
  } else {
658
695
  toolCallPart.state = "output-denied";
659
696
  (toolCallPart as ToolUIPart & { approval?: object }).approval = {
697
+ ...approval,
660
698
  id: typedPart.approvalId,
661
699
  approved: false,
662
700
  reason: typedPart.reason,
@@ -710,6 +748,12 @@ function createAssistantUIMessage<
710
748
  // update state if not in a final state
711
749
  (toolCallPart as ToolUIPart & { approval?: object }).approval = {
712
750
  id: approvalPart.approvalId,
751
+ ...(approvalPart.isAutomatic !== undefined
752
+ ? { isAutomatic: approvalPart.isAutomatic }
753
+ : {}),
754
+ ...(approvalPart.signature !== undefined
755
+ ? { signature: approvalPart.signature }
756
+ : {}),
713
757
  };
714
758
  if (!finalStates.has(toolCallPart.state)) {
715
759
  toolCallPart.state = "approval-requested";
@@ -725,7 +769,13 @@ function createAssistantUIMessage<
725
769
 
726
770
  if (toolCallPart) {
727
771
  // Always update approval info, but only update state if not in a final state
772
+ const approval = (
773
+ toolCallPart as ToolUIPart & {
774
+ approval?: { isAutomatic?: boolean; signature?: string };
775
+ }
776
+ ).approval;
728
777
  (toolCallPart as ToolUIPart & { approval?: object }).approval = {
778
+ ...approval,
729
779
  id: approvalPart.approvalId,
730
780
  approved: approvalPart.approved,
731
781
  reason: approvalPart.reason,
@@ -777,6 +827,48 @@ function createAssistantUIMessage<
777
827
  };
778
828
  }
779
829
 
830
+ function toUIReasoningFilePart(part: ReasoningFilePart): ReasoningFileUIPart {
831
+ const data = part.data;
832
+ return {
833
+ type: "reasoning-file",
834
+ mediaType: part.mediaType,
835
+ url: toUIReasoningFileUrl(data, part.mediaType),
836
+ providerMetadata: part.providerOptions,
837
+ };
838
+ }
839
+
840
+ function toUIReasoningFileUrl(
841
+ data: ReasoningFilePart["data"],
842
+ mediaType: string,
843
+ ): string {
844
+ if (isTaggedReasoningFileData(data)) {
845
+ if (data.type === "url") return data.url.toString();
846
+ data = data.data;
847
+ }
848
+ if (data instanceof URL) return data.toString();
849
+ if (typeof data === "string") {
850
+ return data.startsWith("data:") ? data : `data:${mediaType};base64,${data}`;
851
+ }
852
+ const bytes =
853
+ data instanceof ArrayBuffer
854
+ ? new Uint8Array(data)
855
+ : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
856
+ return `data:${mediaType};base64,${convertUint8ArrayToBase64(bytes)}`;
857
+ }
858
+
859
+ function isTaggedReasoningFileData(
860
+ data: ReasoningFilePart["data"],
861
+ ): data is Extract<ReasoningFilePart["data"], { type: string }> {
862
+ return (
863
+ typeof data === "object" &&
864
+ data !== null &&
865
+ !(data instanceof URL) &&
866
+ !(data instanceof ArrayBuffer) &&
867
+ !(data instanceof Uint8Array) &&
868
+ "type" in data
869
+ );
870
+ }
871
+
780
872
  function toSourcePart(
781
873
  part: SourcePart | Infer<typeof vSource>,
782
874
  ): SourceUrlUIPart | SourceDocumentUIPart {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "vitest";
2
- import { Agent, createTool } from "./index.js";
2
+ import { Agent, createTool } from "../index.js";
3
3
  import type {
4
4
  DataModelFromSchemaDefinition,
5
5
  ApiFromModules,
@@ -145,6 +145,14 @@ export const testApproveFlow = action({
145
145
  threadId: thread.threadId,
146
146
  paginationOpts: { cursor: null, numItems: 20 },
147
147
  });
148
+ const toolResults = allMessages.page.flatMap((message) =>
149
+ Array.isArray(message.message?.content)
150
+ ? message.message.content.filter(
151
+ (part) =>
152
+ part.type === "tool-result" && part.toolCallId === "tc-approve",
153
+ )
154
+ : [],
155
+ );
148
156
 
149
157
  return {
150
158
  approvalId,
@@ -154,8 +162,9 @@ export const testApproveFlow = action({
154
162
  secondSavedCount: result2.savedMessages?.length ?? 0,
155
163
  totalThreadMessages: allMessages.page.length,
156
164
  threadMessageRoles: allMessages.page.map((m) => m.message?.role),
165
+ toolResults,
157
166
  usageCallCount: usageCalls.length,
158
- // Verify usage data includes detail fields (AI SDK v6)
167
+ // Verify usage data includes detail fields.
159
168
  lastUsage: usageCalls.at(-1),
160
169
  };
161
170
  },
@@ -431,8 +440,6 @@ describe("Tool Approval Workflow", () => {
431
440
  expect(result.firstSavedCount).toBeGreaterThanOrEqual(2);
432
441
  // Second call: tool-result + assistant text
433
442
  expect(result.secondSavedCount).toBeGreaterThanOrEqual(1);
434
- // Thread should have (ascending): user, assistant(tool-call+approval),
435
- // tool(approval-response), tool(tool-result), assistant(text)
436
443
  // listMessages returns descending order:
437
444
  expect(result.threadMessageRoles).toEqual([
438
445
  "assistant", // final text
@@ -441,9 +448,17 @@ describe("Tool Approval Workflow", () => {
441
448
  "assistant", // tool-call + approval-request
442
449
  "user", // prompt
443
450
  ]);
451
+ expect(result.toolResults).toEqual([
452
+ {
453
+ type: "tool-result",
454
+ toolCallId: "tc-approve",
455
+ toolName: "deleteFile",
456
+ output: { type: "text", value: "Deleted: test.txt" },
457
+ },
458
+ ]);
444
459
  // Usage handler should be called for each generateText call
445
460
  expect(result.usageCallCount).toBeGreaterThanOrEqual(2);
446
- // Usage data should include AI SDK v6 detail fields
461
+ // Usage data should include detail fields.
447
462
  expect(result.lastUsage).toBeDefined();
448
463
  expect(result.lastUsage!.inputTokenDetails).toBeDefined();
449
464
  expect(result.lastUsage!.outputTokenDetails).toBeDefined();
@@ -457,9 +472,7 @@ describe("Tool Approval Workflow", () => {
457
472
  expect(result.approvalId).toBeDefined();
458
473
  expect(result.firstText).toBe("");
459
474
  expect(result.secondText).toBe("OK, I won't delete that file.");
460
- // Same message ordering as approve flow:
461
- // user, assistant(tool-call+approval), tool(denial-response),
462
- // tool(execution-denied result), assistant(text)
475
+ // Same message ordering as approve flow.
463
476
  expect(result.threadMessageRoles).toEqual([
464
477
  "assistant",
465
478
  "tool",
@@ -483,14 +496,11 @@ describe("Tool Approval Workflow", () => {
483
496
  expect(result.secondText).toBe(
484
497
  "Done! Deleted old.txt and renamed a.txt to b.txt.",
485
498
  );
486
- // Both approval responses should be merged into one tool message
487
- // (write-time merge in respondToToolCallApproval via findApprovalContext)
488
- // Thread: user, assistant(2 tool-calls + 2 approvals),
489
- // tool(2 approval-responses merged), tool(2 tool-results), assistant(text)
499
+ // Executed results are persisted separately from approval responses.
490
500
  expect(result.threadMessageRoles).toEqual([
491
501
  "assistant", // final text
492
502
  "tool", // tool-results
493
- "tool", // approval-responses (merged)
503
+ "tool", // approval-responses
494
504
  "assistant", // tool-calls + approval-requests
495
505
  "user", // prompt
496
506
  ]);