@convex-dev/agent 0.6.3 → 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 (246) 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/apiKeys.js +5 -5
  11. package/dist/component/apiKeys.js.map +1 -1
  12. package/dist/component/files.d.ts.map +1 -1
  13. package/dist/component/files.js +13 -11
  14. package/dist/component/files.js.map +1 -1
  15. package/dist/component/messages.d.ts +1252 -201
  16. package/dist/component/messages.d.ts.map +1 -1
  17. package/dist/component/messages.js +83 -47
  18. package/dist/component/messages.js.map +1 -1
  19. package/dist/component/schema.d.ts +2593 -332
  20. package/dist/component/schema.d.ts.map +1 -1
  21. package/dist/component/streams.d.ts +7 -1
  22. package/dist/component/streams.d.ts.map +1 -1
  23. package/dist/component/streams.js +46 -47
  24. package/dist/component/streams.js.map +1 -1
  25. package/dist/component/threads.d.ts.map +1 -1
  26. package/dist/component/threads.js +8 -9
  27. package/dist/component/threads.js.map +1 -1
  28. package/dist/component/users.js +2 -2
  29. package/dist/component/users.js.map +1 -1
  30. package/dist/component/vector/index.d.ts.map +1 -1
  31. package/dist/component/vector/index.js +14 -8
  32. package/dist/component/vector/index.js.map +1 -1
  33. package/dist/react/index.d.ts +5 -5
  34. package/dist/react/index.d.ts.map +1 -1
  35. package/dist/react/index.js +5 -5
  36. package/dist/react/index.js.map +1 -1
  37. package/dist/shared.d.ts +19 -7
  38. package/dist/shared.d.ts.map +1 -1
  39. package/dist/shared.js +4 -4
  40. package/dist/shared.js.map +1 -1
  41. package/dist/streaming/materializePersistedUIMessageChunks.d.ts +16 -0
  42. package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -0
  43. package/dist/streaming/materializePersistedUIMessageChunks.js +302 -0
  44. package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -0
  45. package/dist/streaming/persistedUIMessageChunks.d.ts +106 -0
  46. package/dist/streaming/persistedUIMessageChunks.d.ts.map +1 -0
  47. package/dist/streaming/persistedUIMessageChunks.js +475 -0
  48. package/dist/streaming/persistedUIMessageChunks.js.map +1 -0
  49. package/dist/validators.d.ts +10026 -1055
  50. package/dist/validators.d.ts.map +1 -1
  51. package/dist/validators.js +71 -9
  52. package/dist/validators.js.map +1 -1
  53. package/dist/{UIMessages.d.ts → vercel/UIMessages.d.ts} +1 -1
  54. package/dist/vercel/UIMessages.d.ts.map +1 -0
  55. package/dist/{UIMessages.js → vercel/UIMessages.js} +63 -1
  56. package/dist/vercel/UIMessages.js.map +1 -0
  57. package/dist/{client → vercel/client}/createTool.d.ts +23 -16
  58. package/dist/vercel/client/createTool.d.ts.map +1 -0
  59. package/dist/{client → vercel/client}/createTool.js +2 -0
  60. package/dist/vercel/client/createTool.js.map +1 -0
  61. package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts +593 -134
  62. package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts.map +1 -1
  63. package/dist/{client → vercel/client}/definePlaygroundAPI.js +13 -9
  64. package/dist/vercel/client/definePlaygroundAPI.js.map +1 -0
  65. package/dist/{client → vercel/client}/files.d.ts +2 -2
  66. package/dist/vercel/client/files.d.ts.map +1 -0
  67. package/dist/vercel/client/files.js.map +1 -0
  68. package/dist/vercel/client/messages.d.ts +542 -0
  69. package/dist/vercel/client/messages.d.ts.map +1 -0
  70. package/dist/vercel/client/messages.js +66 -0
  71. package/dist/vercel/client/messages.js.map +1 -0
  72. package/dist/{client → vercel/client}/mockModel.d.ts +17 -17
  73. package/dist/vercel/client/mockModel.d.ts.map +1 -0
  74. package/dist/{client → vercel/client}/mockModel.js +12 -13
  75. package/dist/vercel/client/mockModel.js.map +1 -0
  76. package/dist/{client → vercel/client}/saveInputMessages.d.ts +2 -2
  77. package/dist/vercel/client/saveInputMessages.d.ts.map +1 -0
  78. package/dist/vercel/client/saveInputMessages.js.map +1 -0
  79. package/dist/{client → vercel/client}/search.d.ts +119 -22
  80. package/dist/vercel/client/search.d.ts.map +1 -0
  81. package/dist/{client → vercel/client}/search.js +2 -2
  82. package/dist/vercel/client/search.js.map +1 -0
  83. package/dist/{client → vercel/client}/start.d.ts +26 -11
  84. package/dist/vercel/client/start.d.ts.map +1 -0
  85. package/dist/{client → vercel/client}/start.js +64 -22
  86. package/dist/vercel/client/start.js.map +1 -0
  87. package/dist/{client → vercel/client}/streamText.d.ts +13 -12
  88. package/dist/vercel/client/streamText.d.ts.map +1 -0
  89. package/dist/{client → vercel/client}/streamText.js +43 -9
  90. package/dist/vercel/client/streamText.js.map +1 -0
  91. package/dist/{client → vercel/client}/streaming.d.ts +1410 -183
  92. package/dist/vercel/client/streaming.d.ts.map +1 -0
  93. package/dist/{client → vercel/client}/streaming.js +120 -52
  94. package/dist/vercel/client/streaming.js.map +1 -0
  95. package/dist/vercel/client/types.d.ts +456 -0
  96. package/dist/vercel/client/types.d.ts.map +1 -0
  97. package/dist/vercel/client/types.js +3 -0
  98. package/dist/vercel/client/types.js.map +1 -0
  99. package/dist/{client → vercel/client}/utils.d.ts +3 -2
  100. package/dist/vercel/client/utils.d.ts.map +1 -0
  101. package/dist/{client → vercel/client}/utils.js +2 -2
  102. package/dist/vercel/client/utils.js.map +1 -0
  103. package/dist/vercel/deltas.d.ts +31 -0
  104. package/dist/vercel/deltas.d.ts.map +1 -0
  105. package/dist/vercel/deltas.js +525 -0
  106. package/dist/vercel/deltas.js.map +1 -0
  107. package/dist/{client → vercel}/index.d.ts +484 -97
  108. package/dist/vercel/index.d.ts.map +1 -0
  109. package/dist/{client → vercel}/index.js +65 -38
  110. package/dist/vercel/index.js.map +1 -0
  111. package/dist/{mapping.d.ts → vercel/mapping.d.ts} +5 -13
  112. package/dist/vercel/mapping.d.ts.map +1 -0
  113. package/dist/{mapping.js → vercel/mapping.js} +411 -112
  114. package/dist/vercel/mapping.js.map +1 -0
  115. package/dist/{react → vercel/react}/optimisticallySendMessage.d.ts +2 -2
  116. package/dist/vercel/react/optimisticallySendMessage.d.ts.map +1 -0
  117. package/dist/vercel/react/optimisticallySendMessage.js.map +1 -0
  118. package/dist/{react → vercel/react}/types.d.ts +1 -1
  119. package/dist/vercel/react/types.d.ts.map +1 -0
  120. package/dist/{react → vercel/react}/types.js.map +1 -1
  121. package/dist/{react → vercel/react}/useDeltaStreams.d.ts +1 -1
  122. package/dist/vercel/react/useDeltaStreams.d.ts.map +1 -0
  123. package/dist/{react → vercel/react}/useDeltaStreams.js +1 -1
  124. package/dist/vercel/react/useDeltaStreams.js.map +1 -0
  125. package/dist/{react → vercel/react}/useStreamingUIMessages.d.ts +1 -1
  126. package/dist/vercel/react/useStreamingUIMessages.d.ts.map +1 -0
  127. package/dist/vercel/react/useStreamingUIMessages.js +112 -0
  128. package/dist/vercel/react/useStreamingUIMessages.js.map +1 -0
  129. package/dist/{react → vercel/react}/useThreadMessages.d.ts +1 -1
  130. package/dist/vercel/react/useThreadMessages.d.ts.map +1 -0
  131. package/dist/{react → vercel/react}/useThreadMessages.js +1 -1
  132. package/dist/vercel/react/useThreadMessages.js.map +1 -0
  133. package/dist/{react → vercel/react}/useUIMessages.d.ts +2 -1
  134. package/dist/vercel/react/useUIMessages.d.ts.map +1 -0
  135. package/dist/{react → vercel/react}/useUIMessages.js +8 -4
  136. package/dist/vercel/react/useUIMessages.js.map +1 -0
  137. package/package.json +30 -22
  138. package/src/client/messages.ts +5 -107
  139. package/src/client/types.ts +0 -621
  140. package/src/component/_generated/component.ts +1089 -48
  141. package/src/component/apiKeys.ts +5 -5
  142. package/src/component/files.test.ts +1 -1
  143. package/src/component/files.ts +14 -12
  144. package/src/component/messages.test.ts +273 -1
  145. package/src/component/messages.ts +99 -57
  146. package/src/component/streams.ts +80 -64
  147. package/src/component/threads.ts +12 -9
  148. package/src/component/users.ts +2 -2
  149. package/src/component/vector/index.ts +14 -7
  150. package/src/eslintConfig.test.ts +44 -0
  151. package/src/react/index.ts +8 -5
  152. package/src/shared.ts +26 -36
  153. package/src/streaming/materializePersistedUIMessageChunks.test.ts +513 -0
  154. package/src/streaming/materializePersistedUIMessageChunks.ts +398 -0
  155. package/src/streaming/persistedUIMessageChunks.test.ts +205 -0
  156. package/src/streaming/persistedUIMessageChunks.ts +658 -0
  157. package/src/validators.test.ts +4 -1
  158. package/src/validators.ts +89 -8
  159. package/src/{UIMessages.combineUIMessages.test.ts → vercel/UIMessages.combineUIMessages.test.ts} +4 -1
  160. package/src/{UIMessages.ts → vercel/UIMessages.ts} +96 -5
  161. package/src/{client → vercel/client}/approval.test.ts +48 -19
  162. package/src/{client → vercel/client}/createTool.ts +63 -23
  163. package/src/{client → vercel/client}/definePlaygroundAPI.ts +44 -29
  164. package/src/{client → vercel/client}/files.ts +2 -2
  165. package/src/{client → vercel/client}/index.test.ts +162 -3
  166. package/src/vercel/client/messages.ts +182 -0
  167. package/src/{client → vercel/client}/mockModel.ts +38 -39
  168. package/src/{client → vercel/client}/saveInputMessages.test.ts +1 -1
  169. package/src/{client → vercel/client}/saveInputMessages.ts +7 -4
  170. package/src/{client → vercel/client}/search.test.ts +3 -3
  171. package/src/{client → vercel/client}/search.ts +4 -4
  172. package/src/{client → vercel/client}/setup.test.ts +6 -3
  173. package/src/vercel/client/start.test.ts +14 -0
  174. package/src/{client → vercel/client}/start.ts +147 -36
  175. package/src/vercel/client/streamText.test.ts +223 -0
  176. package/src/{client → vercel/client}/streamText.ts +102 -31
  177. package/src/{client → vercel/client}/streaming.integration.test.ts +40 -115
  178. package/src/{client → vercel/client}/streaming.test.ts +178 -4
  179. package/src/{client → vercel/client}/streaming.ts +114 -52
  180. package/src/vercel/client/types.test.ts +13 -0
  181. package/src/vercel/client/types.ts +679 -0
  182. package/src/{client → vercel/client}/utils.ts +13 -7
  183. package/src/vercel/deltas.test.ts +898 -0
  184. package/src/vercel/deltas.ts +633 -0
  185. package/src/{fromUIMessages.test.ts → vercel/fromUIMessages.test.ts} +17 -1
  186. package/src/{client → vercel}/index.ts +169 -62
  187. package/src/vercel/mapping.test.ts +965 -0
  188. package/src/{mapping.ts → vercel/mapping.ts} +501 -131
  189. package/src/{react → vercel/react}/optimisticallySendMessage.ts +2 -2
  190. package/src/{react → vercel/react}/types.ts +1 -1
  191. package/src/{react → vercel/react}/useDeltaStreams.ts +6 -2
  192. package/src/{react → vercel/react}/useStreamingUIMessages.ts +67 -35
  193. package/src/{react → vercel/react}/useThreadMessages.ts +2 -2
  194. package/src/{react → vercel/react}/useUIMessages.test.ts +80 -1
  195. package/src/{react → vercel/react}/useUIMessages.ts +13 -5
  196. package/src/{toUIMessages.test.ts → vercel/toUIMessages.test.ts} +49 -2
  197. package/dist/UIMessages.d.ts.map +0 -1
  198. package/dist/UIMessages.js.map +0 -1
  199. package/dist/client/createTool.d.ts.map +0 -1
  200. package/dist/client/createTool.js.map +0 -1
  201. package/dist/client/definePlaygroundAPI.js.map +0 -1
  202. package/dist/client/files.d.ts.map +0 -1
  203. package/dist/client/files.js.map +0 -1
  204. package/dist/client/index.d.ts.map +0 -1
  205. package/dist/client/index.js.map +0 -1
  206. package/dist/client/mockModel.d.ts.map +0 -1
  207. package/dist/client/mockModel.js.map +0 -1
  208. package/dist/client/saveInputMessages.d.ts.map +0 -1
  209. package/dist/client/saveInputMessages.js.map +0 -1
  210. package/dist/client/search.d.ts.map +0 -1
  211. package/dist/client/search.js.map +0 -1
  212. package/dist/client/start.d.ts.map +0 -1
  213. package/dist/client/start.js.map +0 -1
  214. package/dist/client/streamText.d.ts.map +0 -1
  215. package/dist/client/streamText.js.map +0 -1
  216. package/dist/client/streaming.d.ts.map +0 -1
  217. package/dist/client/streaming.js.map +0 -1
  218. package/dist/client/utils.d.ts.map +0 -1
  219. package/dist/client/utils.js.map +0 -1
  220. package/dist/deltas.d.ts +0 -43
  221. package/dist/deltas.d.ts.map +0 -1
  222. package/dist/deltas.js +0 -446
  223. package/dist/deltas.js.map +0 -1
  224. package/dist/mapping.d.ts.map +0 -1
  225. package/dist/mapping.js.map +0 -1
  226. package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
  227. package/dist/react/optimisticallySendMessage.js.map +0 -1
  228. package/dist/react/types.d.ts.map +0 -1
  229. package/dist/react/useDeltaStreams.d.ts.map +0 -1
  230. package/dist/react/useDeltaStreams.js.map +0 -1
  231. package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
  232. package/dist/react/useStreamingUIMessages.js +0 -92
  233. package/dist/react/useStreamingUIMessages.js.map +0 -1
  234. package/dist/react/useThreadMessages.d.ts.map +0 -1
  235. package/dist/react/useThreadMessages.js.map +0 -1
  236. package/dist/react/useUIMessages.d.ts.map +0 -1
  237. package/dist/react/useUIMessages.js.map +0 -1
  238. package/src/client/streamText.test.ts +0 -102
  239. package/src/deltas.test.ts +0 -626
  240. package/src/deltas.ts +0 -569
  241. package/src/mapping.test.ts +0 -402
  242. /package/dist/{client → vercel/client}/files.js +0 -0
  243. /package/dist/{client → vercel/client}/saveInputMessages.js +0 -0
  244. /package/dist/{react → vercel/react}/optimisticallySendMessage.js +0 -0
  245. /package/dist/{react → vercel/react}/types.js +0 -0
  246. /package/src/{UIMessages.test.ts → vercel/UIMessages.test.ts} +0 -0
