@convex-dev/agent 0.0.1-alpha.0 → 0.0.1-alpha.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 (70) hide show
  1. package/README.md +297 -6
  2. package/dist/commonjs/client/index.d.ts +521 -62
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +230 -118
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/client/types.d.ts +3 -0
  7. package/dist/commonjs/client/types.d.ts.map +1 -1
  8. package/dist/commonjs/component/messages.d.ts +22 -17
  9. package/dist/commonjs/component/messages.d.ts.map +1 -1
  10. package/dist/commonjs/component/messages.js +107 -47
  11. package/dist/commonjs/component/messages.js.map +1 -1
  12. package/dist/commonjs/component/schema.d.ts +16 -12
  13. package/dist/commonjs/component/schema.d.ts.map +1 -1
  14. package/dist/commonjs/component/schema.js +10 -3
  15. package/dist/commonjs/component/schema.js.map +1 -1
  16. package/dist/commonjs/component/vector/index.d.ts +41 -0
  17. package/dist/commonjs/component/vector/index.d.ts.map +1 -0
  18. package/dist/commonjs/component/vector/index.js +120 -0
  19. package/dist/commonjs/component/vector/index.js.map +1 -0
  20. package/dist/commonjs/component/vector/tables.d.ts.map +1 -1
  21. package/dist/commonjs/component/vector/tables.js +4 -2
  22. package/dist/commonjs/component/vector/tables.js.map +1 -1
  23. package/dist/commonjs/mapping.d.ts +6 -1
  24. package/dist/commonjs/mapping.d.ts.map +1 -1
  25. package/dist/commonjs/mapping.js +25 -0
  26. package/dist/commonjs/mapping.js.map +1 -1
  27. package/dist/commonjs/validators.d.ts +1375 -0
  28. package/dist/commonjs/validators.d.ts.map +1 -1
  29. package/dist/commonjs/validators.js +27 -0
  30. package/dist/commonjs/validators.js.map +1 -1
  31. package/dist/esm/client/index.d.ts +521 -62
  32. package/dist/esm/client/index.d.ts.map +1 -1
  33. package/dist/esm/client/index.js +230 -118
  34. package/dist/esm/client/index.js.map +1 -1
  35. package/dist/esm/client/types.d.ts +3 -0
  36. package/dist/esm/client/types.d.ts.map +1 -1
  37. package/dist/esm/component/messages.d.ts +22 -17
  38. package/dist/esm/component/messages.d.ts.map +1 -1
  39. package/dist/esm/component/messages.js +107 -47
  40. package/dist/esm/component/messages.js.map +1 -1
  41. package/dist/esm/component/schema.d.ts +16 -12
  42. package/dist/esm/component/schema.d.ts.map +1 -1
  43. package/dist/esm/component/schema.js +10 -3
  44. package/dist/esm/component/schema.js.map +1 -1
  45. package/dist/esm/component/vector/index.d.ts +41 -0
  46. package/dist/esm/component/vector/index.d.ts.map +1 -0
  47. package/dist/esm/component/vector/index.js +120 -0
  48. package/dist/esm/component/vector/index.js.map +1 -0
  49. package/dist/esm/component/vector/tables.d.ts.map +1 -1
  50. package/dist/esm/component/vector/tables.js +4 -2
  51. package/dist/esm/component/vector/tables.js.map +1 -1
  52. package/dist/esm/mapping.d.ts +6 -1
  53. package/dist/esm/mapping.d.ts.map +1 -1
  54. package/dist/esm/mapping.js +25 -0
  55. package/dist/esm/mapping.js.map +1 -1
  56. package/dist/esm/validators.d.ts +1375 -0
  57. package/dist/esm/validators.d.ts.map +1 -1
  58. package/dist/esm/validators.js +27 -0
  59. package/dist/esm/validators.js.map +1 -1
  60. package/package.json +2 -2
  61. package/src/client/index.ts +428 -232
  62. package/src/client/types.ts +4 -0
  63. package/src/component/_generated/api.d.ts +138 -11
  64. package/src/component/messages.ts +136 -66
  65. package/src/component/schema.ts +10 -3
  66. package/src/component/vector/index.ts +144 -0
  67. package/src/component/vector/tables.ts +7 -5
  68. package/src/mapping.ts +46 -11
  69. package/src/validators.test.ts +9 -0
  70. package/src/validators.ts +32 -0
