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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +9 -7
  2. package/dist/commonjs/client/index.d.ts +12 -5
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +127 -44
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/messages.d.ts +114 -103
  7. package/dist/commonjs/component/messages.d.ts.map +1 -1
  8. package/dist/commonjs/component/messages.js +143 -246
  9. package/dist/commonjs/component/messages.js.map +1 -1
  10. package/dist/commonjs/component/schema.d.ts +808 -738
  11. package/dist/commonjs/component/schema.d.ts.map +1 -1
  12. package/dist/commonjs/component/schema.js +12 -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 +33 -21
  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/validators.d.ts +101 -89
  23. package/dist/commonjs/validators.d.ts.map +1 -1
  24. package/dist/commonjs/validators.js +8 -1
  25. package/dist/commonjs/validators.js.map +1 -1
  26. package/dist/esm/client/index.d.ts +12 -5
  27. package/dist/esm/client/index.d.ts.map +1 -1
  28. package/dist/esm/client/index.js +127 -44
  29. package/dist/esm/client/index.js.map +1 -1
  30. package/dist/esm/component/messages.d.ts +114 -103
  31. package/dist/esm/component/messages.d.ts.map +1 -1
  32. package/dist/esm/component/messages.js +143 -246
  33. package/dist/esm/component/messages.js.map +1 -1
  34. package/dist/esm/component/schema.d.ts +808 -738
  35. package/dist/esm/component/schema.d.ts.map +1 -1
  36. package/dist/esm/component/schema.js +12 -3
  37. package/dist/esm/component/schema.js.map +1 -1
  38. package/dist/esm/component/vector/index.d.ts +21 -6
  39. package/dist/esm/component/vector/index.d.ts.map +1 -1
  40. package/dist/esm/component/vector/index.js +33 -21
  41. package/dist/esm/component/vector/index.js.map +1 -1
  42. package/dist/esm/component/vector/tables.d.ts +25 -5
  43. package/dist/esm/component/vector/tables.d.ts.map +1 -1
  44. package/dist/esm/component/vector/tables.js +14 -6
  45. package/dist/esm/component/vector/tables.js.map +1 -1
  46. package/dist/esm/validators.d.ts +101 -89
  47. package/dist/esm/validators.d.ts.map +1 -1
  48. package/dist/esm/validators.js +8 -1
  49. package/dist/esm/validators.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/client/index.ts +149 -50
  52. package/src/component/_generated/api.d.ts +28 -16
  53. package/src/component/messages.ts +175 -289
  54. package/src/component/schema.ts +13 -3
  55. package/src/component/vector/index.ts +65 -26
  56. package/src/component/vector/tables.ts +38 -6
  57. package/src/validators.ts +11 -4
