@convex-dev/agent 0.0.1-alpha.1 → 0.0.1-alpha.3

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 (70) hide show
  1. package/README.md +293 -6
  2. package/dist/commonjs/client/index.d.ts +520 -96
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +210 -131
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/client/types.d.ts +3 -0
  7. package/dist/commonjs/client/types.d.ts.map +1 -1
  8. package/dist/commonjs/component/messages.d.ts +57 -56
  9. package/dist/commonjs/component/messages.d.ts.map +1 -1
  10. package/dist/commonjs/component/messages.js +184 -147
  11. package/dist/commonjs/component/messages.js.map +1 -1
  12. package/dist/commonjs/component/schema.d.ts +260 -256
  13. package/dist/commonjs/component/schema.d.ts.map +1 -1
  14. package/dist/commonjs/component/schema.js +25 -18
  15. package/dist/commonjs/component/schema.js.map +1 -1
  16. package/dist/commonjs/component/vector/index.d.ts +4 -4
  17. package/dist/commonjs/component/vector/index.d.ts.map +1 -1
  18. package/dist/commonjs/component/vector/index.js +12 -10
  19. package/dist/commonjs/component/vector/index.js.map +1 -1
  20. package/dist/commonjs/component/vector/tables.d.ts +3 -3
  21. package/dist/commonjs/component/vector/tables.js +6 -6
  22. package/dist/commonjs/component/vector/tables.js.map +1 -1
  23. package/dist/commonjs/mapping.d.ts +6 -1
  24. package/dist/commonjs/mapping.d.ts.map +1 -1
  25. package/dist/commonjs/mapping.js +25 -0
  26. package/dist/commonjs/mapping.js.map +1 -1
  27. package/dist/commonjs/validators.d.ts +1376 -1
  28. package/dist/commonjs/validators.d.ts.map +1 -1
  29. package/dist/commonjs/validators.js +28 -1
  30. package/dist/commonjs/validators.js.map +1 -1
  31. package/dist/esm/client/index.d.ts +520 -96
  32. package/dist/esm/client/index.d.ts.map +1 -1
  33. package/dist/esm/client/index.js +210 -131
  34. package/dist/esm/client/index.js.map +1 -1
  35. package/dist/esm/client/types.d.ts +3 -0
  36. package/dist/esm/client/types.d.ts.map +1 -1
  37. package/dist/esm/component/messages.d.ts +57 -56
  38. package/dist/esm/component/messages.d.ts.map +1 -1
  39. package/dist/esm/component/messages.js +184 -147
  40. package/dist/esm/component/messages.js.map +1 -1
  41. package/dist/esm/component/schema.d.ts +260 -256
  42. package/dist/esm/component/schema.d.ts.map +1 -1
  43. package/dist/esm/component/schema.js +25 -18
  44. package/dist/esm/component/schema.js.map +1 -1
  45. package/dist/esm/component/vector/index.d.ts +4 -4
  46. package/dist/esm/component/vector/index.d.ts.map +1 -1
  47. package/dist/esm/component/vector/index.js +12 -10
  48. package/dist/esm/component/vector/index.js.map +1 -1
  49. package/dist/esm/component/vector/tables.d.ts +3 -3
  50. package/dist/esm/component/vector/tables.js +6 -6
  51. package/dist/esm/component/vector/tables.js.map +1 -1
  52. package/dist/esm/mapping.d.ts +6 -1
  53. package/dist/esm/mapping.d.ts.map +1 -1
  54. package/dist/esm/mapping.js +25 -0
  55. package/dist/esm/mapping.js.map +1 -1
  56. package/dist/esm/validators.d.ts +1376 -1
  57. package/dist/esm/validators.d.ts.map +1 -1
  58. package/dist/esm/validators.js +28 -1
  59. package/dist/esm/validators.js.map +1 -1
  60. package/package.json +3 -3
  61. package/src/client/index.ts +384 -276
  62. package/src/client/types.ts +4 -0
  63. package/src/component/_generated/api.d.ts +54 -53
  64. package/src/component/messages.ts +219 -177
  65. package/src/component/schema.ts +25 -18
  66. package/src/component/vector/index.ts +13 -11
  67. package/src/component/vector/tables.ts +6 -6
  68. package/src/mapping.ts +46 -11
  69. package/src/validators.test.ts +9 -0
  70. package/src/validators.ts +36 -1
@@ -2,19 +2,14 @@ import { assert, omit, pick } from "convex-helpers";
2
2
  import { paginator } from "convex-helpers/server/pagination";
3
3
  import { mergedStream, stream } from "convex-helpers/server/stream";
4
4
  import { nullable, partial } from "convex-helpers/validators";
5
- import { Infer, ObjectType } from "convex/values";
5
+ import { ObjectType } from "convex/values";
6
6
  import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
