@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
@@ -1,20 +1,20 @@
1
1
  import {
2
- readUIMessageStream,
2
+ type CustomContentUIPart,
3
3
  type DynamicToolUIPart,
4
4
  type ProviderMetadata,
5
+ type ReasoningFileUIPart,
5
6
  type ReasoningUIPart,
6
7
  type TextUIPart,
7
8
  type ToolUIPart,
8
9
  type UIMessageChunk,
9
10
  } from "ai";
10
- import { assert } from "convex-helpers";
11
11
  import { type UIMessage } from "./UIMessages.js";
12
- import { joinText, sorted } from "./shared.js";
12
+ import { joinText, sorted } from "../shared.js";
13
13
  import {
14
14
  type MessageStatus,
15
15
  type StreamDelta,
16
16
  type StreamMessage,
17
- } from "./validators.js";
17
+ } from "../validators.js";
18
18
 
19
19
  export function blankUIMessage<METADATA = unknown>(
20
20
  streamMessage: StreamMessage & { metadata?: METADATA },
@@ -50,58 +50,6 @@ export function statusFromStreamStatus(
50
50
  }
51
51
  }
52
52
 
53
- export async function updateFromUIMessageChunks(
54
- uiMessage: UIMessage,
55
- parts: UIMessageChunk[],
56
- ) {
57
- if (parts.length === 0) {
58
- return uiMessage;
59
- }
60
- const partsStream = new ReadableStream<UIMessageChunk>({
61
- start(controller) {
62
- for (const part of parts) {
63
- controller.enqueue(part);
64
- }
65
- controller.close();
66
- },
67
- });
68
- let failed = false;
69
- let suppressError = false;
70
- const messageStream = readUIMessageStream({
71
- message: uiMessage,
72
- stream: partsStream,
73
- onError: (e) => {
74
- const errorMessage = e instanceof Error ? e.message : String(e);
75
- if (errorMessage.toLowerCase().includes("no tool invocation found")) {
76
- suppressError = true;
77
- return;
78
- }
79
- failed = true;
80
- console.error("Error in stream", e);
81
- },
82
- terminateOnError: true,
83
- });
84
- let message = uiMessage;
85
- try {
86
- for await (const messagePart of messageStream) {
87
- assert(
88
- messagePart.id === message.id,
89
- `Expecting to only make one UIMessage in a stream`,
90
- );
91
- message = messagePart;
92
- }
93
- } catch (e) {
94
- if (!suppressError) {
95
- throw e;
96
- }
97
- }
98
- if (failed) {
99
- message.status = "failed";
100
- }
101
- message.text = joinText(message.parts);
102
- return message;
103
- }
104
-
105
53
  type ToolPart = ToolUIPart | DynamicToolUIPart;
106
54
 
