@convex-dev/agent 0.0.1-alpha.4 → 0.0.2

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 (66) hide show
  1. package/README.md +35 -7
  2. package/dist/commonjs/client/index.d.ts +800 -84
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +176 -130
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/messages.d.ts +124 -105
  7. package/dist/commonjs/component/messages.d.ts.map +1 -1
  8. package/dist/commonjs/component/messages.js +145 -247
  9. package/dist/commonjs/component/messages.js.map +1 -1
  10. package/dist/commonjs/component/schema.d.ts +822 -746
  11. package/dist/commonjs/component/schema.d.ts.map +1 -1
  12. package/dist/commonjs/component/schema.js +13 -3
  13. package/dist/commonjs/component/schema.js.map +1 -1
  14. package/dist/commonjs/component/vector/index.d.ts +21 -6
  15. package/dist/commonjs/component/vector/index.d.ts.map +1 -1
  16. package/dist/commonjs/component/vector/index.js +32 -20
  17. package/dist/commonjs/component/vector/index.js.map +1 -1
  18. package/dist/commonjs/component/vector/tables.d.ts +25 -5
  19. package/dist/commonjs/component/vector/tables.d.ts.map +1 -1
  20. package/dist/commonjs/component/vector/tables.js +14 -6
  21. package/dist/commonjs/component/vector/tables.js.map +1 -1
  22. package/dist/commonjs/mapping.d.ts +3 -2
  23. package/dist/commonjs/mapping.d.ts.map +1 -1
  24. package/dist/commonjs/mapping.js +45 -13
  25. package/dist/commonjs/mapping.js.map +1 -1
  26. package/dist/commonjs/validators.d.ts +2817 -145
  27. package/dist/commonjs/validators.d.ts.map +1 -1
  28. package/dist/commonjs/validators.js +79 -7
  29. package/dist/commonjs/validators.js.map +1 -1
  30. package/dist/esm/client/index.d.ts +800 -84
  31. package/dist/esm/client/index.d.ts.map +1 -1
  32. package/dist/esm/client/index.js +176 -130
  33. package/dist/esm/client/index.js.map +1 -1
  34. package/dist/esm/component/messages.d.ts +124 -105
  35. package/dist/esm/component/messages.d.ts.map +1 -1
  36. package/dist/esm/component/messages.js +145 -247
  37. package/dist/esm/component/messages.js.map +1 -1
  38. package/dist/esm/component/schema.d.ts +822 -746
  39. package/dist/esm/component/schema.d.ts.map +1 -1
  40. package/dist/esm/component/schema.js +13 -3
  41. package/dist/esm/component/schema.js.map +1 -1
  42. package/dist/esm/component/vector/index.d.ts +21 -6
  43. package/dist/esm/component/vector/index.d.ts.map +1 -1
  44. package/dist/esm/component/vector/index.js +32 -20
  45. package/dist/esm/component/vector/index.js.map +1 -1
  46. package/dist/esm/component/vector/tables.d.ts +25 -5
  47. package/dist/esm/component/vector/tables.d.ts.map +1 -1
  48. package/dist/esm/component/vector/tables.js +14 -6
  49. package/dist/esm/component/vector/tables.js.map +1 -1
  50. package/dist/esm/mapping.d.ts +3 -2
  51. package/dist/esm/mapping.d.ts.map +1 -1
  52. package/dist/esm/mapping.js +45 -13
  53. package/dist/esm/mapping.js.map +1 -1
  54. package/dist/esm/validators.d.ts +2817 -145
  55. package/dist/esm/validators.d.ts.map +1 -1
  56. package/dist/esm/validators.js +79 -7
  57. package/dist/esm/validators.js.map +1 -1
  58. package/package.json +7 -7
  59. package/src/client/index.ts +347 -170
  60. package/src/component/_generated/api.d.ts +33 -16
  61. package/src/component/messages.ts +177 -290
  62. package/src/component/schema.ts +14 -3
  63. package/src/component/vector/index.ts +59 -26
  64. package/src/component/vector/tables.ts +38 -6
  65. package/src/mapping.ts +60 -19
  66. package/src/validators.ts +112 -12
