@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
@@ -3,74 +3,74 @@ 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
5
  import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
6
- import { vChatStatus, vMessageStatus, vMessageWithFileAndId, vSearchOptions, vStepWithMessagesWithFileAndId, } from "../validators.js";
6
+ import { vThreadStatus, vMessageStatus, vMessageWithFileAndId, vSearchOptions, vStepWithMessagesWithFileAndId, } from "../validators.js";
7
7
  import { api, internal } from "./_generated/api.js";
8
8
  import { action, internalMutation, internalQuery, mutation, query, } from "./_generated/server.js";
9
9
  import { schema, v } from "./schema.js";
10
10
  import { getVectorTableName, VectorDimensions, vVectorId, } from "./vector/tables.js";
11
- export const getChat = query({
12
- args: { chatId: v.id("chats") },
11
+ export const getThread = query({
12
+ args: { threadId: v.id("threads") },
13
13
  handler: async (ctx, args) => {
14
- return ctx.db.get(args.chatId);
14
+ return ctx.db.get(args.threadId);
15
15
  },
16
- returns: v.union(v.doc("chats"), v.null()),
16
+ returns: v.union(v.doc("threads"), v.null()),
17
17
  });
18
- export const getChatsByUserId = query({
18
+ export const getThreadsByUserId = query({
19
19
  args: {
20
20
  userId: v.string(),
21
21
  // Note: the other arguments cannot change from when the cursor was created.
22
22
  cursor: v.optional(v.union(v.string(), v.null())),
23
23
  limit: v.optional(v.number()),
24
24
  offset: v.optional(v.number()),
25
- statuses: v.optional(v.array(vChatStatus)),
25
+ statuses: v.optional(v.array(vThreadStatus)),
26
26
  },
27
27
  handler: async (ctx, args) => {
28
28
  const streams = (args.statuses ?? ["active"]).map((status) => stream(ctx.db, schema)
29
- .query("chats")
29
+ .query("threads")
30
30
  .withIndex("status_userId_order", (q) => q
31
31
  .eq("status", status)
32
32
  .eq("userId", args.userId)
33
33
  .gte("order", args.offset ?? 0)));
34
- const chats = await mergedStream(streams, ["order", "stepOrder"]).paginate({
34
+ const threads = await mergedStream(streams, ["order"]).paginate({
35
35
  numItems: args.limit ?? 100,
36
36
  cursor: args.cursor ?? null,
37
37
  });
38
38
  return {
39
- chats: chats.page,
40
- continueCursor: chats.continueCursor,
41
- isDone: chats.isDone,
39
+ threads: threads.page,
40
+ continueCursor: threads.continueCursor,
41
+ isDone: threads.isDone,
42
42
  };
43
43
  },
44
44
  returns: v.object({
45
- chats: v.array(v.doc("chats")),
45
+ threads: v.array(v.doc("threads")),
46
46
  continueCursor: v.string(),
47
47
  isDone: v.boolean(),
48
48
  }),
49
49
  });
50
- const vChat = schema.tables.chats.validator;
51
- const statuses = vChat.fields.status.members.map((m) => m.value);
52
- export const createChat = mutation({
53
- args: omit(vChat.fields, ["order", "status"]),
50
+ const vThread = schema.tables.threads.validator;
51
+ const statuses = vThread.fields.status.members.map((m) => m.value);
52
+ export const createThread = mutation({
53
+ args: omit(vThread.fields, ["order", "status"]),
54
54
  handler: async (ctx, args) => {
55
55
  const streams = statuses.map((status) => stream(ctx.db, schema)
56
- .query("chats")
56
+ .query("threads")
57
57
  .withIndex("status_userId_order", (q) => q.eq("status", status).eq("userId", args.userId))
58
58
  .order("desc"));
59
- const latestChat = await mergedStream(streams, ["order"]).first();
60
- const order = (latestChat?.order ?? -1) + 1;
61
- const chatId = await ctx.db.insert("chats", {
59
+ const latestThread = await mergedStream(streams, ["order"]).first();
60
+ const order = (latestThread?.order ?? -1) + 1;
61
+ const threadId = await ctx.db.insert("threads", {
62
62
  ...args,
63
63
  order,
64
64
  status: "active",
65
65
  });
66
- return (await ctx.db.get(chatId));
66
+ return (await ctx.db.get(threadId));
67
67
  },
68
- returns: v.doc("chats"),
68
+ returns: v.doc("threads"),
69
69
  });
70
- export const updateChat = mutation({
70
+ export const updateThread = mutation({
71
71
  args: {
72
- chatId: v.id("chats"),
73
- patch: v.object(partial(pick(vChat.fields, [
72
+ threadId: v.id("threads"),
73
+ patch: v.object(partial(pick(vThread.fields, [
74
74
  "title",
75
75
  "summary",
76
76
  "defaultSystemPrompt",
@@ -78,37 +78,37 @@ export const updateChat = mutation({
78
78
  ]))),
79
79
  },
80
80
  handler: async (ctx, args) => {
81
- const chat = await ctx.db.get(args.chatId);
82
- assert(chat, `Chat ${args.chatId} not found`);
83
- await ctx.db.patch(args.chatId, args.patch);
84
- return (await ctx.db.get(args.chatId));
81
+ const thread = await ctx.db.get(args.threadId);
82
+ assert(thread, `Thread ${args.threadId} not found`);
83
+ await ctx.db.patch(args.threadId, args.patch);
84
+ return (await ctx.db.get(args.threadId));
85
85
  },
86
- returns: v.doc("chats"),
86
+ returns: v.doc("threads"),
87
87
  });
88
- export const archiveChat = mutation({
89
- args: { chatId: v.id("chats") },
88
+ export const archiveThread = mutation({
89
+ args: { threadId: v.id("threads") },
90
90
  handler: async (ctx, args) => {
91
- const chat = await ctx.db.get(args.chatId);
92
- assert(chat, `Chat ${args.chatId} not found`);
93
- await ctx.db.patch(args.chatId, { status: "archived" });
94
- return (await ctx.db.get(args.chatId));
91
+ const thread = await ctx.db.get(args.threadId);
92
+ assert(thread, `Thread ${args.threadId} not found`);
93
+ await ctx.db.patch(args.threadId, { status: "archived" });
94
+ return (await ctx.db.get(args.threadId));
95
95
  },
96
- returns: v.doc("chats"),
96
+ returns: v.doc("threads"),
97
97
  });
98
98
  export const deleteAllForUserId = action({
99
99
  args: { userId: v.string() },
100
100
  handler: async (ctx, args) => {
101
101
  let messagesCursor = null;
102
- let chatsCursor = null;
102
+ let threadsCursor = null;
103
103
  let isDone = false;
104
104
  while (!isDone) {
105
105
  const result = await ctx.runMutation(internal.messages._deletePageForUserId, {
106
106
  userId: args.userId,
107
107
  messagesCursor,
108
- chatsCursor,
108
+ threadsCursor,
109
109
  });
110
110
  messagesCursor = result.messagesCursor;
111
- chatsCursor = result.chatsCursor;
111
+ threadsCursor = result.threadsCursor;
112
112
  isDone = result.isDone;
113
113
  }
114
114
  },
@@ -122,7 +122,7 @@ export const deleteAllForUserIdAsync = mutation({
122
122
  const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
123
123
  userId: args.userId,
124
124
  messagesCursor: null,
125
- chatsCursor: null,
125
+ threadsCursor: null,
126
126
  });
127
127
  return isDone;
128
128
  },
@@ -131,11 +131,11 @@ export const deleteAllForUserIdAsync = mutation({
131
131
  const deleteAllArgs = {
132
132
  userId: v.string(),
133
133
  messagesCursor: nullable(v.string()),
134
- chatsCursor: nullable(v.string()),
134
+ threadsCursor: nullable(v.string()),
135
135
  };
136
136
  const deleteAllReturns = {
137
137
  messagesCursor: v.string(),
138
- chatsCursor: nullable(v.string()),
138
+ threadsCursor: nullable(v.string()),
139
139
  isDone: v.boolean(),
140
140
  };
141
141
  export const _deleteAllForUserIdAsync = internalMutation({
@@ -149,7 +149,7 @@ async function deleteAllFroUserIdAsyncHandler(ctx, args) {
149
149
  await ctx.scheduler.runAfter(0, internal.messages._deleteAllForUserIdAsync, {
150
150
  userId: args.userId,
151
151
  messagesCursor: result.messagesCursor,
152
- chatsCursor: result.chatsCursor,
152
+ threadsCursor: result.threadsCursor,
153
153
  });
154
154
  }
155
155
  return result.isDone;
@@ -161,34 +161,34 @@ export const _deletePageForUserId = internalMutation({
161
161
  });
162
162
  async function deletePageForUserId(ctx, args) {
163
163
  const streams = statuses.map((status) => stream(ctx.db, schema)
164
- .query("chats")
164
+ .query("threads")
165
165
  .withIndex("status_userId_order", (q) => q.eq("status", status).eq("userId", args.userId))
166
166
  .order("desc"));
167
- const chatStreams = mergedStream(streams, ["order"]);
168
- const messages = await chatStreams
167
+ const threadStreams = mergedStream(streams, ["order"]);
168
+ const messages = await threadStreams
169
169
  .flatMap(async (c) => stream(ctx.db, schema)
170
170
  .query("messages")
171
- .withIndex("chatId_status_tool_order_stepOrder", (q) => q.eq("chatId", c._id).eq("status", "success")), ["tool", "order", "stepOrder"])
171
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", c._id).eq("status", "success")), ["tool", "order", "stepOrder"])
172
172
  .paginate({
173
173
  numItems: 100,
174
174
  cursor: args.messagesCursor ?? null,
175
175
  });
176
176
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
177
177
  if (messages.isDone) {
178
- const chats = await chatStreams.paginate({
178
+ const threads = await threadStreams.paginate({
179
179
  numItems: 100,
180
- cursor: args.chatsCursor ?? null,
180
+ cursor: args.threadsCursor ?? null,
181
181
  });
182
- await Promise.all(chats.page.map((c) => ctx.db.delete(c._id)));
182
+ await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
183
183
  return {
184
184
  messagesCursor: messages.continueCursor,
185
- chatsCursor: chats.continueCursor,
186
- isDone: chats.isDone,
185
+ threadsCursor: threads.continueCursor,
186
+ isDone: threads.isDone,
187
187
  };
188
188
  }
189
189
  return {
190
190
  messagesCursor: messages.continueCursor,
191
- chatsCursor: null,
191
+ threadsCursor: null,
192
192
  isDone: messages.isDone,
193
193
  };
194
194
  }
@@ -201,52 +201,52 @@ async function deleteMessage(ctx, messageDoc) {
201
201
  }
202
202
  }
203
203
  }
204
- const deleteChatArgs = {
205
- chatId: v.id("chats"),
204
+ const deleteThreadArgs = {
205
+ threadId: v.id("threads"),
206
206
  cursor: v.optional(v.string()),
207
207
  limit: v.optional(v.number()),
208
208
  };
209
- const deleteChatReturns = {
209
+ const deleteThreadReturns = {
210
210
  cursor: v.string(),
211
211
  isDone: v.boolean(),
212
212
  };
213
- export const deleteAllForChatIdSync = action({
214
- args: deleteChatArgs,
213
+ export const deleteAllForThreadIdSync = action({
214
+ args: deleteThreadArgs,
215
215
  handler: async (ctx, args) => {
216
- const result = await ctx.runMutation(internal.messages._deletePageForChatId, { chatId: args.chatId, cursor: args.cursor, limit: args.limit });
216
+ const result = await ctx.runMutation(internal.messages._deletePageForThreadId, { threadId: args.threadId, cursor: args.cursor, limit: args.limit });
217
217
  return result;
218
218
  },
219
- returns: deleteChatReturns,
219
+ returns: deleteThreadReturns,
220
220
  });
221
- export const deleteAllForChatIdAsync = mutation({
222
- args: deleteChatArgs,
221
+ export const deleteAllForThreadIdAsync = mutation({
222
+ args: deleteThreadArgs,
223
223
  handler: async (ctx, args) => {
224
- const result = await deletePageForChatIdHandler(ctx, args);
224
+ const result = await deletePageForThreadIdHandler(ctx, args);
225
225
  if (!result.isDone) {
226
- await ctx.scheduler.runAfter(0, api.messages.deleteAllForChatIdAsync, {
227
- chatId: args.chatId,
226
+ await ctx.scheduler.runAfter(0, api.messages.deleteAllForThreadIdAsync, {
227
+ threadId: args.threadId,
228
228
  cursor: result.cursor,
229
229
  });
230
230
  }
231
231
  return result;
232
232
  },
233
- returns: deleteChatReturns,
233
+ returns: deleteThreadReturns,
234
234
  });
235
- export const _deletePageForChatId = internalMutation({
236
- args: deleteChatArgs,
237
- handler: deletePageForChatIdHandler,
238
- returns: deleteChatReturns,
235
+ export const _deletePageForThreadId = internalMutation({
236
+ args: deleteThreadArgs,
237
+ handler: deletePageForThreadIdHandler,
238
+ returns: deleteThreadReturns,
239
239
  });
240
- async function deletePageForChatIdHandler(ctx, args) {
240
+ async function deletePageForThreadIdHandler(ctx, args) {
241
241
  const messages = await stream(ctx.db, schema)
242
242
  .query("messages")
243
- .withIndex("chatId_status_tool_order_stepOrder", (q) => q.eq("chatId", args.chatId).eq("status", "success"))
243
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", args.threadId).eq("status", "success"))
244
244
  .paginate({
245
245
  numItems: args.limit ?? 100,
246
246
  cursor: args.cursor ?? null,
247
247
  });
248
248
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
249
- await ctx.db.delete(args.chatId);
249
+ await ctx.db.delete(args.threadId);
250
250
  return {
251
251
  cursor: messages.continueCursor,
252
252
  isDone: messages.isDone,
@@ -280,7 +280,8 @@ export const getFilesToDelete = query({
280
280
  export const vMessageDoc = schema.tables.messages.validator;
281
281
  export const messageStatuses = vMessageDoc.fields.status.members.map((m) => m.value);
282
282
  const addMessagesArgs = {
283
- chatId: v.id("chats"),
283
+ userId: v.optional(v.string()),
284
+ threadId: v.optional(v.id("threads")),
284
285
  stepId: v.optional(v.id("steps")),
285
286
  parentMessageId: v.optional(v.id("messages")),
286
287
  messages: v.array(vMessageWithFileAndId),
@@ -298,23 +299,25 @@ export const addMessages = mutation({
298
299
  }),
299
300
  });
300
301
  async function addMessagesHandler(ctx, args) {
301
- const chat = await ctx.db.get(args.chatId);
302
- assert(chat, `Chat ${args.chatId} not found`);
302
+ let userId = args.userId;
303
+ const threadId = args.threadId;
304
+ if (!userId && args.threadId) {
305
+ const thread = await ctx.db.get(args.threadId);
306
+ assert(thread, `Thread ${args.threadId} not found`);
307
+ userId = thread._id;
308
+ }
303
309
  const { failPendingSteps, pending, messages, parentMessageId, ...rest } = args;
304
- if (failPendingSteps) {
310
+ const parent = parentMessageId && (await ctx.db.get(parentMessageId));
311
+ if (failPendingSteps && parent?.status !== "pending") {
312
+ assert(args.threadId, "threadId is required to fail pending steps");
305
313
  const pendingMessages = await ctx.db
306
314
  .query("messages")
307
- .withIndex("chatId_status_tool_order_stepOrder", (q) => q.eq("chatId", args.chatId).eq("status", "pending"))
315
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", threadId).eq("status", "pending"))
308
316
  .collect();
309
317
  await Promise.all(pendingMessages.map((m) => ctx.db.patch(m._id, { status: "failed", text: "Restarting" })));
310
318
  }
311
- let order;
312
- const maxMessage = await getMaxMessage(ctx, args.chatId);
313
- // If the previous message isn't our parent, we make a new thread.
314
- const threadId = parentMessageId && maxMessage?._id === parentMessageId
315
- ? maxMessage.threadId ?? parentMessageId
316
- : parentMessageId;
317
- order = maxMessage?.order ?? -1;
319
+ const maxMessage = await getMaxMessage(ctx, threadId, userId);
320
+ let order = maxMessage?.order ?? -1;
318
321
  const toReturn = [];
319
322
  if (messages.length > 0) {
320
323
  for (const { message, fileId, id } of messages) {
@@ -325,8 +328,8 @@ async function addMessagesHandler(ctx, args) {
325
328
  const text = extractText(message);
326
329
  const messageId = await ctx.db.insert("messages", {
327
330
  ...rest,
328
- threadId,
329
- userId: chat.userId,
331
+ parentMessageId,
332
+ userId,
330
333
  message,
331
334
  id,
332
335
  order,
@@ -340,14 +343,23 @@ async function addMessagesHandler(ctx, args) {
340
343
  }
341
344
  return { messages: toReturn };
342
345
  }
343
- async function getMaxMessage(ctx, chatId) {
344
- return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
345
- .query("messages")
346
- .withIndex("chatId_status_tool_order_stepOrder", (q) => q.eq("chatId", chatId).eq("status", status).eq("tool", false))
347
- .order("desc")), ["order", "stepOrder"]).first();
346
+ async function getMaxMessage(ctx, threadId, userId) {
347
+ assert(threadId || userId, "One of threadId or userId is required");
348
+ if (threadId) {
349
+ return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
350
+ .query("messages")
351
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", threadId).eq("status", status).eq("tool", false))
352
+ .order("desc")), ["order", "stepOrder"]).first();
353
+ }
354
+ else {
355
+ return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
356
+ .query("messages")
357
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", userId).eq("status", status).eq("tool", false))
358
+ .order("desc")), ["order", "stepOrder"]).first();
359
+ }
348
360
  }
349
361
  const addStepsArgs = {
350
- chatId: v.id("chats"),
362
+ threadId: v.id("threads"),
351
363
  messageId: v.id("messages"),
352
364
  steps: v.array(vStepWithMessagesWithFileAndId),
353
365
  failPendingSteps: v.optional(v.boolean()),
@@ -379,7 +391,7 @@ async function addStepsHandler(ctx, args) {
379
391
  let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
380
392
  for (const { step, messages } of args.steps) {
381
393
  const stepId = await ctx.db.insert("steps", {
382
- chatId: args.chatId,
394
+ threadId: args.threadId,
383
395
  parentMessageId: args.messageId,
384
396
  order,
385
397
  stepOrder: nextStepOrder,
@@ -387,7 +399,7 @@ async function addStepsHandler(ctx, args) {
387
399
  step,
388
400
  });
389
401
  await addMessagesHandler(ctx, {
390
- chatId: args.chatId,
402
+ threadId: args.threadId,
391
403
  parentMessageId: args.messageId,
392
404
  stepId,
393
405
  messages,
@@ -396,7 +408,8 @@ async function addStepsHandler(ctx, args) {
396
408
  pending: step.finishReason === "stop" ? false : true,
397
409
  failPendingSteps: false,
398
410
  });
399
- if (step.finishReason === "stop") {
411
+ // We don't commit if the parent is still pending.
412
+ if (step.finishReason === "stop" && parentMessage.status === "success") {
400
413
  await commitMessageHandler(ctx, { messageId: args.messageId });
401
414
  }
402
415
  steps.push((await ctx.db.get(stepId)));
@@ -441,18 +454,19 @@ async function commitMessageHandler(ctx, { messageId }) {
441
454
  const order = message.order;
442
455
  const messages = await mergedStream([true, false].map((tool) => stream(ctx.db, schema)
443
456
  .query("messages")
444
- .withIndex("chatId_status_tool_order_stepOrder", (q) => q
445
- .eq("chatId", message.chatId)
457
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q
458
+ .eq("threadId", message.threadId)
446
459
  .eq("status", "pending")
447
460
  .eq("tool", tool)
448
461
  .eq("order", order))), ["order", "stepOrder"]).collect();
449
462
  for (const message of messages) {
450
463
  await ctx.db.patch(message._id, { status: "success" });
464
+ // TODO: recursively commit steps & messages that might depend on this one.
451
465
  }
452
466
  }
453
- export const getChatMessages = query({
467
+ export const getThreadMessages = query({
454
468
  args: {
455
- chatId: v.id("chats"),
469
+ threadId: v.id("threads"),
456
470
  isTool: v.optional(v.boolean()),
457
471
  order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
458
472
  limit: v.optional(v.number()),
@@ -468,9 +482,9 @@ export const getChatMessages = query({
468
482
  const order = args.order ?? "desc";
469
483
  const streams = toolOptions.flatMap((tool) => statuses.map((status) => stream(ctx.db, schema)
470
484
  .query("messages")
471
- .withIndex("chatId_status_tool_order_stepOrder", (q) => {
485
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => {
472
486
  const qq = q
473
- .eq("chatId", args.chatId)
487
+ .eq("threadId", args.threadId)
474
488
  .eq("status", status)
475
489
  .eq("tool", tool);
476
490
  if (parent) {
@@ -501,19 +515,19 @@ export const getChatMessages = query({
501
515
  export const searchMessages = action({
502
516
  args: {
503
517
  userId: v.optional(v.string()),
504
- chatId: v.optional(v.id("chats")),
518
+ threadId: v.optional(v.id("threads")),
505
519
  parentMessageId: v.optional(v.id("messages")),
506
520
  ...vSearchOptions.fields,
507
521
  },
508
522
  returns: v.array(v.doc("messages")),
509
523
  handler: async (ctx, args) => {
510
- assert(args.userId || args.chatId, "Specify userId or chatId");
524
+ assert(args.userId || args.threadId, "Specify userId or threadId");
511
525
  const limit = args.limit;
512
526
  let textSearchMessages;
513
527
  if (args.text) {
514
528
  textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
515
529
  userId: args.userId,
516
- chatId: args.chatId,
530
+ threadId: args.threadId,
517
531
  text: args.text,
518
532
  limit,
519
533
  });
@@ -528,8 +542,8 @@ export const searchMessages = action({
528
542
  const vectors = (await ctx.vectorSearch(tableName, "vector", {
529
543
  vector: args.vector,
530
544
  filter: (q) => args.userId
531
- ? q.eq("model_kind_userId", [model, "chat", args.userId])
532
- : q.eq("model_kind_chatId", [model, "chat", args.chatId]),
545
+ ? q.eq("model_kind_userId", [model, "thread", args.userId])
546
+ : q.eq("model_kind_threadId", [model, "thread", args.threadId]),
533
547
  limit,
534
548
  })).filter((v) => v._score > 0.5);
535
549
  // Reciprocal rank fusion
@@ -545,7 +559,7 @@ export const searchMessages = action({
545
559
  const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
546
560
  const messages = await ctx.runQuery(internal.messages._fetchVectorMessages, {
547
561
  userId: args.userId,
548
- chatId: args.chatId,
562
+ threadId: args.threadId,
549
563
  vectorIds,
550
564
  textSearchMessages: textSearchMessages?.filter((m) => !vectorIds.includes(m.embeddingId)),
551
565
  messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
@@ -560,7 +574,7 @@ export const searchMessages = action({
560
574
  export const _fetchVectorMessages = internalQuery({
561
575
  args: {
562
576
  userId: v.optional(v.string()),
563
- chatId: v.optional(v.id("chats")),
577
+ threadId: v.optional(v.id("threads")),
564
578
  vectorIds: v.array(vVectorId),
565
579
  textSearchMessages: v.optional(v.array(v.doc("messages"))),
566
580
  messageRange: v.object({ before: v.number(), after: v.number() }),
@@ -570,60 +584,83 @@ export const _fetchVectorMessages = internalQuery({
570
584
  returns: v.array(v.doc("messages")),
571
585
  handler: async (ctx, args) => {
572
586
  const parent = args.parentMessageId && (await ctx.db.get(args.parentMessageId));
587
+ const { userId, threadId } = args;
588
+ assert(userId || threadId, "Specify userId or threadId to search");
573
589
  let messages = (await Promise.all(args.vectorIds.map((embeddingId) => ctx.db
574
590
  .query("messages")
575
591
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
576
- .filter((q) => args.userId
577
- ? q.eq("userId", args.userId)
592
+ .filter((q) => userId
593
+ ? q.eq("userId", userId)
578
594
  : // eslint-disable-next-line @typescript-eslint/no-explicit-any
579
- q.eq("chatId", args.chatId) // not sure why it's failing...
580
- )
595
+ q.eq("threadId", threadId))
581
596
  .first()))).filter((m) => m !== undefined && m !== null && (!parent || m.order <= parent.order));
582
597
  messages.push(...(args.textSearchMessages ?? []));
583
598
  // TODO: prioritize more recent messages
584
599
  messages.sort((a, b) => a.order - b.order);
585
600
  messages = messages.slice(0, args.limit);
586
601
  // Fetch the surrounding messages
602
+ if (!threadId) {
603
+ return messages.sort((a, b) => a.order - b.order);
604
+ }
587
605
  const included = {};
588
606
  for (const m of messages) {
589
- if (!included[m.chatId]) {
590
- included[m.chatId] = new Set();
607
+ const searchId = m.threadId ?? m.userId;
608
+ if (!included[searchId]) {
609
+ included[searchId] = new Set();
591
610
  }
592
- included[m.chatId].add(m.order);
611
+ included[searchId].add(m.order);
593
612
  }
594
613
  const ranges = {};
595
614
  const { before, after } = args.messageRange;
596
615
  for (const m of messages) {
616
+ const searchId = m.threadId ?? m.userId;
597
617
  const order = m.order;
598
618
  let earliest = order - before;
599
619
  let latest = order + after;
600
620
  for (; earliest <= latest; earliest++) {
601
- if (!included[m.chatId].has(earliest)) {
621
+ if (!included[searchId].has(earliest)) {
602
622
  break;
603
623
  }
604
624
  }
605
625
  for (; latest >= earliest; latest--) {
606
- if (!included[m.chatId].has(latest)) {
626
+ if (!included[searchId].has(latest)) {
607
627
  break;
608
628
  }
609
629
  }
610
630
  for (let i = earliest; i <= latest; i++) {
611
- included[m.chatId].add(i);
631
+ included[searchId].add(i);
612
632
  }
613
633
  if (earliest !== latest) {
614
- const surrounding = await ctx.db
615
- .query("messages")
616
- .withIndex("chatId_status_tool_order_stepOrder", (q) => q
617
- .eq("chatId", m.chatId)
618
- .eq("status", "success")
619
- .eq("tool", false)
620
- .gt("order", earliest)
621
- .lt("order", latest))
622
- .collect();
623
- if (!ranges[m.chatId]) {
624
- ranges[m.chatId] = [];
634
+ if (m.threadId) {
635
+ const surrounding = await ctx.db
636
+ .query("messages")
637
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q
638
+ .eq("threadId", m.threadId)
639
+ .eq("status", "success")
640
+ .eq("tool", false)
641
+ .gt("order", earliest)
642
+ .lt("order", latest))
643
+ .collect();
644
+ if (!ranges[searchId]) {
645
+ ranges[searchId] = [];
646
+ }
647
+ ranges[searchId].push(...surrounding);
648
+ }
649
+ else {
650
+ const surrounding = await ctx.db
651
+ .query("messages")
652
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q
653
+ .eq("userId", m.userId)
654
+ .eq("status", "success")
655
+ .eq("tool", false)
656
+ .gt("order", earliest)
657
+ .lt("order", latest))
658
+ .collect();
659
+ if (!ranges[searchId]) {
660
+ ranges[searchId] = [];
661
+ }
662
+ ranges[searchId].push(...surrounding);
625
663
  }
626
- ranges[m.chatId].push(...surrounding);
627
664
  }
628
665
  }
629
666
  return Object.values(ranges)
@@ -635,18 +672,18 @@ export const _fetchVectorMessages = internalQuery({
635
672
  // excluding duplicates in later ranges.
636
673
  export const textSearch = query({
637
674
  args: {
638
- chatId: v.optional(v.id("chats")),
675
+ threadId: v.optional(v.id("threads")),
639
676
  userId: v.optional(v.string()),
640
677
  text: v.string(),
641
678
  limit: v.number(),
642
679
  },
643
680
  handler: async (ctx, args) => {
644
- assert(args.userId || args.chatId, "Specify userId or chatId");
681
+ assert(args.userId || args.threadId, "Specify userId or threadId");
645
682
  const messages = await ctx.db
646
683
  .query("messages")
647
684
  .withSearchIndex("text_search", (q) => args.userId
648
685
  ? q.search("text", args.text).eq("userId", args.userId)
649
- : q.search("text", args.text).eq("chatId", args.chatId))
686
+ : q.search("text", args.text).eq("threadId", args.threadId))
650
687
  .take(args.limit);
651
688
  return messages;
652
689
  },
@@ -695,9 +732,9 @@ export const textSearch = query({
695
732
  // ),
696
733
  // });
697
734
  // const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
698
- // export const getChatMessagesPage = query({
735
+ // export const getThreadMessagesPage = query({
699
736
  // args: {
700
- // threadId: v.string(),
737
+ // parentMessageId: v.string(),
701
738
  // selectBy: v.optional(vSelectBy),
702
739
  // // Unimplemented and as far I can tell no storage provider has either.
703
740
  // // memoryConfig: v.optional(vMemoryConfig),
@@ -705,7 +742,7 @@ export const textSearch = query({
705
742
  // handler: async (ctx, args): Promise<SerializedMessage[]> => {
706
743
  // const messages = await ctx.db
707
744
  // .query("messages")
708
- // .withIndex("threadId", (q) => q.eq("threadId", args.threadId))
745
+ // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", args.parentMessageId))
709
746
  // .order("desc")
710
747
  // .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
711
748
  // const handled: boolean[] = [];
@@ -758,9 +795,9 @@ export const textSearch = query({
758
795
  // ranges.map(async (range) => {
759
796
  // return await ctx.db
760
797
  // .query("messages")
761
- // .withIndex("threadId", (q) =>
798
+ // .withIndex("parentMessageId", (q) =>
762
799
  // q
763
- // .eq("threadId", args.threadId)
800
+ // .eq("parentMessageId", args.parentMessageId)
764
801
  // .gte("threadOrder", range.start)
765
802
  // .lte("threadOrder", range.end)
766
803
  // )
@@ -776,21 +813,21 @@ export const textSearch = query({
776
813
  // export const saveMessages = mutation({
777
814
  // args: { messages: v.array(vSerializedMessage) },
778
815
  // handler: async (ctx, args) => {
779
- // const messagesByThreadId: Record<string, SerializedMessage[]> = {};
816
+ // const messagesByParentMessageId: Record<string, SerializedMessage[]> = {};
780
817
  // for (const message of args.messages) {
781
- // messagesByThreadId[message.threadId] = [
782
- // ...(messagesByThreadId[message.threadId] ?? []),
818
+ // messagesByParentMessageId[message.parentMessageId] = [
819
+ // ...(messagesByParentMessageId[message.parentMessageId] ?? []),
783
820
  // message,
784
821
  // ];
785
822
  // }
786
- // for (const threadId in messagesByThreadId) {
823
+ // for (const parentMessageId in messagesByParentMessageId) {
787
824
  // const lastMessage = await ctx.db
788
825
  // .query("messages")
789
- // .withIndex("threadId", (q) => q.eq("threadId", threadId))
826
+ // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", parentMessageId))
790
827
  // .order("desc")
791
828
  // .first();
792
829
  // let threadOrder = lastMessage?.threadOrder ?? 0;
793
- // for (const message of messagesByThreadId[threadId]) {
830
+ // for (const message of messagesByParentMessageId[parentMessageId]) {
794
831
  // threadOrder++;
795
832
  // await ctx.db.insert("messages", {
796
833
  // ...message,