@convex-dev/agent 0.0.1-alpha.4 → 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
@@ -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,14 +297,15 @@ 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));
311
310
  if (failPendingSteps && parent?.status !== "pending") {
312
311
  assert(args.threadId, "threadId is required to fail pending steps");
@@ -320,7 +319,18 @@ async function addMessagesHandler(ctx, args) {
320
319
  let order = maxMessage?.order ?? -1;
321
320
  const toReturn = [];
322
321
  if (messages.length > 0) {
323
- for (const { message, fileId, id } of messages) {
322
+ for (const [i, { message, fileId, id }] of messages.entries()) {
323
+ const embedding = embeddings?.vectors[i] ?? undefined;
324
+ let embeddingId;
325
+ if (embeddings && embedding) {
326
+ embeddingId = await insertVector(ctx, embeddings.dimension, {
327
+ vector: embedding,
328
+ model: embeddings.model,
329
+ table: "messages",
330
+ userId,
331
+ threadId,
332
+ });
333
+ }
324
334
  const tool = isTool(message);
325
335
  if (!tool) {
326
336
  order++;
@@ -328,6 +338,7 @@ async function addMessagesHandler(ctx, args) {
328
338
  const text = extractText(message);
329
339
  const messageId = await ctx.db.insert("messages", {
330
340
  ...rest,
341
+ embeddingId,
331
342
  parentMessageId,
332
343
  userId,
333
344
  message,
@@ -337,7 +348,13 @@ async function addMessagesHandler(ctx, args) {
337
348
  text,
338
349
  fileId,
339
350
  status: pending ? "pending" : "success",
351
+ stepOrder: 0,
340
352
  });
353
+ if (fileId) {
354
+ await ctx.db.patch(fileId, {
355
+ refcount: (await ctx.db.get(fileId)).refcount + 1,
356
+ });
357
+ }
341
358
  toReturn.push((await ctx.db.get(messageId)));
342
359
  }
343
360
  }
@@ -352,24 +369,39 @@ async function getMaxMessage(ctx, threadId, userId) {
352
369
  .order("desc")), ["order", "stepOrder"]).first();
353
370
  }
354
371
  else {
355
- return mergedStream(["success", "pending"].map((status) => stream(ctx.db, schema)
372
+ // DO explicitly
373
+ const maxPending = await ctx.db
356
374
  .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();
375
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", userId).eq("status", "pending").eq("tool", false))
376
+ .order("desc")
377
+ .first();
378
+ const maxSuccess = await ctx.db
379
+ .query("messages")
380
+ .withIndex("userId_status_tool_order_stepOrder", (q) => q.eq("userId", userId).eq("status", "success").eq("tool", false))
381
+ .order("desc")
382
+ .first();
383
+ return maxPending
384
+ ? maxSuccess
385
+ ? maxPending.order > maxSuccess.order
386
+ ? maxPending
387
+ : maxSuccess
388
+ : maxPending
389
+ : maxSuccess ?? null;
359
390
  }
360
391
  }
361
- const addStepsArgs = {
392
+ const addStepArgs = {
362
393
  threadId: v.id("threads"),
363
394
  messageId: v.id("messages"),
364
- steps: v.array(vStepWithMessagesWithFileAndId),
395
+ step: vStepWithMessages,
365
396
  failPendingSteps: v.optional(v.boolean()),
397
+ embeddings: v.optional(vEmbeddingsWithMetadata),
366
398
  };
367
- export const addSteps = mutation({
368
- args: addStepsArgs,
399
+ export const addStep = mutation({
400
+ args: addStepArgs,
369
401
  returns: v.array(v.doc("steps")),
370
- handler: addStepsHandler,
402
+ handler: addStepHandler,
371
403
  });
372
- async function addStepsHandler(ctx, args) {
404
+ async function addStepHandler(ctx, args) {
373
405
  const parentMessage = await ctx.db.get(args.messageId);
374
406
  assert(parentMessage, `Message ${args.messageId} not found`);
375
407
  const order = parentMessage.order;
@@ -386,35 +418,33 @@ async function addStepsHandler(ctx, args) {
386
418
  await ctx.db.patch(step._id, { status: "failed" });
387
419
  }
388
420
  }
389
- steps = steps.filter((s) => s.status === "success");
421
+ steps = steps.filter((s) => s.status !== "failed");
390
422
  }
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++;
423
+ const { step, messages } = args.step;
424
+ const stepId = await ctx.db.insert("steps", {
425
+ threadId: args.threadId,
426
+ parentMessageId: args.messageId,
427
+ order,
428
+ stepOrder: (steps.at(-1)?.stepOrder ?? -1) + 1,
429
+ status: step.finishReason === "stop" ? "success" : "pending",
430
+ step,
431
+ });
432
+ await addMessagesHandler(ctx, {
433
+ threadId: args.threadId,
434
+ parentMessageId: args.messageId,
435
+ stepId,
436
+ messages,
437
+ model: parentMessage.model,
438
+ agentName: parentMessage.agentName,
439
+ pending: step.finishReason === "stop" ? false : true,
440
+ failPendingSteps: false,
441
+ embeddings: args.embeddings,
442
+ });
443
+ // We don't commit if the parent is still pending.
444
+ if (step.finishReason === "stop") {
445
+ await commitMessageHandler(ctx, { messageId: args.messageId });
417
446
  }
447
+ steps.push((await ctx.db.get(stepId)));
418
448
  return steps;
419
449
  }
420
450
  export const rollbackMessage = mutation({
@@ -426,6 +456,19 @@ export const rollbackMessage = mutation({
426
456
  handler: async (ctx, { messageId, error }) => {
427
457
  const message = await ctx.db.get(messageId);
428
458
  assert(message, `Message ${messageId} not found`);
459
+ // TODO: do BFS to fail all associated messages, then steps
460
+ // with parentMessageId of those messages, etc.
461
+ const steps = await ctx.db
462
+ .query("steps")
463
+ .withIndex("parentMessageId_order_stepOrder", (q) =>
464
+ // TODO: fetch pending, and commit later
465
+ q.eq("parentMessageId", messageId))
466
+ .collect();
467
+ for (const step of steps) {
468
+ if (step.status === "pending") {
469
+ await ctx.db.patch(step._id, { status: "failed" });
470
+ }
471
+ }
429
472
  await ctx.db.patch(messageId, {
430
473
  status: "failed",
431
474
  text: error ?? message.text,
@@ -537,15 +580,14 @@ export const searchMessages = action({
537
580
  if (!VectorDimensions.includes(dimension)) {
538
581
  throw new Error(`Unsupported vector dimension: ${dimension}`);
539
582
  }
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]),
583
+ const vectors = (await searchVectors(ctx, args.vector, {
584
+ dimension,
585
+ model: args.vectorModel ?? "unknown",
586
+ table: "messages",
587
+ userId: args.userId,
588
+ threadId: args.threadId,
547
589
  limit,
548
- })).filter((v) => v._score > 0.5);
590
+ })).filter((v) => v._score > (args.vectorScoreThreshold ?? 0));
549
591
  // Reciprocal rank fusion
550
592
  const k = 10;
551
593
  const textEmbeddingIds = textSearchMessages?.map((m) => m.embeddingId);
@@ -553,7 +595,7 @@ export const searchMessages = action({
553
595
  .map((v, i) => ({
554
596
  id: v._id,
555
597
  score: 1 / (i + k) +
556
- 1 / (textEmbeddingIds?.indexOf(v._id) ?? Infinity + k),
598
+ 1 / ((textEmbeddingIds?.indexOf(v._id) ?? Infinity) + k),
557
599
  }))
558
600
  .sort((a, b) => b.score - a.score);
559
601
  const vectorIds = vectorScores.slice(0, limit).map((v) => v.id);
@@ -590,9 +632,10 @@ export const _fetchVectorMessages = internalQuery({
590
632
  .query("messages")
591
633
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
592
634
  .filter((q) => userId
593
- ? q.eq("userId", userId)
594
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
595
- q.eq("threadId", threadId))
635
+ ? q.eq(q.field("userId"), userId)
636
+ : q.eq(q.field("threadId"), threadId))
637
+ // Don't include pending. Failed messages hopefully are deleted but may as well be safe.
638
+ .filter((q) => q.eq(q.field("status"), "success"))
596
639
  .first()))).filter((m) => m !== undefined && m !== null && (!parent || m.order <= parent.order));
597
640
  messages.push(...(args.textSearchMessages ?? []));
598
641
  // TODO: prioritize more recent messages
@@ -638,8 +681,8 @@ export const _fetchVectorMessages = internalQuery({
638
681
  .eq("threadId", m.threadId)
639
682
  .eq("status", "success")
640
683
  .eq("tool", false)
641
- .gt("order", earliest)
642
- .lt("order", latest))
684
+ .gte("order", earliest)
685
+ .lte("order", latest))
643
686
  .collect();
644
687
  if (!ranges[searchId]) {
645
688
  ranges[searchId] = [];
@@ -653,8 +696,8 @@ export const _fetchVectorMessages = internalQuery({
653
696
  .eq("userId", m.userId)
654
697
  .eq("status", "success")
655
698
  .eq("tool", false)
656
- .gt("order", earliest)
657
- .lt("order", latest))
699
+ .gte("order", earliest)
700
+ .lte("order", latest))
658
701
  .collect();
659
702
  if (!ranges[searchId]) {
660
703
  ranges[searchId] = [];
@@ -663,9 +706,12 @@ export const _fetchVectorMessages = internalQuery({
663
706
  }
664
707
  }
665
708
  }
666
- return Object.values(ranges)
667
- .map((r) => r.sort((a, b) => a.order - b.order))
668
- .flat();
709
+ for (const r of Object.values(ranges).flat()) {
710
+ if (!messages.includes(r)) {
711
+ messages.push(r);
712
+ }
713
+ }
714
+ return messages.sort((a, b) => a.order - b.order);
669
715
  },
670
716
  });
671
717
  // returns ranges of messages in order of text search relevance,
@@ -689,153 +735,4 @@ export const textSearch = query({
689
735
  },
690
736
  returns: v.array(v.doc("messages")),
691
737
  });
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
738
  //# sourceMappingURL=messages.js.map