@convex-dev/agent 0.0.16 → 0.0.17-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +159 -98
  2. package/dist/commonjs/client/index.d.ts +806 -189
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +178 -28
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/client/playground.d.ts +609 -0
  7. package/dist/commonjs/client/playground.d.ts.map +1 -0
  8. package/dist/commonjs/client/playground.js +200 -0
  9. package/dist/commonjs/client/playground.js.map +1 -0
  10. package/dist/commonjs/component/apiKeys.d.ts +11 -0
  11. package/dist/commonjs/component/apiKeys.d.ts.map +1 -0
  12. package/dist/commonjs/component/apiKeys.js +69 -0
  13. package/dist/commonjs/component/apiKeys.js.map +1 -0
  14. package/dist/commonjs/component/files.d.ts +31 -0
  15. package/dist/commonjs/component/files.d.ts.map +1 -0
  16. package/dist/commonjs/component/files.js +61 -0
  17. package/dist/commonjs/component/files.js.map +1 -0
  18. package/dist/commonjs/component/messages.d.ts +720 -676
  19. package/dist/commonjs/component/messages.d.ts.map +1 -1
  20. package/dist/commonjs/component/messages.js +99 -287
  21. package/dist/commonjs/component/messages.js.map +1 -1
  22. package/dist/commonjs/component/schema.d.ts +1124 -1324
  23. package/dist/commonjs/component/schema.d.ts.map +1 -1
  24. package/dist/commonjs/component/schema.js +26 -14
  25. package/dist/commonjs/component/schema.js.map +1 -1
  26. package/dist/commonjs/component/threads.d.ts +95 -0
  27. package/dist/commonjs/component/threads.d.ts.map +1 -0
  28. package/dist/commonjs/component/threads.js +151 -0
  29. package/dist/commonjs/component/threads.js.map +1 -0
  30. package/dist/commonjs/component/users.d.ts +37 -0
  31. package/dist/commonjs/component/users.d.ts.map +1 -0
  32. package/dist/commonjs/component/users.js +118 -0
  33. package/dist/commonjs/component/users.js.map +1 -0
  34. package/dist/commonjs/component/vector/index.d.ts.map +1 -1
  35. package/dist/commonjs/component/vector/index.js +4 -6
  36. package/dist/commonjs/component/vector/index.js.map +1 -1
  37. package/dist/commonjs/component/vector/tables.d.ts +10 -10
  38. package/dist/commonjs/component/vector/tables.d.ts.map +1 -1
  39. package/dist/commonjs/component/vector/tables.js.map +1 -1
  40. package/dist/commonjs/mapping.d.ts +7 -1
  41. package/dist/commonjs/mapping.d.ts.map +1 -1
  42. package/dist/commonjs/mapping.js +39 -18
  43. package/dist/commonjs/mapping.js.map +1 -1
  44. package/dist/commonjs/validators.d.ts +1669 -2340
  45. package/dist/commonjs/validators.d.ts.map +1 -1
  46. package/dist/commonjs/validators.js +35 -26
  47. package/dist/commonjs/validators.js.map +1 -1
  48. package/dist/esm/client/index.d.ts +806 -189
  49. package/dist/esm/client/index.d.ts.map +1 -1
  50. package/dist/esm/client/index.js +178 -28
  51. package/dist/esm/client/index.js.map +1 -1
  52. package/dist/esm/client/playground.d.ts +609 -0
  53. package/dist/esm/client/playground.d.ts.map +1 -0
  54. package/dist/esm/client/playground.js +200 -0
  55. package/dist/esm/client/playground.js.map +1 -0
  56. package/dist/esm/component/apiKeys.d.ts +11 -0
  57. package/dist/esm/component/apiKeys.d.ts.map +1 -0
  58. package/dist/esm/component/apiKeys.js +69 -0
  59. package/dist/esm/component/apiKeys.js.map +1 -0
  60. package/dist/esm/component/files.d.ts +31 -0
  61. package/dist/esm/component/files.d.ts.map +1 -0
  62. package/dist/esm/component/files.js +61 -0
  63. package/dist/esm/component/files.js.map +1 -0
  64. package/dist/esm/component/messages.d.ts +720 -676
  65. package/dist/esm/component/messages.d.ts.map +1 -1
  66. package/dist/esm/component/messages.js +99 -287
  67. package/dist/esm/component/messages.js.map +1 -1
  68. package/dist/esm/component/schema.d.ts +1124 -1324
  69. package/dist/esm/component/schema.d.ts.map +1 -1
  70. package/dist/esm/component/schema.js +26 -14
  71. package/dist/esm/component/schema.js.map +1 -1
  72. package/dist/esm/component/threads.d.ts +95 -0
  73. package/dist/esm/component/threads.d.ts.map +1 -0
  74. package/dist/esm/component/threads.js +151 -0
  75. package/dist/esm/component/threads.js.map +1 -0
  76. package/dist/esm/component/users.d.ts +37 -0
  77. package/dist/esm/component/users.d.ts.map +1 -0
  78. package/dist/esm/component/users.js +118 -0
  79. package/dist/esm/component/users.js.map +1 -0
  80. package/dist/esm/component/vector/index.d.ts.map +1 -1
  81. package/dist/esm/component/vector/index.js +4 -6
  82. package/dist/esm/component/vector/index.js.map +1 -1
  83. package/dist/esm/component/vector/tables.d.ts +10 -10
  84. package/dist/esm/component/vector/tables.d.ts.map +1 -1
  85. package/dist/esm/component/vector/tables.js.map +1 -1
  86. package/dist/esm/mapping.d.ts +7 -1
  87. package/dist/esm/mapping.d.ts.map +1 -1
  88. package/dist/esm/mapping.js +39 -18
  89. package/dist/esm/mapping.js.map +1 -1
  90. package/dist/esm/validators.d.ts +1669 -2340
  91. package/dist/esm/validators.d.ts.map +1 -1
  92. package/dist/esm/validators.js +35 -26
  93. package/dist/esm/validators.js.map +1 -1
  94. package/package.json +8 -1
  95. package/src/client/index.ts +220 -59
  96. package/src/client/playground.ts +257 -0
  97. package/src/component/_generated/api.d.ts +601 -400
  98. package/src/component/apiKeys.ts +74 -0
  99. package/src/component/files.ts +72 -0
  100. package/src/component/messages.test.ts +23 -0
  101. package/src/component/messages.ts +127 -341
  102. package/src/component/schema.ts +32 -13
  103. package/src/component/threads.ts +184 -0
  104. package/src/component/users.ts +145 -0
  105. package/src/component/vector/index.ts +5 -6
  106. package/src/component/vector/tables.ts +15 -15
  107. package/src/mapping.ts +65 -32
  108. package/src/validators.ts +47 -28