@@ -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,15 +381,25 @@ 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));
407
404
  if (failPendingSteps && parent?.status !== "pending") {
408
405
  assert(args.threadId, "threadId is required to fail pending steps");
@@ -422,7 +419,18 @@ async function addMessagesHandler(
422
419
  let order = maxMessage?.order ?? -1;
423
420
  const toReturn: Doc<"messages">[] = [];
424
421
  if (messages.length > 0) {
425
- for (const { message, fileId, id } of messages) {
422
+ for (const [i, { message, fileId, id }] of messages.entries()) {
423
+ const embedding = embeddings?.vectors[i] ?? undefined;
424
+ let embeddingId: VectorTableId | undefined;
425
+ if (embeddings && embedding) {
426
+ embeddingId = await insertVector(ctx, embeddings.dimension, {
427
+ vector: embedding,
428
+ model: embeddings.model,
429
+ table: "messages",
430
+ userId,
431
+ threadId,
432
+ });
433
+ }
426
434
  const tool = isTool(message);
427
435
  if (!tool) {
428
436
  order++;
@@ -430,6 +438,7 @@ async function addMessagesHandler(
430
438
  const text = extractText(message);
431
439
  const messageId = await ctx.db.insert("messages", {
432
440
  ...rest,
441
+ embeddingId,
433
442
  parentMessageId,
434
443
  userId,
435
444
  message,
@@ -439,7 +448,13 @@ async function addMessagesHandler(
439
448
  text,
440
449
  fileId,
441
450
  status: pending ? "pending" : "success",
451
+ stepOrder: 0,
442
452
  });
453
+ if (fileId) {
454
+ await ctx.db.patch(fileId, {
455
+ refcount: (await ctx.db.get(fileId))!.refcount + 1,
456
+ });
457
+ }
443
458
  toReturn.push((await ctx.db.get(messageId))!);
444
459
  }
445
460
  }
@@ -465,35 +480,47 @@ async function getMaxMessage(
465
480
  ["order", "stepOrder"]
466
481
  ).first();
467
482
  } 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();
483
+ // DO explicitly
484
+ const maxPending = await ctx.db
485
+ .query("messages")
486
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
487
+ q.eq("userId", userId).eq("status", "pending").eq("tool", false)
488
+ )
489
+ .order("desc")
490
+ .first();
491
+ const maxSuccess = await ctx.db
492
+ .query("messages")
493
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
494
+ q.eq("userId", userId).eq("status", "success").eq("tool", false)
495
+ )
496
+ .order("desc")
497
+ .first();
498
+ return maxPending
499
+ ? maxSuccess
500
+ ? maxPending.order > maxSuccess.order
501
+ ? maxPending
502
+ : maxSuccess
503
+ : maxPending
504
+ : maxSuccess ?? null;
479
505
  }
480
506
  }
481
507
 
482
- const addStepsArgs = {
508
+ const addStepArgs = {
483
509
  threadId: v.id("threads"),
484
510
  messageId: v.id("messages"),
485
- steps: v.array(vStepWithMessagesWithFileAndId),
511
+ step: vStepWithMessages,
486
512
  failPendingSteps: v.optional(v.boolean()),
513
+ embeddings: v.optional(vEmbeddingsWithMetadata),
487
514
  };
488
515
 
489
- export const addSteps = mutation({
490
- args: addStepsArgs,
516
+ export const addStep = mutation({
517
+ args: addStepArgs,
491
518
  returns: v.array(v.doc("steps")),
492
- handler: addStepsHandler,
519
+ handler: addStepHandler,
493
520
  });
494
- async function addStepsHandler(
521
+ async function addStepHandler(
495
522
  ctx: MutationCtx,
496
- args: ObjectType<typeof addStepsArgs>
523
+ args: ObjectType<typeof addStepArgs>
497
524
  ) {
498
525
  const parentMessage = await ctx.db.get(args.messageId);
499
526
  assert(parentMessage, `Message ${args.messageId} not found`);
@@ -512,35 +539,33 @@ async function addStepsHandler(
512
539
  await ctx.db.patch(step._id, { status: "failed" });
513
540
  }
514
541
  }
515
- steps = steps.filter((s) => s.status === "success");
542
+ steps = steps.filter((s) => s.status !== "failed");
516
543
  }
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++;
544
+ const { step, messages } = args.step;
545
+ const stepId = await ctx.db.insert("steps", {
546
+ threadId: args.threadId,
547
+ parentMessageId: args.messageId,
548
+ order,
549
+ stepOrder: (steps.at(-1)?.stepOrder ?? -1) + 1,
550
+ status: step.finishReason === "stop" ? "success" : "pending",
551
+ step,
552
+ });
553
+ await addMessagesHandler(ctx, {
554
+ threadId: args.threadId,
555
+ parentMessageId: args.messageId,
556
+ stepId,
557
+ messages,
558
+ model: parentMessage.model,
559
+ agentName: parentMessage.agentName,
560
+ pending: step.finishReason === "stop" ? false : true,
561
+ failPendingSteps: false,
562
+ embeddings: args.embeddings,
563
+ });
564
+ // We don't commit if the parent is still pending.
565
+ if (step.finishReason === "stop") {
566
+ await commitMessageHandler(ctx, { messageId: args.messageId });
543
567
  }
568
+ steps.push((await ctx.db.get(stepId))!);
544
569
  return steps;
545
570
  }
546
571
 
@@ -553,6 +578,20 @@ export const rollbackMessage = mutation({
553
578
  handler: async (ctx, { messageId, error }) => {
554
579
  const message = await ctx.db.get(messageId);
555
580
  assert(message, `Message ${messageId} not found`);
581
+ // TODO: do BFS to fail all associated messages, then steps
582
+ // with parentMessageId of those messages, etc.
583
+ const steps = await ctx.db
584
+ .query("steps")
585
+ .withIndex("parentMessageId_order_stepOrder", (q) =>
586
+ // TODO: fetch pending, and commit later
587
+ q.eq("parentMessageId", messageId)
588
+ )
589
+ .collect();
590
+ for (const step of steps) {
591
+ if (step.status === "pending") {
592
+ await ctx.db.patch(step._id, { status: "failed" });
593
+ }
594
+ }
556
595
  await ctx.db.patch(messageId, {
557
596
  status: "failed",
558
597
  text: error ?? message.text,
@@ -686,18 +725,16 @@ export const searchMessages = action({
686
725
  if (!VectorDimensions.includes(dimension)) {
687
726
  throw new Error(`Unsupported vector dimension: ${dimension}`);
688
727
  }
689
- const model = args.vectorModel ?? "unknown";
690
- const tableName = getVectorTableName(dimension);
691
728
  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!]),
729
+ await searchVectors(ctx, args.vector, {
730
+ dimension,
731
+ model: args.vectorModel ?? "unknown",
732
+ table: "messages",
733
+ userId: args.userId,
734
+ threadId: args.threadId,
698
735
  limit,
699
736
  })
700
- ).filter((v) => v._score > 0.5);
737
+ ).filter((v) => v._score > (args.vectorScoreThreshold ?? 0));
701
738
  // Reciprocal rank fusion
702
739
  const k = 10;
703
740
  const textEmbeddingIds = textSearchMessages?.map((m) => m.embeddingId);
@@ -706,11 +743,10 @@ export const searchMessages = action({
706
743
  id: v._id,
707
744
  score:
708
745
  1 / (i + k) +
709
- 1 / (textEmbeddingIds?.indexOf(v._id) ?? Infinity + k),
746
+ 1 / ((textEmbeddingIds?.indexOf(v._id) ?? Infinity) + k),
710
747
  }))
711
748
  .sort((a, b) => b.score - a.score);
712
749
  const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
713
-
714
750
  const messages: Doc<"messages">[] = await ctx.runQuery(
715
751
  internal.messages._fetchVectorMessages,
716
752
  {
@@ -755,10 +791,11 @@ export const _fetchVectorMessages = internalQuery({
755
791
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
756
792
  .filter((q) =>
757
793
  userId
758
- ? q.eq("userId", userId)
759
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
760
- q.eq("threadId", threadId as any)
794
+ ? q.eq(q.field("userId"), userId)
795
+ : q.eq(q.field("threadId"), threadId)
761
796
  )
797
+ // Don't include pending. Failed messages hopefully are deleted but may as well be safe.
798
+ .filter((q) => q.eq(q.field("status"), "success"))
762
799
  .first()
763
800
  )
764
801
  )
@@ -811,8 +848,8 @@ export const _fetchVectorMessages = internalQuery({
811
848
  .eq("threadId", m.threadId)
812
849
  .eq("status", "success")
813
850
  .eq("tool", false)
814
- .gt("order", earliest)
815
- .lt("order", latest)
851
+ .gte("order", earliest)
852
+ .lte("order", latest)
816
853
  )
817
854
  .collect();
818
855
  if (!ranges[searchId]) {
@@ -827,8 +864,8 @@ export const _fetchVectorMessages = internalQuery({
827
864
  .eq("userId", m.userId!)
828
865
  .eq("status", "success")
829
866
  .eq("tool", false)
830
- .gt("order", earliest)
831
- .lt("order", latest)
867
+ .gte("order", earliest)
868
+ .lte("order", latest)
832
869
  )
833
870
  .collect();
834
871
  if (!ranges[searchId]) {
@@ -838,9 +875,12 @@ export const _fetchVectorMessages = internalQuery({
838
875
  }
839
876
  }
840
877
  }
841
- return Object.values(ranges)
842
- .map((r) => r.sort((a, b) => a.order! - b.order!))
843
- .flat();
878
+ for (const r of Object.values(ranges).flat()) {
879
+ if (!messages.includes(r)) {
880
+ messages.push(r);
881
+ }
882
+ }
883
+ return messages.sort((a, b) => a.order - b.order);
844
884
  },
845
885
  });
846
886
 
@@ -867,157 +907,3 @@ export const textSearch = query({
867
907
  },
868
908
  returns: v.array(v.doc("messages")),
869
909
  });
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
- // });