@convex-dev/agent 0.1.15-alpha.2 → 0.1.16-alpha.0

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 (55) hide show
  1. package/README.md +41 -700
  2. package/dist/client/index.d.ts +23 -20
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +34 -18
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/streaming.d.ts +24 -2
  7. package/dist/client/streaming.d.ts.map +1 -1
  8. package/dist/client/streaming.js +35 -4
  9. package/dist/client/streaming.js.map +1 -1
  10. package/dist/client/types.d.ts +2 -20
  11. package/dist/client/types.d.ts.map +1 -1
  12. package/dist/component/_generated/api.d.ts +7 -150
  13. package/dist/component/messages.d.ts +0 -59
  14. package/dist/component/messages.d.ts.map +1 -1
  15. package/dist/component/messages.js +8 -24
  16. package/dist/component/messages.js.map +1 -1
  17. package/dist/component/schema.d.ts +4 -4
  18. package/dist/component/schema.d.ts.map +1 -1
  19. package/dist/component/schema.js +1 -1
  20. package/dist/component/schema.js.map +1 -1
  21. package/dist/component/streams.d.ts +7 -2
  22. package/dist/component/streams.d.ts.map +1 -1
  23. package/dist/component/streams.js +40 -14
  24. package/dist/component/streams.js.map +1 -1
  25. package/dist/component/vector/tables.d.ts +3 -3
  26. package/dist/component/vector/tables.d.ts.map +1 -1
  27. package/dist/mapping.d.ts +5 -0
  28. package/dist/mapping.d.ts.map +1 -1
  29. package/dist/mapping.js +8 -1
  30. package/dist/mapping.js.map +1 -1
  31. package/dist/react/deltas.d.ts.map +1 -1
  32. package/dist/react/deltas.js +20 -1
  33. package/dist/react/deltas.js.map +1 -1
  34. package/dist/react/index.js +1 -1
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/react/toUIMessages.js +2 -2
  37. package/dist/react/toUIMessages.js.map +1 -1
  38. package/dist/validators.d.ts +217 -265
  39. package/dist/validators.d.ts.map +1 -1
  40. package/dist/validators.js +1 -7
  41. package/dist/validators.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/client/index.ts +41 -29
  44. package/src/client/streaming.ts +55 -4
  45. package/src/client/types.ts +2 -21
  46. package/src/component/_generated/api.d.ts +7 -150
  47. package/src/component/messages.ts +13 -30
  48. package/src/component/schema.ts +1 -1
  49. package/src/component/streams.ts +49 -16
  50. package/src/component/vector/tables.ts +5 -5
  51. package/src/mapping.ts +8 -1
  52. package/src/react/deltas.ts +24 -3
  53. package/src/react/index.ts +1 -1
  54. package/src/react/toUIMessages.ts +2 -2
  55. package/src/validators.ts +1 -26
@@ -578,160 +578,11 @@ export type Mounts = {
578
578
  >;
579
579
  }>
580
580
  >;
