@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
@@ -34,6 +34,7 @@ import {
34
34
  import { schema, v } from "./schema.js";
35
35
  import { insertVector, searchVectors } from "./vector/index.js";
36
36
  import {
37
+ getVectorIdInfo,
37
38
  validateVectorDimension,
38
39
  type VectorTableId,
39
40
  vVectorId,
@@ -50,9 +51,10 @@ export async function deleteMessage(
50
51
  ctx: MutationCtx,
51
52
  messageDoc: Doc<"messages">,
52
53
  ) {
53
- await ctx.db.delete(messageDoc._id);
54
+ await ctx.db.delete("messages", messageDoc._id);
54
55
  if (messageDoc.embeddingId) {
55
- await ctx.db.delete(messageDoc.embeddingId);
56
+ const { tableName } = getVectorIdInfo(ctx, messageDoc.embeddingId);
57
+ await ctx.db.delete(tableName, messageDoc.embeddingId);
56
58
  }
57
59
  if (messageDoc.fileIds) {
58
60
  await changeRefcount(ctx, messageDoc.fileIds, []);
@@ -65,7 +67,7 @@ export const deleteByIds = mutation({
65
67
  handler: async (ctx, args) => {
66
68
  const deletedMessageIds = await Promise.all(
67
69
  args.messageIds.map(async (id) => {
68
- const message = await ctx.db.get(id);
70
+ const message = await ctx.db.get("messages", id);
69
71
  if (message) {
70
72
  await deleteMessage(ctx, message);
71
73
  return id;
@@ -81,6 +83,25 @@ export const messageStatuses = vMessageDoc.fields.status.members.map(
81
83
  (m) => m.value,
82
84
  );
83
85
 
86
+ const STREAM_RECOVERY_FAILURE =
87
+ "Failed to recover persisted assistant stream output";
88
+
89
+ async function markPendingMessageFailed(
90
+ ctx: MutationCtx,
91
+ message: Doc<"messages">,
92
+ error: string,
93
+ ) {
94
+ if (message.embeddingId) {
95
+ const { tableName } = getVectorIdInfo(ctx, message.embeddingId);
96
+ await ctx.db.delete(tableName, message.embeddingId);
97
+ }
98
+ await ctx.db.patch("messages", message._id, {
99
+ status: "failed",
100
+ error,
101
+ embeddingId: undefined,
102
+ });
103
+ }
104
+
84
105
  export const deleteByOrder = mutation({
85
106
  args: {
86
107
  threadId: v.id("threads"),
@@ -157,7 +178,7 @@ async function addMessagesHandler(
157
178
  let userId = args.userId;
158
179
  const threadId = args.threadId;
159
180
  if (!userId && args.threadId) {
160
- const thread = await ctx.db.get(args.threadId);
181
+ const thread = await ctx.db.get("threads", args.threadId);
161
182
  assert(thread, `Thread ${args.threadId} not found`);
162
183
  userId = thread.userId;
163
184
  }
@@ -172,7 +193,8 @@ async function addMessagesHandler(
172
193
  hideFromUserIdSearch,
173
194
  ...rest
174
195
  } = args;
175
- const promptMessage = promptMessageId && (await ctx.db.get(promptMessageId));
196
+ const promptMessage =
197
+ promptMessageId && (await ctx.db.get("messages", promptMessageId));
176
198
  if (failPendingSteps) {
177
199
  assert(args.threadId, "threadId is required to fail pending steps");
178
200
  const pendingMessages = await ctx.db
@@ -188,9 +210,10 @@ async function addMessagesHandler(
188
210
  .filter((m) => !pendingMessageId || m._id !== pendingMessageId)
189
211
  .map(async (m) => {
190
212
  if (m.embeddingId) {
191
- await ctx.db.delete(m.embeddingId);
213
+ const { tableName } = getVectorIdInfo(ctx, m.embeddingId);
214
+ await ctx.db.delete(tableName, m.embeddingId);
192
215
  }
193
- await ctx.db.patch(m._id, {
216
+ await ctx.db.patch("messages", m._id, {
194
217
  status: "failed",
195
218
  error: "Restarting",
196
219
  embeddingId: undefined,
@@ -257,7 +280,7 @@ async function addMessagesHandler(
257
280
  // If there is a pending message, we replace that one with the first message
258
281
  // and subsequent ones will follow the regular order/subOrder advancement.
259
282
  if (i === 0 && pendingMessageId) {
260
- const pendingMessage = await ctx.db.get(pendingMessageId);
283
+ const pendingMessage = await ctx.db.get("messages", pendingMessageId);
261
284
  assert(pendingMessage, `Pending msg ${pendingMessageId} not found`);
262
285
  if (pendingMessage.status === "failed") {
263
286
  fail = true;
@@ -274,12 +297,12 @@ async function addMessagesHandler(
274
297
  message.fileIds,
275
298
  );
276
299
  }
277
- await ctx.db.replace(pendingMessage._id, {
300
+ await ctx.db.replace("messages", pendingMessage._id, {
278
301
  ...messageDoc,
279
302
  order: pendingMessage.order,
280
303
  stepOrder: pendingMessage.stepOrder,
281
304
  });
282
- toReturn.push((await ctx.db.get(pendingMessage._id))!);
305
+ toReturn.push((await ctx.db.get("messages", pendingMessage._id))!);
283
306
  continue;
284
307
  }
285
308
  if (message.message.role === "user") {
@@ -306,7 +329,7 @@ async function addMessagesHandler(
306
329
  await changeRefcount(ctx, [], message.fileIds);
307
330
  }
308
331
  // TODO: delete the associated stream data for the order/stepOrder
309
- toReturn.push((await ctx.db.get(messageId))!);
332
+ toReturn.push((await ctx.db.get("messages", messageId))!);
310
333
  }
311
334
  // Atomically finish the stream if requested, preventing UI flickering
312
335
  // from separate mutations for message save and stream finish (issue #181).
@@ -375,7 +398,7 @@ export const finalizeMessage = mutation({
375
398
  },
376
399
  returns: v.null(),
377
400
  handler: async (ctx, { messageId, result }) => {
378
- const message = await ctx.db.get(messageId);
401
+ const message = await ctx.db.get("messages", messageId);
379
402
  assert(message, `Message ${messageId} not found`);
380
403
  if (message.status !== "pending") {
381
404
  console.debug(
@@ -386,11 +409,20 @@ export const finalizeMessage = mutation({
386
409
  }
387
410
  // See if we can add any in-progress data
388
411
  if (!message.message?.content.length) {
389
- const messages = await getStreamingMessagesWithMetadata(
390
- ctx,
391
- message,
392
- result,
393
- );
412
+ const { messages, materializationFailures } =
413
+ await getStreamingMessagesWithMetadata(ctx, message, result);
414
+ if (materializationFailures.length > 0) {
415
+ console.error(
416
+ "Failed to materialize persisted assistant streams",
417
+ materializationFailures,
418
+ );
419
+ await markPendingMessageFailed(
420
+ ctx,
421
+ message,
422
+ result.status === "failed" ? result.error : STREAM_RECOVERY_FAILURE,
423
+ );
424
+ return;
425
+ }
394
426
  if (messages.length > 0) {
395
427
  await addMessagesHandler(ctx, {
396
428
  messages,
@@ -405,16 +437,9 @@ export const finalizeMessage = mutation({
405
437
  }
406
438
  }
407
439
  if (result.status === "failed") {
408
- if (message.embeddingId) {
409
- await ctx.db.delete(message.embeddingId);
410
- }
411
- await ctx.db.patch(messageId, {
412
- status: "failed",
413
- error: result.error,
414
- embeddingId: undefined,
415
- });
440
+ await markPendingMessageFailed(ctx, message, result.error);
416
441
  } else {
417
- await ctx.db.patch(messageId, { status: "success" });
442
+ await ctx.db.patch("messages", messageId, { status: "success" });
418
443
  }
419
444
  },
420
445
  });
@@ -439,7 +464,7 @@ export const updateMessage = mutation({
439
464
  },
440
465
  returns: vMessageDoc,
441
466
  handler: async (ctx, args) => {
442
- const message = await ctx.db.get(args.messageId);
467
+ const message = await ctx.db.get("messages", args.messageId);
443
468
  assert(message, `Message ${args.messageId} not found`);
444
469
 
445
470
  if (args.patch.fileIds) {
@@ -456,13 +481,14 @@ export const updateMessage = mutation({
456
481
 
457
482
  if (args.patch.status === "failed") {
458
483
  if (message.embeddingId) {
459
- await ctx.db.delete(message.embeddingId);
484
+ const { tableName } = getVectorIdInfo(ctx, message.embeddingId);
485
+ await ctx.db.delete(tableName, message.embeddingId);
460
486
  }
461
487
  patch.embeddingId = undefined;
462
488
  }
463
489
 
464
- await ctx.db.patch(args.messageId, patch);
465
- return publicMessage((await ctx.db.get(args.messageId))!);
490
+ await ctx.db.patch("messages", args.messageId, patch);
491
+ return publicMessage((await ctx.db.get("messages", args.messageId))!);
466
492
  },
467
493
  });
468
494
 
@@ -540,7 +566,8 @@ export const cloneMessageBatch = internalMutation({
540
566
  }
541
567
  let embeddingId: VectorTableId | undefined = undefined;
542
568
  if (m.embeddingId) {
543
- const vector = await ctx.db.get(m.embeddingId);
569
+ const { tableName } = getVectorIdInfo(ctx, m.embeddingId);
570
+ const vector = await ctx.db.get(tableName, m.embeddingId);
544
571
  assert(vector, `Vector ${m.embeddingId} not found`);
545
572
  const dimension = vector.vector.length;
546
573
  validateVectorDimension(dimension);
@@ -636,7 +663,7 @@ async function listMessagesByThreadIdHandler(
636
663
  const statuses = args.statuses ?? vMessageStatus.members.map((m) => m.value);
637
664
  const last =
638
665
  args.upToAndIncludingMessageId &&
639
- (await ctx.db.get(args.upToAndIncludingMessageId));
666
+ (await ctx.db.get("messages", args.upToAndIncludingMessageId));
640
667
  assert(
641
668
  !last || last.threadId === args.threadId,
642
669
  "upToAndIncludingMessageId must be a message in the thread",
@@ -679,9 +706,9 @@ async function listMessagesByThreadIdHandler(
679
706
  export const getMessagesByIds = query({
680
707
  args: { messageIds: v.array(v.id("messages")) },
681
708
  handler: async (ctx, args) => {
682
- return (await Promise.all(args.messageIds.map((id) => ctx.db.get(id)))).map(
683
- (m) => (m ? publicMessage(m) : null),
684
- );
709
+ return (
710
+ await Promise.all(args.messageIds.map((id) => ctx.db.get("messages", id)))
711
+ ).map((m) => (m ? publicMessage(m) : null));
685
712
  },
686
713
  returns: v.array(v.union(v.null(), vMessageDoc)),
687
714
  });
@@ -793,7 +820,8 @@ export const _fetchSearchMessages = internalQuery({
793
820
  returns: v.array(vMessageDoc),
794
821
  handler: async (ctx, args): Promise<MessageDoc[]> => {
795
822
  const beforeMessage =
796
- args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
823
+ args.beforeMessageId &&
824
+ (await ctx.db.get("messages", args.beforeMessageId));
797
825
  const { searchAllMessagesForUserId, threadId } = args;
798
826
  assert(
799
827
  searchAllMessagesForUserId || threadId,
@@ -809,6 +837,7 @@ export const _fetchSearchMessages = internalQuery({
809
837
  ? q.eq("embeddingId", embeddingId)
810
838
  : q.eq("embeddingId", embeddingId).eq("threadId", threadId!),
811
839
  )
840
+ // eslint-disable-next-line @convex-dev/no-filter-in-query -- We do not expect many messages with the same embeddingId for different users / threads
812
841
  .filter((q) =>
813
842
  q.and(
814
843
  q.eq(q.field("status"), "success"),
@@ -821,16 +850,18 @@ export const _fetchSearchMessages = internalQuery({
821
850
  ),
822
851
  )
823
852
  )
824
- .filter(
825
- (m): m is Doc<"messages"> =>
826
- m !== undefined &&
827
- m !== null &&
828
- !m.tool &&
829
- (!beforeMessage ||
830
- m.order < beforeMessage.order ||
831
- (m.order === beforeMessage.order &&
832
- m.stepOrder < beforeMessage.stepOrder)),
833
- )
853
+ .filter((m): m is Doc<"messages"> => {
854
+ if (m === undefined || m === null || m.tool) return false;
855
+ if (!beforeMessage) return true;
856
+ // The order filter is only meaningful within the same thread.
857
+ // Messages from other threads have independent order sequences.
858
+ if (m.threadId !== beforeMessage.threadId) return true;
859
+ return (
860
+ m.order < beforeMessage.order ||
861
+ (m.order === beforeMessage.order &&
862
+ m.stepOrder < beforeMessage.stepOrder)
863
+ );
864
+ })
834
865
  .map(publicMessage);
835
866
  messages.push(...(args.textSearchMessages ?? []));
836
867
  // TODO: prioritize more recent messages
@@ -911,13 +942,18 @@ export const textSearch = query({
911
942
  "Specify userId or threadId",
912
943
  );
913
944
  const targetMessage =
914
- args.targetMessageId && (await ctx.db.get(args.targetMessageId));
915
- const order = targetMessage?.order;
945
+ args.targetMessageId &&
946
+ (await ctx.db.get("messages", args.targetMessageId));
916
947
  const text = args.text || targetMessage?.text;
917
948
  if (!text) {
918
949
  console.warn("No text to search", targetMessage, args.text);
919
950
  return [];
920
951
  }
952
+ // When searching across threads (searchAllMessagesForUserId), the
953
+ // targetMessage's order is only meaningful within its own thread, so we
954
+ // can't apply it as a database-level filter. We still apply it post-fetch
955
+ // for same-thread results below.
956
+ const restrictOrderInDb = !args.searchAllMessagesForUserId && targetMessage;
921
957
  const messages = await ctx.db
922
958
  .query("messages")
923
959
  .withSearchIndex("text_search", (q) =>
@@ -926,22 +962,27 @@ export const textSearch = query({
926
962
  : q.search("text", text).eq("threadId", args.threadId!),
927
963
  )
928
964
  // Just in case tool messages slip through
965
+ // eslint-disable-next-line @convex-dev/no-filter-in-query -- we can't do this in the search index, but text search ideally isn't for really old orders
929
966
  .filter((q) => {
930
967
  const qq = q.eq(q.field("tool"), false);
931
- if (order) {
932
- return q.and(qq, q.lte(q.field("order"), order));
968
+ if (restrictOrderInDb) {
969
+ return q.and(qq, q.lte(q.field("order"), targetMessage.order));
933
970
  }
934
971
  return qq;
935
972
  })
936
973
  .take(args.limit);
937
974
  return messages
938
- .filter(
939
- (m) =>
940
- !targetMessage ||
975
+ .filter((m) => {
976
+ if (!targetMessage) return true;
977
+ // Order is only meaningful within the same thread; cross-thread
978
+ // results have independent order sequences and should pass through.
979
+ if (m.threadId !== targetMessage.threadId) return true;
980
+ return (
941
981
  m.order < targetMessage.order ||
942
982
  (m.order === targetMessage.order &&
943
- m.stepOrder < targetMessage.stepOrder),
944
- )
983
+ m.stepOrder < targetMessage.stepOrder)
984
+ );
985
+ })
945
986
  .map(publicMessage);
946
987
  },
947
988
  returns: v.array(vMessageDoc),
@@ -964,12 +1005,13 @@ export const getMessageSearchFields = query({
964
1005
  embedding?: number[] | undefined;
965
1006
  embeddingModel?: string | undefined;
966
1007
  }> => {
967
- const message = await ctx.db.get(args.messageId);
1008
+ const message = await ctx.db.get("messages", args.messageId);
968
1009
  const text = message?.text;
969
1010
  let embedding = undefined;
970
1011
  let embeddingModel = undefined;
971
1012
  if (message?.embeddingId) {
972
- const target = await ctx.db.get(message.embeddingId);
1013
+ const { tableName } = getVectorIdInfo(ctx, message.embeddingId);
1014
+ const target = await ctx.db.get(tableName, message.embeddingId);
973
1015
  embedding = target?.vector;
974
1016
  embeddingModel = target?.model;
975
1017
  }
@@ -21,8 +21,10 @@ import { stream } from "convex-helpers/server/stream";
21
21
  import { mergedStream } from "convex-helpers/server/stream";
22
22
  import { paginator } from "convex-helpers/server/pagination";
23
23
  import type { WithoutSystemFields } from "convex/server";
24
- import { deriveUIMessagesFromDeltas } from "../deltas.js";
25
- import { fromUIMessages } from "../UIMessages.js";
24
+ import {
25
+ getPersistedUIMessageChunkParts,
26
+ projectPersistedUIMessageChunks,
27
+ } from "../streaming/materializePersistedUIMessageChunks.js";
26
28
 
27
29
  const SECOND = 1000;
28
30
  const MINUTE = 60 * SECOND;
@@ -38,7 +40,7 @@ export const addDelta = mutation({
38
40
  args: deltaValidator,
39
41
  returns: v.boolean(),
40
42
  handler: async (ctx, args) => {
41
- const stream = await ctx.db.get(args.streamId);
43
+ const stream = await ctx.db.get("streamingMessages", args.streamId);
42
44
  if (!stream) {
43
45
  console.warn("Stream not found", args.streamId);
44
46
  return false;
@@ -101,7 +103,9 @@ export const create = mutation({
101
103
  internal.streams.timeoutStream,
102
104
  { streamId },
103
105
  );
104
- await ctx.db.patch(streamId, { state: { ...state, timeoutFnId } });
106
+ await ctx.db.patch("streamingMessages", streamId, {
107
+ state: { ...state, timeoutFnId },
108
+ });
105
109
  return streamId;
106
110
  },
107
111
  });
@@ -197,7 +201,7 @@ async function abortById(
197
201
  finalDelta?: WithoutSystemFields<Doc<"streamDeltas">>;
198
202
  },
199
203
  ) {
200
- const stream = await ctx.db.get(args.streamId);
204
+ const stream = await ctx.db.get("streamingMessages", args.streamId);
201
205
  if (!stream) {
202
206
  throw new Error(`Stream not found: ${args.streamId}`);
203
207
  }
@@ -208,7 +212,7 @@ async function abortById(
208
212
  return false;
209
213
  }
210
214
  await cleanupTimeoutFn(ctx, stream);
211
- await ctx.db.patch(args.streamId, {
215
+ await ctx.db.patch("streamingMessages", args.streamId, {
212
216
  state: { kind: "aborted", reason: args.reason },
213
217
  });
214
218
  return true;
@@ -219,7 +223,10 @@ async function cleanupTimeoutFn(
219
223
  stream: Doc<"streamingMessages">,
220
224
  ) {
221
225
  if (stream.state.kind === "streaming" && stream.state.timeoutFnId) {
222
- const timeoutFn = await ctx.db.system.get(stream.state.timeoutFnId);
226
+ const timeoutFn = await ctx.db.system.get(
227
+ "_scheduled_functions",
228
+ stream.state.timeoutFnId,
229
+ );
223
230
  if (timeoutFn?.state.kind === "pending") {
224
231
  await ctx.scheduler.cancel(stream.state.timeoutFnId);
225
232
  }
@@ -246,7 +253,7 @@ export async function finishHandler(
246
253
  if (args.finalDelta) {
247
254
  await ctx.db.insert("streamDeltas", args.finalDelta);
248
255
  }
249
- const stream = await ctx.db.get(args.streamId);
256
+ const stream = await ctx.db.get("streamingMessages", args.streamId);
250
257
  if (!stream) {
251
258
  throw new Error(`Stream not found: ${args.streamId}`);
252
259
  }
@@ -262,7 +269,7 @@ export async function finishHandler(
262
269
  api.streams.deleteStreamAsync,
263
270
  { streamId: args.streamId },
264
271
  );
265
- await ctx.db.patch(args.streamId, {
272
+ await ctx.db.patch("streamingMessages", args.streamId, {
266
273
  state: { kind: "finished", endedAt: Date.now(), cleanupFnId },
267
274
  });
268
275
  }
@@ -279,7 +286,7 @@ async function heartbeatStream(
279
286
  ctx: MutationCtx,
280
287
  args: { streamId: Id<"streamingMessages"> },
281
288
  ): Promise<void> {
282
- const stream = await ctx.db.get(args.streamId);
289
+ const stream = await ctx.db.get("streamingMessages", args.streamId);
283
290
  if (!stream) {
284
291
  console.warn("Stream not found", args.streamId);
285
292
  return;
@@ -294,7 +301,10 @@ async function heartbeatStream(
294
301
  if (!stream.state.timeoutFnId) {
295
302
  throw new Error("Stream has no timeout function");
296
303
  }
297
- const timeoutFn = await ctx.db.system.get(stream.state.timeoutFnId);
304
+ const timeoutFn = await ctx.db.system.get(
305
+ "_scheduled_functions",
306
+ stream.state.timeoutFnId,
307
+ );
298
308
  if (!timeoutFn) {
299
309
  throw new Error("Timeout function not found");
300
310
  }
@@ -307,7 +317,7 @@ async function heartbeatStream(
307
317
  internal.streams.timeoutStream,
308
318
  { streamId: args.streamId },
309
319
  );
310
- await ctx.db.patch(args.streamId, {
320
+ await ctx.db.patch("streamingMessages", args.streamId, {
311
321
  state: { kind: "streaming", lastHeartbeat: Date.now(), timeoutFnId },
312
322
  });
313
323
  }
@@ -316,12 +326,12 @@ export const timeoutStream = internalMutation({
316
326
  args: { streamId: v.id("streamingMessages") },
317
327
  returns: v.null(),
318
328
  handler: async (ctx, args) => {
319
- const stream = await ctx.db.get(args.streamId);
329
+ const stream = await ctx.db.get("streamingMessages", args.streamId);
320
330
  if (!stream || stream.state.kind !== "streaming") {
321
331
  console.warn("Stream not found", args.streamId);
322
332
  return;
323
333
  }
324
- await ctx.db.patch(args.streamId, {
334
+ await ctx.db.patch("streamingMessages", args.streamId, {
325
335
  state: { kind: "aborted", reason: "timeout" },
326
336
  });
327
337
  },
@@ -338,15 +348,23 @@ async function deletePageForStreamId(
338
348
  numItems: MAX_DELTAS_PER_REQUEST,
339
349
  cursor: args.cursor ?? null,
340
350
  });
341
- await Promise.all(deltas.page.map((d) => ctx.db.delete(d._id)));
351
+ await Promise.all(
352
+ deltas.page.map((d) => ctx.db.delete("streamDeltas", d._id)),
353
+ );
342
354
  if (deltas.isDone) {
343
- const stream = await ctx.db.get(args.streamId);
355
+ const stream = await ctx.db.get("streamingMessages", args.streamId);
344
356
  if (stream) {
345
357
  await cleanupTimeoutFn(ctx, stream);
346
358
  if (stream.state.kind === "finished" && stream.state.cleanupFnId) {
347
- await ctx.scheduler.cancel(stream.state.cleanupFnId);
359
+ const scheduledFunction = await ctx.db.system.get(
360
+ "_scheduled_functions",
361
+ stream.state.cleanupFnId,
362
+ );
363
+ if (scheduledFunction?.state.kind === "pending") {
364
+ await ctx.scheduler.cancel(stream.state.cleanupFnId);
365
+ }
348
366
  }
349
- await ctx.db.delete(args.streamId);
367
+ await ctx.db.delete("streamingMessages", args.streamId);
350
368
  }
351
369
  }
352
370
  return deltas;
@@ -434,7 +452,7 @@ export const deleteAllStreamsForThreadIdAsync = mutation({
434
452
  },
435
453
  );
436
454
  } else {
437
- await ctx.db.delete(args.threadId);
455
+ await ctx.db.delete("threads", args.threadId);
438
456
  }
439
457
  return result;
440
458
  },
@@ -520,7 +538,13 @@ export async function getStreamingMessagesWithMetadata(
520
538
  stepOrder,
521
539
  }: { threadId: Id<"threads">; order: number; stepOrder: number },
522
540
  metadata: { status: "success" | "failed"; error?: string },
523
- ): Promise<MessageWithMetadataInternal[]> {
541
+ ): Promise<{
542
+ messages: MessageWithMetadataInternal[];
543
+ materializationFailures: Array<{
544
+ streamId: Id<"streamingMessages">;
545
+ reason: string;
546
+ }>;
547
+ }> {
524
548
  // See if there are any streaming messages for this order
525
549
  const streamingMessages = await getStreamingMessages(
526
550
  ctx,
@@ -528,50 +552,42 @@ export async function getStreamingMessagesWithMetadata(
528
552
  order,
529
553
  stepOrder,
530
554
  );
531
- const messages = (
532
- await Promise.all(
533
- streamingMessages.map(async (streamingMessage) => {
534
- const deltas = await ctx.db
535
- .query("streamDeltas")
536
- .withIndex("streamId_start_end", (q) =>
537
- q.eq("streamId", streamingMessage._id),
538
- )
539
- .take(1000);
540
- const uiMessages = await deriveUIMessagesFromDeltas(
541
- threadId,
542
- [publicStreamMessage(streamingMessage)],
543
- deltas,
544
- );
555
+ const materializedStreams = await Promise.all(
556
+ streamingMessages.map(async (streamingMessage) => {
557
+ const deltas = await ctx.db
558
+ .query("streamDeltas")
559
+ .withIndex("streamId_start_end", (q) =>
560
+ q.eq("streamId", streamingMessage._id),
561
+ )
562
+ .take(1000);
563
+ try {
564
+ const streamMessage = publicStreamMessage(streamingMessage);
565
+ const { parts } = getPersistedUIMessageChunkParts(deltas);
545
566
  // We don't save messages that have already been saved
546
567
  const numToSkip = stepOrder - streamingMessage.stepOrder;
547
- const messages = await Promise.all(
548
- (await fromUIMessages(uiMessages, streamingMessage))
549
- .slice(numToSkip)
550
- .filter((m) => m.message !== undefined)
551
- .map(async (msg) => {
552
- return {
553
- ...pick(msg, [
554
- "message",
555
- "fileIds",
556
- "status",
557
- "finishReason",
558
- "model",
559
- "provider",
560
- "providerMetadata",
561
- "sources",
562
- "reasoning",
563
- "reasoningDetails",
564
- "usage",
565
- "warnings",
566
- "error",
567
- ]),
568
- ...metadata,
569
- } as MessageWithMetadataInternal;
570
- }),
571
- );
572
- return messages;
573
- }),
574
- )
575
- ).flat();
576
- return messages;
568
+ return {
569
+ messages: projectPersistedUIMessageChunks(
570
+ streamMessage,
571
+ parts,
572
+ metadata,
573
+ ).slice(numToSkip),
574
+ failure: undefined,
575
+ };
576
+ } catch (error) {
577
+ return {
578
+ messages: [],
579
+ failure: {
580
+ streamId: streamingMessage._id,
581
+ reason: error instanceof Error ? error.message : String(error),
582
+ },
583
+ };
584
+ }
585
+ }),
586
+ );
587
+ return {
588
+ messages: materializedStreams.flatMap(({ messages }) => messages),
589
+ materializationFailures: materializedStreams.flatMap(({ failure }) =>
590
+ failure ? [failure] : [],
591
+ ),
592
+ };
577
593
  }
@@ -3,8 +3,11 @@ import { paginator } from "convex-helpers/server/pagination";
3
3
  import { partial } from "convex-helpers/validators";
4
4
  import { paginationOptsValidator } from "convex/server";
5
5
  import type { ObjectType } from "convex/values";
6
- import { type ThreadDoc, vThreadDoc } from "../client/index.js";
7
- import { vPaginationResult } from "../validators.js";
6
+ import {
7
+ type ThreadDoc,
8
+ vPaginationResult,
9
+ vThreadDoc,
10
+ } from "../validators.js";
8
11
  import { api, internal } from "./_generated/api.js";
9
12
  import type { Doc } from "./_generated/dataModel.js";
10
13
  import {
@@ -32,7 +35,7 @@ function publicThread(thread: Doc<"threads">): ThreadDoc {
32
35
  export const getThread = query({
33
36
  args: { threadId: v.id("threads") },
34
37
  handler: async (ctx, args) => {
35
- return publicThreadOrNull(await ctx.db.get(args.threadId));
38
+ return publicThreadOrNull(await ctx.db.get("threads", args.threadId));
36
39
  },
37
40
  returns: v.union(vThreadDoc, v.null()),
38
41
  });
@@ -66,7 +69,7 @@ export const createThread = mutation({
66
69
  ...args,
67
70
  status: "active",
68
71
  });
69
- return publicThread((await ctx.db.get(threadId))!);
72
+ return publicThread((await ctx.db.get("threads", threadId))!);
70
73
  },
71
74
  returns: vThreadDoc,
72
75
  });
@@ -84,10 +87,10 @@ export const updateThread = mutation({
84
87
  patch: v.object(partial(pick(vThread.fields, threadFieldsSupportingPatch))),
85
88
  },
86
89
  handler: async (ctx, args) => {
87
- const thread = await ctx.db.get(args.threadId);
90
+ const thread = await ctx.db.get("threads", args.threadId);
88
91
  assert(thread, `Thread ${args.threadId} not found`);
89
- await ctx.db.patch(args.threadId, args.patch);
90
- return publicThread((await ctx.db.get(args.threadId))!);
92
+ await ctx.db.patch("threads", args.threadId, args.patch);
93
+ return publicThread((await ctx.db.get("threads", args.threadId))!);
91
94
  },
92
95
  returns: vThreadDoc,
93
96
  });
@@ -231,9 +234,9 @@ async function deletePageForThreadIdHandler(
231
234
  });
232
235
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
233
236
  if (messages.isDone) {
234
- const thread = await ctx.db.get(args.threadId);
237
+ const thread = await ctx.db.get("threads", args.threadId);
235
238
  if (thread) {
236
- await ctx.db.delete(args.threadId);
239
+ await ctx.db.delete("threads", args.threadId);
237
240
  }
238
241
  }
239
242
  return {
@@ -227,7 +227,7 @@ async function deletePageForUserId(
227
227
 
228
228
  if (streamResult.isDone) {
229
229
  // Streams are done, delete the thread and reset for next thread
230
- await ctx.db.delete(threadInProgress);
230
+ await ctx.db.delete("threads", threadInProgress);
231
231
  threadInProgress = null;
232
232
  messagesCursor = null;
233
233
  streamsInProgress = false;
@@ -256,7 +256,7 @@ export const getThreadUserId = internalQuery({
256
256
  },
257
257
  returns: v.union(v.string(), v.null()),
258
258
  handler: async (ctx, args) => {
259
- const thread = await ctx.db.get(args.threadId);
259
+ const thread = await ctx.db.get("threads", args.threadId);
260
260
  return thread?.userId ?? null;
261
261
  },
262
262
  });