@convex-dev/agent 0.5.0-alpha.1 → 0.6.0-alpha.1

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 (235) hide show
  1. package/MIGRATION.md +153 -0
  2. package/README.md +32 -27
  3. package/dist/UIMessages.d.ts +46 -0
  4. package/dist/UIMessages.d.ts.map +1 -0
  5. package/dist/UIMessages.js +546 -0
  6. package/dist/UIMessages.js.map +1 -0
  7. package/dist/client/createTool.d.ts +126 -27
  8. package/dist/client/createTool.d.ts.map +1 -1
  9. package/dist/client/createTool.js +67 -12
  10. package/dist/client/createTool.js.map +1 -1
  11. package/dist/client/defaultComponent.d.ts +11 -0
  12. package/dist/client/defaultComponent.d.ts.map +1 -0
  13. package/dist/client/defaultComponent.js +7 -0
  14. package/dist/client/defaultComponent.js.map +1 -0
  15. package/dist/client/definePlaygroundAPI.d.ts +1335 -204
  16. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  17. package/dist/client/definePlaygroundAPI.js +52 -28
  18. package/dist/client/definePlaygroundAPI.js.map +1 -1
  19. package/dist/client/files.d.ts +20 -7
  20. package/dist/client/files.d.ts.map +1 -1
  21. package/dist/client/files.js +68 -11
  22. package/dist/client/files.js.map +1 -1
  23. package/dist/client/index.d.ts +1116 -978
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +332 -747
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/messages.d.ts +461 -0
  28. package/dist/client/messages.d.ts.map +1 -0
  29. package/dist/client/messages.js +106 -0
  30. package/dist/client/messages.js.map +1 -0
  31. package/dist/client/mockModel.d.ts +42 -0
  32. package/dist/client/mockModel.d.ts.map +1 -0
  33. package/dist/client/mockModel.js +182 -0
  34. package/dist/client/mockModel.js.map +1 -0
  35. package/dist/client/saveInputMessages.d.ts +20 -0
  36. package/dist/client/saveInputMessages.d.ts.map +1 -0
  37. package/dist/client/saveInputMessages.js +58 -0
  38. package/dist/client/saveInputMessages.js.map +1 -0
  39. package/dist/client/search.d.ts +350 -39
  40. package/dist/client/search.d.ts.map +1 -1
  41. package/dist/client/search.js +350 -39
  42. package/dist/client/search.js.map +1 -1
  43. package/dist/client/start.d.ts +84 -0
  44. package/dist/client/start.d.ts.map +1 -0
  45. package/dist/client/start.js +185 -0
  46. package/dist/client/start.js.map +1 -0
  47. package/dist/client/streamText.d.ts +46 -0
  48. package/dist/client/streamText.d.ts.map +1 -0
  49. package/dist/client/streamText.js +117 -0
  50. package/dist/client/streamText.js.map +1 -0
  51. package/dist/client/streaming.d.ts +3716 -32
  52. package/dist/client/streaming.d.ts.map +1 -1
  53. package/dist/client/streaming.js +161 -59
  54. package/dist/client/streaming.js.map +1 -1
  55. package/dist/client/threads.d.ts +46 -0
  56. package/dist/client/threads.d.ts.map +1 -0
  57. package/dist/client/threads.js +49 -0
  58. package/dist/client/threads.js.map +1 -0
  59. package/dist/client/types.d.ts +266 -128
  60. package/dist/client/types.d.ts.map +1 -1
  61. package/dist/client/utils.d.ts +4 -0
  62. package/dist/client/utils.d.ts.map +1 -0
  63. package/dist/client/utils.js +21 -0
  64. package/dist/client/utils.js.map +1 -0
  65. package/dist/component/_generated/api.d.ts +24 -2178
  66. package/dist/component/_generated/api.d.ts.map +1 -1
  67. package/dist/component/_generated/api.js +10 -1
  68. package/dist/component/_generated/api.js.map +1 -1
  69. package/dist/component/_generated/component.d.ts +3120 -0
  70. package/dist/component/_generated/component.d.ts.map +1 -0
  71. package/dist/component/_generated/component.js +11 -0
  72. package/dist/component/_generated/component.js.map +1 -0
  73. package/dist/component/_generated/dataModel.d.ts +4 -18
  74. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  75. package/dist/component/_generated/dataModel.js +11 -0
  76. package/dist/component/_generated/dataModel.js.map +1 -0
  77. package/dist/component/_generated/server.d.ts +10 -38
  78. package/dist/component/_generated/server.d.ts.map +1 -1
  79. package/dist/component/_generated/server.js +9 -5
  80. package/dist/component/_generated/server.js.map +1 -1
  81. package/dist/component/files.d.ts +16 -10
  82. package/dist/component/files.d.ts.map +1 -1
  83. package/dist/component/files.js +10 -2
  84. package/dist/component/files.js.map +1 -1
  85. package/dist/component/messages.d.ts +2578 -366
  86. package/dist/component/messages.d.ts.map +1 -1
  87. package/dist/component/messages.js +397 -154
  88. package/dist/component/messages.js.map +1 -1
  89. package/dist/component/schema.d.ts +5697 -3584
  90. package/dist/component/schema.d.ts.map +1 -1
  91. package/dist/component/schema.js +18 -41
  92. package/dist/component/schema.js.map +1 -1
  93. package/dist/component/streams.d.ts +39 -339
  94. package/dist/component/streams.d.ts.map +1 -1
  95. package/dist/component/streams.js +114 -73
  96. package/dist/component/streams.js.map +1 -1
  97. package/dist/component/threads.d.ts +13 -13
  98. package/dist/component/users.d.ts +7 -7
  99. package/dist/component/vector/index.d.ts +1 -1
  100. package/dist/component/vector/index.d.ts.map +1 -1
  101. package/dist/component/vector/index.js +1 -3
  102. package/dist/component/vector/index.js.map +1 -1
  103. package/dist/deltas.d.ts +43 -0
  104. package/dist/deltas.d.ts.map +1 -0
  105. package/dist/deltas.js +446 -0
  106. package/dist/deltas.js.map +1 -0
  107. package/dist/mapping.d.ts +38 -20
  108. package/dist/mapping.d.ts.map +1 -1
  109. package/dist/mapping.js +365 -97
  110. package/dist/mapping.js.map +1 -1
  111. package/dist/react/SmoothText.d.ts +5 -0
  112. package/dist/react/SmoothText.d.ts.map +1 -0
  113. package/dist/react/SmoothText.js +6 -0
  114. package/dist/react/SmoothText.js.map +1 -0
  115. package/dist/react/index.d.ts +5 -77
  116. package/dist/react/index.d.ts.map +1 -1
  117. package/dist/react/index.js +6 -160
  118. package/dist/react/index.js.map +1 -1
  119. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  120. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  121. package/dist/react/optimisticallySendMessage.js +35 -9
  122. package/dist/react/optimisticallySendMessage.js.map +1 -1
  123. package/dist/react/types.d.ts +4 -18
  124. package/dist/react/types.d.ts.map +1 -1
  125. package/dist/react/useDeltaStreams.d.ts +10 -0
  126. package/dist/react/useDeltaStreams.d.ts.map +1 -0
  127. package/dist/react/useDeltaStreams.js +106 -0
  128. package/dist/react/useDeltaStreams.js.map +1 -0
  129. package/dist/react/useSmoothText.d.ts +13 -12
  130. package/dist/react/useSmoothText.d.ts.map +1 -1
  131. package/dist/react/useSmoothText.js +32 -15
  132. package/dist/react/useSmoothText.js.map +1 -1
  133. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  134. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  135. package/dist/react/useStreamingUIMessages.js +92 -0
  136. package/dist/react/useStreamingUIMessages.js.map +1 -0
  137. package/dist/react/useThreadMessages.d.ts +104 -0
  138. package/dist/react/useThreadMessages.d.ts.map +1 -0
  139. package/dist/react/useThreadMessages.js +148 -0
  140. package/dist/react/useThreadMessages.js.map +1 -0
  141. package/dist/react/useUIMessages.d.ts +96 -0
  142. package/dist/react/useUIMessages.d.ts.map +1 -0
  143. package/dist/react/useUIMessages.js +108 -0
  144. package/dist/react/useUIMessages.js.map +1 -0
  145. package/dist/shared.d.ts +20 -4
  146. package/dist/shared.d.ts.map +1 -1
  147. package/dist/shared.js +45 -8
  148. package/dist/shared.js.map +1 -1
  149. package/dist/validators.d.ts +22981 -5666
  150. package/dist/validators.d.ts.map +1 -1
  151. package/dist/validators.js +245 -137
  152. package/dist/validators.js.map +1 -1
  153. package/package.json +101 -51
  154. package/src/UIMessages.combineUIMessages.test.ts +239 -0
  155. package/src/UIMessages.test.ts +273 -0
  156. package/src/UIMessages.ts +739 -0
  157. package/src/client/approval.test.ts +350 -0
  158. package/src/client/createTool.ts +291 -76
  159. package/src/client/defaultComponent.ts +17 -0
  160. package/src/client/definePlaygroundAPI.ts +67 -31
  161. package/src/client/files.ts +100 -20
  162. package/src/client/index.test.ts +40 -85
  163. package/src/client/index.ts +638 -1289
  164. package/src/client/messages.ts +237 -0
  165. package/src/client/mockModel.ts +252 -0
  166. package/src/client/saveInputMessages.test.ts +583 -0
  167. package/src/client/saveInputMessages.ts +101 -0
  168. package/src/client/search.test.ts +1207 -0
  169. package/src/client/search.ts +581 -70
  170. package/src/client/start.ts +327 -0
  171. package/src/client/streamText.ts +187 -0
  172. package/src/client/streaming.test.ts +186 -0
  173. package/src/client/streaming.ts +241 -97
  174. package/src/client/threads.ts +83 -0
  175. package/src/client/types.ts +370 -219
  176. package/src/client/utils.ts +27 -0
  177. package/src/component/_generated/api.ts +64 -0
  178. package/src/component/_generated/component.ts +4902 -0
  179. package/src/component/_generated/{server.d.ts → server.ts} +33 -21
  180. package/src/component/files.ts +11 -2
  181. package/src/component/messages.test.ts +195 -51
  182. package/src/component/messages.ts +500 -201
  183. package/src/component/schema.ts +20 -46
  184. package/src/component/setup.test.ts +7 -0
  185. package/src/component/streams.ts +184 -83
  186. package/src/component/users.test.ts +0 -1
  187. package/src/component/vector/index.ts +1 -3
  188. package/src/deltas.test.ts +626 -0
  189. package/src/deltas.ts +569 -0
  190. package/src/fromUIMessages.test.ts +497 -0
  191. package/src/mapping.test.ts +180 -6
  192. package/src/mapping.ts +479 -162
  193. package/src/react/SmoothText.tsx +9 -0
  194. package/src/react/index.ts +10 -230
  195. package/src/react/optimisticallySendMessage.ts +55 -12
  196. package/src/react/types.ts +6 -39
  197. package/src/react/useDeltaStreams.ts +160 -0
  198. package/src/react/useSmoothText.ts +56 -36
  199. package/src/react/useStreamingUIMessages.ts +143 -0
  200. package/src/react/useThreadMessages.ts +262 -0
  201. package/src/react/useUIMessages.test.ts +255 -0
  202. package/src/react/useUIMessages.ts +195 -0
  203. package/src/shared.ts +88 -12
  204. package/src/test.ts +18 -0
  205. package/src/toUIMessages.test.ts +1269 -0
  206. package/src/validators.test.ts +18 -19
  207. package/src/validators.ts +325 -185
  208. package/dist/client/_generated/_ignore.d.ts +0 -1
  209. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  210. package/dist/client/_generated/_ignore.js +0 -3
  211. package/dist/client/_generated/_ignore.js.map +0 -1
  212. package/dist/client/listMessages.d.ts +0 -22
  213. package/dist/client/listMessages.d.ts.map +0 -1
  214. package/dist/client/listMessages.js +0 -25
  215. package/dist/client/listMessages.js.map +0 -1
  216. package/dist/package.json +0 -3
  217. package/dist/react/deltas.d.ts +0 -26
  218. package/dist/react/deltas.d.ts.map +0 -1
  219. package/dist/react/deltas.js +0 -384
  220. package/dist/react/deltas.js.map +0 -1
  221. package/dist/react/toUIMessages.d.ts +0 -15
  222. package/dist/react/toUIMessages.d.ts.map +0 -1
  223. package/dist/react/toUIMessages.js +0 -211
  224. package/dist/react/toUIMessages.js.map +0 -1
  225. package/src/client/listMessages.ts +0 -38
  226. package/src/component/_generated/api.d.ts +0 -2202
  227. package/src/component/_generated/api.js +0 -23
  228. package/src/component/_generated/server.js +0 -90
  229. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  230. package/src/react/deltas.test.ts +0 -315
  231. package/src/react/deltas.ts +0 -478
  232. package/src/react/toUIMessages.test.ts +0 -420
  233. package/src/react/toUIMessages.ts +0 -253
  234. package/src/vitest.config.ts +0 -7
  235. /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { defineSchema, defineTable } from "convex/server";