package/src/deltas.ts DELETED
@@ -1,569 +0,0 @@
1
- import {
2
- readUIMessageStream,
3
- type DynamicToolUIPart,
4
- type ProviderMetadata,
5
- type ReasoningUIPart,
6
- type TextStreamPart,
7
- type TextUIPart,
8
- type ToolSet,
9
- type ToolUIPart,
10
- type UIMessageChunk,
11
- } from "ai";
12
- import { assert, pick } from "convex-helpers";
13
- import { type UIMessage } from "./UIMessages.js";
14
- import { joinText, sorted } from "./shared.js";
15
- import {
16
- type MessageStatus,
17
- type StreamDelta,
18
- type StreamMessage,
19
- } from "./validators.js";
20
- import { getErrorMessage } from "@ai-sdk/provider-utils";
21
-
22
- export function blankUIMessage<METADATA = unknown>(
23
- streamMessage: StreamMessage & { metadata?: METADATA },
24
- threadId: string,
25
- ): UIMessage<METADATA> {
26
- return {
27
- id: `stream:${streamMessage.streamId}`,
28
- key: `${threadId}-${streamMessage.order}-${streamMessage.stepOrder}`,
29
- order: streamMessage.order,
30
- stepOrder: streamMessage.stepOrder,
31
- status: statusFromStreamStatus(streamMessage.status),
32
- agentName: streamMessage.agentName,
33
- text: "",
34
- _creationTime: Date.now(),
35
- role: "assistant",
36
- parts: [],
37
- ...(streamMessage.metadata ? { metadata: streamMessage.metadata } : {}),
38
- };
39
- }
40
-
41
- export function statusFromStreamStatus(
42
- status: StreamMessage["status"],
43
- ): MessageStatus | "streaming" {
44
- switch (status) {
45
- case "streaming":
46
- return "streaming";
47
- case "finished":
48
- return "success";
49
- case "aborted":
50
- return "failed";
51
- default:
52
- return "pending";
53
- }
54
- }
55
-
56
- export async function updateFromUIMessageChunks(
57
- uiMessage: UIMessage,
58
- parts: UIMessageChunk[],
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
- // Tool invocation errors can be safely ignored when streaming continuation
76
- // after tool approval - the stored messages have the complete tool context
77
- if (errorMessage.toLowerCase().includes("no tool invocation found")) {
78
- // Silently suppress - this is expected after tool approval when the
79
- // continuation stream has tool-result without the original tool-call
80
- suppressError = true;
81
- return;
82
- }
83
- failed = true;
84
- console.error("Error in stream", e);
85
- },
86
- terminateOnError: true,
87
- });
88
- let message = uiMessage;
89
- try {
90
- for await (const messagePart of messageStream) {
91
- assert(
92
- messagePart.id === message.id,
93
- `Expecting to only make one UIMessage in a stream`,
94
- );
95
- message = messagePart;
96
- }
97
- } catch (e) {
98
- // If we've already handled this error in onError and marked it as suppressed,
99
- // don't rethrow - the stored messages provide the fallback
100
- if (!suppressError) {
101
- throw e;
102
- }
103
- }
104
- if (failed) {
105
- message.status = "failed";
106
- }
107
- message.text = joinText(message.parts);
108
- return message;
109
- }
110
-
111
- export async function deriveUIMessagesFromDeltas(
112
- threadId: string,
113
- streamMessages: StreamMessage[],
114
- allDeltas: StreamDelta[],
115
- ): Promise<UIMessage[]> {
116
- const messages: UIMessage[] = [];
117
- for (const streamMessage of streamMessages) {
118
- if (streamMessage.format === "UIMessageChunk") {
119
- const { parts } = getParts<UIMessageChunk>(
120
- allDeltas.filter((d) => d.streamId === streamMessage.streamId),
121
- 0,
122
- );
123
- const uiMessage = await updateFromUIMessageChunks(
124
- blankUIMessage(streamMessage, threadId),
125
- parts,
126
- );
127
- messages.push(uiMessage);
128
- } else {
129
- const [uiMessages] = deriveUIMessagesFromTextStreamParts(
130
- threadId,
131
- [streamMessage],
132
- [],
133
- allDeltas,
134
- );
135
- messages.push(...uiMessages);
136
- }
137
- }
138
- return sorted(messages);
139
- }
140
-
141
- /**
142
- *
143
- */
144
-
145
- export function deriveUIMessagesFromTextStreamParts(
146
- threadId: string,
147
- streamMessages: StreamMessage[],
148
- existingStreams: Array<{
149
- streamId: string;
150
- cursor: number;
151
- message: UIMessage;
152
- }>,
153
- allDeltas: StreamDelta[],
154
- ): [
155
- UIMessage[],
156
- Array<{ streamId: string; cursor: number; message: UIMessage }>,
157
- boolean,
158
- ] {
159
- const newStreams: Array<{
160
- streamId: string;
161
- cursor: number;
162
- message: UIMessage;
163
- }> = [];
164
- // Seed the existing chunks
165
- let changed = false;
166
- for (const streamMessage of streamMessages) {
167
- const deltas = allDeltas.filter(
168
- (d) => d.streamId === streamMessage.streamId,
169
- );
170
- const existing = existingStreams.find(
171
- (s) => s.streamId === streamMessage.streamId,
172
- );
173
- const [newStream, messageChanged] = updateFromTextStreamParts(
174
- threadId,
175
- streamMessage,
176
- existing,
177
- deltas,
178
- );
179
- newStreams.push(newStream);
180
- if (messageChanged) changed = true;
181
- }
182
- for (const { streamId } of existingStreams) {
183
- if (!newStreams.find((s) => s.streamId === streamId)) {
184
- // There's a stream that's no longer active.
185
- changed = true;
186
- }
187
- }
188
- const messages = sorted(newStreams.map((s) => s.message));
189
- return [messages, newStreams, changed];
190
- }
191
-
192
- export function getParts<T extends StreamDelta["parts"][number]>(
193
- deltas: StreamDelta[],
194
- fromCursor?: number,
195
- ): { parts: T[]; cursor: number } {
196
- const parts: T[] = [];
197
- let cursor = fromCursor ?? 0;
198
- for (const delta of deltas.sort((a, b) => a.start - b.start)) {
199
- if (delta.parts.length === 0) {
200
- console.debug(`Got delta with no parts: ${JSON.stringify(delta)}`);
201
- continue;
202
- }
203
- if (cursor !== delta.start) {
204
- if (cursor >= delta.end) {
205
- continue;
206
- } else if (cursor < delta.start) {
207
- console.warn(
208
- `Got delta for stream ${delta.streamId} that has a gap ${cursor} -> ${delta.start}`,
209
- );
210
- break;
211
- } else {
212
- throw new Error(
213
- `Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`,
214
- );
215
- }
216
- }
217
- parts.push(...delta.parts);
218
- cursor = delta.end;
219
- }
220
- return { parts, cursor };
221
- }
222
-
223
- /**
224
- * This is historically from when we would use the onChunk callback instead of
225
- * consuming the full UIMessageStream.
226
- */
227
-
228
- // exported for testing
229
- export function updateFromTextStreamParts(
230
- threadId: string,
231
- streamMessage: StreamMessage,
232
- existing:
233
- | { streamId: string; cursor: number; message: UIMessage }
234
- | undefined,
235
- deltas: StreamDelta[],
236
- ): [{ streamId: string; cursor: number; message: UIMessage }, boolean] {
237
- const { cursor, parts } = getParts<TextStreamPart<ToolSet>>(
238
- deltas,
239
- existing?.cursor,
240
- );
241
- const changed =
242
- parts.length > 0 ||
243
- (existing &&
244
- statusFromStreamStatus(streamMessage.status) !== existing.message.status);
245
- const existingMessage =
246
- existing?.message ?? blankUIMessage(streamMessage, threadId);
247
- if (!changed) {
248
- return [
249
- existing ?? {
250
- streamId: streamMessage.streamId,
251
- cursor,
252
- message: existingMessage,
253
- },
254
- false,
255
- ];
256
- }
257
-
258
- const message: UIMessage = structuredClone(existingMessage);
259
- message.status = statusFromStreamStatus(streamMessage.status);
260
-
261
- const textPartsById = new Map<string, TextUIPart>();
262
- const toolPartsById = new Map<string, ToolUIPart | DynamicToolUIPart>(
263
- message.parts
264
- .filter(
265
- (p): p is ToolUIPart | DynamicToolUIPart =>
266
- p.type.startsWith("tool-") || p.type === "dynamic-tool",
267
- )
268
- .map((p) => [p.toolCallId, p]),
269
- );
270
- const reasoningPartsById = new Map<string, ReasoningUIPart>();
271
-
272
- for (const part of parts) {
273
- switch (part.type) {
274
- case "text-start":
275
- case "text-delta": {
276
- if (!textPartsById.has(part.id)) {
277
- const lastPart = message.parts.at(-1);
278
- if (lastPart?.type === "text") {
279
- textPartsById.set(part.id, lastPart);
280
- } else {
281
- const newPart = {
282
- type: "text",
283
- text: "",
284
- providerMetadata: part.providerMetadata,
285
- } satisfies TextUIPart;
286
- textPartsById.set(part.id, newPart);
287
- message.parts.push(newPart);
288
- }
289
- }
290
- if (part.type === "text-delta") {
291
- const textPart = textPartsById.get(part.id)!;
292
- textPart.text += part.text;
293
- textPart.providerMetadata = mergeProviderMetadata(
294
- textPart.providerMetadata,
295
- part.providerMetadata,
296
- );
297
- }
298
- break;
299
- }
300
- case "tool-input-start": {
301
- let newPart: ToolUIPart | DynamicToolUIPart;
302
- if (part.dynamic) {
303
- newPart = {
304
- type: "dynamic-tool",
305
- toolCallId: part.id,
306
- toolName: part.toolName,
307
- state: "input-streaming",
308
- input: "",
309
- } satisfies DynamicToolUIPart;
310
- } else {
311
- newPart = {
312
- type: `tool-${part.toolName}`,
313
- toolCallId: part.id,
314
- state: "input-streaming",
315
- input: "",
316
- providerExecuted: part.providerExecuted,
317
- } satisfies ToolUIPart;
318
- }
319
- toolPartsById.set(part.id, newPart);
320
- message.parts.push(newPart);
321
- break;
322
- }
323
- case "tool-input-delta":
324
- {
325
- const toUpdate = toolPartsById.get(part.id);
326
- assert(
327
- toUpdate,
328
- `Expected to find tool call part ${part.id} to update`,
329
- );
330
- toUpdate.input = (toUpdate.input ?? "") + part.delta;
331
- }
332
- break;
333
- case "tool-input-end":
334
- {
335
- const toUpdate = toolPartsById.get(part.id);
336
- assert(
337
- toUpdate,
338
- `Expected to find tool call part ${part.id} to update`,
339
- );
340
- toUpdate.state = "input-available";
341
- if (part.providerMetadata) {
342
- const updatable = toUpdate as Extract<
343
- ToolUIPart | DynamicToolUIPart,
344
- { state: "input-available" }
345
- >;
346
- updatable.callProviderMetadata = mergeProviderMetadata(
347
- updatable.callProviderMetadata,
348
- part.providerMetadata,
349
- );
350
- }
351
- }
352
- break;
353
- case "tool-call": {
354
- let newPart: ToolUIPart | DynamicToolUIPart;
355
- if (part.dynamic) {
356
- newPart = {
357
- type: "dynamic-tool",
358
- toolCallId: part.toolCallId,
359
- toolName: part.toolName,
360
- input: part.input,
361
- state: "input-available",
362
- };
363
- } else {
364
- newPart = {
365
- type: `tool-${part.toolName}`,
366
- toolCallId: part.toolCallId,
367
- input: part.input,
368
- state: "input-available",
369
- };
370
- if (part.providerExecuted) {
371
- newPart.providerExecuted = part.providerExecuted;
372
- }
373
- }
374
- if (part.providerMetadata) {
375
- newPart.callProviderMetadata = part.providerMetadata;
376
- }
377
- if (toolPartsById.has(part.toolCallId)) {
378
- const toUpdate = toolPartsById.get(part.toolCallId)!;
379
- Object.assign(toUpdate, newPart);
380
- } else {
381
- toolPartsById.set(part.toolCallId, newPart);
382
- message.parts.push(newPart);
383
- }
384
- break;
385
- }
386
- case "tool-result": {
387
- const toolCall = toolPartsById.get(part.toolCallId);
388
- assert(
389
- toolCall,
390
- `Expected to find tool call part ${part.toolCallId} to update with result`,
391
- );
392
- let newPart: ToolUIPart | DynamicToolUIPart;
393
- if (toolCall.type === "dynamic-tool") {
394
- newPart = {
395
- ...toolCall,
396
- state: "output-available",
397
- input: part.input ?? toolCall.input,
398
- output: part.output ?? toolCall.output,
399
- ...pick(part, ["preliminary"]),
400
- } as DynamicToolUIPart;
401
- } else {
402
- newPart = {
403
- ...toolCall,
404
- state: "output-available",
405
- input: part.input ?? toolCall.input,
406
- output: part.output ?? toolCall.output,
407
- preliminary: part.preliminary,
408
- } as ToolUIPart;
409
- }
410
- Object.assign(toolCall, newPart);
411
- break;
412
- }
413
- case "reasoning-start":
414
- case "reasoning-delta": {
415
- if (!reasoningPartsById.has(part.id)) {
416
- const lastPart = message.parts.at(-1);
417
- if (lastPart?.type === "reasoning") {
418
- reasoningPartsById.set(part.id, lastPart);
419
- } else {
420
- const newPart = {
421
- type: "reasoning",
422
- state: "streaming",
423
- text: "",
424
- providerMetadata: part.providerMetadata,
425
- } satisfies ReasoningUIPart;
426
- reasoningPartsById.set(part.id, newPart);
427
- message.parts.push(newPart);
428
- }
429
- }
430
- const reasoningPart = reasoningPartsById.get(part.id)!;
431
- if (part.type === "reasoning-delta") {
432
- reasoningPart.text += part.text;
433
- reasoningPart.providerMetadata = mergeProviderMetadata(
434
- reasoningPart.providerMetadata,
435
- part.providerMetadata,
436
- );
437
- }
438
- break;
439
- }
440
- case "reasoning-end": {
441
- const reasoningPart =
442
- reasoningPartsById.get(part.id) ??
443
- message.parts.find(
444
- (p): p is ReasoningUIPart =>
445
- p.type === "reasoning" && p.state === "streaming",
446
- )!;
447
- if (reasoningPart) {
448
- reasoningPart.state = "done";
449
- } else {
450
- console.warn(
451
- `Expected to find reasoning part ${part.id} to finish, but found none`,
452
- );
453
- }
454
- break;
455
- }
456
- case "source":
457
- if (part.sourceType === "url") {
458
- message.parts.push({
459
- type: "source-url",
460
- url: part.url,
461
- sourceId: part.id,
462
- providerMetadata: part.providerMetadata,
463
- title: part.title,
464
- });
465
- } else if (part.sourceType === "document") {
466
- message.parts.push({
467
- type: "source-document",
468
- mediaType: part.mediaType,
469
- sourceId: part.id,
470
- title: part.title,
471
- filename: part.filename,
472
- providerMetadata: part.providerMetadata,
473
- });
474
- } else {
475
- console.warn("Got source part with unknown source type", part);
476
- }
477
- break;
478
- case "abort":
479
- message.status = "failed";
480
- break;
481
- case "error":
482
- message.status = "failed";
483
- console.warn("Generation failed with error", part.error);
484
- break;
485
- case "tool-error": {
486
- const toolPart = toolPartsById.get(part.toolCallId);
487
- if (toolPart) {
488
- toolPart.errorText = getErrorMessage(part.error);
489
- }
490
- break;
491
- }
492
- case "tool-approval-request": {
493
- const typedPart = part as unknown as {
494
- type: "tool-approval-request";
495
- toolCallId: string;
496
- approvalId: string;
497
- };
498
- const toolPart = toolPartsById.get(typedPart.toolCallId);
499
- if (toolPart) {
500
- toolPart.state = "approval-requested";
501
- (toolPart as ToolUIPart & { approval?: object }).approval = {
502
- id: typedPart.approvalId,
503
- };
504
- } else {
505
- console.warn(
506
- `Expected tool call part ${typedPart.toolCallId} for approval request`,
507
- );
508
- }
509
- break;
510
- }
511
- case "file":
512
- case "text-end":
513
- case "finish-step":
514
- case "finish":
515
- case "raw":
516
- case "start-step":
517
- case "start":
518
- // ignore
519
- break;
520
- default: {
521
- // Exhaustiveness check disabled intentionally for forwards compatibility.
522
- // New TextStreamPart types from future AI SDK versions will trigger a
523
- // runtime warning rather than a compile error, allowing graceful degradation.
524
- // const _: never = part;
525
- console.warn(`Received unexpected part: ${JSON.stringify(part)}`);
526
- break;
527
- }
528
- }
529
- }
530
- // Consider reasoning done once something else happens
531
- for (let i = 0; i < message.parts.length - 1; i++) {
532
- const part = message.parts[i];
533
- if (part.type === "reasoning") {
534
- part.state = "done";
535
- }
536
- }
537
- message.text = joinText(message.parts);
538
- return [
539
- {
540
- streamId: streamMessage.streamId,
541
- cursor,
542
- message,
543
- },
544
- true,
545
- ];
546
- }
547
-
548
- function mergeProviderMetadata(
549
- existing: ProviderMetadata | undefined,
550
- part: ProviderMetadata | undefined,
551
- ): ProviderMetadata | undefined {
552
- if (!existing && !part) {
553
- return undefined;
554
- }
555
- if (!existing) {
556
- return part;
557
- }
558
- if (!part) {
559
- return existing;
560
- }
561
- const merged: ProviderMetadata = existing;
562
- for (const [provider, metadata] of Object.entries(part)) {
563
- merged[provider] = {
564
- ...merged[provider],
565
- ...metadata,
566
- };
567
- }
568
- return merged;
569
- }