@@ -1,7 +1,5 @@
1
- import { assert, omit, pick } from "convex-helpers";
2
- import { paginator } from "convex-helpers/server/pagination";
1
+ import { assert } from "convex-helpers";
3
2
  import { mergedStream, stream } from "convex-helpers/server/stream";
4
- import { nullable, partial } from "convex-helpers/validators";
5
3
  import { ObjectType } from "convex/values";
6
4
  import {
7
5
  DEFAULT_MESSAGE_RANGE,
@@ -20,7 +18,6 @@ import { api, internal } from "./_generated/api.js";
20
18
  import { Doc, Id } from "./_generated/dataModel.js";
21
19
  import {
22
20
  action,
23
- internalMutation,
24
21
  internalQuery,
25
22
  mutation,
26
23
  MutationCtx,
@@ -35,293 +32,40 @@ import {
35
32
  VectorTableId,
36
33
  vVectorId,
37
34
  } from "./vector/tables.js";
35
+ import {
36
+ listThreadsByUserId as _listThreadsByUserId,
37
+ getThread as _getThread,
38
+ updateThread as _updateThread,
39
+ } from "./threads.js";
38
40
  import { paginationOptsValidator } from "convex/server";
39
41
 
40
- export const getThread = query({
41
- args: { threadId: v.id("threads") },
42
- handler: async (ctx, args) => {
43
- return ctx.db.get(args.threadId);
44
- },
45
- returns: v.union(v.doc("threads"), v.null()),
46
- });
47
-
48
- export const getThreadsByUserId = query({
49
- args: {
50
- userId: v.string(),
51
- order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
52
- paginationOpts: v.optional(paginationOptsValidator),
53
- },
54
- handler: async (ctx, args) => {
55
- const threads = await paginator(ctx.db, schema)
56
- .query("threads")
57
- .withIndex("userId", (q) => q.eq("userId", args.userId))
58
- .order(args.order ?? "desc")
59
- .paginate(args.paginationOpts ?? { cursor: null, numItems: 100 });
60
- return threads;
61
- },
62
- returns: paginationResultValidator(v.doc("threads")),
63
- });
64
-
65
- const vThread = schema.tables.threads.validator;
66
-
67
- export const createThread = mutation({
68
- args: omit(vThread.fields, ["order", "status"]),
69
- handler: async (ctx, args) => {
70
- const threadId = await ctx.db.insert("threads", {
71
- ...args,
72
- status: "active",
73
- });
74
- return (await ctx.db.get(threadId))!;
75
- },
76
- returns: v.doc("threads"),
77
- });
78
-
79
- export const updateThread = mutation({
80
- args: {
81
- threadId: v.id("threads"),
82
- patch: v.object(
83
- partial(pick(vThread.fields, ["title", "summary", "status"]))
84
- ),
85
- },
86
- handler: async (ctx, args) => {
87
- const thread = await ctx.db.get(args.threadId);
88
- assert(thread, `Thread ${args.threadId} not found`);
89
- await ctx.db.patch(args.threadId, args.patch);
90
- return (await ctx.db.get(args.threadId))!;
91
- },
92
- returns: v.doc("threads"),
93
- });
94
-
95
- // When we expose this, we need to also hide all the messages and steps
96
- // export const archiveThread = mutation({
97
- // args: { threadId: v.id("threads") },
98
- // handler: async (ctx, args) => {
99
- // const thread = await ctx.db.get(args.threadId);
100
- // assert(thread, `Thread ${args.threadId} not found`);
101
- // await ctx.db.patch(args.threadId, { status: "archived" });
102
- // return (await ctx.db.get(args.threadId))!;
103
- // },
104
- // returns: v.doc("threads"),
105
- // });
106
-
107
- export const deleteAllForUserId = action({
108
- args: { userId: v.string() },
109
- handler: async (ctx, args) => {
110
- let messagesCursor = null;
111
- let threadsCursor = null;
112
- let isDone = false;
113
- while (!isDone) {
114
- const result: {
115
- messagesCursor: string;
116
- threadsCursor: string | null;
117
- isDone: boolean;
118
- } = await ctx.runMutation(internal.messages._deletePageForUserId, {
119
- userId: args.userId,
120
- messagesCursor,
121
- threadsCursor,
122
- });
123
- messagesCursor = result.messagesCursor;
124
- threadsCursor = result.threadsCursor;
125
- isDone = result.isDone;
126
- }
127
- },
128
- returns: v.null(),
129
- });
130
-
131
- export const deleteAllForUserIdAsync = mutation({
132
- args: {
133
- userId: v.string(),
134
- },
135
- handler: async (ctx, args) => {
136
- const isDone = await deleteAllForUserIdAsyncHandler(ctx, {
137
- userId: args.userId,
138
- messagesCursor: null,
139
- threadsCursor: null,
140
- });
141
- return isDone;
142
- },
143
- returns: v.boolean(),
144
- });
145
42
 
146
- const deleteAllArgs = {
147
- userId: v.string(),
148
- messagesCursor: nullable(v.string()),
149
- threadsCursor: nullable(v.string()),
150
- };
151
- type DeleteAllArgs = ObjectType<typeof deleteAllArgs>;
152
- const deleteAllReturns = {
153
- messagesCursor: v.string(),
154
- threadsCursor: nullable(v.string()),
155
- isDone: v.boolean(),
156
- };
157
- type DeleteAllReturns = ObjectType<typeof deleteAllReturns>;
43
+ /** @deprecated Use *.threads.listMessagesByThreadId instead. */
44
+ export const listThreadsByUserId= _listThreadsByUserId
158
45
 
159
- export const _deleteAllForUserIdAsync = internalMutation({
160
- args: deleteAllArgs,
161
- handler: deleteAllForUserIdAsyncHandler,
162
- returns: v.boolean(),
163
- });
46
+ /** @deprecated Use *.threads.getThread */
47
+ export const getThread = _getThread;
164
48
 
165
- async function deleteAllForUserIdAsyncHandler(
166
- ctx: MutationCtx,
167
- args: DeleteAllArgs
168
- ): Promise<boolean> {
169
- const result = await deletePageForUserId(ctx, args);
170
- if (!result.isDone) {
171
- await ctx.scheduler.runAfter(
172
- 0,
173
- internal.messages._deleteAllForUserIdAsync,
174
- {
175
- userId: args.userId,
176
- messagesCursor: result.messagesCursor,
177
- threadsCursor: result.threadsCursor,
178
- }
179
- );
180
- }
181
- return result.isDone;
182
- }
49
+ /** @deprecated Use *.threads.updateThread instead */
50
+ export const updateThread= _updateThread;
183
51
 
184
- export const _deletePageForUserId = internalMutation({
185
- args: deleteAllArgs,
186
- handler: deletePageForUserId,
187
- returns: deleteAllReturns,
188
- });
189
- async function deletePageForUserId(
52
+ export async function deleteMessage(
190
53
  ctx: MutationCtx,
191
- args: DeleteAllArgs
192
- ): Promise<DeleteAllReturns> {
193
- const threads = await paginator(ctx.db, schema)
194
- .query("threads")
195
- .withIndex("userId", (q) => q.eq("userId", args.userId))
196
- .order("desc")
197
- .paginate({
198
- numItems: 100,
199
- cursor: args.threadsCursor ?? null,
200
- });
201
- await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
202
- const messages = await paginator(ctx.db, schema)
203
- .query("messages")
204
- .withIndex("userId_status_tool_order_stepOrder", (q) =>
205
- q.eq("userId", args.userId)
206
- )
207
- .order("desc")
208
- .paginate({
209
- numItems: 100,
210
- cursor: args.messagesCursor ?? null,
211
- });
212
- await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
213
- return {
214
- messagesCursor: messages.continueCursor,
215
- threadsCursor: threads.continueCursor,
216
- isDone: messages.isDone,
217
- };
218
- }
219
-
220
- async function deleteMessage(ctx: MutationCtx, messageDoc: Doc<"messages">) {
54
+ messageDoc: Doc<"messages">
55
+ ) {
221
56
  await ctx.db.delete(messageDoc._id);
222
57
  if (messageDoc.embeddingId) {
223
58
  await ctx.db.delete(messageDoc.embeddingId);
224
59
  }
225
- if (messageDoc.fileId) {
226
- const file = await ctx.db.get(messageDoc.fileId);
60
+ for (const { fileId } of messageDoc.files ?? []) {
61
+ if (!fileId) continue;
62
+ const file = await ctx.db.get(fileId);
227
63
  if (file) {
228
- await ctx.db.patch(messageDoc.fileId, { refcount: file.refcount - 1 });
64
+ await ctx.db.patch(fileId, { refcount: file.refcount - 1 });
229
65
  }
230
66
  }
231
67
  }
232
68
 
233
- const deleteThreadArgs = {
234
- threadId: v.id("threads"),
235
- cursor: v.optional(v.string()),
236
- limit: v.optional(v.number()),
237
- };
238
- type DeleteThreadArgs = ObjectType<typeof deleteThreadArgs>;
239
- const deleteThreadReturns = {
240
- cursor: v.string(),
241
- isDone: v.boolean(),
242
- };
243
- type DeleteThreadReturns = ObjectType<typeof deleteThreadReturns>;
244
-
245
- export const deleteAllForThreadIdSync = action({
246
- args: deleteThreadArgs,
247
- handler: async (ctx, args) => {
248
- const result: DeleteThreadReturns = await ctx.runMutation(
249
- internal.messages._deletePageForThreadId,
250
- { threadId: args.threadId, cursor: args.cursor, limit: args.limit }
251
- );
252
- return result;
253
- },
254
- returns: deleteThreadReturns,
255
- });
256
-
257
- export const deleteAllForThreadIdAsync = mutation({
258
- args: deleteThreadArgs,
259
- handler: async (ctx, args) => {
260
- const result = await deletePageForThreadIdHandler(ctx, args);
261
- if (!result.isDone) {
262
- await ctx.scheduler.runAfter(0, api.messages.deleteAllForThreadIdAsync, {
263
- threadId: args.threadId,
264
- cursor: result.cursor,
265
- });
266
- }
267
- return result;
268
- },
269
- returns: deleteThreadReturns,
270
- });
271
-
272
- export const _deletePageForThreadId = internalMutation({
273
- args: deleteThreadArgs,
274
- handler: deletePageForThreadIdHandler,
275
- returns: deleteThreadReturns,
276
- });
277
-
278
- async function deletePageForThreadIdHandler(
279
- ctx: MutationCtx,
280
- args: DeleteThreadArgs
281
- ): Promise<DeleteThreadReturns> {
282
- const messages = await paginator(ctx.db, schema)
283
- .query("messages")
284
- .withIndex("threadId_status_tool_order_stepOrder", (q) =>
285
- q.eq("threadId", args.threadId)
286
- )
287
- .paginate({
288
- numItems: args.limit ?? 100,
289
- cursor: args.cursor ?? null,
290
- });
291
- await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
292
- await ctx.db.delete(args.threadId);
293
- return {
294
- cursor: messages.continueCursor,
295
- isDone: messages.isDone,
296
- };
297
- }
298
-
299
- export const getFilesToDelete = query({
300
- args: {
301
- cursor: v.optional(v.string()),
302
- limit: v.optional(v.number()),
303
- },
304
- handler: async (ctx, args) => {
305
- const files = await paginator(ctx.db, schema)
306
- .query("files")
307
- .withIndex("refcount", (q) => q.eq("refcount", 0))
308
- .paginate({
309
- numItems: args.limit ?? 100,
310
- cursor: args.cursor ?? null,
311
- });
312
- return {
313
- files: files.page,
314
- continueCursor: files.continueCursor,
315
- isDone: files.isDone,
316
- };
317
- },
318
- returns: v.object({
319
- files: v.array(v.doc("files")),
320
- continueCursor: v.string(),
321
- isDone: v.boolean(),
322
- }),
323
- });
324
-
325
69
  export const vMessageDoc = schema.tables.messages.validator;
326
70
  export const messageStatuses = vMessageDoc.fields.status.members.map(
327
71
  (m) => m.value
@@ -358,9 +102,7 @@ async function addMessagesHandler(
358
102
  }
359
103
  const { failPendingSteps, pending, messages, parentMessageId, ...rest } =
360
104
  args;
361
- const parent = parentMessageId && (await ctx.db.get(parentMessageId));
362
- // TODO: I think this is a bug - parent will be pending always?
363
- if (failPendingSteps && parent?.status !== "pending") {
105
+ if (failPendingSteps) {
364
106
  assert(args.threadId, "threadId is required to fail pending steps");
365
107
  const pendingMessages = await ctx.db
366
108
  .query("messages")
@@ -376,9 +118,11 @@ async function addMessagesHandler(
376
118
  }
377
119
  const maxMessage = await getMaxMessage(ctx, threadId, userId);
378
120
  let order = maxMessage?.order ?? -1;
121
+ let stepOrder = maxMessage?.stepOrder ?? 0;
122
+ let lastMessageIsTool = maxMessage?.tool ?? false;
379
123
  const toReturn: Doc<"messages">[] = [];
380
124
  if (messages.length > 0) {
381
- for (const { message, fileId, embedding, ...fields } of messages) {
125
+ for (const { message, files, embedding, ...fields } of messages) {
382
126
  let embeddingId: VectorTableId | undefined;
383
127
  if (embedding) {
384
128
  embeddingId = await insertVector(ctx, embedding.dimension, {
@@ -390,9 +134,13 @@ async function addMessagesHandler(
390
134
  });
391
135
  }
392
136
  const tool = isTool(message);
393
- if (!tool) {
137
+ if (lastMessageIsTool) {
138
+ stepOrder++;
139
+ } else {
394
140
  order++;
141
+ stepOrder = 0;
395
142
  }
143
+ lastMessageIsTool = tool;
396
144
  const text = extractText(message);
397
145
  const messageId = await ctx.db.insert("messages", {
398
146
  ...rest,
@@ -404,11 +152,17 @@ async function addMessagesHandler(
404
152
  order,
405
153
  tool,
406
154
  text,
407
- fileId,
155
+ files,
408
156
  status: pending ? "pending" : "success",
409
- stepOrder: 0,
157
+ stepOrder,
410
158
  });
411
- if (fileId) {
159
+ if (!fields.id) {
160
+ await ctx.db.patch(messageId, {
161
+ id: messageId,
162
+ });
163
+ }
164
+ for (const { fileId } of files ?? []) {
165
+ if (!fileId) continue;
412
166
  await ctx.db.patch(fileId, {
413
167
  refcount: (await ctx.db.get(fileId))!.refcount + 1,
414
168
  });
@@ -419,7 +173,8 @@ async function addMessagesHandler(
419
173
  return { messages: toReturn };
420
174
  }
421
175
 
422
- async function getMaxMessage(
176
+ // exported for tests
177
+ export async function getMaxMessage(
423
178
  ctx: QueryCtx,
424
179
  threadId: Id<"threads"> | undefined,
425
180
  userId: string | undefined
@@ -427,46 +182,39 @@ async function getMaxMessage(
427
182
  assert(threadId || userId, "One of threadId or userId is required");
428
183
  if (threadId) {
429
184
  return mergedStream(
430
- ["success" as const, "pending" as const].map((status) =>
431
- stream(ctx.db, schema)
432
- .query("messages")
433
- .withIndex("threadId_status_tool_order_stepOrder", (q) =>
434
- q.eq("threadId", threadId).eq("status", status).eq("tool", false)
435
- )
436
- .order("desc")
185
+ [true, false].flatMap((tool) =>
186
+ ["success" as const, "pending" as const].map((status) =>
187
+ stream(ctx.db, schema)
188
+ .query("messages")
189
+ .withIndex("threadId_status_tool_order_stepOrder", (q) =>
190
+ q.eq("threadId", threadId).eq("status", status).eq("tool", tool)
191
+ )
192
+ .order("desc")
193
+ )
437
194
  ),
438
195
  ["order", "stepOrder"]
439
196
  ).first();
440
197
  } else {
441
- // DO explicitly
442
- const maxPending = await ctx.db
443
- .query("messages")
444
- .withIndex("userId_status_tool_order_stepOrder", (q) =>
445
- q.eq("userId", userId).eq("status", "pending").eq("tool", false)
446
- )
447
- .order("desc")
448
- .first();
449
- const maxSuccess = await ctx.db
450
- .query("messages")
451
- .withIndex("userId_status_tool_order_stepOrder", (q) =>
452
- q.eq("userId", userId).eq("status", "success").eq("tool", false)
453
- )
454
- .order("desc")
455
- .first();
456
- return maxPending
457
- ? maxSuccess
458
- ? maxPending.order > maxSuccess.order
459
- ? maxPending
460
- : maxSuccess
461
- : maxPending
462
- : maxSuccess ?? null;
198
+ return mergedStream(
199
+ [true, false].flatMap((tool) =>
200
+ ["success" as const, "pending" as const].map((status) =>
201
+ stream(ctx.db, schema)
202
+ .query("messages")
203
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
204
+ q.eq("userId", userId).eq("status", status).eq("tool", tool)
205
+ )
206
+ .order("desc")
207
+ )
208
+ ),
209
+ ["order", "stepOrder"]
210
+ ).first();
463
211
  }
464
212
  }
465
213
 
466
214
  const addStepArgs = {
467
215
  userId: v.optional(v.string()),
468
216
  threadId: v.id("threads"),
469
- messageId: v.id("messages"),
217
+ parentMessageId: v.id("messages"),
470
218
  step: vStepWithMessages,
471
219
  failPendingSteps: v.optional(v.boolean()),
472
220
  };
@@ -480,15 +228,16 @@ async function addStepHandler(
480
228
  ctx: MutationCtx,
481
229
  args: ObjectType<typeof addStepArgs>
482
230
  ) {
483
- const parentMessage = await ctx.db.get(args.messageId);
484
- assert(parentMessage, `Message ${args.messageId} not found`);
231
+ const parentMessage = await ctx.db.get(args.parentMessageId);
232
+ assert(parentMessage, `Message ${args.parentMessageId} not found`);
485
233
  const order = parentMessage.order;
486
- assert(order !== undefined, `${args.messageId} has no order`);
234
+ assert(order !== undefined, `${args.parentMessageId} has no order`);
235
+ // TODO: only fetch the last one if we aren't failing pending steps
487
236
  let steps = await ctx.db
488
237
  .query("steps")
489
238
  .withIndex("parentMessageId_order_stepOrder", (q) =>
490
239
  // TODO: fetch pending, and commit later
491
- q.eq("parentMessageId", args.messageId)
240
+ q.eq("parentMessageId", args.parentMessageId)
492
241
  )
493
242
  .collect();
494
243
  if (args.failPendingSteps) {
@@ -502,7 +251,7 @@ async function addStepHandler(
502
251
  const { step, messages } = args.step;
503
252
  const stepId = await ctx.db.insert("steps", {
504
253
  threadId: args.threadId,
505
- parentMessageId: args.messageId,
254
+ parentMessageId: args.parentMessageId,
506
255
  order,
507
256
  stepOrder: (steps.at(-1)?.stepOrder ?? -1) + 1,
508
257
  status: step.finishReason === "stop" ? "success" : "pending",
@@ -512,7 +261,7 @@ async function addStepHandler(
512
261
  userId: args.userId,
513
262
  threadId: args.threadId,
514
263
  stepId,
515
- parentMessageId: args.messageId,
264
+ parentMessageId: args.parentMessageId,
516
265
  agentName: parentMessage.agentName,
517
266
  messages,
518
267
  pending: step.finishReason === "stop" ? false : true,
@@ -520,7 +269,7 @@ async function addStepHandler(
520
269
  });
521
270
  // We don't commit if the parent is still pending.
522
271
  if (step.finishReason === "stop") {
523
- await commitMessageHandler(ctx, { messageId: args.messageId });
272
+ await commitMessageHandler(ctx, { messageId: args.parentMessageId });
524
273
  }
525
274
  steps.push((await ctx.db.get(stepId))!);
526
275
  return steps;
@@ -602,21 +351,23 @@ async function commitMessageHandler(
602
351
  }
603
352
  }
604
353
 
605
- export const getThreadMessages = query({
354
+ export const listMessagesByThreadId = query({
606
355
  args: {
607
356
  threadId: v.id("threads"),
608
- isTool: v.optional(v.boolean()),
357
+ excludeToolMessages: v.optional(v.boolean()),
358
+ /** @deprecated Use excludeToolMessages instead. */
359
+ isTool: v.optional(v.literal("use excludeToolMessages instead of this")),
609
360
  order: v.optional(v.union(v.literal("asc"), v.literal("desc"))),
610
361
  paginationOpts: v.optional(paginationOptsValidator),
611
362
  statuses: v.optional(v.array(vMessageStatus)),
612
- parentMessageId: v.optional(v.id("messages")),
363
+ beforeMessageId: v.optional(v.id("messages")),
613
364
  },
614
365
  handler: async (ctx, args) => {
615
- const statuses = args.statuses ?? ["success"];
616
- const parent =
617
- args.parentMessageId && (await ctx.db.get(args.parentMessageId));
618
- const toolOptions =
619
- args.isTool === undefined ? [true, false] : [args.isTool];
366
+ const statuses =
367
+ args.statuses ?? vMessageStatus.members.map((m) => m.value);
368
+ const before =
369
+ args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
370
+ const toolOptions = args.excludeToolMessages ? [false] : [true, false];
620
371
  const order = args.order ?? "desc";
621
372
  const streams = toolOptions.flatMap((tool) =>
622
373
  statuses.map((status) =>
@@ -627,12 +378,18 @@ export const getThreadMessages = query({
627
378
  .eq("threadId", args.threadId)
628
379
  .eq("status", status)
629
380
  .eq("tool", tool);
630
- if (parent) {
631
- return qq.lte("order", parent.order);
381
+ if (before) {
382
+ return qq.lte("order", before.order);
632
383
  }
633
384
  return qq;
634
385
  })
635
386
  .order(order)
387
+ .filterWith(
388
+ async (m) =>
389
+ !before ||
390
+ m.order < before.order ||
391
+ (m.order === before.order && m.stepOrder < before.stepOrder)
392
+ )
636
393
  )
637
394
  );
638
395
  const messages = await mergedStream(streams, [
@@ -649,11 +406,20 @@ export const getThreadMessages = query({
649
406
  returns: paginationResultValidator(v.doc("messages")),
650
407
  });
651
408
 
409
+ /** @deprecated Use listMessagesByThreadId instead. */
410
+ export const getThreadMessages = query({
411
+ args: { deprecated: v.literal("Use listMessagesByThreadId instead") },
412
+ handler: async () => {
413
+ throw new Error("Use listMessagesByThreadId instead of getThreadMessages");
414
+ },
415
+ returns: paginationResultValidator(v.doc("messages")),
416
+ });
417
+
652
418
  export const searchMessages = action({
653
419
  args: {
654
420
  userId: v.optional(v.string()),
655
421
  threadId: v.optional(v.id("threads")),
656
- parentMessageId: v.optional(v.id("messages")),
422
+ beforeMessageId: v.optional(v.id("messages")),
657
423
  ...vSearchOptions.fields,
658
424
  },
659
425
  returns: v.array(v.doc("messages")),
@@ -667,6 +433,7 @@ export const searchMessages = action({
667
433
  threadId: args.threadId,
668
434
  text: args.text,
669
435
  limit,
436
+ beforeMessageId: args.beforeMessageId,
670
437
  });
671
438
  }
672
439
  if (args.vector) {
@@ -697,7 +464,7 @@ export const searchMessages = action({
697
464
  .sort((a, b) => b.score - a.score);
698
465
  const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
699
466
  const messages: Doc<"messages">[] = await ctx.runQuery(
700
- internal.messages._fetchVectorMessages,
467
+ internal.messages._fetchSearchMessages,
701
468
  {
702
469
  userId: args.userId,
703
470
  threadId: args.threadId,
@@ -706,7 +473,7 @@ export const searchMessages = action({
706
473
  (m) => !vectorIds.includes(m.embeddingId!)
707
474
  ),
708
475
  messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
709
- parentMessageId: args.parentMessageId,
476
+ beforeMessageId: args.beforeMessageId,
710
477
  limit,
711
478
  }
712
479
  );
@@ -716,20 +483,20 @@ export const searchMessages = action({
716
483
  },
717
484
  });
718
485
 
719
- export const _fetchVectorMessages = internalQuery({
486
+ export const _fetchSearchMessages = internalQuery({
720
487
  args: {
721
488
  userId: v.optional(v.string()),
722
489
  threadId: v.optional(v.id("threads")),
723
490
  vectorIds: v.array(vVectorId),
724
491
  textSearchMessages: v.optional(v.array(v.doc("messages"))),
725
492
  messageRange: v.object({ before: v.number(), after: v.number() }),
726
- parentMessageId: v.optional(v.id("messages")),
493
+ beforeMessageId: v.optional(v.id("messages")),
727
494
  limit: v.number(),
728
495
  },
729
496
  returns: v.array(v.doc("messages")),
730
497
  handler: async (ctx, args): Promise<Doc<"messages">[]> => {
731
- const parent =
732
- args.parentMessageId && (await ctx.db.get(args.parentMessageId));
498
+ const beforeMessage =
499
+ args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
733
500
  const { userId, threadId } = args;
734
501
  assert(userId || threadId, "Specify userId or threadId to search");
735
502
  let messages = (
@@ -753,7 +520,10 @@ export const _fetchVectorMessages = internalQuery({
753
520
  m !== undefined &&
754
521
  m !== null &&
755
522
  !m.tool &&
756
- (!parent || m.order <= parent.order)
523
+ (!beforeMessage ||
524
+ m.order < beforeMessage.order ||
525
+ (m.order === beforeMessage.order &&
526
+ m.stepOrder < beforeMessage.stepOrder))
757
527
  );
758
528
  messages.push(...(args.textSearchMessages ?? []));
759
529
  // TODO: prioritize more recent messages
@@ -844,9 +614,13 @@ export const textSearch = query({
844
614
  userId: v.optional(v.string()),
845
615
  text: v.string(),
846
616
  limit: v.number(),
617
+ beforeMessageId: v.optional(v.id("messages")),
847
618
  },
848
619
  handler: async (ctx, args) => {
849
620
  assert(args.userId || args.threadId, "Specify userId or threadId");
621
+ const beforeMessage =
622
+ args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
623
+ const order = beforeMessage?.order;
850
624
  const messages = await ctx.db
851
625
  .query("messages")
852
626
  .withSearchIndex("text_search", (q) =>
@@ -855,9 +629,21 @@ export const textSearch = query({
855
629
  : q.search("text", args.text).eq("threadId", args.threadId!)
856
630
  )
857
631
  // Just in case tool messages slip through
858
- .filter((q) => q.eq(q.field("tool"), false))
632
+ .filter((q) => {
633
+ const qq = q.eq(q.field("tool"), false);
634
+ if (order) {
635
+ return q.and(qq, q.lte(q.field("order"), order));
636
+ }
637
+ return qq;
638
+ })
859
639
  .take(args.limit);
860
- return messages;
640
+ return messages.filter(
641
+ (m) =>
642
+ !beforeMessage ||
643
+ m.order < beforeMessage.order ||
644
+ (m.order === beforeMessage.order &&
645
+ m.stepOrder < beforeMessage.stepOrder)
646
+ );
861
647
  },
862
648
  returns: v.array(v.doc("messages")),
863
649
  });