@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
@@ -0,0 +1,658 @@
1
+ import type { ProviderMetadata, StreamDelta } from "../validators.js";
2
+
3
+ export type PersistedUIMessageChunkRecord = Record<string, unknown> & {
4
+ type: string;
5
+ };
6
+
7
+ export class PersistedUIMessageChunkError extends Error {
8
+ constructor(
9
+ readonly code: "missing-part" | "missing-approval" | "unsupported-chunk",
10
+ message: string,
11
+ readonly chunkType: string,
12
+ readonly id?: string,
13
+ readonly partType?: string,
14
+ ) {
15
+ super(message);
16
+ }
17
+ }
18
+
19
+ export type PersistedTextPart = {
20
+ type: "text";
21
+ text: string;
22
+ state: "streaming" | "done";
23
+ providerMetadata?: ProviderMetadata;
24
+ };
25
+
26
+ export type PersistedReasoningPart = {
27
+ type: "reasoning";
28
+ text: string;
29
+ state: "streaming" | "done";
30
+ providerMetadata?: ProviderMetadata;
31
+ };
32
+
33
+ export type PersistedFilePart = {
34
+ type: "file";
35
+ url: string;
36
+ mediaType: string;
37
+ filename?: string;
38
+ providerMetadata?: ProviderMetadata;
39
+ };
40
+
41
+ export type PersistedReasoningFilePart = {
42
+ type: "reasoning-file";
43
+ url: string;
44
+ mediaType: string;
45
+ providerMetadata?: ProviderMetadata;
46
+ };
47
+
48
+ export type PersistedCustomPart = {
49
+ type: "custom";
50
+ kind: string;
51
+ providerMetadata?: ProviderMetadata;
52
+ };
53
+
54
+ export type PersistedSourcePart =
55
+ | {
56
+ type: "source-url";
57
+ sourceId: string;
58
+ url: string;
59
+ title?: string;
60
+ providerMetadata?: ProviderMetadata;
61
+ }
62
+ | {
63
+ type: "source-document";
64
+ sourceId: string;
65
+ mediaType: string;
66
+ title: string;
67
+ filename?: string;
68
+ providerMetadata?: ProviderMetadata;
69
+ };
70
+
71
+ export type PersistedToolPart = {
72
+ type: `tool-${string}` | "dynamic-tool";
73
+ toolName?: string;
74
+ toolCallId: string;
75
+ state:
76
+ | "input-streaming"
77
+ | "input-available"
78
+ | "approval-requested"
79
+ | "approval-responded"
80
+ | "output-available"
81
+ | "output-error"
82
+ | "output-denied";
83
+ input?: unknown;
84
+ rawInput?: unknown;
85
+ output?: unknown;
86
+ errorText?: string;
87
+ providerExecuted?: boolean;
88
+ callProviderMetadata?: ProviderMetadata;
89
+ resultProviderMetadata?: ProviderMetadata;
90
+ preliminary?: boolean;
91
+ title?: string;
92
+ toolMetadata?: Record<string, unknown>;
93
+ approval?: {
94
+ id: string;
95
+ approved?: boolean;
96
+ reason?: string;
97
+ isAutomatic?: boolean;
98
+ signature?: string;
99
+ };
100
+ };
101
+
102
+ export type PersistedDataPart = {
103
+ type: `data-${string}`;
104
+ id?: string;
105
+ data: unknown;
106
+ };
107
+
108
+ export type PersistedUIMessagePart =
109
+ | PersistedTextPart
110
+ | PersistedReasoningPart
111
+ | PersistedFilePart
112
+ | PersistedReasoningFilePart
113
+ | PersistedCustomPart
114
+ | PersistedSourcePart
115
+ | PersistedToolPart
116
+ | PersistedDataPart
117
+ | { type: "step-start" };
118
+
119
+ /** Incremental state for persisted UI-message chunks without a provider SDK. */
120
+ export type PersistedUIMessageChunkState = {
121
+ readonly format: "UIMessageChunk";
122
+ parts: PersistedUIMessagePart[];
123
+ activeText: Map<string, number>;
124
+ activeReasoning: Map<string, number>;
125
+ toolIndexById: Map<string, number>;
126
+ toolInputText: Map<string, string>;
127
+ };
128
+
129
+ export function createPersistedUIMessageChunkState(): PersistedUIMessageChunkState {
130
+ return {
131
+ format: "UIMessageChunk",
132
+ parts: [],
133
+ activeText: new Map(),
134
+ activeReasoning: new Map(),
135
+ toolIndexById: new Map(),
136
+ toolInputText: new Map(),
137
+ };
138
+ }
139
+
140
+ export function reducePersistedUIMessageChunks(
141
+ previous: PersistedUIMessageChunkState,
142
+ chunks: readonly unknown[],
143
+ ): {
144
+ state: PersistedUIMessageChunkState;
145
+ touchedToolCallIds: string[];
146
+ } {
147
+ const parts = structuredClone(previous.parts);
148
+ const activeText = new Map(previous.activeText);
149
+ const activeReasoning = new Map(previous.activeReasoning);
150
+ const toolIndexById = new Map(previous.toolIndexById);
151
+ const toolInputText = new Map(previous.toolInputText);
152
+ const touchedToolCallIds = new Set<string>();
153
+ const toolPart = (toolCallId: string): PersistedToolPart | undefined => {
154
+ const index = toolIndexById.get(toolCallId);
155
+ return index === undefined
156
+ ? undefined
157
+ : (parts[index] as PersistedToolPart | undefined);
158
+ };
159
+ const addToolPart = (part: PersistedToolPart) => {
160
+ parts.push(part);
161
+ toolIndexById.set(part.toolCallId, parts.length - 1);
162
+ return part;
163
+ };
164
+
165
+ chunkLoop: for (const value of chunks) {
166
+ const chunk = chunkRecord(value);
167
+ assertSupportedPersistedUIMessageChunk(chunk);
168
+ switch (chunk.type) {
169
+ case "text-start": {
170
+ const part: PersistedTextPart = {
171
+ type: "text",
172
+ text: "",
173
+ state: "streaming",
174
+ providerMetadata: providerMetadata(chunk.providerMetadata),
175
+ };
176
+ parts.push(part);
177
+ activeText.set(stringField(chunk, "id"), parts.length - 1);
178
+ break;
179
+ }
180
+ case "text-delta": {
181
+ const id = stringField(chunk, "id");
182
+ const index = activeText.get(id);
183
+ if (index === undefined) {
184
+ throw missingPart(chunk.type, id, "text");
185
+ }
186
+ const part = parts[index] as PersistedTextPart;
187
+ part.text += stringField(chunk, "delta");
188
+ part.providerMetadata =
189
+ providerMetadata(chunk.providerMetadata) ?? part.providerMetadata;
190
+ break;
191
+ }
192
+ case "text-end": {
193
+ const id = stringField(chunk, "id");
194
+ const index = activeText.get(id);
195
+ if (index === undefined) {
196
+ throw missingPart(chunk.type, id, "text");
197
+ }
198
+ const part = parts[index] as PersistedTextPart;
199
+ part.state = "done";
200
+ part.providerMetadata =
201
+ providerMetadata(chunk.providerMetadata) ?? part.providerMetadata;
202
+ activeText.delete(id);
203
+ break;
204
+ }
205
+ case "reasoning-start": {
206
+ const part: PersistedReasoningPart = {
207
+ type: "reasoning",
208
+ text: "",
209
+ state: "streaming",
210
+ providerMetadata: providerMetadata(chunk.providerMetadata),
211
+ };
212
+ parts.push(part);
213
+ activeReasoning.set(stringField(chunk, "id"), parts.length - 1);
214
+ break;
215
+ }
216
+ case "reasoning-delta": {
217
+ const id = stringField(chunk, "id");
218
+ const index = activeReasoning.get(id);
219
+ if (index === undefined) {
220
+ throw missingPart(chunk.type, id, "reasoning");
221
+ }
222
+ const part = parts[index] as PersistedReasoningPart;
223
+ part.text += stringField(chunk, "delta");
224
+ part.providerMetadata =
225
+ providerMetadata(chunk.providerMetadata) ?? part.providerMetadata;
226
+ break;
227
+ }
228
+ case "reasoning-end": {
229
+ const id = stringField(chunk, "id");
230
+ const index = activeReasoning.get(id);
231
+ if (index === undefined) {
232
+ throw missingPart(chunk.type, id, "reasoning");
233
+ }
234
+ const part = parts[index] as PersistedReasoningPart;
235
+ part.state = "done";
236
+ part.providerMetadata =
237
+ providerMetadata(chunk.providerMetadata) ?? part.providerMetadata;
238
+ activeReasoning.delete(id);
239
+ break;
240
+ }
241
+ case "tool-input-start": {
242
+ const toolCallId = stringField(chunk, "toolCallId");
243
+ const toolName = stringField(chunk, "toolName");
244
+ const dynamic = chunk.dynamic === true;
245
+ addToolPart({
246
+ type: dynamic ? "dynamic-tool" : `tool-${toolName}`,
247
+ toolName: dynamic ? toolName : undefined,
248
+ toolCallId,
249
+ state: "input-streaming",
250
+ providerExecuted: optionalBoolean(chunk.providerExecuted),
251
+ callProviderMetadata: providerMetadata(chunk.providerMetadata),
252
+ title: optionalString(chunk.title),
253
+ toolMetadata: optionalRecord(chunk.toolMetadata),
254
+ });
255
+ toolInputText.set(toolCallId, "");
256
+ break;
257
+ }
258
+ case "tool-input-delta": {
259
+ const toolCallId = stringField(chunk, "toolCallId");
260
+ if (!toolIndexById.has(toolCallId)) {
261
+ throw missingPart(chunk.type, toolCallId, "tool call");
262
+ }
263
+ toolInputText.set(
264
+ toolCallId,
265
+ (toolInputText.get(toolCallId) ?? "") +
266
+ stringField(chunk, "inputTextDelta"),
267
+ );
268
+ touchedToolCallIds.add(toolCallId);
269
+ break;
270
+ }
271
+ case "tool-input-available": {
272
+ const toolCallId = stringField(chunk, "toolCallId");
273
+ const toolName = stringField(chunk, "toolName");
274
+ const dynamic = chunk.dynamic === true;
275
+ const part =
276
+ toolPart(toolCallId) ??
277
+ addToolPart({
278
+ type: dynamic ? "dynamic-tool" : `tool-${toolName}`,
279
+ toolName: dynamic ? toolName : undefined,
280
+ toolCallId,
281
+ state: "input-available",
282
+ });
283
+ Object.assign(part, {
284
+ state: "input-available" as const,
285
+ input: chunk.input,
286
+ callProviderMetadata:
287
+ providerMetadata(chunk.providerMetadata) ??
288
+ part.callProviderMetadata,
289
+ title: optionalString(chunk.title) ?? part.title,
290
+ toolMetadata: optionalRecord(chunk.toolMetadata) ?? part.toolMetadata,
291
+ providerExecuted:
292
+ optionalBoolean(chunk.providerExecuted) ?? part.providerExecuted,
293
+ });
294
+ touchedToolCallIds.delete(toolCallId);
295
+ toolInputText.delete(toolCallId);
296
+ break;
297
+ }
298
+ case "tool-input-error": {
299
+ const toolCallId = stringField(chunk, "toolCallId");
300
+ const toolName = stringField(chunk, "toolName");
301
+ const dynamic = chunk.dynamic === true;
302
+ const part =
303
+ toolPart(toolCallId) ??
304
+ addToolPart({
305
+ type: dynamic ? "dynamic-tool" : `tool-${toolName}`,
306
+ toolName: dynamic ? toolName : undefined,
307
+ toolCallId,
308
+ state: "output-error",
309
+ });
310
+ Object.assign(part, {
311
+ state: "output-error" as const,
312
+ errorText: stringField(chunk, "errorText"),
313
+ ...(part.type === "dynamic-tool"
314
+ ? { input: chunk.input, rawInput: undefined }
315
+ : { input: undefined, rawInput: chunk.input }),
316
+ resultProviderMetadata:
317
+ providerMetadata(chunk.providerMetadata) ??
318
+ part.resultProviderMetadata,
319
+ title: optionalString(chunk.title) ?? part.title,
320
+ toolMetadata: optionalRecord(chunk.toolMetadata) ?? part.toolMetadata,
321
+ providerExecuted:
322
+ optionalBoolean(chunk.providerExecuted) ?? part.providerExecuted,
323
+ });
324
+ touchedToolCallIds.delete(toolCallId);
325
+ toolInputText.delete(toolCallId);
326
+ break;
327
+ }
328
+ case "tool-output-available": {
329
+ const toolCallId = stringField(chunk, "toolCallId");
330
+ const part = toolPart(toolCallId);
331
+ if (!part) {
332
+ // Orphan continuation of a prior message's tool call: stop reducing
333
+ // rather than persist parts referencing results this stream lacks.
334
+ break chunkLoop;
335
+ }
336
+ Object.assign(part, {
337
+ state: "output-available" as const,
338
+ output: chunk.output,
339
+ preliminary: optionalBoolean(chunk.preliminary),
340
+ providerExecuted:
341
+ optionalBoolean(chunk.providerExecuted) ?? part.providerExecuted,
342
+ resultProviderMetadata:
343
+ providerMetadata(chunk.providerMetadata) ??
344
+ part.resultProviderMetadata,
345
+ toolMetadata: optionalRecord(chunk.toolMetadata) ?? part.toolMetadata,
346
+ });
347
+ break;
348
+ }
349
+ case "tool-output-error": {
350
+ const toolCallId = stringField(chunk, "toolCallId");
351
+ const part = toolPart(toolCallId);
352
+ if (!part) {
353
+ break chunkLoop;
354
+ }
355
+ Object.assign(part, {
356
+ state: "output-error" as const,
357
+ errorText: stringField(chunk, "errorText"),
358
+ output: undefined,
359
+ preliminary: undefined,
360
+ providerExecuted:
361
+ optionalBoolean(chunk.providerExecuted) ?? part.providerExecuted,
362
+ resultProviderMetadata:
363
+ providerMetadata(chunk.providerMetadata) ??
364
+ part.resultProviderMetadata,
365
+ toolMetadata: optionalRecord(chunk.toolMetadata) ?? part.toolMetadata,
366
+ });
367
+ break;
368
+ }
369
+ case "tool-output-denied": {
370
+ const toolCallId = stringField(chunk, "toolCallId");
371
+ const part = toolPart(toolCallId);
372
+ if (!part) {
373
+ break chunkLoop;
374
+ }
375
+ if (!part.approval) {
376
+ throw new PersistedUIMessageChunkError(
377
+ "missing-approval",
378
+ `Received tool-output-denied for tool call with ID "${toolCallId}" without a preceding tool-approval-request.`,
379
+ chunk.type,
380
+ toolCallId,
381
+ );
382
+ }
383
+ part.state = "output-denied";
384
+ break;
385
+ }
386
+ case "tool-approval-request": {
387
+ const toolCallId = stringField(chunk, "toolCallId");
388
+ const part = toolPart(toolCallId);
389
+ if (!part) {
390
+ break chunkLoop;
391
+ }
392
+ part.state = "approval-requested";
393
+ part.approval = {
394
+ id: stringField(chunk, "approvalId"),
395
+ isAutomatic: optionalBoolean(chunk.isAutomatic),
396
+ signature: optionalString(chunk.signature),
397
+ };
398
+ break;
399
+ }
400
+ case "tool-approval-response": {
401
+ const approvalId = stringField(chunk, "approvalId");
402
+ const part = parts.find(
403
+ (candidate): candidate is PersistedToolPart =>
404
+ isToolPart(candidate) && candidate.approval?.id === approvalId,
405
+ );
406
+ if (!part) {
407
+ break chunkLoop;
408
+ }
409
+ part.state = "approval-responded";
410
+ part.approval = {
411
+ ...part.approval!,
412
+ approved: booleanField(chunk, "approved"),
413
+ reason: optionalString(chunk.reason),
414
+ };
415
+ part.providerExecuted =
416
+ optionalBoolean(chunk.providerExecuted) ?? part.providerExecuted;
417
+ part.callProviderMetadata =
418
+ providerMetadata(chunk.providerMetadata) ?? part.callProviderMetadata;
419
+ break;
420
+ }
421
+ case "custom":
422
+ parts.push({
423
+ type: "custom",
424
+ kind: stringField(chunk, "kind"),
425
+ providerMetadata: providerMetadata(chunk.providerMetadata),
426
+ });
427
+ break;
428
+ case "source-url":
429
+ parts.push({
430
+ type: "source-url",
431
+ sourceId: stringField(chunk, "sourceId"),
432
+ url: stringField(chunk, "url"),
433
+ title: optionalString(chunk.title),
434
+ providerMetadata: providerMetadata(chunk.providerMetadata),
435
+ });
436
+ break;
437
+ case "source-document":
438
+ parts.push({
439
+ type: "source-document",
440
+ sourceId: stringField(chunk, "sourceId"),
441
+ mediaType: stringField(chunk, "mediaType"),
442
+ title: stringField(chunk, "title"),
443
+ filename: optionalString(chunk.filename),
444
+ providerMetadata: providerMetadata(chunk.providerMetadata),
445
+ });
446
+ break;
447
+ case "file":
448
+ parts.push({
449
+ type: "file",
450
+ mediaType: stringField(chunk, "mediaType"),
451
+ url: stringField(chunk, "url"),
452
+ filename: optionalString(chunk.filename),
453
+ providerMetadata: providerMetadata(chunk.providerMetadata),
454
+ });
455
+ break;
456
+ case "reasoning-file":
457
+ parts.push({
458
+ type: "reasoning-file",
459
+ mediaType: stringField(chunk, "mediaType"),
460
+ url: stringField(chunk, "url"),
461
+ providerMetadata: providerMetadata(chunk.providerMetadata),
462
+ });
463
+ break;
464
+ case "start-step":
465
+ parts.push({ type: "step-start" });
466
+ break;
467
+ case "finish-step":
468
+ activeText.clear();
469
+ activeReasoning.clear();
470
+ break;
471
+ case "start":
472
+ case "finish":
473
+ case "message-metadata":
474
+ case "error":
475
+ case "abort":
476
+ break;
477
+ default:
478
+ if (chunk.type.startsWith("data-")) {
479
+ if (chunk.transient === true) break;
480
+ const id = optionalString(chunk.id);
481
+ const existingIndex =
482
+ id === undefined
483
+ ? -1
484
+ : parts.findIndex(
485
+ (part) =>
486
+ part.type === chunk.type && "id" in part && part.id === id,
487
+ );
488
+ if (existingIndex >= 0) {
489
+ (parts[existingIndex] as PersistedDataPart).data = chunk.data;
490
+ } else {
491
+ parts.push({
492
+ type: chunk.type as `data-${string}`,
493
+ id,
494
+ data: chunk.data,
495
+ });
496
+ }
497
+ } else {
498
+ throw new PersistedUIMessageChunkError(
499
+ "unsupported-chunk",
500
+ `Unsupported durable UI message chunk type "${chunk.type}"`,
501
+ chunk.type,
502
+ );
503
+ }
504
+ }
505
+ }
506
+
507
+ return {
508
+ state: {
509
+ format: "UIMessageChunk",
510
+ parts,
511
+ activeText,
512
+ activeReasoning,
513
+ toolIndexById,
514
+ toolInputText,
515
+ },
516
+ touchedToolCallIds: [...touchedToolCallIds],
517
+ };
518
+ }
519
+
520
+ export function setParsedToolInput(
521
+ state: PersistedUIMessageChunkState,
522
+ toolCallId: string,
523
+ input: unknown,
524
+ ): void {
525
+ const index = state.toolIndexById.get(toolCallId);
526
+ if (index === undefined) return;
527
+ const part = state.parts[index];
528
+ if (part && isToolPart(part) && part.input === undefined) {
529
+ part.input = input;
530
+ }
531
+ }
532
+
533
+ export function collectContiguousStreamParts<
534
+ T extends StreamDelta["parts"][number],
535
+ >(
536
+ deltas: readonly StreamDelta[],
537
+ fromCursor = 0,
538
+ ): { parts: T[]; cursor: number } {
539
+ const parts: T[] = [];
540
+ let cursor = fromCursor;
541
+ for (const delta of [...deltas].sort((a, b) => a.start - b.start)) {
542
+ if (delta.parts.length === 0) continue;
543
+ if (cursor !== delta.start) {
544
+ if (cursor >= delta.end) continue;
545
+ if (cursor < delta.start) break;
546
+ throw new Error(
547
+ `Got unexpected delta for stream ${delta.streamId}: delta: ${delta.start} -> ${delta.end} existing cursor: ${cursor}`,
548
+ );
549
+ }
550
+ parts.push(...(delta.parts as T[]));
551
+ cursor = delta.end;
552
+ }
553
+ return { parts, cursor };
554
+ }
555
+
556
+ function missingPart(
557
+ chunkType: string,
558
+ id: string,
559
+ partType: string,
560
+ ): PersistedUIMessageChunkError {
561
+ return new PersistedUIMessageChunkError(
562
+ "missing-part",
563
+ `Received ${chunkType} for missing ${partType} part with ID "${id}".`,
564
+ chunkType,
565
+ id,
566
+ partType,
567
+ );
568
+ }
569
+
570
+ function chunkRecord(value: unknown): PersistedUIMessageChunkRecord {
571
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
572
+ throw new Error("Invalid persisted UIMessageChunk");
573
+ }
574
+ const record = value as Record<string, unknown>;
575
+ if (typeof record.type !== "string") {
576
+ throw new Error("Persisted UIMessageChunk is missing a type");
577
+ }
578
+ return record as PersistedUIMessageChunkRecord;
579
+ }
580
+
581
+ const supportedChunkTypes = new Set([
582
+ "text-start",
583
+ "text-delta",
584
+ "text-end",
585
+ "reasoning-start",
586
+ "reasoning-delta",
587
+ "reasoning-end",
588
+ "custom",
589
+ "error",
590
+ "tool-input-start",
591
+ "tool-input-delta",
592
+ "tool-input-available",
593
+ "tool-input-error",
594
+ "tool-approval-request",
595
+ "tool-approval-response",
596
+ "tool-output-available",
597
+ "tool-output-error",
598
+ "tool-output-denied",
599
+ "source-url",
600
+ "source-document",
601
+ "file",
602
+ "reasoning-file",
603
+ "start-step",
604
+ "finish-step",
605
+ "start",
606
+ "finish",
607
+ "abort",
608
+ "message-metadata",
609
+ ]);
610
+
611
+ function assertSupportedPersistedUIMessageChunk(
612
+ chunk: PersistedUIMessageChunkRecord,
613
+ ): void {
614
+ if (supportedChunkTypes.has(chunk.type) || chunk.type.startsWith("data-")) {
615
+ return;
616
+ }
617
+ throw new Error(
618
+ `persisted chunk type "${chunk.type}" is not part of the supported UIMessageChunk wire format`,
619
+ );
620
+ }
621
+
622
+ function stringField(record: Record<string, unknown>, field: string): string {
623
+ const value = record[field];
624
+ if (typeof value !== "string") {
625
+ throw new Error(`Persisted UIMessageChunk field ${field} must be a string`);
626
+ }
627
+ return value;
628
+ }
629
+
630
+ function booleanField(record: Record<string, unknown>, field: string): boolean {
631
+ const value = record[field];
632
+ if (typeof value !== "boolean") {
633
+ throw new Error(`Persisted UIMessageChunk field ${field} must be a boolean`);
634
+ }
635
+ return value;
636
+ }
637
+
638
+ function optionalString(value: unknown): string | undefined {
639
+ return typeof value === "string" ? value : undefined;
640
+ }
641
+
642
+ function optionalBoolean(value: unknown): boolean | undefined {
643
+ return typeof value === "boolean" ? value : undefined;
644
+ }
645
+
646
+ function optionalRecord(value: unknown): Record<string, unknown> | undefined {
647
+ return value !== null && typeof value === "object" && !Array.isArray(value)
648
+ ? (value as Record<string, unknown>)
649
+ : undefined;
650
+ }
651
+
652
+ function providerMetadata(value: unknown): ProviderMetadata | undefined {
653
+ return optionalRecord(value) as ProviderMetadata | undefined;
654
+ }
655
+
656
+ function isToolPart(part: PersistedUIMessagePart): part is PersistedToolPart {
657
+ return part.type === "dynamic-tool" || part.type.startsWith("tool-");
658
+ }
@@ -1,6 +1,9 @@
1
1
  import type { GenericId, Infer } from "convex/values";
2
2
  import { expectTypeOf, test } from "vitest";
3
- import type { ContextOptions, StorageOptions } from "./client/types.js";
3
+ import type {
4
+ ContextOptions,
5
+ StorageOptions,
6
+ } from "./vercel/client/types.js";
4
7
  import { vContextOptions, vMessageDoc, vStorageOptions } from "./validators.js";
5
8
  import type { Doc } from "./component/_generated/dataModel.js";
6
9