107
55
  function transitionToolPart<S extends ToolPart["state"]>(
@@ -133,11 +81,18 @@ export function emptyIncrementalStreamState(): IncrementalStreamState {
133
81
  * indices stay stable across the structuredClone between batches. Behavior
134
82
  * mirrors the AI SDK's processUIMessageStream.
135
83
  */
136
- export function applyUIMessageChunksIncremental(
137
- uiMessage: UIMessage,
84
+ export function applyUIMessageChunksIncremental<
85
+ METADATA = unknown,
86
+ DATA_PARTS extends import("ai").UIDataTypes = import("ai").UIDataTypes,
87
+ TOOLS extends import("ai").UITools = import("ai").UITools,
88
+ >(
89
+ uiMessage: UIMessage<METADATA, DATA_PARTS, TOOLS>,
138
90
  newParts: UIMessageChunk[],
139
91
  prev: IncrementalStreamState,
140
- ): { message: UIMessage; streamState: IncrementalStreamState } {
92
+ ): {
93
+ message: UIMessage<METADATA, DATA_PARTS, TOOLS>;
94
+ streamState: IncrementalStreamState;
95
+ } {
141
96
  const message: UIMessage = structuredClone(uiMessage);
142
97
  const activeText: Record<string, number> = { ...prev.activeText };
143
98
  const activeReasoning: Record<string, number> = { ...prev.activeReasoning };
@@ -174,7 +129,9 @@ export function applyUIMessageChunksIncremental(
174
129
  type: "text",
175
130
  text: "",
176
131
  state: "streaming",
177
- providerMetadata: part.providerMetadata,
132
+ ...(part.providerMetadata
133
+ ? { providerMetadata: part.providerMetadata }
134
+ : {}),
178
135
  };
179
136
  message.parts.push(newPart);
180
137
  activeText[part.id] = message.parts.length - 1;
@@ -185,10 +142,11 @@ export function applyUIMessageChunksIncremental(
185
142
  if (idx !== undefined) {
186
143
  const textPart = message.parts[idx] as TextUIPart;
187
144
  textPart.text += part.delta;
188
- textPart.providerMetadata = mergeProviderMetadata(
145
+ const providerMetadata = mergeProviderMetadata(
189
146
  textPart.providerMetadata,
190
147
  part.providerMetadata,
191
148
  );
149
+ if (providerMetadata) textPart.providerMetadata = providerMetadata;
192
150
  }
193
151
  break;
194
152
  }
@@ -197,10 +155,11 @@ export function applyUIMessageChunksIncremental(
197
155
  if (idx !== undefined) {
198
156
  const textPart = message.parts[idx] as TextUIPart;
199
157
  textPart.state = "done";
200
- textPart.providerMetadata = mergeProviderMetadata(
158
+ const providerMetadata = mergeProviderMetadata(
201
159
  textPart.providerMetadata,
202
160
  part.providerMetadata,
203
161
  );
162
+ if (providerMetadata) textPart.providerMetadata = providerMetadata;
204
163
  delete activeText[part.id];
205
164
  }
206
165
  break;
@@ -210,7 +169,9 @@ export function applyUIMessageChunksIncremental(
210
169
  type: "reasoning",
211
170
  text: "",
212
171
  state: "streaming",
213
- providerMetadata: part.providerMetadata,
172
+ ...(part.providerMetadata
173
+ ? { providerMetadata: part.providerMetadata }
174
+ : {}),
214
175
  };
215
176
  message.parts.push(newPart);
216
177
  activeReasoning[part.id] = message.parts.length - 1;
@@ -221,10 +182,12 @@ export function applyUIMessageChunksIncremental(
221
182
  if (idx !== undefined) {
222
183
  const reasoningPart = message.parts[idx] as ReasoningUIPart;
223
184
  reasoningPart.text += part.delta;
224
- reasoningPart.providerMetadata = mergeProviderMetadata(
185
+ const providerMetadata = mergeProviderMetadata(
225
186
  reasoningPart.providerMetadata,
226
187
  part.providerMetadata,
227
188
  );
189
+ if (providerMetadata)
190
+ reasoningPart.providerMetadata = providerMetadata;
228
191
  }
229
192
  break;
230
193
  }
@@ -233,14 +196,35 @@ export function applyUIMessageChunksIncremental(
233
196
  if (idx !== undefined) {
234
197
  const reasoningPart = message.parts[idx] as ReasoningUIPart;
235
198
  reasoningPart.state = "done";
236
- reasoningPart.providerMetadata = mergeProviderMetadata(
199
+ const providerMetadata = mergeProviderMetadata(
237
200
  reasoningPart.providerMetadata,
238
201
  part.providerMetadata,
239
202
  );
203
+ if (providerMetadata)
204
+ reasoningPart.providerMetadata = providerMetadata;
240
205
  delete activeReasoning[part.id];
241
206
  }
242
207
  break;
243
208
  }
209
+ case "reasoning-file":
210
+ message.parts.push({
211
+ type: "reasoning-file",
212
+ url: part.url,
213
+ mediaType: part.mediaType,
214
+ ...(part.providerMetadata
215
+ ? { providerMetadata: part.providerMetadata }
216
+ : {}),
217
+ } satisfies ReasoningFileUIPart);
218
+ break;
219
+ case "custom":
220
+ message.parts.push({
221
+ type: "custom",
222
+ kind: part.kind,
223
+ ...(part.providerMetadata
224
+ ? { providerMetadata: part.providerMetadata }
225
+ : {}),
226
+ } satisfies CustomContentUIPart);
227
+ break;
244
228
  case "tool-input-start": {
245
229
  const newToolPart: ToolUIPart | DynamicToolUIPart = part.dynamic
246
230
  ? ({
@@ -249,13 +233,32 @@ export function applyUIMessageChunksIncremental(
249
233
  toolName: part.toolName,
250
234
  state: "input-streaming",
251
235
  input: undefined,
236
+ ...(part.providerExecuted === undefined
237
+ ? {}
238
+ : { providerExecuted: part.providerExecuted }),
239
+ ...(part.title === undefined ? {} : { title: part.title }),
240
+ ...(part.toolMetadata === undefined
241
+ ? {}
242
+ : { toolMetadata: part.toolMetadata }),
243
+ ...(part.providerMetadata
244
+ ? { callProviderMetadata: part.providerMetadata }
245
+ : {}),
252
246
  } satisfies DynamicToolUIPart)
253
247
  : ({
254
248
  type: `tool-${part.toolName}`,
255
249
  toolCallId: part.toolCallId,
256
250
  state: "input-streaming",
257
251
  input: undefined,
258
- providerExecuted: part.providerExecuted,
252
+ ...(part.providerExecuted === undefined
253
+ ? {}
254
+ : { providerExecuted: part.providerExecuted }),
255
+ ...(part.title === undefined ? {} : { title: part.title }),
256
+ ...(part.toolMetadata === undefined
257
+ ? {}
258
+ : { toolMetadata: part.toolMetadata }),
259
+ ...(part.providerMetadata
260
+ ? { callProviderMetadata: part.providerMetadata }
261
+ : {}),
259
262
  } satisfies ToolUIPart);
260
263
  message.parts.push(newToolPart);
261
264
  toolIndexById.set(part.toolCallId, message.parts.length - 1);
@@ -275,18 +278,42 @@ export function applyUIMessageChunksIncremental(
275
278
  break;
276
279
  }
277
280
  case "tool-input-available": {
278
- const toolPart = toolPartAt(part.toolCallId);
279
- if (toolPart) {
280
- transitionToolPart(toolPart, {
281
- state: "input-available",
282
- input: part.input,
283
- callProviderMetadata: mergeProviderMetadata(
284
- (toolPart as { callProviderMetadata?: ProviderMetadata })
285
- .callProviderMetadata,
286
- part.providerMetadata,
287
- ),
288
- });
281
+ let toolPart = toolPartAt(part.toolCallId);
282
+ if (!toolPart) {
283
+ toolPart = part.dynamic
284
+ ? ({
285
+ type: "dynamic-tool",
286
+ toolCallId: part.toolCallId,
287
+ toolName: part.toolName,
288
+ state: "input-available",
289
+ input: part.input,
290
+ } satisfies DynamicToolUIPart)
291
+ : ({
292
+ type: `tool-${part.toolName}`,
293
+ toolCallId: part.toolCallId,
294
+ state: "input-available",
295
+ input: part.input,
296
+ } satisfies ToolUIPart);
297
+ message.parts.push(toolPart);
298
+ toolIndexById.set(part.toolCallId, message.parts.length - 1);
289
299
  }
300
+ const callProviderMetadata = mergeProviderMetadata(
301
+ (toolPart as { callProviderMetadata?: ProviderMetadata })
302
+ .callProviderMetadata,
303
+ part.providerMetadata,
304
+ );
305
+ transitionToolPart(toolPart, {
306
+ state: "input-available",
307
+ input: part.input,
308
+ ...(part.providerExecuted === undefined
309
+ ? {}
310
+ : { providerExecuted: part.providerExecuted }),
311
+ ...(part.title === undefined ? {} : { title: part.title }),
312
+ ...(part.toolMetadata === undefined
313
+ ? {}
314
+ : { toolMetadata: part.toolMetadata }),
315
+ ...(callProviderMetadata ? { callProviderMetadata } : {}),
316
+ });
290
317
  touchedTools.delete(part.toolCallId);
291
318
  // The raw JSON buffer is no longer needed; drop it so it doesn't get
292
319
  // carried through every later batch on the hot path.
@@ -294,22 +321,53 @@ export function applyUIMessageChunksIncremental(
294
321
  break;
295
322
  }
296
323
  case "tool-input-error": {
297
- const toolPart = toolPartAt(part.toolCallId);
298
- if (toolPart) {
299
- transitionToolPart(toolPart, {
300
- state: "output-error",
301
- errorText: part.errorText,
302
- providerExecuted: part.providerExecuted,
303
- ...(toolPart.type === "dynamic-tool"
304
- ? { input: part.input }
305
- : { input: undefined, rawInput: part.input }),
306
- callProviderMetadata: mergeProviderMetadata(
307
- (toolPart as { callProviderMetadata?: ProviderMetadata })
308
- .callProviderMetadata,
309
- part.providerMetadata,
310
- ),
311
- });
324
+ let toolPart = toolPartAt(part.toolCallId);
325
+ if (!toolPart) {
326
+ toolPart = part.dynamic
327
+ ? ({
328
+ type: "dynamic-tool",
329
+ toolCallId: part.toolCallId,
330
+ toolName: part.toolName,
331
+ state: "output-error",
332
+ input: part.input,
333
+ errorText: part.errorText,
334
+ } satisfies DynamicToolUIPart)
335
+ : ({
336
+ type: `tool-${part.toolName}`,
337
+ toolCallId: part.toolCallId,
338
+ state: "output-error",
339
+ input: undefined,
340
+ rawInput: part.input,
341
+ errorText: part.errorText,
342
+ } satisfies ToolUIPart);
343
+ message.parts.push(toolPart);
344
+ toolIndexById.set(part.toolCallId, message.parts.length - 1);
312
345
  }
346
+ const resultProviderMetadata = mergeProviderMetadata(
347
+ (toolPart as { resultProviderMetadata?: ProviderMetadata })
348
+ .resultProviderMetadata,
349
+ part.providerMetadata,
350
+ );
351
+ transitionToolPart(toolPart, {
352
+ state: "output-error",
353
+ errorText: part.errorText,
354
+ ...(part.providerExecuted === undefined
355
+ ? {}
356
+ : { providerExecuted: part.providerExecuted }),
357
+ ...(toolPart.type === "dynamic-tool"
358
+ ? {
359
+ input: part.input,
360
+ }
361
+ : {
362
+ input: undefined,
363
+ rawInput: part.input,
364
+ }),
365
+ ...(part.title === undefined ? {} : { title: part.title }),
366
+ ...(part.toolMetadata === undefined
367
+ ? {}
368
+ : { toolMetadata: part.toolMetadata }),
369
+ ...(resultProviderMetadata ? { resultProviderMetadata } : {}),
370
+ });
313
371
  touchedTools.delete(part.toolCallId);
314
372
  delete toolInputText[part.toolCallId];
315
373
  break;
@@ -321,7 +379,15 @@ export function applyUIMessageChunksIncremental(
321
379
  state: "output-available",
322
380
  output: part.output,
323
381
  preliminary: part.preliminary,
324
- providerExecuted: part.providerExecuted,
382
+ ...(part.providerExecuted === undefined
383
+ ? {}
384
+ : { providerExecuted: part.providerExecuted }),
385
+ ...(part.providerMetadata
386
+ ? { resultProviderMetadata: part.providerMetadata }
387
+ : {}),
388
+ ...(part.toolMetadata === undefined
389
+ ? {}
390
+ : { toolMetadata: part.toolMetadata }),
325
391
  });
326
392
  }
327
393
  break;
@@ -332,8 +398,20 @@ export function applyUIMessageChunksIncremental(
332
398
  transitionToolPart(toolPart, {
333
399
  state: "output-error",
334
400
  errorText: part.errorText,
335
- providerExecuted: part.providerExecuted,
401
+ output: undefined,
402
+ ...(part.providerExecuted === undefined
403
+ ? {}
404
+ : { providerExecuted: part.providerExecuted }),
405
+ ...(part.providerMetadata
406
+ ? { resultProviderMetadata: part.providerMetadata }
407
+ : {}),
408
+ ...(part.toolMetadata === undefined
409
+ ? {}
410
+ : { toolMetadata: part.toolMetadata }),
336
411
  });
412
+ // The SDK clears a prior preliminary result when the final outcome
413
+ // is an error. `preliminary` is not part of the error-state type.
414
+ Object.assign(toolPart, { preliminary: undefined });
337
415
  }
338
416
  break;
339
417
  }
@@ -349,7 +427,43 @@ export function applyUIMessageChunksIncremental(
349
427
  if (toolPart) {
350
428
  transitionToolPart(toolPart, {
351
429
  state: "approval-requested",
352
- approval: { id: part.approvalId },
430
+ approval: {
431
+ id: part.approvalId,
432
+ ...(part.isAutomatic === undefined
433
+ ? {}
434
+ : { isAutomatic: part.isAutomatic }),
435
+ ...(part.signature === undefined
436
+ ? {}
437
+ : { signature: part.signature }),
438
+ },
439
+ });
440
+ }
441
+ break;
442
+ }
443
+ case "tool-approval-response": {
444
+ const toolPart = message.parts.find(
445
+ (candidate) =>
446
+ "approval" in candidate &&
447
+ (candidate as ToolPart).approval?.id === part.approvalId,
448
+ ) as ToolPart | undefined;
449
+ if (toolPart) {
450
+ const callProviderMetadata = mergeProviderMetadata(
451
+ (toolPart as { callProviderMetadata?: ProviderMetadata })
452
+ .callProviderMetadata,
453
+ part.providerMetadata,
454
+ );
455
+ transitionToolPart(toolPart, {
456
+ state: "approval-responded",
457
+ approval: {
458
+ ...toolPart.approval,
459
+ id: part.approvalId,
460
+ approved: part.approved,
461
+ ...(part.reason === undefined ? {} : { reason: part.reason }),
462
+ },
463
+ ...(part.providerExecuted === undefined
464
+ ? {}
465
+ : { providerExecuted: part.providerExecuted }),
466
+ ...(callProviderMetadata ? { callProviderMetadata } : {}),
353
467
  });
354
468
  }
355
469
  break;
@@ -378,6 +492,7 @@ export function applyUIMessageChunksIncremental(
378
492
  type: "file",
379
493
  mediaType: part.mediaType,
380
494
  url: part.url,
495
+ providerMetadata: part.providerMetadata,
381
496
  });
382
497
  break;
383
498
  case "start-step":
@@ -414,6 +529,9 @@ export function applyUIMessageChunksIncremental(
414
529
  (p as { id?: string }).id === dataPart.id,
415
530
  )
416
531
  : -1;
532
+ if (dataPart.transient) {
533
+ break;
534
+ }
417
535
  if (existingIdx >= 0) {
418
536
  (message.parts[existingIdx] as { data?: unknown }).data =
419
537
  dataPart.data;
@@ -445,7 +563,7 @@ export function applyUIMessageChunksIncremental(
445
563
 
446
564
  message.text = joinText(message.parts);
447
565
  return {
448
- message,
566
+ message: message as UIMessage<METADATA, DATA_PARTS, TOOLS>,
449
567
  streamState: { activeText, activeReasoning, toolInputText },
450
568
  };
451
569
  }
@@ -466,10 +584,11 @@ export async function deriveUIMessagesFromDeltas(
466
584
  allDeltas.filter((d) => d.streamId === streamMessage.streamId),
467
585
  0,
468
586
  );
469
- const uiMessage = await updateFromUIMessageChunks(
587
+ const uiMessage = applyUIMessageChunksIncremental(
470
588
  blankUIMessage(streamMessage, threadId),
471
589
  parts,
472
- );
590
+ emptyIncrementalStreamState(),
591
+ ).message;
473
592
  messages.push(uiMessage);
474
593
  }
475
594
  return sorted(messages);
@@ -510,21 +629,5 @@ function mergeProviderMetadata(
510
629
  existing: ProviderMetadata | undefined,
511
630
  part: ProviderMetadata | undefined,
512
631
  ): ProviderMetadata | undefined {
513
- if (!existing && !part) {
514
- return undefined;
515
- }
516
- if (!existing) {
517
- return part;
518
- }
519
- if (!part) {
520
- return existing;
521
- }
522
- const merged: ProviderMetadata = existing;
523
- for (const [provider, metadata] of Object.entries(part)) {
524
- merged[provider] = {
525
- ...merged[provider],
526
- ...metadata,
527
- };
528
- }
529
- return merged;
632
+ return part ?? existing;
530
633
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import type { MessageDoc } from "./client/index.js";
2
+ import type { MessageDoc } from "../validators.js";
3
3
  import type { UIMessage } from "./UIMessages.js";
4
4
  import { fromUIMessages, toUIMessages } from "./UIMessages.js";
5
5
 
@@ -310,6 +310,7 @@ describe("fromUIMessages round-trip tests", () => {
310
310
  id: "source2",
311
311
  mediaType: "application/pdf",
312
312
  title: "Document Source",
313
+ filename: "document.pdf",
313
314
  },
314
315
  ],
315
316
  }),
@@ -327,6 +328,13 @@ describe("fromUIMessages round-trip tests", () => {
327
328
  (part) => part.type === "source-url" || part.type === "source-document",
328
329
  );
329
330
  expect(sourceParts).toHaveLength(2);
331
+ expect(sourceParts).toContainEqual({
332
+ type: "source-document",
333
+ sourceId: "source2",
334
+ mediaType: "application/pdf",
335
+ title: "Document Source",
336
+ filename: "document.pdf",
337
+ });
330
338
 
331
339
  expect(backToMessageDocs).toHaveLength(1);
332
340
  expect(backToMessageDocs[0].sources).toHaveLength(2);
@@ -337,6 +345,14 @@ describe("fromUIMessages round-trip tests", () => {
337
345
  url: "https://example.com",
338
346
  title: "Example Source",
339
347
  });
348
+ expect(backToMessageDocs[0].sources![1]).toMatchObject({
349
+ type: "source",
350
+ sourceType: "document",
351
+ id: "source2",
352
+ mediaType: "application/pdf",
353
+ title: "Document Source",
354
+ filename: "document.pdf",
355
+ });
340
356
  });
341
357
 
342
358
  it("preserves metadata when provided", async () => {