581
- getThreadMessages: FunctionReference<
582
- "query",
583
- "public",
584
- { deprecated: "Use listMessagesByThreadId instead" },
585
- {
586
- continueCursor: string;
587
- isDone: boolean;
588
- page: Array<{
589
- _creationTime: number;
590
- _id: string;
591
- agentName?: string;
592
- embeddingId?: string;
593
- error?: string;
594
- fileIds?: Array<string>;
595
- finishReason?:
596
- | "stop"
597
- | "length"
598
- | "content-filter"
599
- | "tool-calls"
600
- | "error"
601
- | "other"
602
- | "unknown";
603
- id?: string;
604
- message?:
605
- | {
606
- content:
607
- | string
608
- | Array<
609
- | {
610
- providerOptions?: Record<string, Record<string, any>>;
611
- text: string;
612
- type: "text";
613
- }
614
- | {
615
- image: string | ArrayBuffer;
616
- mimeType?: string;
617
- providerOptions?: Record<string, Record<string, any>>;
618
- type: "image";
619
- }
620
- | {
621
- data: string | ArrayBuffer;
622
- filename?: string;
623
- mimeType: string;
624
- providerOptions?: Record<string, Record<string, any>>;
625
- type: "file";
626
- }
627
- >;
628
- providerOptions?: Record<string, Record<string, any>>;
629
- role: "user";
630
- }
631
- | {
632
- content:
633
- | string
634
- | Array<
635
- | {
636
- providerOptions?: Record<string, Record<string, any>>;
637
- text: string;
638
- type: "text";
639
- }
640
- | {
641
- data: string | ArrayBuffer;
642
- filename?: string;
643
- mimeType: string;
644
- providerOptions?: Record<string, Record<string, any>>;
645
- type: "file";
646
- }
647
- | {
648
- providerOptions?: Record<string, Record<string, any>>;
649
- signature?: string;
650
- text: string;
651
- type: "reasoning";
652
- }
653
- | {
654
- data: string;
655
- providerOptions?: Record<string, Record<string, any>>;
656
- type: "redacted-reasoning";
657
- }
658
- | {
659
- args: any;
660
- providerOptions?: Record<string, Record<string, any>>;
661
- toolCallId: string;
662
- toolName: string;
663
- type: "tool-call";
664
- }
665
- >;
666
- providerOptions?: Record<string, Record<string, any>>;
667
- role: "assistant";
668
- }
669
- | {
670
- content: Array<{
671
- args?: any;
672
- experimental_content?: Array<
673
- | { text: string; type: "text" }
674
- | { data: string; mimeType?: string; type: "image" }
675
- >;
676
- isError?: boolean;
677
- providerOptions?: Record<string, Record<string, any>>;
678
- result: any;
679
- toolCallId: string;
680
- toolName: string;
681
- type: "tool-result";
682
- }>;
683
- providerOptions?: Record<string, Record<string, any>>;
684
- role: "tool";
685
- }
686
- | {
687
- content: string;
688
- providerOptions?: Record<string, Record<string, any>>;
689
- role: "system";
690
- };
691
- model?: string;
692
- order: number;
693
- provider?: string;
694
- providerMetadata?: Record<string, Record<string, any>>;
695
- providerOptions?: Record<string, Record<string, any>>;
696
- reasoning?: string;
697
- reasoningDetails?: Array<
698
- | { signature?: string; text: string; type: "text" }
699
- | { data: string; type: "redacted" }
700
- >;
701
- sources?: Array<{
702
- id: string;
703
- providerOptions?: Record<string, Record<string, any>>;
704
- sourceType: "url";
705
- title?: string;
706
- url: string;
707
- }>;
708
- status: "pending" | "success" | "failed";
709
- stepOrder: number;
710
- text?: string;
711
- threadId: string;
712
- tool: boolean;
713
- usage?: {
714
- completionTokens: number;
715
- promptTokens: number;
716
- totalTokens: number;
717
- };
718
- userId?: string;
719
- warnings?: Array<
720
- | { details?: string; setting: string; type: "unsupported-setting" }
721
- | { details?: string; tool: any; type: "unsupported-tool" }
722
- | { message: string; type: "other" }
723
- >;
724
- }>;
725
- pageStatus?: "SplitRecommended" | "SplitRequired" | null;
726
- splitCursor?: string | null;
727
- }
728
- >;
729
581
  listMessagesByThreadId: FunctionReference<
730
582
  "query",
731
583
  "public",