7
7
  import {
8
- Message,
9
- MessageWithFileAndId,
10
- vAssistantMessage,
11
- vChatStatus,
8
+ vThreadStatus,
12
9
  vMessageStatus,
13
10
  vMessageWithFileAndId,
14
11
  vSearchOptions,
15
- vStep,
16
12
  vStepWithMessagesWithFileAndId,
17
- vToolMessage,
18
13
  } from "../validators.js";
19
14
  import { api, internal } from "./_generated/api.js";
20
15
  import { Doc, Id } from "./_generated/dataModel.js";
@@ -35,27 +30,27 @@ import {
35
30
  vVectorId,
36
31
  } from "./vector/tables.js";
37
32
 
38
- export const getChat = query({
39
- args: { chatId: v.id("chats") },
33
+ export const getThread = query({
34
+ args: { threadId: v.id("threads") },
40
35
  handler: async (ctx, args) => {
41
- return ctx.db.get(args.chatId);
36
+ return ctx.db.get(args.threadId);
42
37
  },
43
- returns: v.union(v.doc("chats"), v.null()),
38
+ returns: v.union(v.doc("threads"), v.null()),
44
39
  });
45
40
 
46
- export const getChatsByUserId = query({
41
+ export const getThreadsByUserId = query({
47
42
  args: {
48
43
  userId: v.string(),
49
44
  // Note: the other arguments cannot change from when the cursor was created.
50
45
  cursor: v.optional(v.union(v.string(), v.null())),
51
46
  limit: v.optional(v.number()),
52
47
  offset: v.optional(v.number()),
53
- statuses: v.optional(v.array(vChatStatus)),
48
+ statuses: v.optional(v.array(vThreadStatus)),
54
49
  },
55
50
  handler: async (ctx, args) => {
56
51
  const streams = (args.statuses ?? ["active"]).map((status) =>
57
52
  stream(ctx.db, schema)
58
- .query("chats")
53
+ .query("threads")
59
54
  .withIndex("status_userId_order", (q) =>
60
55
  q
61
56
  .eq("status", status)
@@ -63,55 +58,55 @@ export const getChatsByUserId = query({
63
58
  .gte("order", args.offset ?? 0)
64
59
  )
65
60
  );
66
- const chats = await mergedStream(streams, ["order", "stepOrder"]).paginate({
61
+ const threads = await mergedStream(streams, ["order"]).paginate({
67
62
  numItems: args.limit ?? 100,
68
63
  cursor: args.cursor ?? null,
69
64
  });
70
65
  return {
71
- chats: chats.page,
72
- continueCursor: chats.continueCursor,
73
- isDone: chats.isDone,
66
+ threads: threads.page,
67
+ continueCursor: threads.continueCursor,
68
+ isDone: threads.isDone,
74
69
  };
75
70
  },
76
71
  returns: v.object({
77
- chats: v.array(v.doc("chats")),
72
+ threads: v.array(v.doc("threads")),
78
73
  continueCursor: v.string(),
79
74
  isDone: v.boolean(),
80
75
  }),
81
76
  });
82
77
 
83
- const vChat = schema.tables.chats.validator;
84
- const statuses = vChat.fields.status.members.map((m) => m.value);
78
+ const vThread = schema.tables.threads.validator;
79
+ const statuses = vThread.fields.status.members.map((m) => m.value);
85
80
 
86
- export const createChat = mutation({
87
- args: omit(vChat.fields, ["order", "status"]),
81
+ export const createThread = mutation({
82
+ args: omit(vThread.fields, ["order", "status"]),
88
83
  handler: async (ctx, args) => {
89
84
  const streams = statuses.map((status) =>
90
85
  stream(ctx.db, schema)
91
- .query("chats")
86
+ .query("threads")
92
87
  .withIndex("status_userId_order", (q) =>
93
88
  q.eq("status", status).eq("userId", args.userId)
94
89
  )
95
90
  .order("desc")
96
91
  );
97
- const latestChat = await mergedStream(streams, ["order"]).first();
98
- const order = (latestChat?.order ?? -1) + 1;
99
- const chatId = await ctx.db.insert("chats", {
92
+ const latestThread = await mergedStream(streams, ["order"]).first();
93
+ const order = (latestThread?.order ?? -1) + 1;
94
+ const threadId = await ctx.db.insert("threads", {
100
95
  ...args,
101
96
  order,
102
97
  status: "active",
103
98
  });
104
- return (await ctx.db.get(chatId))!;
99
+ return (await ctx.db.get(threadId))!;
105
100
  },
106
- returns: v.doc("chats"),
101
+ returns: v.doc("threads"),
107
102
  });
108
103
 
109
- export const updateChat = mutation({
104
+ export const updateThread = mutation({
110
105
  args: {
111
- chatId: v.id("chats"),
106
+ threadId: v.id("threads"),
112
107
  patch: v.object(
113
108
  partial(
114
- pick(vChat.fields, [
109
+ pick(vThread.fields, [
115
110
  "title",
116
111
  "summary",
117
112
  "defaultSystemPrompt",
@@ -121,43 +116,43 @@ export const updateChat = mutation({
121
116
  ),
122
117
  },
123
118
  handler: async (ctx, args) => {
124
- const chat = await ctx.db.get(args.chatId);
125
- assert(chat, `Chat ${args.chatId} not found`);
126
- await ctx.db.patch(args.chatId, args.patch);
127
- return (await ctx.db.get(args.chatId))!;
119
+ const thread = await ctx.db.get(args.threadId);
120
+ assert(thread, `Thread ${args.threadId} not found`);
121
+ await ctx.db.patch(args.threadId, args.patch);
122
+ return (await ctx.db.get(args.threadId))!;
128
123
  },
129
- returns: v.doc("chats"),
124
+ returns: v.doc("threads"),
130
125
  });
131
126
 
132
- export const archiveChat = mutation({
133
- args: { chatId: v.id("chats") },
127
+ export const archiveThread = mutation({
128
+ args: { threadId: v.id("threads") },
134
129
  handler: async (ctx, args) => {
135
- const chat = await ctx.db.get(args.chatId);
136
- assert(chat, `Chat ${args.chatId} not found`);
137
- await ctx.db.patch(args.chatId, { status: "archived" });
138
- return (await ctx.db.get(args.chatId))!;
130
+ const thread = await ctx.db.get(args.threadId);
131
+ assert(thread, `Thread ${args.threadId} not found`);
132
+ await ctx.db.patch(args.threadId, { status: "archived" });
133
+ return (await ctx.db.get(args.threadId))!;
139
134
  },
140
- returns: v.doc("chats"),
135
+ returns: v.doc("threads"),
141
136
  });
142
137
 
143
138
  export const deleteAllForUserId = action({
144
139
  args: { userId: v.string() },
145
140
  handler: async (ctx, args) => {
146
141
  let messagesCursor = null;
147
- let chatsCursor = null;
142
+ let threadsCursor = null;
148
143
  let isDone = false;
149
144
  while (!isDone) {
150
145
  const result: {
151
146
  messagesCursor: string;
152
- chatsCursor: string | null;
147
+ threadsCursor: string | null;
153
148
  isDone: boolean;
154
149
  } = await ctx.runMutation(internal.messages._deletePageForUserId, {
155
150
  userId: args.userId,
156
151
  messagesCursor,
157
- chatsCursor,
152
+ threadsCursor,
158
153
  });
159
154
  messagesCursor = result.messagesCursor;
160
- chatsCursor = result.chatsCursor;
155
+ threadsCursor = result.threadsCursor;
161
156
  isDone = result.isDone;
162
157
  }
163
158
  },
@@ -172,7 +167,7 @@ export const deleteAllForUserIdAsync = mutation({
172
167
  const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
173
168
  userId: args.userId,
174
169
  messagesCursor: null,
175
- chatsCursor: null,
170
+ threadsCursor: null,
176
171
  });
177
172
  return isDone;
178
173
  },
@@ -182,12 +177,12 @@ export const deleteAllForUserIdAsync = mutation({
182
177
  const deleteAllArgs = {
183
178
  userId: v.string(),
184
179
  messagesCursor: nullable(v.string()),
185
- chatsCursor: nullable(v.string()),
180
+ threadsCursor: nullable(v.string()),
186
181
  };
187
182
  type DeleteAllArgs = ObjectType<typeof deleteAllArgs>;
188
183
  const deleteAllReturns = {
189
184
  messagesCursor: v.string(),
190
- chatsCursor: nullable(v.string()),
185
+ threadsCursor: nullable(v.string()),
191
186
  isDone: v.boolean(),
192
187
  };
193
188
  type DeleteAllReturns = ObjectType<typeof deleteAllReturns>;
@@ -210,7 +205,7 @@ async function deleteAllFroUserIdAsyncHandler(
210
205
  {
211
206
  userId: args.userId,
212
207
  messagesCursor: result.messagesCursor,
213
- chatsCursor: result.chatsCursor,
208
+ threadsCursor: result.threadsCursor,
214
209
  }
215
210
  );
216
211
  }
@@ -228,20 +223,20 @@ async function deletePageForUserId(
228
223
  ): Promise<DeleteAllReturns> {
229
224
  const streams = statuses.map((status) =>
230
225
  stream(ctx.db, schema)
231
- .query("chats")
226
+ .query("threads")
232
227
  .withIndex("status_userId_order", (q) =>
233
228
  q.eq("status", status).eq("userId", args.userId)
234
229
  )
235
230
  .order("desc")
236
231
  );
237
- const chatStreams = mergedStream(streams, ["order"]);
238
- const messages = await chatStreams
232
+ const threadStreams = mergedStream(streams, ["order"]);
233
+ const messages = await threadStreams
239
234
  .flatMap(
240
235
  async (c) =>
241
236
  stream(ctx.db, schema)
242
237
  .query("messages")
243
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
244
- q.eq("chatId", c._id).eq("status", "success")
238
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
239
+ q.eq("threadId", c._id).eq("status", "success")
245
240
  ),
246
241
  ["tool", "order", "stepOrder"]
247
242
  )
@@ -251,20 +246,20 @@ async function deletePageForUserId(
251
246
  });
252
247
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
253
248
  if (messages.isDone) {
254
- const chats = await chatStreams.paginate({
249
+ const threads = await threadStreams.paginate({
255
250
  numItems: 100,
256
- cursor: args.chatsCursor ?? null,
251
+ cursor: args.threadsCursor ?? null,
257
252
  });
258
- await Promise.all(chats.page.map((c) => ctx.db.delete(c._id)));
253
+ await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
259
254
  return {
260
255
  messagesCursor: messages.continueCursor,
261
- chatsCursor: chats.continueCursor,
262
- isDone: chats.isDone,
256
+ threadsCursor: threads.continueCursor,
257
+ isDone: threads.isDone,
263
258
  };
264
259
  }
265
260
  return {
266
261
  messagesCursor: messages.continueCursor,
267
- chatsCursor: null,
262
+ threadsCursor: null,
268
263
  isDone: messages.isDone,
269
264
  };
270
265
  }
@@ -279,66 +274,66 @@ async function deleteMessage(ctx: MutationCtx, messageDoc: Doc<"messages">) {
279
274
  }
280
275
  }
281
276
 
282
- const deleteChatArgs = {
283
- chatId: v.id("chats"),
277
+ const deleteThreadArgs = {
278
+ threadId: v.id("threads"),
284
279
  cursor: v.optional(v.string()),
285
280
  limit: v.optional(v.number()),
286
281
  };
287
- type DeleteChatArgs = ObjectType<typeof deleteChatArgs>;
288
- const deleteChatReturns = {
282
+ type DeleteThreadArgs = ObjectType<typeof deleteThreadArgs>;
283
+ const deleteThreadReturns = {
289
284
  cursor: v.string(),
290
285
  isDone: v.boolean(),
291
286
  };
292
- type DeleteChatReturns = ObjectType<typeof deleteChatReturns>;
287
+ type DeleteThreadReturns = ObjectType<typeof deleteThreadReturns>;
293
288
 
294
- export const deleteAllForChatIdSync = action({
295
- args: deleteChatArgs,
289
+ export const deleteAllForThreadIdSync = action({
290
+ args: deleteThreadArgs,
296
291
  handler: async (ctx, args) => {
297
- const result: DeleteChatReturns = await ctx.runMutation(
298
- internal.messages._deletePageForChatId,
299
- { chatId: args.chatId, cursor: args.cursor, limit: args.limit }
292
+ const result: DeleteThreadReturns = await ctx.runMutation(
293
+ internal.messages._deletePageForThreadId,
294
+ { threadId: args.threadId, cursor: args.cursor, limit: args.limit }
300
295
  );
301
296
  return result;
302
297
  },
303
- returns: deleteChatReturns,
298
+ returns: deleteThreadReturns,
304
299
  });
305
300
 
306
- export const deleteAllForChatIdAsync = mutation({
307
- args: deleteChatArgs,
301
+ export const deleteAllForThreadIdAsync = mutation({
302
+ args: deleteThreadArgs,
308
303
  handler: async (ctx, args) => {
309
- const result = await deletePageForChatIdHandler(ctx, args);
304
+ const result = await deletePageForThreadIdHandler(ctx, args);
310
305
  if (!result.isDone) {
311
- await ctx.scheduler.runAfter(0, api.messages.deleteAllForChatIdAsync, {
312
- chatId: args.chatId,
306
+ await ctx.scheduler.runAfter(0, api.messages.deleteAllForThreadIdAsync, {
307
+ threadId: args.threadId,
313
308
  cursor: result.cursor,
314
309
  });
315
310
  }
316
311
  return result;
317
312
  },
318
- returns: deleteChatReturns,
313
+ returns: deleteThreadReturns,
319
314
  });
320
315
 
321
- export const _deletePageForChatId = internalMutation({
322
- args: deleteChatArgs,
323
- handler: deletePageForChatIdHandler,
324
- returns: deleteChatReturns,
316
+ export const _deletePageForThreadId = internalMutation({
317
+ args: deleteThreadArgs,
318
+ handler: deletePageForThreadIdHandler,
319
+ returns: deleteThreadReturns,
325
320
  });
326
321
 
327
- async function deletePageForChatIdHandler(
322
+ async function deletePageForThreadIdHandler(
328
323
  ctx: MutationCtx,
329
- args: DeleteChatArgs
330
- ): Promise<DeleteChatReturns> {
324
+ args: DeleteThreadArgs
325
+ ): Promise<DeleteThreadReturns> {
331
326
  const messages = await stream(ctx.db, schema)
332
327
  .query("messages")
333
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
334
- q.eq("chatId", args.chatId).eq("status", "success")
328
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
329
+ q.eq("threadId", args.threadId).eq("status", "success")
335
330
  )
336
331
  .paginate({
337
332
  numItems: args.limit ?? 100,
338
333
  cursor: args.cursor ?? null,
339
334
  });
340
335
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
341
- await ctx.db.delete(args.chatId);
336
+ await ctx.db.delete(args.threadId);
342
337
  return {
343
338
  cursor: messages.continueCursor,
344
339
  isDone: messages.isDone,
@@ -377,7 +372,8 @@ export const messageStatuses = vMessageDoc.fields.status.members.map(
377
372
  );
378
373
 
379
374
  const addMessagesArgs = {
380
- chatId: v.id("chats"),
375
+ userId: v.optional(v.string()),
376
+ threadId: v.optional(v.id("threads")),
381
377
  stepId: v.optional(v.id("steps")),
382
378
  parentMessageId: v.optional(v.id("messages")),
383
379
  messages: v.array(vMessageWithFileAndId),
@@ -398,15 +394,22 @@ async function addMessagesHandler(
398
394
  ctx: MutationCtx,
399
395
  args: ObjectType<typeof addMessagesArgs>
400
396
  ) {
401
- const chat = await ctx.db.get(args.chatId);
402
- assert(chat, `Chat ${args.chatId} not found`);
397
+ let userId = args.userId;
398
+ const threadId = args.threadId;
399
+ if (!userId && args.threadId) {
400
+ const thread = await ctx.db.get(args.threadId);
401
+ assert(thread, `Thread ${args.threadId} not found`);
402
+ userId = thread._id;
403
+ }
403
404
  const { failPendingSteps, pending, messages, parentMessageId, ...rest } =
404
405
  args;
405
- if (failPendingSteps) {
406
+ const parent = parentMessageId && (await ctx.db.get(parentMessageId));
407
+ if (failPendingSteps && parent?.status !== "pending") {
408
+ assert(args.threadId, "threadId is required to fail pending steps");
406
409
  const pendingMessages = await ctx.db
407
410
  .query("messages")
408
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
409
- q.eq("chatId", args.chatId).eq("status", "pending")
411
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
412
+ q.eq("threadId", threadId).eq("status", "pending")
410
413
  )
411
414
  .collect();
412
415
  await Promise.all(
@@ -415,14 +418,8 @@ async function addMessagesHandler(
415
418
  )
416
419
  );
417
420
  }
418
- let order: number | undefined;
419
- const maxMessage = await getMaxMessage(ctx, args.chatId);
420
- // If the previous message isn't our parent, we make a new thread.
421
- const threadId =
422
- parentMessageId && maxMessage?._id === parentMessageId
423
- ? maxMessage.threadId ?? parentMessageId
424
- : parentMessageId;
425
- order = maxMessage?.order ?? -1;
421
+ const maxMessage = await getMaxMessage(ctx, threadId, userId);
422
+ let order = maxMessage?.order ?? -1;
426
423
  const toReturn: Doc<"messages">[] = [];
427
424
  if (messages.length > 0) {
428
425
  for (const { message, fileId, id } of messages) {
@@ -433,8 +430,8 @@ async function addMessagesHandler(
433
430
  const text = extractText(message);
434
431
  const messageId = await ctx.db.insert("messages", {
435
432
  ...rest,
436
- threadId,
437
- userId: chat.userId,
433
+ parentMessageId,
434
+ userId,
438
435
  message,
439
436
  id,
440
437
  order,
@@ -449,22 +446,41 @@ async function addMessagesHandler(
449
446
  return { messages: toReturn };
450
447
  }
451
448
 
452
- async function getMaxMessage(ctx: QueryCtx, chatId: Id<"chats">) {
453
- return mergedStream(
454
- ["success" as const, "pending" as const].map((status) =>
455
- stream(ctx.db, schema)
456
- .query("messages")
457
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
458
- q.eq("chatId", chatId).eq("status", status).eq("tool", false)
459
- )
460
- .order("desc")
461
- ),
462
- ["order", "stepOrder"]
463
- ).first();
449
+ async function getMaxMessage(
450
+ ctx: QueryCtx,
451
+ threadId: Id<"threads"> | undefined,
452
+ userId: string | undefined
453
+ ) {
454
+ assert(threadId || userId, "One of threadId or userId is required");
455
+ if (threadId) {
456
+ return mergedStream(
457
+ ["success" as const, "pending" as const].map((status) =>
458
+ stream(ctx.db, schema)
459
+ .query("messages")
460
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
461
+ q.eq("threadId", threadId).eq("status", status).eq("tool", false)
462
+ )
463
+ .order("desc")
464
+ ),
465
+ ["order", "stepOrder"]
466
+ ).first();
467
+ } else {
468
+ return mergedStream(
469
+ ["success" as const, "pending" as const].map((status) =>
470
+ stream(ctx.db, schema)
471
+ .query("messages")
472
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
473
+ q.eq("userId", userId).eq("status", status).eq("tool", false)
474
+ )
475
+ .order("desc")
476
+ ),
477
+ ["order", "stepOrder"]
478
+ ).first();
479
+ }
464
480
  }
465
481
 
466
482
  const addStepsArgs = {
467
- chatId: v.id("chats"),
483
+ threadId: v.id("threads"),
468
484
  messageId: v.id("messages"),
469
485
  steps: v.array(vStepWithMessagesWithFileAndId),
470
486
  failPendingSteps: v.optional(v.boolean()),
@@ -501,7 +517,7 @@ async function addStepsHandler(
501
517
  let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
502
518
  for (const { step, messages } of args.steps) {
503
519
  const stepId = await ctx.db.insert("steps", {
504
- chatId: args.chatId,
520
+ threadId: args.threadId,
505
521
  parentMessageId: args.messageId,
506
522
  order,
507
523
  stepOrder: nextStepOrder,
@@ -509,7 +525,7 @@ async function addStepsHandler(
509
525
  step,
510
526
  });
511
527
  await addMessagesHandler(ctx, {
512
- chatId: args.chatId,
528
+ threadId: args.threadId,
513
529
  parentMessageId: args.messageId,
514
530
  stepId,
515
531
  messages,
@@ -518,7 +534,8 @@ async function addStepsHandler(
518
534
  pending: step.finishReason === "stop" ? false : true,
519
535
  failPendingSteps: false,
520
536
  });
521
- if (step.finishReason === "stop") {
537
+ // We don't commit if the parent is still pending.
538
+ if (step.finishReason === "stop" && parentMessage.status === "success") {
522
539
  await commitMessageHandler(ctx, { messageId: args.messageId });
523
540
  }
524
541
  steps.push((await ctx.db.get(stepId))!);
@@ -573,9 +590,9 @@ async function commitMessageHandler(
573
590
  [true, false].map((tool) =>
574
591
  stream(ctx.db, schema)
575
592
  .query("messages")
576
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
593
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
577
594
  q
578
- .eq("chatId", message.chatId)
595
+ .eq("threadId", message.threadId)
579
596
  .eq("status", "pending")
580
597
  .eq("tool", tool)
581
598
  .eq("order", order)
@@ -585,12 +602,13 @@ async function commitMessageHandler(
585
602
  ).collect();
586
603
  for (const message of messages) {
587
604
  await ctx.db.patch(message._id, { status: "success" });
605
+ // TODO: recursively commit steps & messages that might depend on this one.
588
606
  }
589
607
  }
590
608
 
591
- export const getChatMessages = query({
609
+ export const getThreadMessages = query({
592
610
  args: {
593
- chatId: v.id("chats"),
611
+ threadId: v.id("threads"),
594
612
  isTool: v.optional(v.boolean()),
595
613
  order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
596
614
  limit: v.optional(v.number()),
@@ -610,9 +628,9 @@ export const getChatMessages = query({
610
628
  statuses.map((status) =>
611
629
  stream(ctx.db, schema)
612
630
  .query("messages")
613
- .withIndex("chatId_status_tool_order_stepOrder", (q) => {
631
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => {
614
632
  const qq = q
615
- .eq("chatId", args.chatId)
633
+ .eq("threadId", args.threadId)
616
634
  .eq("status", status)
617
635
  .eq("tool", tool);
618
636
  if (parent) {
@@ -646,19 +664,19 @@ export const getChatMessages = query({
646
664
  export const searchMessages = action({
647
665
  args: {
648
666
  userId: v.optional(v.string()),
649
- chatId: v.optional(v.id("chats")),
667
+ threadId: v.optional(v.id("threads")),
650
668
  parentMessageId: v.optional(v.id("messages")),
651
669
  ...vSearchOptions.fields,
652
670
  },
653
671
  returns: v.array(v.doc("messages")),
654
672
  handler: async (ctx, args): Promise<Doc<"messages">[]> => {
655
- assert(args.userId || args.chatId, "Specify userId or chatId");
673
+ assert(args.userId || args.threadId, "Specify userId or threadId");
656
674
  const limit = args.limit;
657
675
  let textSearchMessages: Doc<"messages">[] | undefined;
658
676
  if (args.text) {
659
677
  textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
660
678
  userId: args.userId,
661
- chatId: args.chatId,
679
+ threadId: args.threadId,
662
680
  text: args.text,
663
681
  limit,
664
682
  });
@@ -675,8 +693,8 @@ export const searchMessages = action({
675
693
  vector: args.vector,
676
694
  filter: (q) =>
677
695
  args.userId
678
- ? q.eq("model_kind_userId", [model, "chat", args.userId])
679
- : q.eq("model_kind_chatId", [model, "chat", args.chatId!]),
696
+ ? q.eq("model_kind_userId", [model, "thread", args.userId])
697
+ : q.eq("model_kind_threadId", [model, "thread", args.threadId!]),
680
698
  limit,
681
699
  })
682
700
  ).filter((v) => v._score > 0.5);
@@ -697,7 +715,7 @@ export const searchMessages = action({
697
715
  internal.messages._fetchVectorMessages,
698
716
  {
699
717
  userId: args.userId,
700
- chatId: args.chatId,
718
+ threadId: args.threadId,
701
719
  vectorIds,
702
720
  textSearchMessages: textSearchMessages?.filter(
703
721
  (m) => !vectorIds.includes(m.embeddingId!)
@@ -716,7 +734,7 @@ export const searchMessages = action({
716
734
  export const _fetchVectorMessages = internalQuery({
717
735
  args: {
718
736
  userId: v.optional(v.string()),
719
- chatId: v.optional(v.id("chats")),
737
+ threadId: v.optional(v.id("threads")),
720
738
  vectorIds: v.array(vVectorId),
721
739
  textSearchMessages: v.optional(v.array(v.doc("messages"))),
722
740
  messageRange: v.object({ before: v.number(), after: v.number() }),
@@ -727,18 +745,19 @@ export const _fetchVectorMessages = internalQuery({
727
745
  handler: async (ctx, args): Promise<Doc<"messages">[]> => {
728
746
  const parent =
729
747
  args.parentMessageId && (await ctx.db.get(args.parentMessageId));
748
+ const { userId, threadId } = args;
749
+ assert(userId || threadId, "Specify userId or threadId to search");
730
750
  let messages = (
731
751
  await Promise.all(
732
752
  args.vectorIds.map((embeddingId) =>
733
753
  ctx.db
734
754
  .query("messages")
735
755
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
736
- .filter(
737
- (q) =>
738
- args.userId
739
- ? q.eq("userId", args.userId)
740
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
741
- q.eq("chatId", args.chatId as any) // not sure why it's failing...
756
+ .filter((q) =>
757
+ userId
758
+ ? q.eq("userId", userId)
759
+ : // eslint-disable-next-line @typescript-eslint/no-explicit-any
760
+ q.eq("threadId", threadId as any)
742
761
  )
743
762
  .first()
744
763
  )
@@ -752,48 +771,71 @@ export const _fetchVectorMessages = internalQuery({
752
771
  messages.sort((a, b) => a.order! - b.order!);
753
772
  messages = messages.slice(0, args.limit);
754
773
  // Fetch the surrounding messages
755
- const included: Record<Id<"chats">, Set<number>> = {};
774
+ if (!threadId) {
775
+ return messages.sort((a, b) => a.order - b.order);
776
+ }
777
+ const included: Record<string, Set<number>> = {};
756
778
  for (const m of messages) {
757
- if (!included[m.chatId]) {
758
- included[m.chatId] = new Set();
779
+ const searchId = m.threadId ?? m.userId!;
780
+ if (!included[searchId]) {
781
+ included[searchId] = new Set();
759
782
  }
760
- included[m.chatId].add(m.order!);
783
+ included[searchId].add(m.order!);
761
784
  }
762
- const ranges: Record<Id<"chats">, Doc<"messages">[]> = {};
785
+ const ranges: Record<string, Doc<"messages">[]> = {};
763
786
  const { before, after } = args.messageRange;
764
787
  for (const m of messages) {
788
+ const searchId = m.threadId ?? m.userId!;
765
789
  const order = m.order!;
766
790
  let earliest = order - before;
767
791
  let latest = order + after;
768
792
  for (; earliest <= latest; earliest++) {
769
- if (!included[m.chatId].has(earliest)) {
793
+ if (!included[searchId].has(earliest)) {
770
794
  break;
771
795
  }
772
796
  }
773
797
  for (; latest >= earliest; latest--) {
774
- if (!included[m.chatId].has(latest)) {
798
+ if (!included[searchId].has(latest)) {
775
799
  break;
776
800
  }
777
801
  }
778
802
  for (let i = earliest; i <= latest; i++) {
779
- included[m.chatId].add(i);
803
+ included[searchId].add(i);
780
804
  }
781
805
  if (earliest !== latest) {
782
- const surrounding = await ctx.db
783
- .query("messages")
784
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
785
- q
786
- .eq("chatId", m.chatId)
787
- .eq("status", "success")
788
- .eq("tool", false)
789
- .gt("order", earliest)
790
- .lt("order", latest)
791
- )
792
- .collect();
793
- if (!ranges[m.chatId]) {
794
- ranges[m.chatId] = [];
806
+ if (m.threadId) {
807
+ const surrounding = await ctx.db
808
+ .query("messages")
809
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
810
+ q
811
+ .eq("threadId", m.threadId)
812
+ .eq("status", "success")
813
+ .eq("tool", false)
814
+ .gt("order", earliest)
815
+ .lt("order", latest)
816
+ )
817
+ .collect();
818
+ if (!ranges[searchId]) {
819
+ ranges[searchId] = [];
820
+ }
821
+ ranges[searchId].push(...surrounding);
822
+ } else {
823
+ const surrounding = await ctx.db
824
+ .query("messages")
825
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
826
+ q
827
+ .eq("userId", m.userId!)
828
+ .eq("status", "success")
829
+ .eq("tool", false)
830
+ .gt("order", earliest)
831
+ .lt("order", latest)
832
+ )
833
+ .collect();
834
+ if (!ranges[searchId]) {
835
+ ranges[searchId] = [];
836
+ }
837
+ ranges[searchId].push(...surrounding);
795
838
  }
796
- ranges[m.chatId].push(...surrounding);
797
839
  }
798
840
  }
799
841
  return Object.values(ranges)
@@ -806,19 +848,19 @@ export const _fetchVectorMessages = internalQuery({
806
848
  // excluding duplicates in later ranges.
807
849
  export const textSearch = query({
808
850
  args: {
809
- chatId: v.optional(v.id("chats")),
851
+ threadId: v.optional(v.id("threads")),
810
852
  userId: v.optional(v.string()),
811
853
  text: v.string(),
812
854
  limit: v.number(),
813
855
  },
814
856
  handler: async (ctx, args) => {
815
- assert(args.userId || args.chatId, "Specify userId or chatId");
857
+ assert(args.userId || args.threadId, "Specify userId or threadId");
816
858
  const messages = await ctx.db
817
859
  .query("messages")
818
860
  .withSearchIndex("text_search", (q) =>
819
861
  args.userId
820
862
  ? q.search("text", args.text).eq("userId", args.userId)
821
- : q.search("text", args.text).eq("chatId", args.chatId!)
863
+ : q.search("text", args.text).eq("threadId", args.threadId!)
822
864
  )
823
865
  .take(args.limit);
824
866
  return messages;
@@ -871,9 +913,9 @@ export const textSearch = query({
871
913
 
872
914
  // const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
873
915
 
874
- // export const getChatMessagesPage = query({
916
+ // export const getThreadMessagesPage = query({
875
917
  // args: {
876
- // threadId: v.string(),
918
+ // parentMessageId: v.string(),
877
919
  // selectBy: v.optional(vSelectBy),
878
920
  // // Unimplemented and as far I can tell no storage provider has either.
879
921
  // // memoryConfig: v.optional(vMemoryConfig),
@@ -881,7 +923,7 @@ export const textSearch = query({
881
923
  // handler: async (ctx, args): Promise<SerializedMessage[]> => {
882
924
  // const messages = await ctx.db
883
925
  // .query("messages")
884
- // .withIndex("threadId", (q) => q.eq("threadId", args.threadId))
926
+ // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", args.parentMessageId))
885
927
  // .order("desc")
886
928
  // .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
887
929
 
@@ -935,9 +977,9 @@ export const textSearch = query({
935
977
  // ranges.map(async (range) => {
936
978
  // return await ctx.db
937
979
  // .query("messages")
938
- // .withIndex("threadId", (q) =>
980
+ // .withIndex("parentMessageId", (q) =>
939
981
  // q
940
- // .eq("threadId", args.threadId)
982
+ // .eq("parentMessageId", args.parentMessageId)
941
983
  // .gte("threadOrder", range.start)
942
984
  // .lte("threadOrder", range.end)
943
985
  // )
@@ -954,21 +996,21 @@ export const textSearch = query({
954
996
  // export const saveMessages = mutation({
955
997
  // args: { messages: v.array(vSerializedMessage) },
956
998
  // handler: async (ctx, args) => {
957
- // const messagesByThreadId: Record<string, SerializedMessage[]> = {};
999
+ // const messagesByParentMessageId: Record<string, SerializedMessage[]> = {};
958
1000
  // for (const message of args.messages) {
959
- // messagesByThreadId[message.threadId] = [
960
- // ...(messagesByThreadId[message.threadId] ?? []),
1001
+ // messagesByParentMessageId[message.parentMessageId] = [
1002
+ // ...(messagesByParentMessageId[message.parentMessageId] ?? []),
961
1003
  // message,
962
1004
  // ];
963
1005
  // }
964
- // for (const threadId in messagesByThreadId) {
1006
+ // for (const parentMessageId in messagesByParentMessageId) {
965
1007
  // const lastMessage = await ctx.db
966
1008
  // .query("messages")
967
- // .withIndex("threadId", (q) => q.eq("threadId", threadId))
1009
+ // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", parentMessageId))
968
1010
  // .order("desc")
969
1011
  // .first();
970
1012
  // let threadOrder = lastMessage?.threadOrder ?? 0;
971
- // for (const message of messagesByThreadId[threadId]) {
1013
+ // for (const message of messagesByParentMessageId[parentMessageId]) {
972
1014
  // threadOrder++;
973
1015
  // await ctx.db.insert("messages", {
974
1016
  // ...message,