@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
@@ -3,11 +3,12 @@ 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 { vThreadStatus, vMessageStatus, vMessageWithFileAndId, vSearchOptions, vStepWithMessagesWithFileAndId, } from "../validators.js";
6
+ import { vEmbeddingsWithMetadata, vMessageStatus, vMessageWithFileAndId, vSearchOptions, vStepWithMessages, vThreadStatus, } 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
- import { getVectorTableName, VectorDimensions, vVectorId, } from "./vector/tables.js";
10
+ import { insertVector, searchVectors } from "./vector/index.js";
11
+ import { VectorDimensions, vVectorId, } from "./vector/tables.js";
11
12
  export const getThread = query({
12
13
  args: { threadId: v.id("threads") },
13
14
  handler: async (ctx, args) => {
@@ -22,16 +23,18 @@ export const getThreadsByUserId = query({
22
23
  cursor: v.optional(v.union(v.string(), v.null())),
23
24
  limit: v.optional(v.number()),
24
25
  offset: v.optional(v.number()),
25
- statuses: v.optional(v.array(vThreadStatus)),
26
+ statuses: v.optional(vThreadStatus),
26
27
  },
27
28
  handler: async (ctx, args) => {
28
- const streams = (args.statuses ?? ["active"]).map((status) => stream(ctx.db, schema)
29
+ const status = args.statuses ?? "active";
30
+ const threads = await paginator(ctx.db, schema)
29
31
  .query("threads")
30
- .withIndex("status_userId_order", (q) => q
31
- .eq("status", status)
32
+ .withIndex("userId_status_order", (q) => q
32
33
  .eq("userId", args.userId)
33
- .gte("order", args.offset ?? 0)));
34
- const threads = await mergedStream(streams, ["order"]).paginate({
34
+ .eq("status", status)
35
+ .gte("order", args.offset ?? 0))
36
+ .order("desc")
37
+ .paginate({
35
38
  numItems: args.limit ?? 100,
36
39
  cursor: args.cursor ?? null,
37
40
  });
@@ -48,15 +51,14 @@ export const getThreadsByUserId = query({
48
51
  }),
49
52
  });
50
53
  const vThread = schema.tables.threads.validator;
51
- const statuses = vThread.fields.status.members.map((m) => m.value);
52
54
  export const createThread = mutation({
53
55
  args: omit(vThread.fields, ["order", "status"]),
54
56
  handler: async (ctx, args) => {
55
- const streams = statuses.map((status) => stream(ctx.db, schema)
57
+ const latestThread = await ctx.db
56
58
  .query("threads")
57
- .withIndex("status_userId_order", (q) => q.eq("status", status).eq("userId", args.userId))
58
- .order("desc"));
59
- const latestThread = await mergedStream(streams, ["order"]).first();
59
+ .withIndex("userId_status_order", (q) => q.eq("userId", args.userId).eq("status", "active"))
60
+ .order("desc")
61
+ .first();
60
62
  const order = (latestThread?.order ?? -1) + 1;
61
63
  const threadId = await ctx.db.insert("threads", {
62
64
  ...args,
@@ -119,7 +121,7 @@ export const deleteAllForUserIdAsync = mutation({
119
121
  userId: v.string(),
120
122
  },
121
123
  handler: async (ctx, args) => {
122
- const isDone = await deleteAllFroUserIdAsyncHandler(ctx, {
124
+ const isDone = await deleteAllForUserIdAsyncHandler(ctx, {
123
125
  userId: args.userId,
124
126
  messagesCursor: null,
125
127
  threadsCursor: null,
@@ -140,10 +142,10 @@ const deleteAllReturns = {
140
142
  };
141
143
  export const _deleteAllForUserIdAsync = internalMutation({
142
144
  args: deleteAllArgs,
143
- handler: deleteAllFroUserIdAsyncHandler,
145
+ handler: deleteAllForUserIdAsyncHandler,
144
146
  returns: v.boolean(),
145
147
  });
146
- async function deleteAllFroUserIdAsyncHandler(ctx, args) {
148
+ async function deleteAllForUserIdAsyncHandler(ctx, args) {
147
149
  const result = await deletePageForUserId(ctx, args);
148
150
  if (!result.isDone) {
149
151
  await ctx.scheduler.runAfter(0, internal.messages._deleteAllForUserIdAsync, {
@@ -160,40 +162,35 @@ export const _deletePageForUserId = internalMutation({
160
162
  returns: deleteAllReturns,
161
163
  });
162
164
  async function deletePageForUserId(ctx, args) {
163
- const streams = statuses.map((status) => stream(ctx.db, schema)
165
+ const threads = await paginator(ctx.db, schema)
164
166
  .query("threads")
165
- .withIndex("status_userId_order", (q) => q.eq("status", status).eq("userId", args.userId))
166
- .order("desc"));
167
- const threadStreams = mergedStream(streams, ["order"]);
168
- const messages = await threadStreams
169
- .flatMap(async (c) => stream(ctx.db, schema)
167
+ .withIndex("userId_status_order", (q) => q.eq("userId", args.userId))
168
+ .order("desc")
169
+ .paginate({
170
+ numItems: 100,
171
+ cursor: args.threadsCursor ?? null,
172
+ });
173
+ await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
174
+ const messages = await paginator(ctx.db, schema)
170
175
  .query("messages")
171
- .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", c._id).eq("status", "success")), ["tool", "order", "stepOrder"])
176
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", args.userId))
177
+ .order("desc")
172
178
  .paginate({
173
179
  numItems: 100,
174
180
  cursor: args.messagesCursor ?? null,
175
181
  });
176
182
  await Promise.all(messages.page.map((m) => deleteMessage(ctx, m)));
177
- if (messages.isDone) {
178
- const threads = await threadStreams.paginate({
179
- numItems: 100,
180
- cursor: args.threadsCursor ?? null,
181
- });
182
- await Promise.all(threads.page.map((c) => ctx.db.delete(c._id)));
183
- return {
184
- messagesCursor: messages.continueCursor,
185
- threadsCursor: threads.continueCursor,
186
- isDone: threads.isDone,
187
- };
188
- }
189
183
  return {
190
184
  messagesCursor: messages.continueCursor,
191
- threadsCursor: null,
185
+ threadsCursor: threads.continueCursor,
192
186
  isDone: messages.isDone,
193
187
  };
194
188
  }
195
189
  async function deleteMessage(ctx, messageDoc) {
196
190
  await ctx.db.delete(messageDoc._id);
191
+ if (messageDoc.embeddingId) {
192
+ await ctx.db.delete(messageDoc.embeddingId);
193
+ }
197
194
  if (messageDoc.fileId) {
198
195
  const file = await ctx.db.get(messageDoc.fileId);
199
196
  if (file) {
@@ -238,9 +235,9 @@ export const _deletePageForThreadId = internalMutation({
238
235
  returns: deleteThreadReturns,
239
236
  });
240
237
  async function deletePageForThreadIdHandler(ctx, args) {
241
- const messages = await stream(ctx.db, schema)
238
+ const messages = await paginator(ctx.db, schema)
242
239
  .query("messages")
243
- .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", args.threadId).eq("status", "success"))
240
+ .withIndex("threadId_status_tool_order_stepOrder", (q) => q.eq("threadId", args.threadId))
244
241
  .paginate({
245
242
  numItems: args.limit ?? 100,
246
243
  cursor: args.cursor ?? null,
@@ -281,7 +278,7 @@ export const vMessageDoc = schema.tables.messages.validator;
281
278
  export const messageStatuses = vMessageDoc.fields.status.members.map((m) => m.value);
282
279
  const addMessagesArgs = {
283
280
  userId: v.optional(v.string()),
284
- threadId: v.optional(v.id("threads")),
281
+ threadId: v.id("threads"),
285
282
  stepId: v.optional(v.id("steps")),
286
283
  parentMessageId: v.optional(v.id("messages")),
287
284
  messages: v.array(vMessageWithFileAndId),
@@ -289,6 +286,7 @@ const addMessagesArgs = {
289
286
  agentName: v.optional(v.string()),
290
287
  pending: v.optional(v.boolean()),
291
288
  failPendingSteps: v.optional(v.boolean()),
289
+ embeddings: v.optional(vEmbeddingsWithMetadata),
292
290
  };
293
291
  export const addMessages = mutation({
294
292
  args: addMessagesArgs,
@@ -299,15 +297,17 @@ export const addMessages = mutation({
299
297
  }),
300
298
  });
301
299
  async function addMessagesHandler(ctx, args) {
300
+ assert(!args.embeddings || args.embeddings.vectors.length === args.messages.length, "embeddings must have one vector per message");
302
301
  let userId = args.userId;
303
302
  const threadId = args.threadId;
304
303
  if (!userId && args.threadId) {
305
304
  const thread = await ctx.db.get(args.threadId);
306
305
  assert(thread, `Thread ${args.threadId} not found`);
307
- userId = thread._id;
306
+ userId = thread.userId;
308
307
  }
309
- const { failPendingSteps, pending, messages, parentMessageId, ...rest } = args;
308
+ const { failPendingSteps, pending, messages, parentMessageId, embeddings, ...rest } = args;
310
309
  const parent = parentMessageId && (await ctx.db.get(parentMessageId));
310
+ // TODO: I think this is a bug - parent will be pending always?
311
311
  if (failPendingSteps && parent?.status !== "pending") {
312
312
  assert(args.threadId, "threadId is required to fail pending steps");
313
313
  const pendingMessages = await ctx.db
@@ -320,7 +320,18 @@ async function addMessagesHandler(ctx, args) {
320
320
  let order = maxMessage?.order ?? -1;
321
321
  const toReturn = [];
322
322
  if (messages.length > 0) {
323
- for (const { message, fileId, id } of messages) {
323
+ for (const [i, { message, fileId, id }] of messages.entries()) {
324
+ const embedding = embeddings?.vectors[i] ?? undefined;
325
+ let embeddingId;
326
+ if (embeddings && embedding) {
327
+ embeddingId = await insertVector(ctx, embeddings.dimension, {
328
+ vector: embedding,
329
+ model: embeddings.model,
330
+ table: "messages",
331
+ userId,
332
+ threadId,
333
+ });
334
+ }
324
335
  const tool = isTool(message);
325
336
  if (!tool) {
326
337
  order++;
@@ -328,6 +339,7 @@ async function addMessagesHandler(ctx, args) {
328
339
  const text = extractText(message);
329
340
  const messageId = await ctx.db.insert("messages", {
330
341
  ...rest,
342
+ embeddingId,
331
343
  parentMessageId,
332
344
  userId,
333
345
  message,
@@ -337,7 +349,13 @@ async function addMessagesHandler(ctx, args) {
337
349
  text,
338
350
  fileId,
339
351
  status: pending ? "pending" : "success",
352
+ stepOrder: 0,
340
353
  });
354
+ if (fileId) {
355
+ await ctx.db.patch(fileId, {
356
+ refcount: (await ctx.db.get(fileId)).refcount + 1,
357
+ });
358
+ }
341
359
  toReturn.push((await ctx.db.get(messageId)));
342
360
  }
343
361
  }
@@ -352,24 +370,39 @@ async function getMaxMessage(ctx, threadId, userId) {
352
370
  .order("desc")), ["order", "stepOrder"]).first();
353
371
  }
354
372
  else {
355
- return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
373
+ // DO explicitly
374
+ const maxPending = await ctx.db
356
375
  .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();
376
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", userId).eq("status", "pending").eq("tool", false))
377
+ .order("desc")
378
+ .first();
379
+ const maxSuccess = await ctx.db
380
+ .query("messages")
381
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", userId).eq("status", "success").eq("tool", false))
382
+ .order("desc")
383
+ .first();
384
+ return maxPending
385
+ ? maxSuccess
386
+ ? maxPending.order > maxSuccess.order
387
+ ? maxPending
388
+ : maxSuccess
389
+ : maxPending
390
+ : maxSuccess ?? null;
359
391
  }
360
392
  }
361
- const addStepsArgs = {
393
+ const addStepArgs = {
362
394
  threadId: v.id("threads"),
363
395
  messageId: v.id("messages"),
364
- steps: v.array(vStepWithMessagesWithFileAndId),
396
+ step: vStepWithMessages,
365
397
  failPendingSteps: v.optional(v.boolean()),
398
+ embeddings: v.optional(vEmbeddingsWithMetadata),
366
399
  };
367
- export const addSteps = mutation({
368
- args: addStepsArgs,
400
+ export const addStep = mutation({
401
+ args: addStepArgs,
369
402
  returns: v.array(v.doc("steps")),
370
- handler: addStepsHandler,
403
+ handler: addStepHandler,
371
404
  });
372
- async function addStepsHandler(ctx, args) {
405
+ async function addStepHandler(ctx, args) {
373
406
  const parentMessage = await ctx.db.get(args.messageId);
374
407
  assert(parentMessage, `Message ${args.messageId} not found`);
375
408
  const order = parentMessage.order;
@@ -386,35 +419,33 @@ async function addStepsHandler(ctx, args) {
386
419
  await ctx.db.patch(step._id, { status: "failed" });
387
420
  }
388
421
  }
389
- steps = steps.filter((s) => s.status === "success");
422
+ steps = steps.filter((s) => s.status !== "failed");
390
423
  }
391
- let nextStepOrder = (steps.at(-1)?.stepOrder ?? -1) + 1;
392
- for (const { step, messages } of args.steps) {
393
- const stepId = await ctx.db.insert("steps", {
394
- threadId: args.threadId,
395
- parentMessageId: args.messageId,
396
- order,
397
- stepOrder: nextStepOrder,
398
- status: step.finishReason === "stop" ? "success" : "pending",
399
- step,
400
- });
401
- await addMessagesHandler(ctx, {
402
- threadId: args.threadId,
403
- parentMessageId: args.messageId,
404
- stepId,
405
- messages,
406
- model: parentMessage.model,
407
- agentName: parentMessage.agentName,
408
- pending: step.finishReason === "stop" ? false : true,
409
- failPendingSteps: false,
410
- });
411
- // We don't commit if the parent is still pending.
412
- if (step.finishReason === "stop" && parentMessage.status === "success") {
413
- await commitMessageHandler(ctx, { messageId: args.messageId });
414
- }
415
- steps.push((await ctx.db.get(stepId)));
416
- nextStepOrder++;
424
+ const { step, messages } = args.step;
425
+ const stepId = await ctx.db.insert("steps", {
426
+ threadId: args.threadId,
427
+ parentMessageId: args.messageId,
428
+ order,
429
+ stepOrder: (steps.at(-1)?.stepOrder ?? -1) + 1,
430
+ status: step.finishReason === "stop" ? "success" : "pending",
431
+ step,
432
+ });
433
+ await addMessagesHandler(ctx, {
434
+ threadId: args.threadId,
435
+ parentMessageId: args.messageId,
436
+ stepId,
437
+ messages,
438
+ model: parentMessage.model,
439
+ agentName: parentMessage.agentName,
440
+ pending: step.finishReason === "stop" ? false : true,
441
+ failPendingSteps: false,
442
+ embeddings: args.embeddings,
443
+ });
444
+ // We don't commit if the parent is still pending.
445
+ if (step.finishReason === "stop") {
446
+ await commitMessageHandler(ctx, { messageId: args.messageId });
417
447
  }
448
+ steps.push((await ctx.db.get(stepId)));
418
449
  return steps;
419
450
  }
420
451
  export const rollbackMessage = mutation({
@@ -426,9 +457,22 @@ export const rollbackMessage = mutation({
426
457
  handler: async (ctx, { messageId, error }) => {
427
458
  const message = await ctx.db.get(messageId);
428
459
  assert(message, `Message ${messageId} not found`);
460
+ // TODO: do BFS to fail all associated messages, then steps
461
+ // with parentMessageId of those messages, etc.
462
+ const steps = await ctx.db
463
+ .query("steps")
464
+ .withIndex("parentMessageId_order_stepOrder", (q) =>
465
+ // TODO: fetch pending, and commit later
466
+ q.eq("parentMessageId", messageId))
467
+ .collect();
468
+ for (const step of steps) {
469
+ if (step.status === "pending") {
470
+ await ctx.db.patch(step._id, { status: "failed" });
471
+ }
472
+ }
429
473
  await ctx.db.patch(messageId, {
430
474
  status: "failed",
431
- text: error ?? message.text,
475
+ error: error,
432
476
  });
433
477
  },
434
478
  });
@@ -537,15 +581,14 @@ export const searchMessages = action({
537
581
  if (!VectorDimensions.includes(dimension)) {
538
582
  throw new Error(`Unsupported vector dimension: ${dimension}`);
539
583
  }
540
- const model = args.vectorModel ?? "unknown";
541
- const tableName = getVectorTableName(dimension);
542
- const vectors = (await ctx.vectorSearch(tableName, "vector", {
543
- vector: args.vector,
544
- filter: (q) => args.userId
545
- ? q.eq("model_kind_userId", [model, "thread", args.userId])
546
- : q.eq("model_kind_threadId", [model, "thread", args.threadId]),
584
+ const vectors = (await searchVectors(ctx, args.vector, {
585
+ dimension,
586
+ model: args.vectorModel ?? "unknown",
587
+ table: "messages",
588
+ userId: args.userId,
589
+ threadId: args.threadId,
547
590
  limit,
548
- })).filter((v) => v._score > 0.5);
591
+ })).filter((v) => v._score > (args.vectorScoreThreshold ?? 0));
549
592
  // Reciprocal rank fusion
550
593
  const k = 10;
551
594
  const textEmbeddingIds = textSearchMessages?.map((m) => m.embeddingId);
@@ -553,7 +596,7 @@ export const searchMessages = action({
553
596
  .map((v, i) => ({
554
597
  id: v._id,
555
598
  score: 1 / (i + k) +
556
- 1 / (textEmbeddingIds?.indexOf(v._id) ?? Infinity + k),
599
+ 1 / ((textEmbeddingIds?.indexOf(v._id) ?? Infinity) + k),
557
600
  }))
558
601
  .sort((a, b) => b.score - a.score);
559
602
  const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
@@ -590,9 +633,10 @@ export const _fetchVectorMessages = internalQuery({
590
633
  .query("messages")
591
634
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
592
635
  .filter((q) => userId
593
- ? q.eq("userId", userId)
594
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
595
- q.eq("threadId", threadId))
636
+ ? q.eq(q.field("userId"), userId)
637
+ : q.eq(q.field("threadId"), threadId))
638
+ // Don't include pending. Failed messages hopefully are deleted but may as well be safe.
639
+ .filter((q) => q.eq(q.field("status"), "success"))
596
640
  .first()))).filter((m) => m !== undefined && m !== null && (!parent || m.order <= parent.order));
597
641
  messages.push(...(args.textSearchMessages ?? []));
598
642
  // TODO: prioritize more recent messages
@@ -638,8 +682,8 @@ export const _fetchVectorMessages = internalQuery({
638
682
  .eq("threadId", m.threadId)
639
683
  .eq("status", "success")
640
684
  .eq("tool", false)
641
- .gt("order", earliest)
642
- .lt("order", latest))
685
+ .gte("order", earliest)
686
+ .lte("order", latest))
643
687
  .collect();
644
688
  if (!ranges[searchId]) {
645
689
  ranges[searchId] = [];
@@ -653,8 +697,8 @@ export const _fetchVectorMessages = internalQuery({
653
697
  .eq("userId", m.userId)
654
698
  .eq("status", "success")
655
699
  .eq("tool", false)
656
- .gt("order", earliest)
657
- .lt("order", latest))
700
+ .gte("order", earliest)
701
+ .lte("order", latest))
658
702
  .collect();
659
703
  if (!ranges[searchId]) {
660
704
  ranges[searchId] = [];
@@ -663,9 +707,12 @@ export const _fetchVectorMessages = internalQuery({
663
707
  }
664
708
  }
665
709
  }
666
- return Object.values(ranges)
667
- .map((r) => r.sort((a, b) => a.order - b.order))
668
- .flat();
710
+ for (const r of Object.values(ranges).flat()) {
711
+ if (!messages.includes(r)) {
712
+ messages.push(r);
713
+ }
714
+ }
715
+ return messages.sort((a, b) => a.order - b.order);
669
716
  },
670
717
  });
671
718
  // returns ranges of messages in order of text search relevance,
@@ -689,153 +736,4 @@ export const textSearch = query({
689
736
  },
690
737
  returns: v.array(v.doc("messages")),
691
738
  });
692
- // const vMemoryConfig = v.object({
693
- // lastMessages: v.optional(v.union(v.number(), v.literal(false))),
694
- // semanticRecall: v.optional(
695
- // v.union(
696
- // v.boolean(),
697
- // v.object({
698
- // topK: v.number(),
699
- // messageRange: v.union(
700
- // v.number(),
701
- // v.object({ before: v.number(), after: v.number() }),
702
- // ),
703
- // }),
704
- // ),
705
- // ),
706
- // workingMemory: v.optional(
707
- // v.object({
708
- // enabled: v.boolean(),
709
- // template: v.optional(v.string()),
710
- // use: v.optional(
711
- // v.union(v.literal("text-stream"), v.literal("tool-call")),
712
- // ),
713
- // }),
714
- // ),
715
- // threads: v.optional(
716
- // v.object({
717
- // generateTitle: v.optional(v.boolean()),
718
- // }),
719
- // ),
720
- // });
721
- // const vSelectBy = v.object({
722
- // vectorSearchString: v.optional(v.string()),
723
- // last: v.optional(v.union(v.number(), v.literal(false))),
724
- // include: v.optional(
725
- // v.array(
726
- // v.object({
727
- // id: v.string(),
728
- // withPreviousMessages: v.optional(v.number()),
729
- // withNextMessages: v.optional(v.number()),
730
- // })
731
- // )
732
- // ),
733
- // });
734
- // const DEFAULT_MESSAGES_LIMIT = 40; // What pg & upstash do too.
735
- // export const getThreadMessagesPage = query({
736
- // args: {
737
- // parentMessageId: v.string(),
738
- // selectBy: v.optional(vSelectBy),
739
- // // Unimplemented and as far I can tell no storage provider has either.
740
- // // memoryConfig: v.optional(vMemoryConfig),
741
- // },
742
- // handler: async (ctx, args): Promise<SerializedMessage[]> => {
743
- // const messages = await ctx.db
744
- // .query("messages")
745
- // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", args.parentMessageId))
746
- // .order("desc")
747
- // .take(args.selectBy?.last ? args.selectBy.last : DEFAULT_MESSAGES_LIMIT);
748
- // const handled: boolean[] = [];
749
- // const toFetch: number[] = [];
750
- // for (const m of messages) {
751
- // handled[m.threadOrder] = true;
752
- // }
753
- // await Promise.all(
754
- // args.selectBy?.include?.map(async (range) => {
755
- // const includeDoc = await ctx.db
756
- // .query("messages")
757
- // .withIndex("id", (q) => q.eq("id", range.id))
758
- // .unique();
759
- // if (!includeDoc) {
760
- // console.warn(`Message ${range.id} not found`);
761
- // return;
762
- // }
763
- // if (!range.withPreviousMessages && !range.withNextMessages) {
764
- // messages.push(includeDoc);
765
- // return;
766
- // }
767
- // const order = includeDoc.threadOrder;
768
- // for (
769
- // let i = order - (range.withPreviousMessages ?? 0);
770
- // i < order + (range.withNextMessages ?? 0);
771
- // i++
772
- // ) {
773
- // if (!handled[i]) {
774
- // toFetch.push(i);
775
- // handled[i] = true;
776
- // }
777
- // }
778
- // }) ?? []
779
- // );
780
- // // sort and find unique numbers in toFetch
781
- // const uniqueToFetch = [...new Set(toFetch)].sort();
782
- // // find contiguous ranges in uniqueToFetch
783
- // const ranges: { start: number; end: number }[] = [];
784
- // for (let i = 0; i < uniqueToFetch.length; i++) {
785
- // const start = uniqueToFetch[i];
786
- // let end = start;
787
- // while (i + 1 < uniqueToFetch.length && uniqueToFetch[i + 1] === end + 1) {
788
- // end++;
789
- // i++;
790
- // }
791
- // ranges.push({ start, end });
792
- // }
793
- // const fetched = (
794
- // await Promise.all(
795
- // ranges.map(async (range) => {
796
- // return await ctx.db
797
- // .query("messages")
798
- // .withIndex("parentMessageId", (q) =>
799
- // q
800
- // .eq("parentMessageId", args.parentMessageId)
801
- // .gte("threadOrder", range.start)
802
- // .lte("threadOrder", range.end)
803
- // )
804
- // .collect();
805
- // })
806
- // )
807
- // ).flat();
808
- // messages.push(...fetched);
809
- // return messages.map(messageToSerializedMastra);
810
- // },
811
- // returns: v.array(vSerializedMessage),
812
- // });
813
- // export const saveMessages = mutation({
814
- // args: { messages: v.array(vSerializedMessage) },
815
- // handler: async (ctx, args) => {
816
- // const messagesByParentMessageId: Record<string, SerializedMessage[]> = {};
817
- // for (const message of args.messages) {
818
- // messagesByParentMessageId[message.parentMessageId] = [
819
- // ...(messagesByParentMessageId[message.parentMessageId] ?? []),
820
- // message,
821
- // ];
822
- // }
823
- // for (const parentMessageId in messagesByParentMessageId) {
824
- // const lastMessage = await ctx.db
825
- // .query("messages")
826
- // .withIndex("parentMessageId", (q) => q.eq("parentMessageId", parentMessageId))
827
- // .order("desc")
828
- // .first();
829
- // let threadOrder = lastMessage?.threadOrder ?? 0;
830
- // for (const message of messagesByParentMessageId[parentMessageId]) {
831
- // threadOrder++;
832
- // await ctx.db.insert("messages", {
833
- // ...message,
834
- // threadOrder,
835
- // });
836
- // }
837
- // }
838
- // },
839
- // returns: v.null(),
840
- // });
841
739
  //# sourceMappingURL=messages.js.map