@@ -7,6 +7,10 @@ import {
7
7
  GenericActionCtx,
8
8
  } from "convex/server";
9
9
  import { GenericId } from "convex/values";
10
+ import type { Doc } from "../component/_generated/dataModel";
11
+
12
+ export type ChatDoc = OpaqueIds<Doc<"chats">>;
13
+ export type MessageDoc = OpaqueIds<Doc<"messages">>;
10
14
 
11
15
  /* Type utils follow */
12
16
  export type RunQueryCtx = {
@@ -10,6 +10,7 @@
10
10
 
11
11
  import type * as lib from "../lib.js";
12
12
  import type * as messages from "../messages.js";
13
+ import type * as vector_index from "../vector/index.js";
13
14
  import type * as vector_tables from "../vector/tables.js";
14
15
 
15
16
  import type {
@@ -29,6 +30,7 @@ import type {
29
30
  declare const fullApi: ApiFromModules<{
30
31
  lib: typeof lib;
31
32
  messages: typeof messages;
33
+ "vector/index": typeof vector_index;
32
34
  "vector/tables": typeof vector_tables;
33
35
  }>;
34
36
  export type Mounts = {
@@ -38,7 +40,7 @@ export type Mounts = {
38
40
  "public",
39
41
  {
40
42
  agentName?: string;
41
- chatId: string;
43
+ chatId?: string;
42
44
  failPendingSteps?: boolean;
43
45
  messages: Array<{
44
46
  fileId?: string;
@@ -145,13 +147,14 @@ export type Mounts = {
145
147
  parentMessageId?: string;
146
148
  pending?: boolean;
147
149
  stepId?: string;
150
+ userId?: string;
148
151
  },
149
152
  {
150
153
  messages: Array<{
151
154
  _creationTime: number;
152
155
  _id: string;
153
156
  agentName?: string;
154
- chatId: string;
157
+ chatId?: string;
155
158
  embeddingId?:
156
159
  | string
157
160
  | string
@@ -262,7 +265,7 @@ export type Mounts = {
262
265
  role: "system";
263
266
  };
264
267
  model?: string;
265
- order?: number;
268
+ order: number;
266
269
  status: "pending" | "success" | "failed";
267
270
  stepId?: string;
268
271
  stepOrder?: number;
@@ -275,7 +278,7 @@ export type Mounts = {
275
278
  _creationTime: number;
276
279
  _id: string;
277
280
  agentName?: string;
278
- chatId: string;
281
+ chatId?: string;
279
282
  embeddingId?:
280
283
  | string
281
284
  | string
@@ -386,7 +389,7 @@ export type Mounts = {
386
389
  role: "system";
387
390
  };
388
391
  model?: string;
389
- order?: number;
392
+ order: number;
390
393
  status: "pending" | "success" | "failed";
391
394
  stepId?: string;
392
395
  stepOrder?: number;
@@ -1009,6 +1012,7 @@ export type Mounts = {
1009
1012
  isTool?: boolean;
1010
1013
  limit?: number;
1011
1014
  order?: "asc" | "desc";
1015
+ parentMessageId?: string;
1012
1016
  statuses?: Array<"pending" | "success" | "failed">;
1013
1017
  },
1014
1018
  {
@@ -1018,7 +1022,7 @@ export type Mounts = {
1018
1022
  _creationTime: number;
1019
1023
  _id: string;
1020
1024
  agentName?: string;
1021
- chatId: string;
1025
+ chatId?: string;
1022
1026
  embeddingId?:
1023
1027
  | string
1024
1028
  | string
@@ -1129,7 +1133,7 @@ export type Mounts = {
1129
1133
  role: "system";
1130
1134
  };
1131
1135
  model?: string;
1132
- order?: number;
1136
+ order: number;
1133
1137
  status: "pending" | "success" | "failed";
1134
1138
  stepId?: string;
1135
1139
  stepOrder?: number;
@@ -1195,6 +1199,7 @@ export type Mounts = {
1195
1199
  chatId?: string;
1196
1200
  limit: number;
1197
1201
  messageRange?: { after: number; before: number };
1202
+ parentMessageId?: string;
1198
1203
  text?: string;
1199
1204
  userId?: string;
1200
1205
  vector?: Array<number>;
@@ -1204,7 +1209,7 @@ export type Mounts = {
1204
1209
  _creationTime: number;
1205
1210
  _id: string;
1206
1211
  agentName?: string;
1207
- chatId: string;
1212
+ chatId?: string;
1208
1213
  embeddingId?:
1209
1214
  | string
1210
1215
  | string
@@ -1315,7 +1320,7 @@ export type Mounts = {
1315
1320
  role: "system";
1316
1321
  };
1317
1322
  model?: string;
1318
- order?: number;
1323
+ order: number;
1319
1324
  status: "pending" | "success" | "failed";
1320
1325
  stepId?: string;
1321
1326
  stepOrder?: number;
@@ -1333,7 +1338,7 @@ export type Mounts = {
1333
1338
  _creationTime: number;
1334
1339
  _id: string;
1335
1340
  agentName?: string;
1336
- chatId: string;
1341
+ chatId?: string;
1337
1342
  embeddingId?:
1338
1343
  | string
1339
1344
  | string
@@ -1444,7 +1449,7 @@ export type Mounts = {
1444
1449
  role: "system";
1445
1450
  };
1446
1451
  model?: string;
1447
- order?: number;
1452
+ order: number;
1448
1453
  status: "pending" | "success" | "failed";
1449
1454
  stepId?: string;
1450
1455
  stepOrder?: number;
@@ -1479,6 +1484,128 @@ export type Mounts = {
1479
1484
  }
1480
1485
  >;
1481
1486
  };
1487
+ vector: {
1488
+ index: {
1489
+ deleteBatch: FunctionReference<
1490
+ "mutation",
1491
+ "public",
1492
+ {
1493
+ ids: Array<
1494
+ | string
1495
+ | string
1496
+ | string
1497
+ | string
1498
+ | string
1499
+ | string
1500
+ | string
1501
+ | string
1502
+ | string
1503
+ >;
1504
+ },
1505
+ null
1506
+ >;
1507
+ deleteBatchForChat: FunctionReference<
1508
+ "mutation",
1509
+ "public",
1510
+ {
1511
+ chatId: string;
1512
+ cursor?: string;
1513
+ limit: number;
1514
+ model: string;
1515
+ vectorDimension:
1516
+ | 128
1517
+ | 256
1518
+ | 512
1519
+ | 768
1520
+ | 1024
1521
+ | 1536
1522
+ | 2048
1523
+ | 3072
1524
+ | 4096;
1525
+ },
1526
+ { continueCursor: string; isDone: boolean }
1527
+ >;
1528
+ insertBatch: FunctionReference<
1529
+ "mutation",
1530
+ "public",
1531
+ {
1532
+ vectorDimension:
1533
+ | 128
1534
+ | 256
1535
+ | 512
1536
+ | 768
1537
+ | 1024
1538
+ | 1536
1539
+ | 2048
1540
+ | 3072
1541
+ | 4096;
1542
+ vectors: Array<{
1543
+ chatId?: string;
1544
+ kind: "chat" | "memory";
1545
+ model: string;
1546
+ userId?: string;
1547
+ vector: Array<number>;
1548
+ }>;
1549
+ },
1550
+ null
1551
+ >;
1552
+ paginate: FunctionReference<
1553
+ "query",
1554
+ "public",
1555
+ {
1556
+ cursor?: string;
1557
+ limit: number;
1558
+ targetModel: string;
1559
+ vectorDimension:
1560
+ | 128
1561
+ | 256
1562
+ | 512
1563
+ | 768
1564
+ | 1024
1565
+ | 1536
1566
+ | 2048
1567
+ | 3072
1568
+ | 4096;
1569
+ },
1570
+ {
1571
+ continueCursor: string;
1572
+ ids: Array<
1573
+ | string
1574
+ | string
1575
+ | string
1576
+ | string
1577
+ | string
1578
+ | string
1579
+ | string
1580
+ | string
1581
+ | string
1582
+ >;
1583
+ isDone: boolean;
1584
+ }
1585
+ >;
1586
+ updateBatch: FunctionReference<
1587
+ "mutation",
1588
+ "public",
1589
+ {
1590
+ vectors: Array<{
1591
+ id:
1592
+ | string
1593
+ | string
1594
+ | string
1595
+ | string
1596
+ | string
1597
+ | string
1598
+ | string
1599
+ | string
1600
+ | string;
1601
+ model: string;
1602
+ vector: Array<number>;
1603
+ }>;
1604
+ },
1605
+ null
1606
+ >;
1607
+ };
1608
+ };
1482
1609
  };
1483
1610
  // For now fullApiWithMounts is only fullApi which provides
1484
1611
  // jump-to-definition in component client code.
@@ -2,19 +2,14 @@ import { assert, omit, pick } from "convex-helpers";
2
2
  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
- import { Infer, ObjectType } from "convex/values";
5
+ import { ObjectType } from "convex/values";
6
6
  import { DEFAULT_MESSAGE_RANGE, extractText, isTool } from "../shared.js";
7
7
  import {
8
- Message,
9
- MessageWithFileAndId,
10
- vAssistantMessage,
11
8
  vChatStatus,
12
9
  vMessageStatus,
13
10
  vMessageWithFileAndId,
14
11
  vSearchOptions,
15
- vStep,
16
12
  vStepWithMessagesWithFileAndId,
17
- vToolMessage,
18
13
  } from "../validators.js";
19
14
  import { api, internal } from "./_generated/api.js";
20
15
  import { Doc, Id } from "./_generated/dataModel.js";
@@ -63,7 +58,7 @@ export const getChatsByUserId = query({
63
58
  .gte("order", args.offset ?? 0)
64
59
  )
65
60
  );
66
- const chats = await mergedStream(streams, ["order", "stepOrder"]).paginate({
61
+ const chats = await mergedStream(streams, ["order"]).paginate({
67
62
  numItems: args.limit ?? 100,
68
63
  cursor: args.cursor ?? null,
69
64
  });
@@ -377,7 +372,8 @@ export const messageStatuses = vMessageDoc.fields.status.members.map(
377
372
  );
378
373
 
379
374
  const addMessagesArgs = {
380
- chatId: v.id("chats"),
375
+ userId: v.optional(v.string()),
376
+ chatId: v.optional(v.id("chats")),
381
377
  stepId: v.optional(v.id("steps")),
382
378
  parentMessageId: v.optional(v.id("messages")),
383
379
  messages: v.array(vMessageWithFileAndId),
@@ -398,15 +394,22 @@ async function addMessagesHandler(
398
394
  ctx: MutationCtx,
399
395
  args: ObjectType<typeof addMessagesArgs>
400
396
  ) {
401
- const chat = await ctx.db.get(args.chatId);
402
- assert(chat, `Chat ${args.chatId} not found`);
397
+ let userId = args.userId;
398
+ const chatId = args.chatId;
399
+ if (!userId && args.chatId) {
400
+ const chat = await ctx.db.get(args.chatId);
401
+ assert(chat, `Chat ${args.chatId} not found`);
402
+ userId = chat._id;
403
+ }
403
404
  const { failPendingSteps, pending, messages, parentMessageId, ...rest } =
404
405
  args;
405
- if (failPendingSteps) {
406
+ const parent = parentMessageId && (await ctx.db.get(parentMessageId));
407
+ if (failPendingSteps && parent?.status !== "pending") {
408
+ assert(args.chatId, "chatId is required to fail pending steps");
406
409
  const pendingMessages = await ctx.db
407
410
  .query("messages")
408
411
  .withIndex("chatId_status_tool_order_stepOrder", (q) =>
409
- q.eq("chatId", args.chatId).eq("status", "pending")
412
+ q.eq("chatId", chatId).eq("status", "pending")
410
413
  )
411
414
  .collect();
412
415
  await Promise.all(
@@ -415,14 +418,14 @@ async function addMessagesHandler(
415
418
  )
416
419
  );
417
420
  }
418
- let order: number | undefined;
419
- const maxMessage = await getMaxMessage(ctx, args.chatId);
421
+ let threadId = parentMessageId;
422
+ const maxMessage = await getMaxMessage(ctx, chatId, userId);
420
423
  // If the previous message isn't our parent, we make a new thread.
421
- const threadId =
424
+ threadId =
422
425
  parentMessageId && maxMessage?._id === parentMessageId
423
426
  ? maxMessage.threadId ?? parentMessageId
424
427
  : parentMessageId;
425
- order = maxMessage?.order ?? -1;
428
+ let order = maxMessage?.order ?? -1;
426
429
  const toReturn: Doc<"messages">[] = [];
427
430
  if (messages.length > 0) {
428
431
  for (const { message, fileId, id } of messages) {
@@ -434,7 +437,7 @@ async function addMessagesHandler(
434
437
  const messageId = await ctx.db.insert("messages", {
435
438
  ...rest,
436
439
  threadId,
437
- userId: chat.userId,
440
+ userId,
438
441
  message,
439
442
  id,
440
443
  order,
@@ -449,18 +452,37 @@ async function addMessagesHandler(
449
452
  return { messages: toReturn };
450
453
  }
451
454
 
452
- async function getMaxMessage(ctx: QueryCtx, chatId: Id<"chats">) {
453
- return mergedStream(
454
- ["success" as const, "pending" as const].map((status) =>
455
- stream(ctx.db, schema)
456
- .query("messages")
457
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
458
- q.eq("chatId", chatId).eq("status", status).eq("tool", false)
459
- )
460
- .order("desc")
461
- ),
462
- ["order", "stepOrder"]
463
- ).first();
455
+ async function getMaxMessage(
456
+ ctx: QueryCtx,
457
+ chatId: Id<"chats"> | undefined,
458
+ userId: string | undefined
459
+ ) {
460
+ assert(chatId || userId, "One of chatId or userId is required");
461
+ if (chatId) {
462
+ return mergedStream(
463
+ ["success" as const, "pending" as const].map((status) =>
464
+ stream(ctx.db, schema)
465
+ .query("messages")
466
+ .withIndex("chatId_status_tool_order_stepOrder", (q) =>
467
+ q.eq("chatId", chatId).eq("status", status).eq("tool", false)
468
+ )
469
+ .order("desc")
470
+ ),
471
+ ["order", "stepOrder"]
472
+ ).first();
473
+ } else {
474
+ return mergedStream(
475
+ ["success" as const, "pending" as const].map((status) =>
476
+ stream(ctx.db, schema)
477
+ .query("messages")
478
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
479
+ q.eq("userId", userId).eq("status", status).eq("tool", false)
480
+ )
481
+ .order("desc")
482
+ ),
483
+ ["order", "stepOrder"]
484
+ ).first();
485
+ }
464
486
  }
465
487
 
466
488
  const addStepsArgs = {
@@ -518,7 +540,8 @@ async function addStepsHandler(
518
540
  pending: step.finishReason === "stop" ? false : true,
519
541
  failPendingSteps: false,
520
542
  });
521
- if (step.finishReason === "stop") {
543
+ // We don't commit if the parent is still pending.
544
+ if (step.finishReason === "stop" && parentMessage.status === "success") {
522
545
  await commitMessageHandler(ctx, { messageId: args.messageId });
523
546
  }
524
547
  steps.push((await ctx.db.get(stepId))!);
@@ -585,6 +608,7 @@ async function commitMessageHandler(
585
608
  ).collect();
586
609
  for (const message of messages) {
587
610
  await ctx.db.patch(message._id, { status: "success" });
611
+ // TODO: recursively commit steps & messages that might depend on this one.
588
612
  }
589
613
  }
590
614
 
@@ -597,9 +621,12 @@ export const getChatMessages = query({
597
621
  // Note: the other arguments cannot change from when the cursor was created.
598
622
  cursor: v.optional(v.string()),
599
623
  statuses: v.optional(v.array(vMessageStatus)),
624
+ parentMessageId: v.optional(v.id("messages")),
600
625
  },
601
626
  handler: async (ctx, args) => {
602
627
  const statuses = args.statuses ?? ["success"];
628
+ const parent =
629
+ args.parentMessageId && (await ctx.db.get(args.parentMessageId));
603
630
  const toolOptions =
604
631
  args.isTool === undefined ? [true, false] : [args.isTool];
605
632
  const order = args.order ?? "desc";
@@ -607,9 +634,16 @@ export const getChatMessages = query({
607
634
  statuses.map((status) =>
608
635
  stream(ctx.db, schema)
609
636
  .query("messages")
610
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
611
- q.eq("chatId", args.chatId).eq("status", status).eq("tool", tool)
612
- )
637
+ .withIndex("chatId_status_tool_order_stepOrder", (q) => {
638
+ const qq = q
639
+ .eq("chatId", args.chatId)
640
+ .eq("status", status)
641
+ .eq("tool", tool);
642
+ if (parent) {
643
+ return qq.lte("order", parent.order);
644
+ }
645
+ return qq;
646
+ })
613
647
  .order(order)
614
648
  )
615
649
  );
@@ -637,6 +671,7 @@ export const searchMessages = action({
637
671
  args: {
638
672
  userId: v.optional(v.string()),
639
673
  chatId: v.optional(v.id("chats")),
674
+ parentMessageId: v.optional(v.id("messages")),
640
675
  ...vSearchOptions.fields,
641
676
  },
642
677
  returns: v.array(v.doc("messages")),
@@ -688,10 +723,12 @@ export const searchMessages = action({
688
723
  userId: args.userId,
689
724
  chatId: args.chatId,
690
725
  vectorIds,
691
- textSearchMessages: textSearchMessages
692
- ?.filter((m) => !vectorIds.includes(m.embeddingId!))
693
- .slice(0, limit - vectorIds.length),
726
+ textSearchMessages: textSearchMessages?.filter(
727
+ (m) => !vectorIds.includes(m.embeddingId!)
728
+ ),
694
729
  messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
730
+ parentMessageId: args.parentMessageId,
731
+ limit,
695
732
  }
696
733
  );
697
734
  return messages;
@@ -707,71 +744,104 @@ export const _fetchVectorMessages = internalQuery({
707
744
  vectorIds: v.array(vVectorId),
708
745
  textSearchMessages: v.optional(v.array(v.doc("messages"))),
709
746
  messageRange: v.object({ before: v.number(), after: v.number() }),
747
+ parentMessageId: v.optional(v.id("messages")),
748
+ limit: v.number(),
710
749
  },
711
750
  returns: v.array(v.doc("messages")),
712
751
  handler: async (ctx, args): Promise<Doc<"messages">[]> => {
713
- const messages = (
752
+ const parent =
753
+ args.parentMessageId && (await ctx.db.get(args.parentMessageId));
754
+ const { userId, chatId } = args;
755
+ assert(userId || chatId, "Specify userId or chatId to search");
756
+ let messages = (
714
757
  await Promise.all(
715
758
  args.vectorIds.map((embeddingId) =>
716
759
  ctx.db
717
760
  .query("messages")
718
761
  .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
719
- .filter(
720
- (q) =>
721
- args.userId
722
- ? q.eq("userId", args.userId)
723
- : // eslint-disable-next-line @typescript-eslint/no-explicit-any
724
- q.eq("chatId", args.chatId as any) // not sure why it's failing...
762
+ .filter((q) =>
763
+ userId
764
+ ? q.eq("userId", userId)
765
+ : // eslint-disable-next-line @typescript-eslint/no-explicit-any
766
+ q.eq("chatId", chatId as any)
725
767
  )
726
768
  .first()
727
769
  )
728
770
  )
729
- ).filter((m): m is Doc<"messages"> => m !== undefined);
771
+ ).filter(
772
+ (m): m is Doc<"messages"> =>
773
+ m !== undefined && m !== null && (!parent || m.order <= parent.order)
774
+ );
730
775
  messages.push(...(args.textSearchMessages ?? []));
776
+ // TODO: prioritize more recent messages
731
777
  messages.sort((a, b) => a.order! - b.order!);
778
+ messages = messages.slice(0, args.limit);
732
779
  // Fetch the surrounding messages
733
- const included: Record<Id<"chats">, Set<number>> = {};
780
+ if (!chatId) {
781
+ return messages.sort((a, b) => a.order - b.order);
782
+ }
783
+ const included: Record<string, Set<number>> = {};
734
784
  for (const m of messages) {
735
- if (!included[m.chatId]) {
736
- included[m.chatId] = new Set();
785
+ const searchId = m.chatId ?? m.userId!;
786
+ if (!included[searchId]) {
787
+ included[searchId] = new Set();
737
788
  }
738
- included[m.chatId].add(m.order!);
789
+ included[searchId].add(m.order!);
739
790
  }
740
- const ranges: Record<Id<"chats">, Doc<"messages">[]> = {};
791
+ const ranges: Record<string, Doc<"messages">[]> = {};
741
792
  const { before, after } = args.messageRange;
742
793
  for (const m of messages) {
794
+ const searchId = m.chatId ?? m.userId!;
743
795
  const order = m.order!;
744
796
  let earliest = order - before;
745
797
  let latest = order + after;
746
798
  for (; earliest <= latest; earliest++) {
747
- if (!included[m.chatId].has(earliest)) {
799
+ if (!included[searchId].has(earliest)) {
748
800
  break;
749
801
  }
750
802
  }
751
803
  for (; latest >= earliest; latest--) {
752
- if (!included[m.chatId].has(latest)) {
804
+ if (!included[searchId].has(latest)) {
753
805
  break;
754
806
  }
755
807
  }
756
808
  for (let i = earliest; i <= latest; i++) {
757
- included[m.chatId].add(i);
809
+ included[searchId].add(i);
758
810
  }
759
811
  if (earliest !== latest) {
760
- const surrounding = await ctx.db
761
- .query("messages")
762
- .withIndex("chatId_status_tool_order_stepOrder", (q) =>
763
- q
764
- .eq("chatId", m.chatId)
765
- .eq("status", "success")
766
- .eq("tool", false)
767
- .gt("order", earliest)
768
- .lt("order", latest)
769
- )
770
- .collect();
771
- if (!ranges[m.chatId]) {
772
- ranges[m.chatId] = [];
812
+ if (m.chatId) {
813
+ const surrounding = await ctx.db
814
+ .query("messages")
815
+ .withIndex("chatId_status_tool_order_stepOrder", (q) =>
816
+ q
817
+ .eq("chatId", m.chatId)
818
+ .eq("status", "success")
819
+ .eq("tool", false)
820
+ .gt("order", earliest)
821
+ .lt("order", latest)
822
+ )
823
+ .collect();
824
+ if (!ranges[searchId]) {
825
+ ranges[searchId] = [];
826
+ }
827
+ ranges[searchId].push(...surrounding);
828
+ } else {
829
+ const surrounding = await ctx.db
830
+ .query("messages")
831
+ .withIndex("userId_status_tool_order_stepOrder", (q) =>
832
+ q
833
+ .eq("userId", m.userId!)
834
+ .eq("status", "success")
835
+ .eq("tool", false)
836
+ .gt("order", earliest)
837
+ .lt("order", latest)
838
+ )
839
+ .collect();
840
+ if (!ranges[searchId]) {
841
+ ranges[searchId] = [];
842
+ }
843
+ ranges[searchId].push(...surrounding);
773
844
  }
774
- ranges[m.chatId].push(...surrounding);
775
845
  }
776
846
  }
777
847
  return Object.values(ranges)
@@ -22,7 +22,7 @@ export const schema = defineSchema({
22
22
  messages: defineTable({
23
23
  id: v.optional(v.string()), // external id, e.g. from Vercel AI SDK
24
24
  userId: v.optional(v.string()), // useful for future indexes (text search)
25
- chatId: v.id("chats"),
25
+ chatId: v.optional(v.id("chats")),
26
26
  threadId: v.optional(v.id("messages")),
27
27
  stepId: v.optional(v.id("steps")),
28
28
  agentName: v.optional(v.string()),
@@ -33,8 +33,8 @@ export const schema = defineSchema({
33
33
  // TODO: add sub-messages back in? or be able to skip them?
34
34
  tool: v.boolean(),
35
35
  // Repeats until a non-tool message.
36
- // Set when the message is finished
37
- order: v.optional(v.number()),
36
+ // Unset if it's not in a chat.
37
+ order: v.number(),
38
38
  stepOrder: v.optional(v.number()),
39
39
  fileId: v.optional(v.id("files")),
40
40
  status: vMessageStatus,
@@ -48,6 +48,13 @@ export const schema = defineSchema({
48
48
  "order",
49
49
  "stepOrder",
50
50
  ])
51
+ .index("userId_status_tool_order_stepOrder", [
52
+ "userId",
53
+ "status",
54
+ "tool",
55
+ "order",
56
+ "stepOrder",
57
+ ])
51
58
  // Allows finding all threaded messages in order
52
59
  // Allows finding all failed messages to evaluate
53
60
  // .index("status_threadId_order_stepOrder", [