2
- import { type Infer, v } from "convex/values";
2
+ import { v } from "convex/values";
3
3
  import {
4
4
  vThreadStatus,
5
5
  vMessage,
@@ -11,11 +11,9 @@ import {
11
11
  vProviderOptions,
12
12
  vProviderMetadata,
13
13
  vReasoningDetails,
14
- vTextStreamPart,
15
14
  } from "../validators.js";
16
15
  import { typedV } from "convex-helpers/validators";
17
16
  import vectorTables, { vVectorId } from "./vector/tables.js";
18
- import { omit } from "convex-helpers";
19
17
 
20
18
  export const schema = defineSchema({
21
19
  threads: defineTable({
@@ -29,12 +27,8 @@ export const schema = defineSchema({
29
27
  order: /*DEPRECATED*/ v.optional(v.number()),
30
28
  })
31
29
  .index("userId", ["userId"])
32
- .searchIndex("title", {
33
- searchField: "title",
34
- filterFields: ["userId"],
35
- }),
30
+ .searchIndex("title", { searchField: "title", filterFields: ["userId"] }),
36
31
  messages: defineTable({
37
- id: v.optional(v.string()), // external id, e.g. from Vercel AI SDK
38
32
  userId: v.optional(v.string()), // useful for searching across threads
39
33
  threadId: v.id("threads"),
40
34
  order: v.number(),
@@ -60,11 +54,13 @@ export const schema = defineSchema({
60
54
  usage: v.optional(vUsage),
61
55
  providerMetadata: v.optional(vProviderMetadata), // Received from model
62
56
  sources: v.optional(v.array(vSource)),
63
- reasoning: v.optional(v.string()),
64
- reasoningDetails: v.optional(vReasoningDetails),
65
57
  warnings: v.optional(v.array(vLanguageModelCallWarning)),
66
58
  finishReason: v.optional(vFinishReason),
59
+ // Likely deprecated soon
60
+ reasoning: v.optional(v.string()),
61
+ reasoningDetails: v.optional(vReasoningDetails),
67
62
  // DEPRECATED
63
+ id: v.optional(v.string()), // external id, e.g. from Vercel AI SDK
68
64
  parentMessageId: v.optional(v.id("messages")),
69
65
  stepId: v.optional(v.string()),
70
66
  files: v.optional(v.array(v.any())),
@@ -95,6 +91,12 @@ export const schema = defineSchema({
95
91
  model: v.optional(v.string()),
96
92
  provider: v.optional(v.string()),
97
93
  providerOptions: v.optional(vProviderOptions), // Sent to model
94
+ // The data format for the deltas. By default, we use UIMessageChunks.
95
+ // This format dictates how the messages are materialized for capturing
96
+ // partial messages during failure, as well as on the client side.
97
+ format: v.optional(
98
+ v.union(v.literal("UIMessageChunk"), v.literal("TextStreamPart")),
99
+ ),
98
100
 
99
101
  threadId: v.id("threads"),
100
102
  order: v.number(),
@@ -115,10 +117,7 @@ export const schema = defineSchema({
115
117
  endedAt: v.number(),
116
118
  cleanupFnId: v.optional(v.id("_scheduled_functions")),
117
119
  }),
118
- v.object({
119
- kind: v.literal("aborted"),
120
- reason: v.string(),
121
- }),
120
+ v.object({ kind: v.literal("aborted"), reason: v.string() }),
122
121
  ),
123
122
  })
124
123
  // There should only be one per "order" index
@@ -135,7 +134,7 @@ export const schema = defineSchema({
135
134
  // the indexes work like: 0 <first> 1 <second> 2 <third> 3 ...
136
135
  start: v.number(), // inclusive
137
136
  end: v.number(), // exclusive
138
- parts: v.array(vTextStreamPart),
137
+ parts: v.array(v.any()),
139
138
  }).index("streamId_start_end", ["streamId", "start", "end"]),
140
139
 
141
140
  memories: defineTable({
@@ -150,7 +149,9 @@ export const schema = defineSchema({
150
149
 
151
150
  files: defineTable({
152
151
  storageId: v.string(),
153
- mimeType: v.string(),
152
+ mediaType: v.optional(v.string()),
153
+ /** @deprecated Use `mediaType` instead. */
154
+ mimeType: v.optional(v.string()),
154
155
  filename: v.optional(v.string()),
155
156
  hash: v.string(),
156
157
  refcount: v.number(),
@@ -162,39 +163,12 @@ export const schema = defineSchema({
162
163
  // To authenticate playground usage
163
164
  // Delete a key to invalidate it
164
165
  // Provide a name to easily identify it / invalidate by name
165
- apiKeys: defineTable({
166
- name: v.optional(v.string()),
167
- }).index("name", ["name"]),
166
+ apiKeys: defineTable({ name: v.optional(v.string()) }).index("name", [
167
+ "name",
168
+ ]),
168
169
  });
169
170
 
170
171
  export const vv = typedV(schema);
171
172
  export { vv as v };
172
173
 
173
- // Public
174
- export const vThreadDoc = v.object({
175
- _id: v.string(),
176
- _creationTime: v.number(),
177
- userId: v.optional(v.string()), // Unset for anonymous
178
- title: v.optional(v.string()),
179
- summary: v.optional(v.string()),
180
- status: vThreadStatus,
181
- });
182
- export type ThreadDoc = Infer<typeof vThreadDoc>;
183
-
184
- export const vMessageDoc = v.object({
185
- _id: v.string(),
186
- _creationTime: v.number(),
187
- ...omit(schema.tables.messages.validator.fields, [
188
- "parentMessageId",
189
- "stepId",
190
- "files",
191
- ]),
192
- // Overwrite all the types that have a v.id validator
193
- // Outside of the component, they are strings
194
- threadId: v.string(),
195
- embeddingId: v.optional(v.string()),
196
- fileIds: v.optional(v.array(v.string())),
197
- });
198
- export type MessageDoc = Infer<typeof vMessageDoc>;
199
-
200
174
  export default schema;
@@ -1,5 +1,12 @@
1
1
  /// <reference types="vite/client" />
2
+ import { convexTest } from "convex-test";
2
3
  import { test } from "vitest";
4
+ import schema from "./schema.js";
3
5
  export const modules = import.meta.glob("./**/*.*s");
4
6
 
7
+ export function initConvexTest() {
8
+ const t = convexTest(schema, modules);
9
+ return t;
10
+ }
11
+
5
12
  test("setup", () => {});
@@ -1,7 +1,9 @@
1
1
  import { omit, pick } from "convex-helpers";
2
2
  import { v } from "convex/values";
3
3
  import {
4
+ type MessageWithMetadataInternal,
4
5
  type StreamDelta,
6
+ type StreamMessage,
5
7
  vStreamDelta,
6
8
  vStreamMessage,
7
9
  } from "../validators.js";
@@ -18,6 +20,9 @@ import schema from "./schema.js";
18
20
  import { stream } from "convex-helpers/server/stream";
19
21
  import { mergedStream } from "convex-helpers/server/stream";
20
22
  import { paginator } from "convex-helpers/server/pagination";
23
+ import type { WithoutSystemFields } from "convex/server";
24
+ import { deriveUIMessagesFromDeltas } from "../deltas.js";
25
+ import { fromUIMessages } from "../UIMessages.js";
21
26
 
22
27
  const SECOND = 1000;
23
28
  const MINUTE = 60 * SECOND;
@@ -33,13 +38,16 @@ export const addDelta = mutation({
33
38
  args: deltaValidator,
34
39
  returns: v.boolean(),
35
40
  handler: async (ctx, args) => {
36
- await ctx.db.insert("streamDeltas", args);
37
- await heartbeatStream(ctx, { streamId: args.streamId });
38
41
  const stream = await ctx.db.get(args.streamId);
39
- if (stream?.state.kind !== "streaming") {
40
- console.warn(`Stream is not streaming: ${args.streamId}`);
42
+ if (!stream) {
43
+ console.warn("Stream not found", args.streamId);
44
+ return false;
45
+ }
46
+ if (stream.state.kind !== "streaming") {
41
47
  return false;
42
48
  }
49
+ await ctx.db.insert("streamDeltas", args);
50
+ await heartbeatStream(ctx, { streamId: args.streamId });
43
51
  return true;
44
52
  },
45
53
  });
@@ -48,10 +56,7 @@ export const listDeltas = query({
48
56
  args: {
49
57
  threadId: v.id("threads"),
50
58
  cursors: v.array(
51
- v.object({
52
- streamId: v.id("streamingMessages"),
53
- cursor: v.number(),
54
- }),
59
+ v.object({ streamId: v.id("streamingMessages"), cursor: v.number() }),
55
60
  ),
56
61
  },
57
62
  returns: v.array(vStreamDelta),
@@ -85,10 +90,8 @@ export const create = mutation({
85
90
  args: omit(schema.tables.streamingMessages.validator.fields, ["state"]),
86
91
  returns: v.id("streamingMessages"),
87
92
  handler: async (ctx, args) => {
88
- const state = {
89
- kind: "streaming" as const,
90
- lastHeartbeat: Date.now(),
91
- };
93
+ const state = { kind: "streaming" as const, lastHeartbeat: Date.now() };
94
+ // TODO: enforce order/stepOrder uniqueness?
92
95
  const streamId = await ctx.db.insert("streamingMessages", {
93
96
  ...args,
94
97
  state,
@@ -135,28 +138,29 @@ export const list = query({
135
138
  ["order", "stepOrder"],
136
139
  ).take(100);
137
140
 
138
- return messages.map((m) => ({
139
- streamId: m._id,
140
- status: m.state.kind,
141
- ...pick(m, [
142
- "order",
143
- "stepOrder",
144
- "userId",
145
- "agentName",
146
- "model",
147
- "provider",
148
- "providerOptions",
149
- ]),
150
- }));
141
+ return messages.map((m) => publicStreamMessage(m));
151
142
  },
152
143
  });
153
144
 
145
+ function publicStreamMessage(m: Doc<"streamingMessages">): StreamMessage {
146
+ return {
147
+ streamId: m._id,
148
+ status: m.state.kind,
149
+ ...pick(m, [
150
+ "format",
151
+ "order",
152
+ "stepOrder",
153
+ "userId",
154
+ "agentName",
155
+ "model",
156
+ "provider",
157
+ "providerOptions",
158
+ ]),
159
+ };
160
+ }
161
+
154
162
  export const abortByOrder = mutation({
155
- args: {
156
- threadId: v.id("threads"),
157
- order: v.number(),
158
- reason: v.string(),
159
- },
163
+ args: { threadId: v.id("threads"), order: v.number(), reason: v.string() },
160
164
  returns: v.boolean(),
161
165
  handler: async (ctx, args) => {
162
166
  const streams = await ctx.db
@@ -169,10 +173,7 @@ export const abortByOrder = mutation({
169
173
  )
170
174
  .take(100);
171
175
  for (const stream of streams) {
172
- await abortById(ctx, {
173
- streamId: stream._id,
174
- reason: args.reason,
175
- });
176
+ await abortById(ctx, { streamId: stream._id, reason: args.reason });
176
177
  }
177
178
  return streams.length > 0;
178
179
  },
@@ -182,6 +183,7 @@ export const abort = mutation({
182
183
  args: {
183
184
  streamId: v.id("streamingMessages"),
184
185
  reason: v.string(),
186
+ finalDelta: v.optional(deltaValidator),
185
187
  },
186
188
  returns: v.boolean(),
187
189
  handler: abortById,
@@ -189,16 +191,20 @@ export const abort = mutation({
189
191
 
190
192
  async function abortById(
191
193
  ctx: MutationCtx,
192
- args: { streamId: Id<"streamingMessages">; reason: string },
194
+ args: {
195
+ streamId: Id<"streamingMessages">;
196
+ reason: string;
197
+ finalDelta?: WithoutSystemFields<Doc<"streamDeltas">>;
198
+ },
193
199
  ) {
194
200
  const stream = await ctx.db.get(args.streamId);
195
201
  if (!stream) {
196
202
  throw new Error(`Stream not found: ${args.streamId}`);
197
203
  }
204
+ if (args.finalDelta) {
205
+ await ctx.db.insert("streamDeltas", args.finalDelta);
206
+ }
198
207
  if (stream.state.kind !== "streaming") {
199
- console.warn(
200
- `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`,
201
- );
202
208
  return false;
203
209
  }
204
210
  await cleanupTimeoutFn(ctx, stream);
@@ -220,49 +226,65 @@ async function cleanupTimeoutFn(
220
226
  }
221
227
  }
222
228
 
229
+ // No longer used from the DeltaStreamer
223
230
  export const finish = mutation({
224
231
  args: {
225
232
  streamId: v.id("streamingMessages"),
226
233
  finalDelta: v.optional(deltaValidator),
227
234
  },
228
235
  returns: v.null(),
229
- handler: async (ctx, args) => {
230
- if (args.finalDelta) {
231
- await ctx.db.insert("streamDeltas", args.finalDelta);
232
- }
233
- const stream = await ctx.db.get(args.streamId);
234
- if (!stream) {
235
- throw new Error(`Stream not found: ${args.streamId}`);
236
- }
237
- if (stream.state.kind !== "streaming") {
238
- console.warn(
239
- `Stream trying to finish but not currently streaming: ${args.streamId}`,
240
- );
241
- return;
242
- }
243
- await cleanupTimeoutFn(ctx, stream);
244
- const cleanupFnId = await ctx.scheduler.runAfter(
245
- DELETE_STREAM_DELAY,
246
- api.streams.deleteStreamAsync,
247
- { streamId: args.streamId },
248
- );
249
- await ctx.db.patch(args.streamId, {
250
- state: { kind: "finished", endedAt: Date.now(), cleanupFnId },
251
- });
236
+ handler: finishHandler,
237
+ });
238
+
239
+ export async function finishHandler(
240
+ ctx: MutationCtx,
241
+ args: {
242
+ streamId: Id<"streamingMessages">;
243
+ finalDelta?: WithoutSystemFields<Doc<"streamDeltas">>;
252
244
  },
245
+ ) {
246
+ if (args.finalDelta) {
247
+ await ctx.db.insert("streamDeltas", args.finalDelta);
248
+ }
249
+ const stream = await ctx.db.get(args.streamId);
250
+ if (!stream) {
251
+ throw new Error(`Stream not found: ${args.streamId}`);
252
+ }
253
+ if (stream.state.kind !== "streaming") {
254
+ console.warn(
255
+ `Stream trying to finish ${args.streamId} but is ${stream.state.kind}`,
256
+ );
257
+ return;
258
+ }
259
+ await cleanupTimeoutFn(ctx, stream);
260
+ const cleanupFnId = await ctx.scheduler.runAfter(
261
+ DELETE_STREAM_DELAY,
262
+ api.streams.deleteStreamAsync,
263
+ { streamId: args.streamId },
264
+ );
265
+ await ctx.db.patch(args.streamId, {
266
+ state: { kind: "finished", endedAt: Date.now(), cleanupFnId },
267
+ });
268
+ }
269
+
270
+ // TODO: use this heartbeat while streaming, every 30 seconds or so,
271
+ // then reduce the timeout to 60 seconds.
272
+ export const heartbeat = mutation({
273
+ args: { streamId: v.id("streamingMessages") },
274
+ returns: v.null(),
275
+ handler: heartbeatStream,
253
276
  });
254
277
 
255
278
  async function heartbeatStream(
256
279
  ctx: MutationCtx,
257
280
  args: { streamId: Id<"streamingMessages"> },
258
- ) {
281
+ ): Promise<void> {
259
282
  const stream = await ctx.db.get(args.streamId);
260
283
  if (!stream) {
261
284
  console.warn("Stream not found", args.streamId);
262
285
  return;
263
286
  }
264
287
  if (stream.state.kind !== "streaming") {
265
- console.warn("Stream is not streaming", args.streamId);
266
288
  return;
267
289
  }
268
290
  if (Date.now() - stream.state.lastHeartbeat < TIMEOUT_INTERVAL / 4) {
@@ -286,11 +308,7 @@ async function heartbeatStream(
286
308
  { streamId: args.streamId },
287
309
  );
288
310
  await ctx.db.patch(args.streamId, {
289
- state: {
290
- kind: "streaming",
291
- lastHeartbeat: Date.now(),
292
- timeoutFnId,
293
- },
311
+ state: { kind: "streaming", lastHeartbeat: Date.now(), timeoutFnId },
294
312
  });
295
313
  }
296
314
 
@@ -304,10 +322,7 @@ export const timeoutStream = internalMutation({
304
322
  return;
305
323
  }
306
324
  await ctx.db.patch(args.streamId, {
307
- state: {
308
- kind: "aborted",
309
- reason: "timeout",
310
- },
325
+ state: { kind: "aborted", reason: "timeout" },
311
326
  });
312
327
  },
313
328
  });
@@ -362,11 +377,7 @@ export async function deleteStreamsPageForThreadId(
362
377
  "stepOrder",
363
378
  ]).first();
364
379
  if (!streamMessage) {
365
- return {
366
- isDone: true,
367
- streamOrder: undefined,
368
- deltaCursor: undefined,
369
- };
380
+ return { isDone: true, streamOrder: undefined, deltaCursor: undefined };
370
381
  }
371
382
  const result = await deletePageForStreamId(ctx, {
372
383
  streamId: streamMessage._id,
@@ -375,11 +386,7 @@ export async function deleteStreamsPageForThreadId(
375
386
  if (result.isDone) {
376
387
  deltaCursor = undefined;
377
388
  }
378
- return {
379
- isDone: false,
380
- streamOrder: streamMessage.order,
381
- deltaCursor,
382
- };
389
+ return { isDone: false, streamOrder: streamMessage.order, deltaCursor };
383
390
  }
384
391
 
385
392
  export const deleteStreamsPageForThreadIdMutation = internalMutation({
@@ -407,7 +414,14 @@ export const deleteAllStreamsForThreadIdAsync = mutation({
407
414
  streamOrder: v.optional(v.number()),
408
415
  deltaCursor: v.optional(v.string()),
409
416
  }),
410
- handler: async (ctx, args) => {
417
+ handler: async (
418
+ ctx,
419
+ args,
420
+ ): Promise<{
421
+ isDone: boolean;
422
+ streamOrder?: number;
423
+ deltaCursor?: string;
424
+ }> => {
411
425
  const result = await deleteStreamsPageForThreadId(ctx, args);
412
426
  if (!result.isDone) {
413
427
  await ctx.scheduler.runAfter(
@@ -474,3 +488,90 @@ export const deleteAllStreamsForThreadIdSync = action({
474
488
  }
475
489
  },
476
490
  });
491
+
492
+ export async function getStreamingMessages(
493
+ ctx: MutationCtx,
494
+ threadId: Id<"threads">,
495
+ order: number,
496
+ stepOrder: number,
497
+ ): Promise<Doc<"streamingMessages">[]> {
498
+ return mergedStream(
499
+ (["aborted", "streaming", "finished"] as const).map((state) =>
500
+ stream(ctx.db, schema)
501
+ .query("streamingMessages")
502
+ .withIndex("threadId_state_order_stepOrder", (q) =>
503
+ q
504
+ .eq("threadId", threadId)
505
+ .eq("state.kind", state)
506
+ .eq("order", order)
507
+ .lte("stepOrder", stepOrder),
508
+ )
509
+ .order("desc"),
510
+ ),
511
+ ["stepOrder"],
512
+ ).take(10);
513
+ }
514
+
515
+ export async function getStreamingMessagesWithMetadata(
516
+ ctx: MutationCtx,
517
+ {
518
+ threadId,
519
+ order,
520
+ stepOrder,
521
+ }: { threadId: Id<"threads">; order: number; stepOrder: number },
522
+ metadata: { status: "success" | "failed"; error?: string },
523
+ ): Promise<MessageWithMetadataInternal[]> {
524
+ // See if there are any streaming messages for this order
525
+ const streamingMessages = await getStreamingMessages(
526
+ ctx,
527
+ threadId,
528
+ order,
529
+ stepOrder,
530
+ );
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
+ );
545
+ // We don't save messages that have already been saved
546
+ 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;
577
+ }
@@ -58,7 +58,6 @@ describe("users", () => {
58
58
 
59
59
  expect(secondPage.page).toHaveLength(2);
60
60
  // Should not have duplicate users
61
- console.log(firstPage.page, secondPage.page);
62
61
  expect(
63
62
  firstPage.page.every((user) => !secondPage.page.includes(user)),
64
63
  ).toBe(true);
@@ -35,11 +35,9 @@ export const paginate = query({
35
35
  const tableName = getVectorTableName(args.vectorDimension);
36
36
  const vectors = await paginator(ctx.db, schema)
37
37
  .query(tableName)
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
38
  .withIndex("model_table_threadId" as any, (q) =>
40
39
  args.table
41
- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
- (q.eq("model", args.targetModel) as any).eq("table", args.table)
40
+ ? (q.eq("model", args.targetModel) as any).eq("table", args.table)
43
41
  : q.eq("model", args.targetModel),
44
42
  )
45
43
  .paginate({