@@ -5,11 +5,12 @@ import { nullable, partial } from "convex-helpers/validators";
5
5
  import { ObjectType } from "convex/values";
6
6
  import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
7
7
  import {
8
- vThreadStatus,
8
+ vEmbeddingsWithMetadata,
9
9
  vMessageStatus,
10
10
  vMessageWithFileAndId,
11
11
  vSearchOptions,
12
- vStepWithMessagesWithFileAndId,
12
+ vStepWithMessages,
13
+ vThreadStatus,
13
14
  } from "../validators.js";
14
15
  import { api, internal } from "./_generated/api.js";
15
16
  import { Doc, Id } from "./_generated/dataModel.js";
@@ -23,10 +24,11 @@ import {
23
24
  QueryCtx,
24
25
  } from "./_generated/server.js";
25
26
  import { schema, v } from "./schema.js";
27
+ import { insertVector, searchVectors } from "./vector/index.js";
26
28
  import {
27
- getVectorTableName,
28
29
  VectorDimension,
29
30
  VectorDimensions,
31
+ VectorTableId,
30
32
  vVectorId,
31
33
  } from "./vector/tables.js";
32
34
 
@@ -45,23 +47,23 @@ export const getThreadsByUserId = query({
45
47
  cursor: v.optional(v.union(v.string(), v.null())),
46
48
  limit: v.optional(v.number()),
47
49
  offset: v.optional(v.number()),
48
- statuses: v.optional(v.array(vThreadStatus)),
50
+ statuses: v.optional(vThreadStatus),
49
51
  },
50
52
  handler: async (ctx, args) => {
51
- const streams = (args.statuses ?? ["active"]).map((status) =>
52
- stream(ctx.db, schema)
53
- .query("threads")
54
- .withIndex("status_userId_order", (q) =>
55
- q
56
- .eq("status", status)
57
- .eq("userId", args.userId)
58
- .gte("order", args.offset ?? 0)
59
- )
60
- );
61
- const threads = await mergedStream(streams, ["order"]).paginate({
62
- numItems: args.limit ?? 100,
63
- cursor: args.cursor ?? null,
64
- });
53
+ const status = args.statuses ?? "active";
54
+ const threads = await paginator(ctx.db, schema)
55
+ .query("threads")
56
+ .withIndex("userId_status_order", (q) =>
57
+ q
58
+ .eq("userId", args.userId)
59
+ .eq("status", status)
60
+ .gte("order", args.offset ?? 0)
61
+ )
62
+ .order("desc")
63
+ .paginate({
64
+ numItems: args.limit ?? 100,
65
+ cursor: args.cursor ?? null,
66
+ });
65
67
  return {
66
68
  threads: threads.page,
67
69
  continueCursor: threads.continueCursor,
@@ -76,20 +78,17 @@ export const getThreadsByUserId = query({
76
78
  });
77
79
 
78
80
  const vThread = schema.tables.threads.validator;
79
- const statuses = vThread.fields.status.members.map((m) => m.value);
80
81
 
81
82
  export const createThread = mutation({
82
83
  args: omit(vThread.fields, ["order", "status"]),
83
84
  handler: async (ctx, args) => {
84
- const streams = statuses.map((status) =>
85
- stream(ctx.db, schema)
86
- .query("threads")
87
- .withIndex("status_userId_order", (q) =>
88
- q.eq("status", status).eq("userId", args.userId)
89
- )
90
- .order("desc")
91
- );
92
- const latestThread = await mergedStream(streams, ["order"]).first();
85
+ const latestThread = await ctx.db
86
+ .query("threads")
87
+ .withIndex("userId_status_order", (q) =>
88
+ q.eq("userId", args.userId).eq("status", "active")
89
+ )
90
+ .order("desc")
91
+ .first();
93
92
  const order = (latestThread?.order ?? -1) + 1;
94
93
  const threadId = await ctx.db.insert("threads", {
95
94
  ...args,
@@ -164,7 +163,7 @@ export const deleteAllForUserIdAsync = mutation({
164
163
  userId: v.string(),
165
164
  },
166
165
  handler: async (ctx, args) => {
167
- const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
166
+ const isDone = await deleteAllForUserIdAsyncHandler(ctx, {
168
167
  userId: args.userId,
169
168
  messagesCursor: null,
170
169
  threadsCursor: null,
@@ -189,11 +188,11 @@ type DeleteAllReturns = ObjectType<typeof deleteAllReturns>;
189
188
 
190
189
  export const _deleteAllForUserIdAsync = internalMutation({
191
190
  args: deleteAllArgs,
192
- handler: deleteAllFroUserIdAsyncHandler,
191
+ handler: deleteAllForUserIdAsyncHandler,
193
192
  returns: v.boolean(),
194
193
  });
195
194
 
196
- async function deleteAllFroUserIdAsyncHandler(
195
+ async function deleteAllForUserIdAsyncHandler(
197
196
  ctx: MutationCtx,
198
197
  args: DeleteAllArgs
199
198
  ): Promise<boolean> {
@@ -221,51 +220,38 @@ async function deletePageForUserId(
221
220
  ctx: MutationCtx,
222
221
  args: DeleteAllArgs
223
222
  ): Promise<DeleteAllReturns> {
224
- const streams = statuses.map((status) =>
225
- stream(ctx.db, schema)
226
- .query("threads")
227
- .withIndex("status_userId_order", (q) =>
228
- q.eq("status", status).eq("userId", args.userId)
229
- )
230
- .order("desc")
231
- );
232
- const threadStreams = mergedStream(streams, ["order"]);
233
- const messages = await threadStreams
234
- .flatMap(
235
- async (c) =>
236
- stream(ctx.db, schema)
237
- .query("messages")
238
- .withIndex("threadId_status_tool_order_stepOrder", (q) =>
239
- q.eq("threadId", c._id).eq("status", "success")
240
- ),
241
- ["tool", "order", "stepOrder"]
223
+ const threads = await paginator(ctx.db, schema)
224
+ .query("threads")
225
+ .withIndex("userId_status_order", (q) => q.eq("userId", args.userId))
226
+ .order("desc")
227
+ .paginate({
228
+ numItems: 100,
229
+ cursor: args.threadsCursor ?? null,
230
+ });
231
+ await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
232
+ const messages = await paginator(ctx.db, schema)
233
+ .query("messages")
234
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
235
+ q.eq("userId", args.userId)
242
236
  )
237
+ .order("desc")
243
238
  .paginate({
244
239
  numItems: 100,
245
240
  cursor: args.messagesCursor ?? null,
246
241
  });
247
242
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
248
- if (messages.isDone) {
249
- const threads = await threadStreams.paginate({
250
- numItems: 100,
251
- cursor: args.threadsCursor ?? null,
252
- });
253
- await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
254
- return {
255
- messagesCursor: messages.continueCursor,
256
- threadsCursor: threads.continueCursor,
257
- isDone: threads.isDone,
258
- };
259
- }
260
243
  return {
261
244
  messagesCursor: messages.continueCursor,
262
- threadsCursor: null,
245
+ threadsCursor: threads.continueCursor,
263
246
  isDone: messages.isDone,
264
247
  };
265
248
  }
266
249
 
267
250
  async function deleteMessage(ctx: MutationCtx, messageDoc: Doc<"messages">) {
268
251
  await ctx.db.delete(messageDoc._id);
252
+ if (messageDoc.embeddingId) {
253
+ await ctx.db.delete(messageDoc.embeddingId);
254
+ }
269
255
  if (messageDoc.fileId) {
270
256
  const file = await ctx.db.get(messageDoc.fileId);
271
257
  if (file) {
@@ -323,10 +309,10 @@ async function deletePageForThreadIdHandler(
323
309
  ctx: MutationCtx,
324
310
  args: DeleteThreadArgs
325
311
  ): Promise<DeleteThreadReturns> {
326
- const messages = await stream(ctx.db, schema)
312
+ const messages = await paginator(ctx.db, schema)
327
313
  .query("messages")
328
314
  .withIndex("threadId_status_tool_order_stepOrder", (q) =>
329
- q.eq("threadId", args.threadId).eq("status", "success")
315
+ q.eq("threadId", args.threadId)
330
316
  )
331
317
  .paginate({
332
318
  numItems: args.limit ?? 100,
@@ -373,7 +359,7 @@ export const messageStatuses = vMessageDoc.fields.status.members.map(
373
359
 
374
360
  const addMessagesArgs = {
375
361
  userId: v.optional(v.string()),
376
- threadId: v.optional(v.id("threads")),
362
+ threadId: v.id("threads"),
377
363
  stepId: v.optional(v.id("steps")),
378
364
  parentMessageId: v.optional(v.id("messages")),
379
365
  messages: v.array(vMessageWithFileAndId),
@@ -381,6 +367,7 @@ const addMessagesArgs = {
381
367
  agentName: v.optional(v.string()),
382
368
  pending: v.optional(v.boolean()),
383
369
  failPendingSteps: v.optional(v.boolean()),
370
+ embeddings: v.optional(vEmbeddingsWithMetadata),
384
371
  };
385
372
  export const addMessages = mutation({
386
373
  args: addMessagesArgs,
@@ -394,16 +381,27 @@ async function addMessagesHandler(
394
381
  ctx: MutationCtx,
395
382
  args: ObjectType<typeof addMessagesArgs>
396
383
  ) {
384
+ assert(
385
+ !args.embeddings || args.embeddings.vectors.length === args.messages.length,
386
+ "embeddings must have one vector per message"
387
+ );
397
388
  let userId = args.userId;
398
389
  const threadId = args.threadId;
399
390
  if (!userId && args.threadId) {
400
391
  const thread = await ctx.db.get(args.threadId);
401
392
  assert(thread, `Thread ${args.threadId} not found`);
402
- userId = thread._id;
393
+ userId = thread.userId;
403
394
  }
404
- const { failPendingSteps, pending, messages, parentMessageId, ...rest } =
405
- args;
395
+ const {
396
+ failPendingSteps,
397
+ pending,
398
+ messages,
399
+ parentMessageId,
400
+ embeddings,
401
+ ...rest
402
+ } = args;
406
403
  const parent = parentMessageId && (await ctx.db.get(parentMessageId));
404
+ // TODO: I think this is a bug - parent will be pending always?
407
405
  if (failPendingSteps && parent?.status !== "pending") {
408
406
  assert(args.threadId, "threadId is required to fail pending steps");
409
407
  const pendingMessages = await ctx.db
@@ -422,7 +420,18 @@ async function addMessagesHandler(
422
420
  let order = maxMessage?.order ?? -1;
423
421
  const toReturn: Doc<"messages">[] = [];
424
422
  if (messages.length > 0) {
425
- for (const { message, fileId, id } of messages) {
423
+ for (const [i, { message, fileId, id }] of messages.entries()) {
424
+ const embedding = embeddings?.vectors[i] ?? undefined;
425
+ let embeddingId: VectorTableId | undefined;
426
+ if (embeddings && embedding) {
427
+ embeddingId = await insertVector(ctx, embeddings.dimension, {
428
+ vector: embedding,
429
+ model: embeddings.model,
430
+ table: "messages",
431
+ userId,
432
+ threadId,
433
+ });
434
+ }
426
435
  const tool = isTool(message);
427
436
  if (!tool) {
428
437
  order++;
@@ -430,6 +439,7 @@ async function addMessagesHandler(
430
439
  const text = extractText(message);
431
440
  const messageId = await ctx.db.insert("messages", {
432
441
  ...rest,
442
+ embeddingId,
433
443
  parentMessageId,
434
444
  userId,
435
445
  message,
@@ -439,7 +449,13 @@ async function addMessagesHandler(
439
449
  text,
440
450
  fileId,
441
451
  status: pending ? "pending" : "success",
452
+ stepOrder: 0,
442
453
  });
454
+ if (fileId) {
455
+ await ctx.db.patch(fileId, {
456
+ refcount: (await ctx.db.get(fileId))!.refcount + 1,
457
+ });
458
+ }
443
459
  toReturn.push((await ctx.db.get(messageId))!);
444
460
  }
445
461
  }
@@ -465,35 +481,47 @@ async function getMaxMessage(
465
481
  ["order", "stepOrder"]
466
482
  ).first();
467
483
  } 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();
484
+ // DO explicitly
485
+ const maxPending = await ctx.db
486
+ .query("messages")
487
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
488
+ q.eq("userId", userId).eq("status", "pending").eq("tool", false)
489
+ )
490
+ .order("desc")
491
+ .first();
492
+ const maxSuccess = await ctx.db
493
+ .query("messages")
494
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
495
+ q.eq("userId", userId).eq("status", "success").eq("tool", false)
496
+ )
497
+ .order("desc")
498
+ .first();
499
+ return maxPending
500
+ ? maxSuccess
501
+ ? maxPending.order > maxSuccess.order
502
+ ? maxPending
503
+ : maxSuccess
504
+ : maxPending
505
+ : maxSuccess ?? null;
479
506
  }
480
507
  }
481
508
 
482
- const addStepsArgs = {
509
+ const addStepArgs = {
483
510
  threadId: v.id("threads"),
484
511
  messageId: v.id("messages"),
485
- steps: v.array(vStepWithMessagesWithFileAndId),
512
+ step: vStepWithMessages,
486
513
  failPendingSteps: v.optional(v.boolean()),
514
+ embeddings: v.optional(vEmbeddingsWithMetadata),
487
515
  };
488
516
 
489
- export const addSteps = mutation({
490
- args: addStepsArgs,
517
+ export const addStep = mutation({
518
+ args: addStepArgs,
491
519
  returns: v.array(v.doc("steps")),
492
- handler: addStepsHandler,
520
+ handler: addStepHandler,
493
521
  });
494
- async function addStepsHandler(
522
+ async function addStepHandler(
495
523
  ctx: MutationCtx,
496
- args: ObjectType<typeof addStepsArgs>
524
+ args: ObjectType<typeof addStepArgs>
497
525
  ) {
498
526
  const parentMessage = await ctx.db.get(args.messageId);
499
527
  assert(parentMessage, `Message ${args.messageId} not found`);
@@ -512,35 +540,33 @@ async function addStepsHandler(
512
540
  await ctx.db.patch(step._id, { status: "failed" });
513
541
  }
514
542
  }
515
- steps = steps.filter((s) => s.status === "success");
543
+ steps = steps.filter((s) => s.status !== "failed");
516
544
  }
517
- let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
518
- for (const { step, messages } of args.steps) {
519
- const stepId = await ctx.db.insert("steps", {
520
- threadId: args.threadId,
521
- parentMessageId: args.messageId,
522
- order,
523
- stepOrder: nextStepOrder,
524
- status: step.finishReason === "stop" ? "success" : "pending",
525
- step,
526
- });
527
- await addMessagesHandler(ctx, {
528
- threadId: args.threadId,
529
- parentMessageId: args.messageId,
530
- stepId,
531
- messages,
532
- model: parentMessage.model,
533
- agentName: parentMessage.agentName,
534
- pending: step.finishReason === "stop" ? false : true,
535
- failPendingSteps: false,
536
- });
537
- // We don't commit if the parent is still pending.
538
- if (step.finishReason === "stop" && parentMessage.status === "success") {
539
- await commitMessageHandler(ctx, { messageId: args.messageId });
540
- }
541
- steps.push((await ctx.db.get(stepId))!);
542
- nextStepOrder++;
545
+ const { step, messages } = args.step;
546
+ const stepId = await ctx.db.insert("steps", {
547
+ threadId: args.threadId,
548
+ parentMessageId: args.messageId,
549
+ order,
550
+ stepOrder: (steps.at(-1)?.stepOrder ?? -1) + 1,
551
+ status: step.finishReason === "stop" ? "success" : "pending",
552
+ step,
553
+ });
554
+ await addMessagesHandler(ctx, {
555
+ threadId: args.threadId,
556
+ parentMessageId: args.messageId,
557
+ stepId,
558
+ messages,
559
+ model: parentMessage.model,
560
+ agentName: parentMessage.agentName,
561
+ pending: step.finishReason === "stop" ? false : true,
562
+ failPendingSteps: false,
563
+ embeddings: args.embeddings,
564
+ });
565
+ // We don't commit if the parent is still pending.
566
+ if (step.finishReason === "stop") {
567
+ await commitMessageHandler(ctx, { messageId: args.messageId });
543
568
  }
569
+ steps.push((await ctx.db.get(stepId))!);
544
570
  return steps;
545
571
  }
546
572
 
@@ -553,9 +579,23 @@ export const rollbackMessage = mutation({
553
579
  handler: async (ctx, { messageId, error }) => {
554
580
  const message = await ctx.db.get(messageId);
555
581
  assert(message, `Message ${messageId} not found`);
582
+ // TODO: do BFS to fail all associated messages, then steps
583
+ // with parentMessageId of those messages, etc.
584
+ const steps = await ctx.db
585
+ .query("steps")
586
+ .withIndex("parentMessageId_order_stepOrder", (q) =>
587
+ // TODO: fetch pending, and commit later
588
+ q.eq("parentMessageId", messageId)
589
+ )
590
+ .collect();
591
+ for (const step of steps) {
592
+ if (step.status === "pending") {
593
+ await ctx.db.patch(step._id, { status: "failed" });
594
+ }
595
+ }
556
596
  await ctx.db.patch(messageId, {
557
597
  status: "failed",
558
- text: error ?? message.text,
598
+ error: error,
559
599
  });
560
600
  },
561
601
  });
@@ -686,18 +726,16 @@ export const searchMessages = action({
686
726
  if (!VectorDimensions.includes(dimension)) {
687
727
  throw new Error(`Unsupported vector dimension: ${dimension}`);
688
728
  }
689
- const model = args.vectorModel ?? "unknown";
690
- const tableName = getVectorTableName(dimension);
691
729
  const vectors = (
692
- await ctx.vectorSearch(tableName, "vector", {
693
- vector: args.vector,
694
- filter: (q) =>
695
- args.userId
696
- ? q.eq("model_kind_userId", [model, "thread", args.userId])
697
- : q.eq("model_kind_threadId", [model, "thread", args.threadId!]),
730
+ await searchVectors(ctx, args.vector, {
731
+ dimension,
732
+ model: args.vectorModel ?? "unknown",
733
+ table: "messages",
734
+ userId: args.userId,
735
+ threadId: args.threadId,
698
736
  limit,
699
737
  })
700
- ).filter((v) => v._score > 0.5);
738
+ ).filter((v) => v._score > (args.vectorScoreThreshold ?? 0));
701
739
  // Reciprocal rank fusion
702
740
  const k = 10;
703
741
  const textEmbeddingIds = textSearchMessages?.map((m) => m.embeddingId);
@@ -706,11 +744,10 @@ export const searchMessages = action({
706
744
  id: v._id,
707
745
  score:
708
746
  1 / (i + k) +
709
- 1 / (textEmbeddingIds?.indexOf(v._id) ?? Infinity + k),
747
+ 1 / ((textEmbeddingIds?.indexOf(v._id) ?? Infinity) + k),
710
748
  }))
711
749
  .sort((a, b) => b.score - a.score);
712
750
  const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
713
-
714
751
  const messages: Doc<"messages">[] = await ctx.runQuery(
715
752
  internal.messages._fetchVectorMessages,
716
753
  {
@@ -755,10 +792,11 @@ export const _fetchVectorMessages = internalQuery({
755
792
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
756
793
  .filter((q) =>
757
794
  userId
758
- ? q.eq("userId", userId)
759
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
760
- q.eq("threadId", threadId as any)
795
+ ? q.eq(q.field("userId"), userId)
796
+ : q.eq(q.field("threadId"), threadId)
761
797
  )
798
+ // Don't include pending. Failed messages hopefully are deleted but may as well be safe.
799
+ .filter((q) => q.eq(q.field("status"), "success"))
762
800
  .first()
763
801
  )
764
802
  )
@@ -811,8 +849,8 @@ export const _fetchVectorMessages = internalQuery({
811
849
  .eq("threadId", m.threadId)
812
850
  .eq("status", "success")
813
851
  .eq("tool", false)
814
- .gt("order", earliest)
815
- .lt("order", latest)
852
+ .gte("order", earliest)
853
+ .lte("order", latest)
816
854
  )
817
855
  .collect();
818
856
  if (!ranges[searchId]) {
@@ -827,8 +865,8 @@ export const _fetchVectorMessages = internalQuery({
827
865
  .eq("userId", m.userId!)
828
866
  .eq("status", "success")
829
867
  .eq("tool", false)
830
- .gt("order", earliest)
831
- .lt("order", latest)
868
+ .gte("order", earliest)
869
+ .lte("order", latest)
832
870
  )
833
871
  .collect();
834
872
  if (!ranges[searchId]) {
@@ -838,9 +876,12 @@ export const _fetchVectorMessages = internalQuery({
838
876
  }
839
877
  }
840
878
  }
841
- return Object.values(ranges)
842
- .map((r) => r.sort((a, b) => a.order! - b.order!))
843
- .flat();
879
+ for (const r of Object.values(ranges).flat()) {
880
+ if (!messages.includes(r)) {
881
+ messages.push(r);
882
+ }
883
+ }
884
+ return messages.sort((a, b) => a.order - b.order);
844
885
  },
845
886
  });
846
887
 
@@ -867,157 +908,3 @@ export const textSearch = query({
867
908
  },
868
909
  returns: v.array(v.doc("messages")),
869
910
  });
870
-
871
- // const vMemoryConfig = v.object({
872
- // lastMessages: v.optional(v.union(v.number(), v.literal(false))),
873
- // semanticRecall: v.optional(
874
- // v.union(
875
- // v.boolean(),
876
- // v.object({
877
- // topK: v.number(),
878
- // messageRange: v.union(
879
- // v.number(),
880
- // v.object({ before: v.number(), after: v.number() }),
881
- // ),
882
- // }),
883
- // ),
884
- // ),
885
- // workingMemory: v.optional(
886
- // v.object({
887
- // enabled: v.boolean(),
888
- // template: v.optional(v.string()),
889
- // use: v.optional(
890
- // v.union(v.literal("text-stream"), v.literal("tool-call")),
891
- // ),
892
- // }),
893
- // ),
894
- // threads: v.optional(
895
- // v.object({
896
- // generateTitle: v.optional(v.boolean()),
897
- // }),
898
- // ),
899
- // });
900
- // const vSelectBy = v.object({
901
- // vectorSearchString: v.optional(v.string()),
902
- // last: v.optional(v.union(v.number(), v.literal(false))),
903
- // include: v.optional(
904
- // v.array(
905
- // v.object({
906
- // id: v.string(),
907
- // withPreviousMessages: v.optional(v.number()),
908
- // withNextMessages: v.optional(v.number()),
909
- // })
910
- // )
911
- // ),
912
- // });
913
-
914
- // const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
915
-
916
- // export const getThreadMessagesPage = query({
917
- // args: {
918
- // parentMessageId: v.string(),
919
- // selectBy: v.optional(vSelectBy),
920
- // // Unimplemented and as far I can tell no storage provider has either.
921
- // // memoryConfig: v.optional(vMemoryConfig),
922
- // },
923
- // handler: async (ctx, args): Promise<SerializedMessage[]> => {
924
- // const messages = await ctx.db
925
- // .query("messages")
926
- // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", args.parentMessageId))
927
- // .order("desc")
928
- // .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
929
-
930
- // const handled: boolean[] = [];
931
- // const toFetch: number[] = [];
932
- // for (const m of messages) {
933
- // handled[m.threadOrder] = true;
934
- // }
935
- // await Promise.all(
936
- // args.selectBy?.include?.map(async (range) => {
937
- // const includeDoc = await ctx.db
938
- // .query("messages")
939
- // .withIndex("id", (q) => q.eq("id", range.id))
940
- // .unique();
941
- // if (!includeDoc) {
942
- // console.warn(`Message ${range.id} not found`);
943
- // return;
944
- // }
945
- // if (!range.withPreviousMessages && !range.withNextMessages) {
946
- // messages.push(includeDoc);
947
- // return;
948
- // }
949
- // const order = includeDoc.threadOrder;
950
- // for (
951
- // let i = order - (range.withPreviousMessages ?? 0);
952
- // i < order + (range.withNextMessages ?? 0);
953
- // i++
954
- // ) {
955
- // if (!handled[i]) {
956
- // toFetch.push(i);
957
- // handled[i] = true;
958
- // }
959
- // }
960
- // }) ?? []
961
- // );
962
- // // sort and find unique numbers in toFetch
963
- // const uniqueToFetch = [...new Set(toFetch)].sort();
964
- // // find contiguous ranges in uniqueToFetch
965
- // const ranges: { start: number; end: number }[] = [];
966
- // for (let i = 0; i < uniqueToFetch.length; i++) {
967
- // const start = uniqueToFetch[i];
968
- // let end = start;
969
- // while (i + 1 < uniqueToFetch.length && uniqueToFetch[i + 1] === end + 1) {
970
- // end++;
971
- // i++;
972
- // }
973
- // ranges.push({ start, end });
974
- // }
975
- // const fetched = (
976
- // await Promise.all(
977
- // ranges.map(async (range) => {
978
- // return await ctx.db
979
- // .query("messages")
980
- // .withIndex("parentMessageId", (q) =>
981
- // q
982
- // .eq("parentMessageId", args.parentMessageId)
983
- // .gte("threadOrder", range.start)
984
- // .lte("threadOrder", range.end)
985
- // )
986
- // .collect();
987
- // })
988
- // )
989
- // ).flat();
990
- // messages.push(...fetched);
991
- // return messages.map(messageToSerializedMastra);
992
- // },
993
- // returns: v.array(vSerializedMessage),
994
- // });
995
-
996
- // export const saveMessages = mutation({
997
- // args: { messages: v.array(vSerializedMessage) },
998
- // handler: async (ctx, args) => {
999
- // const messagesByParentMessageId: Record<string, SerializedMessage[]> = {};
1000
- // for (const message of args.messages) {
1001
- // messagesByParentMessageId[message.parentMessageId] = [
1002
- // ...(messagesByParentMessageId[message.parentMessageId] ?? []),
1003
- // message,
1004
- // ];
1005
- // }
1006
- // for (const parentMessageId in messagesByParentMessageId) {
1007
- // const lastMessage = await ctx.db
1008
- // .query("messages")
1009
- // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", parentMessageId))
1010
- // .order("desc")
1011
- // .first();
1012
- // let threadOrder = lastMessage?.threadOrder ?? 0;
1013
- // for (const message of messagesByParentMessageId[parentMessageId]) {
1014
- // threadOrder++;
1015
- // await ctx.db.insert("messages", {
1016
- // ...message,
1017
- // threadOrder,
1018
- // });
1019
- // }
1020
- // }
1021
- // },
1022
- // returns: v.null(),
1023
- // });