732
584
  {
733
585
  excludeToolMessages?: boolean;
734
- isTool?: "use excludeToolMessages instead of this";
735
586
  order: "asc" | "desc";
736
587
  paginationOpts?: {
737
588
  cursor: string | null;
@@ -1437,7 +1288,13 @@ export type Mounts = {
1437
1288
  "mutation",
1438
1289
  "public",
1439
1290
  { reason: string; streamId: string },
1440
- null
1291
+ boolean
1292
+ >;
1293
+ abortByOrder: FunctionReference<
1294
+ "mutation",
1295
+ "public",
1296
+ { order: number; reason: string; threadId: string },
1297
+ boolean
1441
1298
  >;
1442
1299
  addDelta: FunctionReference<
1443
1300
  "mutation",
@@ -40,15 +40,6 @@ import {
40
40
  } from "./vector/tables.js";
41
41
  import { changeRefcount } from "./files.js";
42
42
 
43
- /** @deprecated Use *.threads.listMessagesByThreadId instead. */
44
- export const listThreadsByUserId = _listThreadsByUserId;
45
-
46
- /** @deprecated Use *.threads.getThread */
47
- export const getThread = _getThread;
48
-
49
- /** @deprecated Use *.threads.updateThread instead */
50
- export const updateThread = _updateThread;
51
-
52
43
  function publicMessage(message: Doc<"messages">): MessageDoc {
53
44
  return omit(message, ["parentMessageId", "stepId", "files"]);
54
45
  }
@@ -385,8 +376,6 @@ export const listMessagesByThreadId = query({
385
376
  args: {
386
377
  threadId: v.id("threads"),
387
378
  excludeToolMessages: v.optional(v.boolean()),
388
- /** @deprecated Use excludeToolMessages instead. */
389
- isTool: v.optional(v.literal("use excludeToolMessages instead of this")),
390
379
  /** What order to sort the messages in. To get the latest, use "desc". */
391
380
  order: v.union(v.literal("asc"), v.literal("desc")),
392
381
  paginationOpts: v.optional(paginationOptsValidator),
@@ -421,10 +410,8 @@ export const listMessagesByThreadId = query({
421
410
  })
422
411
  .order(order)
423
412
  .filterWith(
424
- async (m) =>
425
- !last ||
426
- m.order < last.order ||
427
- (m.order === last.order && m.stepOrder <= last.stepOrder)
413
+ // We allow all messages on the same order.
414
+ async (m) => !last || m.order < last.order || m.order === last.order
428
415
  )
429
416
  )
430
417
  );
@@ -452,15 +439,6 @@ export const getMessagesByIds = query({
452
439
  returns: v.array(v.union(v.null(), vMessageDoc)),
453
440
  });
454
441
 
455
- /** @deprecated Use listMessagesByThreadId instead. */
456
- export const getThreadMessages = query({
457
- args: { deprecated: v.literal("Use listMessagesByThreadId instead") },
458
- handler: async () => {
459
- throw new Error("Use listMessagesByThreadId instead of getThreadMessages");
460
- },
461
- returns: vPaginationResult(vMessageDoc),
462
- });
463
-
464
442
  export const searchMessages = action({
465
443
  args: {
466
444
  threadId: v.optional(v.id("threads")),
@@ -563,14 +541,19 @@ export const _fetchSearchMessages = internalQuery({
563
541
  args.vectorIds.map((embeddingId) =>
564
542
  ctx.db
565
543
  .query("messages")
566
- .withIndex("embeddingId", (q) => q.eq("embeddingId", embeddingId))
567
- .filter((q) =>
544
+ .withIndex("embeddingId_threadId", (q) =>
568
545
  searchAllMessagesForUserId
569
- ? q.eq(q.field("userId"), searchAllMessagesForUserId)
570
- : q.eq(q.field("threadId"), threadId!)
546
+ ? q.eq("embeddingId", embeddingId)
547
+ : q.eq("embeddingId", embeddingId).eq("threadId", threadId!)
548
+ )
549
+ .filter((q) =>
550
+ q.and(
551
+ q.eq(q.field("status"), "success"),
552
+ searchAllMessagesForUserId
553
+ ? q.eq(q.field("userId"), searchAllMessagesForUserId)
554
+ : q.eq(q.field("threadId"), threadId)
555
+ )
571
556
  )
572
- // Don't include pending. Failed messages hopefully are deleted but may as well be safe.
573
- .filter((q) => q.eq(q.field("status"), "success"))
574
557
  .first()
575
558
  )
576
559
  )
@@ -85,7 +85,7 @@ export const schema = defineSchema({
85
85
  filterFields: ["userId", "threadId"],
86
86
  })
87
87
  // Allows finding messages by vector embedding id
88
- .index("embeddingId", ["embeddingId"]),
88
+ .index("embeddingId_threadId", ["embeddingId", "threadId"]),
89
89
 
90
90
  // Status: if it's done, it's deleted, then deltas are vacuumed
91
91
  streamingMessages: defineTable({
@@ -151,30 +151,63 @@ export const list = query({
151
151
  },
152
152
  });
153
153
 
154
- export const abort = mutation({
154
+ export const abortByOrder = mutation({
155
155
  args: {
156
- streamId: v.id("streamingMessages"),
156
+ threadId: v.id("threads"),
157
+ order: v.number(),
157
158
  reason: v.string(),
158
159
  },
159
- returns: v.null(),
160
+ returns: v.boolean(),
160
161
  handler: async (ctx, args) => {
161
- const stream = await ctx.db.get(args.streamId);
162
- if (!stream) {
163
- throw new Error(`Stream not found: ${args.streamId}`);
164
- }
165
- if (stream.state.kind !== "streaming") {
166
- console.warn(
167
- `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`
168
- );
169
- return;
162
+ const streams = await ctx.db
163
+ .query("streamingMessages")
164
+ .withIndex("threadId_state_order_stepOrder", (q) =>
165
+ q
166
+ .eq("threadId", args.threadId)
167
+ .eq("state.kind", "streaming")
168
+ .eq("order", args.order)
169
+ )
170
+ .take(100);
171
+ for (const stream of streams) {
172
+ await abortById(ctx, {
173
+ streamId: stream._id,
174
+ reason: args.reason,
175
+ });
170
176
  }
171
- await cleanupTimeoutFn(ctx, stream);
172
- await ctx.db.patch(args.streamId, {
173
- state: { kind: "aborted", reason: args.reason },
174
- });
177
+ return streams.length > 0;
178
+ },
179
+ });
180
+
181
+ export const abort = mutation({
182
+ args: {
183
+ streamId: v.id("streamingMessages"),
184
+ reason: v.string(),
175
185
  },
186
+ returns: v.boolean(),
187
+ handler: abortById,
176
188
  });
177
189
 
190
+ async function abortById(
191
+ ctx: MutationCtx,
192
+ args: { streamId: Id<"streamingMessages">; reason: string }
193
+ ) {
194
+ const stream = await ctx.db.get(args.streamId);
195
+ if (!stream) {
196
+ throw new Error(`Stream not found: ${args.streamId}`);
197
+ }
198
+ if (stream.state.kind !== "streaming") {
199
+ console.warn(
200
+ `Stream trying to abort but not currently streaming (${stream.state.kind}): ${args.streamId}`
201
+ );
202
+ return false;
203
+ }
204
+ await cleanupTimeoutFn(ctx, stream);
205
+ await ctx.db.patch(args.streamId, {
206
+ state: { kind: "aborted", reason: args.reason },
207
+ });
208
+ return true;
209
+ }
210
+
178
211
  async function cleanupTimeoutFn(
179
212
  ctx: MutationCtx,
180
213
  stream: Doc<"streamingMessages">
@@ -37,7 +37,7 @@ export type EmbeddingsWithoutDenormalizedFields = Infer<
37
37
  typeof vEmbeddingsWithoutDenormalizedFields
38
38
  >;
39
39
 
40
- function table<D extends number>(dimensions: D): Table<D> {
40
+ function table<D extends number>(dimensions: D): VectorTable<D> {
41
41
  return defineTable(embeddings)
42
42
  .vectorIndex("vector", {
43
43
  vectorField: "vector",
@@ -47,7 +47,7 @@ function table<D extends number>(dimensions: D): Table<D> {
47
47
  .index("model_table_threadId", ["model", "table", "threadId"]);
48
48
  }
49
49
 
50
- type Table<D extends number> = TableDefinition<
50
+ type VectorTable<D extends number> = TableDefinition<
51
51
  VObject<ObjectType<typeof embeddings>, typeof embeddings>,
52
52
  { model_table_threadId: ["model", "table", "threadId", "_creationTime"] },
53
53
  GenericTableSearchIndexes,
@@ -63,7 +63,7 @@ type VectorIndex<D extends number> = {
63
63
  };
64
64
 
65
65
  export type VectorSchema = SchemaDefinition<
66
- { [key in VectorTableName]: Table<128> },
66
+ { [key in VectorTableName]: VectorTable<128> },
67
67
  true
68
68
  >;
69
69
 
@@ -110,7 +110,7 @@ export function getVectorIdInfo(ctx: QueryCtx, id: VectorTableId) {
110
110
 
111
111
  const tables: {
112
112
  [K in keyof typeof VectorDimensions &
113
- number as `embeddings_${(typeof VectorDimensions)[K]}`]: Table<
113
+ number as `embeddings_${(typeof VectorDimensions)[K]}`]: VectorTable<
114
114
  (typeof VectorDimensions)[K]
115
115
  >;
116
116
  } = Object.fromEntries(
@@ -120,7 +120,7 @@ const tables: {
120
120
  ])
121
121
  ) as Record<
122
122
  `embeddings_${(typeof VectorDimensions)[number]}`,
123
- Table<(typeof VectorDimensions)[number]>
123
+ VectorTable<(typeof VectorDimensions)[number]>
124
124
  >;
125
125
 
126
126
  export default tables;
package/src/mapping.ts CHANGED
@@ -125,7 +125,6 @@ export async function serializeObjectResult(
125
125
  const { message, fileIds } = await serializeMessage(ctx, component, {
126
126
  role: "assistant" as const,
127
127
  content: text,
128
- id: result.response.id,
129
128
  });
130
129
  return {
131
130
  messages: [
@@ -187,6 +186,9 @@ export async function serializeContent(
187
186
  }
188
187
  return { ...part, data };
189
188
  }
189
+ case "tool-result": {
190
+ return { ...part, result: part.result ?? null };
191
+ }
190
192
  default:
191
193
  return part;
192
194
  }
@@ -281,6 +283,11 @@ export function guessMimeType(buf: ArrayBuffer | string): string {
281
283
  return "application/octet-stream";
282
284
  }
283
285
 
286
+ /**
287
+ * Serialize an AI SDK `DataContent` or `URL` to a Convex-serializable format.
288
+ * @param dataOrUrl - The data or URL to serialize.
289
+ * @returns The serialized data as an ArrayBuffer or the URL as a string.
290
+ */
284
291
  export function serializeDataOrUrl(
285
292
  dataOrUrl: DataContent | URL
286
293
  ): ArrayBuffer | string {
@@ -2,6 +2,7 @@ import type { TextPart, ToolCallPart, ToolResultPart } from "ai";
2
2
  import type { MessageDoc } from "../client/index.js";
3
3
  import type {
4
4
  Message,
5
+ MessageStatus,
5
6
  StreamDelta,
6
7
  StreamMessage,
7
8
  TextStreamPart,
@@ -99,6 +100,12 @@ export function applyDeltasToStreamMessage(
99
100
  cursor = delta.end;
100
101
  parts.push(...delta.parts);
101
102
  }
103
+ if (existing && existing.messages.length > 0 && !changed) {
104
+ const lastMessage = existing.messages.at(-1)!;
105
+ if (statusFromStreamStatus(streamMessage.status) !== lastMessage.status) {
106
+ changed = true;
107
+ }
108
+ }
102
109
  if (!changed) {
103
110
  return [
104
111
  existing ?? { streamId: streamMessage.streamId, cursor, messages: [] },
@@ -115,6 +122,7 @@ export function applyDeltasToStreamMessage(
115
122
  currentMessage = {
116
123
  ...lastMessage,
117
124
  message: cloneMessageAndContent(lastMessage.message),
125
+ status: statusFromStreamStatus(streamMessage.status),
118
126
  };
119
127
  } else {
120
128
  const newMessage = createStreamingMessage(
@@ -248,6 +256,21 @@ function getLastContent(message: MessageDoc) {
248
256
  return undefined;
249
257
  }
250
258
 
259
+ function statusFromStreamStatus(
260
+ status: StreamMessage["status"]
261
+ ): MessageStatus {
262
+ switch (status) {
263
+ case "streaming":
264
+ return "pending";
265
+ case "finished":
266
+ return "success";
267
+ case "aborted":
268
+ return "failed";
269
+ default:
270
+ return "pending";
271
+ }
272
+ }
273
+
251
274
  export function createStreamingMessage(
252
275
  threadId: string,
253
276
  message: StreamMessage,
@@ -259,9 +282,7 @@ export function createStreamingMessage(
259
282
  ...rest,
260
283
  _id: `${streamId}-${index}`,
261
284
  _creationTime: Date.now(),
262
- status: (
263
- { streaming: "pending", finished: "success", aborted: "failed" } as const
264
- )[message.status],
285
+ status: statusFromStreamStatus(message.status),
265
286
  threadId,
266
287
  tool: false,
267
288
  };
@@ -114,7 +114,7 @@ export function useThreadMessages<
114
114
  const streamListMessages =
115
115
  streamMessages?.map((m) => ({
116
116
  ...m,
117
- streaming: !m.status || m.status === "streaming",
117
+ streaming: !m.status || m.status === "pending",
118
118
  })) ?? [];
119
119
  return {
120
120
  ...paginated,
@@ -25,7 +25,7 @@ export function toUIMessages(
25
25
  content && typeof content !== "string" ? content : [];
26
26
  if (!coreMessage) continue;
27
27
  const common = {
28
- id: message.id ?? message._id,
28
+ id: message._id,
29
29
  createdAt: new Date(message._creationTime),
30
30
  order: message.order,
31
31
  stepOrder: message.stepOrder,
@@ -82,7 +82,7 @@ export function toUIMessages(
82
82
  : message.status;
83
83
  }
84
84
  // update it to the last message's id
85
- assistantMessage.id = message.id ?? message._id;
85
+ assistantMessage.id = message._id;
86
86
  if (message.reasoning) {
87
87
  assistantMessage.parts.push({
88
88
  type: "reasoning",
package/src/validators.ts CHANGED
@@ -4,7 +4,6 @@ import {
4
4
  type ObjectType,
5
5
  type Validator,
6
6
  type Value,
7
- type VObject,
8
7
  } from "convex/values";
9
8
  import { vVectorDimension } from "./component/vector/tables.js";
10
9
 
@@ -312,30 +311,6 @@ export const vStorageOptions = v.object({
312
311
  ),
313
312
  });
314
313
 
315
- const vStorageOptionsIncludingDeprecated = v.object({
316
- ...vStorageOptions.fields,
317
- saveAllInputMessages: v.optional(v.boolean()),
318
- saveAnyInputMessages: v.optional(v.boolean()),
319
- saveOutputMessages: v.optional(v.boolean()),
320
- }) as VObject<
321
- {
322
- saveMessages?: "all" | "none" | "promptAndOutput";
323
- /**
324
- * @deprecated Use saveMessages instead.
325
- */
326
- saveAllInputMessages?: boolean;
327
- /**
328
- * @deprecated Use saveMessages instead.
329
- */
330
- saveAnyInputMessages?: boolean;
331
- /**
332
- * @deprecated Use saveMessages instead.
333
- */
334
- saveOutputMessages?: boolean;
335
- },
336
- typeof vStorageOptions.fields
337
- >;
338
-
339
314
  const vPromptFields = {
340
315
  system: v.optional(v.string()),
341
316
  prompt: v.optional(v.string()),
@@ -360,7 +335,7 @@ const vCommonArgs = {
360
335
  userId: v.optional(v.string()),
361
336
  threadId: v.optional(v.string()),
362
337
  contextOptions: v.optional(vContextOptions),
363
- storageOptions: v.optional(vStorageOptionsIncludingDeprecated),
338
+ storageOptions: v.optional(vStorageOptions),
364
339
  providerOptions,
365
340
  ...vCallSettingsFields,
366
341
  ...vPromptFields,