@convex-dev/agent 0.2.6-alpha.1 → 0.2.7

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 (101) hide show
  1. package/dist/client/createTool.js +1 -1
  2. package/dist/client/createTool.js.map +1 -1
  3. package/dist/client/definePlaygroundAPI.d.ts +6 -4
  4. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  5. package/dist/client/definePlaygroundAPI.js +15 -6
  6. package/dist/client/definePlaygroundAPI.js.map +1 -1
  7. package/dist/client/index.d.ts +55 -122
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/index.js +47 -371
  10. package/dist/client/index.js.map +1 -1
  11. package/dist/client/messages.d.ts +1 -1
  12. package/dist/client/messages.d.ts.map +1 -1
  13. package/dist/client/mockModel.d.ts +5 -3
  14. package/dist/client/mockModel.d.ts.map +1 -1
  15. package/dist/client/mockModel.js +33 -24
  16. package/dist/client/mockModel.js.map +1 -1
  17. package/dist/client/saveInputMessages.d.ts +20 -0
  18. package/dist/client/saveInputMessages.d.ts.map +1 -0
  19. package/dist/client/saveInputMessages.js +57 -0
  20. package/dist/client/saveInputMessages.js.map +1 -0
  21. package/dist/client/search.d.ts +110 -9
  22. package/dist/client/search.d.ts.map +1 -1
  23. package/dist/client/search.js +271 -39
  24. package/dist/client/search.js.map +1 -1
  25. package/dist/client/start.d.ts +84 -0
  26. package/dist/client/start.d.ts.map +1 -0
  27. package/dist/client/start.js +171 -0
  28. package/dist/client/start.js.map +1 -0
  29. package/dist/client/streaming.d.ts +8 -8
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js +2 -1
  32. package/dist/client/streaming.js.map +1 -1
  33. package/dist/client/textStreamParts.d.ts.map +1 -1
  34. package/dist/client/textStreamParts.js +2 -9
  35. package/dist/client/textStreamParts.js.map +1 -1
  36. package/dist/client/threads.d.ts +1 -1
  37. package/dist/client/threads.d.ts.map +1 -1
  38. package/dist/client/types.d.ts +147 -17
  39. package/dist/client/types.d.ts.map +1 -1
  40. package/dist/component/_generated/api.d.ts +11 -3
  41. package/dist/component/messages.d.ts +13 -4
  42. package/dist/component/messages.d.ts.map +1 -1
  43. package/dist/component/messages.js +67 -25
  44. package/dist/component/messages.js.map +1 -1
  45. package/dist/component/schema.d.ts +2 -1643
  46. package/dist/component/schema.d.ts.map +1 -1
  47. package/dist/component/schema.js +0 -24
  48. package/dist/component/schema.js.map +1 -1
  49. package/dist/mapping.d.ts +5 -8
  50. package/dist/mapping.d.ts.map +1 -1
  51. package/dist/mapping.js +72 -6
  52. package/dist/mapping.js.map +1 -1
  53. package/dist/react/deltas.d.ts.map +1 -1
  54. package/dist/react/deltas.js +1 -0
  55. package/dist/react/deltas.js.map +1 -1
  56. package/dist/react/fromUIMessages.d.ts +13 -0
  57. package/dist/react/fromUIMessages.d.ts.map +1 -0
  58. package/dist/react/fromUIMessages.js +75 -0
  59. package/dist/react/fromUIMessages.js.map +1 -0
  60. package/dist/react/toUIMessages.d.ts +5 -2
  61. package/dist/react/toUIMessages.d.ts.map +1 -1
  62. package/dist/react/toUIMessages.js +3 -0
  63. package/dist/react/toUIMessages.js.map +1 -1
  64. package/dist/shared.d.ts +10 -0
  65. package/dist/shared.d.ts.map +1 -1
  66. package/dist/shared.js +26 -0
  67. package/dist/shared.js.map +1 -1
  68. package/dist/validators.d.ts +1640 -0
  69. package/dist/validators.d.ts.map +1 -1
  70. package/dist/validators.js +41 -0
  71. package/dist/validators.js.map +1 -1
  72. package/package.json +4 -4
  73. package/src/client/createTool.ts +1 -1
  74. package/src/client/definePlaygroundAPI.ts +16 -7
  75. package/src/client/index.test.ts +11 -46
  76. package/src/client/index.ts +128 -573
  77. package/src/client/messages.ts +1 -1
  78. package/src/client/mock.json +68 -0
  79. package/src/client/mockModel.ts +47 -31
  80. package/src/client/saveInputMessages.test.ts +576 -0
  81. package/src/client/saveInputMessages.ts +100 -0
  82. package/src/client/search.test.ts +1017 -0
  83. package/src/client/search.ts +446 -68
  84. package/src/client/start.ts +315 -0
  85. package/src/client/stream.json +48 -0
  86. package/src/client/streaming.ts +3 -3
  87. package/src/client/textStreamParts.ts +2 -11
  88. package/src/client/threads.ts +1 -1
  89. package/src/client/types.ts +153 -22
  90. package/src/component/_generated/api.d.ts +11 -3
  91. package/src/component/messages.ts +73 -27
  92. package/src/component/schema.ts +1 -29
  93. package/src/mapping.ts +83 -6
  94. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  95. package/src/react/deltas.ts +1 -0
  96. package/src/react/fromUIMessages.test.ts +467 -0
  97. package/src/react/fromUIMessages.ts +90 -0
  98. package/src/react/toUIMessages.ts +21 -13
  99. package/src/shared.ts +33 -0
  100. package/src/validators.test.ts +13 -2
  101. package/src/validators.ts +48 -0
@@ -689,6 +689,12 @@ export type Mounts = {
689
689
  },
690
690
  null
691
691
  >;
692
+ getMessageSearchFields: FunctionReference<
693
+ "query",
694
+ "public",
695
+ { messageId: string },
696
+ { embedding?: Array<number>; embeddingModel?: string; text?: string }
697
+ >;
692
698
  getMessagesByIds: FunctionReference<
693
699
  "query",
694
700
  "public",
@@ -1217,15 +1223,17 @@ export type Mounts = {
1217
1223
  "action",
1218
1224
  "public",
1219
1225
  {
1220
- beforeMessageId?: string;
1221
1226
  embedding?: Array<number>;
1222
1227
  embeddingModel?: string;
1223
1228
  limit: number;
1224
1229
  messageRange?: { after: number; before: number };
1225
1230
  searchAllMessagesForUserId?: string;
1231
+ targetMessageId?: string;
1226
1232
  text?: string;
1233
+ textSearch?: boolean;
1227
1234
  threadId?: string;
1228
1235
  vectorScoreThreshold?: number;
1236
+ vectorSearch?: boolean;
1229
1237
  },
1230
1238
  Array<{
1231
1239
  _creationTime: number;
@@ -1464,10 +1472,10 @@ export type Mounts = {
1464
1472
  "query",
1465
1473
  "public",
1466
1474
  {
1467
- beforeMessageId?: string;
1468
1475
  limit: number;
1469
1476
  searchAllMessagesForUserId?: string;
1470
- text: string;
1477
+ targetMessageId?: string;
1478
+ text?: string;
1471
1479
  threadId?: string;
1472
1480
  },
1473
1481
  Array<{
@@ -13,10 +13,12 @@ import {
13
13
  sorted,
14
14
  } from "../shared.js";
15
15
  import {
16
+ vMessageDoc,
16
17
  vMessageEmbeddingsWithDimension,
17
18
  vMessageStatus,
18
19
  vMessageWithMetadataInternal,
19
20
  vPaginationResult,
21
+ type MessageDoc,
20
22
  } from "../validators.js";
21
23
  import { api, internal } from "./_generated/api.js";
22
24
  import type { Doc, Id } from "./_generated/dataModel.js";
@@ -28,12 +30,10 @@ import {
28
30
  query,
29
31
  type QueryCtx,
30
32
  } from "./_generated/server.js";
31
- import type { MessageDoc } from "./schema.js";
32
- import { schema, v, vMessageDoc } from "./schema.js";
33
+ import { schema, v } from "./schema.js";
33
34
  import { insertVector, searchVectors } from "./vector/index.js";
34
35
  import {
35
- type VectorDimension,
36
- VectorDimensions,
36
+ validateVectorDimension,
37
37
  type VectorTableId,
38
38
  vVectorId,
39
39
  } from "./vector/tables.js";
@@ -504,10 +504,12 @@ export const searchMessages = action({
504
504
  args: {
505
505
  threadId: v.optional(v.id("threads")),
506
506
  searchAllMessagesForUserId: v.optional(v.string()),
507
- beforeMessageId: v.optional(v.id("messages")),
507
+ targetMessageId: v.optional(v.id("messages")),
508
508
  embedding: v.optional(v.array(v.number())),
509
509
  embeddingModel: v.optional(v.string()),
510
510
  text: v.optional(v.string()),
511
+ textSearch: v.optional(v.boolean()),
512
+ vectorSearch: v.optional(v.boolean()),
511
513
  limit: v.number(),
512
514
  vectorScoreThreshold: v.optional(v.number()),
513
515
  messageRange: v.optional(
@@ -522,24 +524,38 @@ export const searchMessages = action({
522
524
  );
523
525
  const limit = args.limit;
524
526
  let textSearchMessages: MessageDoc[] | undefined;
525
- if (args.text) {
527
+ if (args.textSearch) {
526
528
  textSearchMessages = await ctx.runQuery(api.messages.textSearch, {
527
529
  searchAllMessagesForUserId: args.searchAllMessagesForUserId,
528
530
  threadId: args.threadId,
531
+ targetMessageId: args.targetMessageId,
529
532
  text: args.text,
530
533
  limit,
531
- beforeMessageId: args.beforeMessageId,
532
534
  });
533
535
  }
534
- if (args.embedding) {
535
- const dimension = args.embedding.length as VectorDimension;
536
- if (!VectorDimensions.includes(dimension)) {
537
- throw new Error(`Unsupported embedding dimension: ${dimension}`);
536
+ if (args.vectorSearch) {
537
+ let embedding = args.embedding;
538
+ let model = args.embeddingModel;
539
+ if (!embedding) {
540
+ if (args.targetMessageId) {
541
+ const target = await ctx.runQuery(
542
+ api.messages.getMessageSearchFields,
543
+ {
544
+ messageId: args.targetMessageId,
545
+ },
546
+ );
547
+ assert(target, "Target message embedding not found.");
548
+ embedding = target.embedding;
549
+ model = target.embeddingModel;
550
+ }
538
551
  }
552
+ assert(embedding && model, "Embedding missing");
553
+ const dimension = embedding.length;
554
+ validateVectorDimension(dimension);
539
555
  const vectors = (
540
- await searchVectors(ctx, args.embedding, {
556
+ await searchVectors(ctx, embedding, {
541
557
  dimension,
542
- model: args.embeddingModel ?? "unknown",
558
+ model,
543
559
  table: "messages",
544
560
  searchAllMessagesForUserId: args.searchAllMessagesForUserId,
545
561
  threadId: args.threadId,
@@ -568,7 +584,7 @@ export const searchMessages = action({
568
584
  (m) => !embeddingIds.includes(m.embeddingId! as VectorTableId),
569
585
  ),
570
586
  messageRange: args.messageRange ?? DEFAULT_MESSAGE_RANGE,
571
- beforeMessageId: args.beforeMessageId,
587
+ beforeMessageId: args.targetMessageId,
572
588
  limit,
573
589
  },
574
590
  );
@@ -699,26 +715,29 @@ export const textSearch = query({
699
715
  args: {
700
716
  threadId: v.optional(v.id("threads")),
701
717
  searchAllMessagesForUserId: v.optional(v.string()),
702
- text: v.string(),
718
+ text: v.optional(v.string()),
719
+ targetMessageId: v.optional(v.id("messages")),
703
720
  limit: v.number(),
704
- beforeMessageId: v.optional(v.id("messages")),
705
721
  },
706
722
  handler: async (ctx, args) => {
707
723
  assert(
708
724
  args.searchAllMessagesForUserId || args.threadId,
709
725
  "Specify userId or threadId",
710
726
  );
711
- const beforeMessage =
712
- args.beforeMessageId && (await ctx.db.get(args.beforeMessageId));
713
- const order = beforeMessage?.order;
727
+ const targetMessage =
728
+ args.targetMessageId && (await ctx.db.get(args.targetMessageId));
729
+ const order = targetMessage?.order;
730
+ const text = args.text || targetMessage?.text;
731
+ if (!text) {
732
+ console.warn("No text to search", targetMessage, args.text);
733
+ return [];
734
+ }
714
735
  const messages = await ctx.db
715
736
  .query("messages")
716
737
  .withSearchIndex("text_search", (q) =>
717
738
  args.searchAllMessagesForUserId
718
- ? q
719
- .search("text", args.text)
720
- .eq("userId", args.searchAllMessagesForUserId)
721
- : q.search("text", args.text).eq("threadId", args.threadId!),
739
+ ? q.search("text", text).eq("userId", args.searchAllMessagesForUserId)
740
+ : q.search("text", text).eq("threadId", args.threadId!),
722
741
  )
723
742
  // Just in case tool messages slip through
724
743
  .filter((q) => {
@@ -732,12 +751,39 @@ export const textSearch = query({
732
751
  return messages
733
752
  .filter(
734
753
  (m) =>
735
- !beforeMessage ||
736
- m.order < beforeMessage.order ||
737
- (m.order === beforeMessage.order &&
738
- m.stepOrder < beforeMessage.stepOrder),
754
+ !targetMessage ||
755
+ m.order < targetMessage.order ||
756
+ (m.order === targetMessage.order &&
757
+ m.stepOrder < targetMessage.stepOrder),
739
758
  )
740
759
  .map(publicMessage);
741
760
  },
742
761
  returns: v.array(vMessageDoc),
743
762
  });
763
+
764
+ export const getMessageSearchFields = query({
765
+ args: {
766
+ messageId: v.id("messages"),
767
+ },
768
+ returns: v.object({
769
+ text: v.optional(v.string()),
770
+ embedding: v.optional(v.array(v.number())),
771
+ embeddingModel: v.optional(v.string()),
772
+ }),
773
+ handler: async (ctx, args) => {
774
+ const message = await ctx.db.get(args.messageId);
775
+ const text = message?.text;
776
+ let embedding = undefined;
777
+ let embeddingModel = undefined;
778
+ if (message?.embeddingId) {
779
+ const target = await ctx.db.get(message.embeddingId);
780
+ embedding = target?.vector;
781
+ embeddingModel = target?.model;
782
+ }
783
+ return {
784
+ text,
785
+ embedding,
786
+ embeddingModel,
787
+ };
788
+ },
789
+ });
@@ -1,5 +1,5 @@
1
1
  import { defineSchema, defineTable } from "convex/server";
2
- import { type Infer, v } from "convex/values";
2
+ import { v } from "convex/values";
3
3
  import {
4
4
  vThreadStatus,
5
5
  vMessage,
@@ -14,7 +14,6 @@ import {
14
14
  } from "../validators.js";
15
15
  import { typedV } from "convex-helpers/validators";
16
16
  import vectorTables, { vVectorId } from "./vector/tables.js";
17
- import { omit } from "convex-helpers";
18
17
 
19
18
  export const schema = defineSchema({
20
19
  threads: defineTable({
@@ -164,31 +163,4 @@ export const schema = defineSchema({
164
163
  export const vv = typedV(schema);
165
164
  export { vv as v };
166
165
 
167
- // Public
168
- export const vThreadDoc = v.object({
169
- _id: v.string(),
170
- _creationTime: v.number(),
171
- userId: v.optional(v.string()), // Unset for anonymous
172
- title: v.optional(v.string()),
173
- summary: v.optional(v.string()),
174
- status: vThreadStatus,
175
- });
176
- export type ThreadDoc = Infer<typeof vThreadDoc>;
177
-
178
- export const vMessageDoc = v.object({
179
- _id: v.string(),
180
- _creationTime: v.number(),
181
- ...omit(schema.tables.messages.validator.fields, [
182
- "parentMessageId",
183
- "stepId",
184
- "files",
185
- ]),
186
- // Overwrite all the types that have a v.id validator
187
- // Outside of the component, they are strings
188
- threadId: v.string(),
189
- embeddingId: v.optional(v.string()),
190
- fileIds: v.optional(v.array(v.string())),
191
- });
192
- export type MessageDoc = Infer<typeof vMessageDoc>;
193
-
194
166
  export default schema;
package/src/mapping.ts CHANGED
@@ -46,6 +46,11 @@ import {
46
46
  type ReasoningPart,
47
47
  } from "@ai-sdk/provider-utils";
48
48
  import { parse, validate } from "convex-helpers/validators";
49
+ import {
50
+ getModelName,
51
+ getProviderName,
52
+ type ModelOrMetadata,
53
+ } from "./shared.js";
49
54
  export type AIMessageWithoutId = Omit<AIMessage, "id">;
50
55
 
51
56
  export type SerializeUrlsAndUint8Arrays<T> = T extends URL
@@ -86,6 +91,21 @@ export async function serializeMessage(
86
91
  };
87
92
  }
88
93
 
94
+ // Similar to serializeMessage, but doesn't save any files and is looser
95
+ // For use on the frontend / in synchronous environments.
96
+ export function fromModelMessage(
97
+ message: ModelMessage,
98
+ ): Message {
99
+ const content = fromModelMessageContent(message.content);
100
+ return {
101
+ role: message.role,
102
+ content,
103
+ ...(message.providerOptions
104
+ ? { providerOptions: message.providerOptions }
105
+ : {}),
106
+ } as SerializedMessage;
107
+ }
108
+
89
109
  export async function serializeOrThrow(
90
110
  message: ModelMessage | Message,
91
111
  ): Promise<SerializedMessage> {
@@ -159,13 +179,13 @@ export async function serializeNewMessagesInStep<TOOLS extends ToolSet>(
159
179
  ctx: ActionCtx,
160
180
  component: AgentComponent,
161
181
  step: StepResult<TOOLS>,
162
- metadata: { model: string; provider: string },
182
+ model: ModelOrMetadata | undefined,
163
183
  ): Promise<{ messages: MessageWithMetadata[] }> {
164
184
  // If there are tool results, there's another message with the tool results
165
185
  // ref: https://github.com/vercel/ai/blob/main/packages/ai/core/generate-text/to-response-messages.ts
166
186
  const assistantFields = {
167
- model: metadata.model,
168
- provider: metadata.provider,
187
+ model: model ? getModelName(model) : undefined,
188
+ provider: model ? getProviderName(model) : undefined,
169
189
  providerMetadata: step.providerMetadata,
170
190
  reasoning: step.reasoningText,
171
191
  reasoningDetails: step.reasoning,
@@ -198,7 +218,7 @@ export async function serializeObjectResult(
198
218
  ctx: ActionCtx,
199
219
  component: AgentComponent,
200
220
  result: GenerateObjectResult<unknown>,
201
- metadata: { model: string; provider: string },
221
+ model: ModelOrMetadata | undefined,
202
222
  ): Promise<{ messages: MessageWithMetadata[] }> {
203
223
  const text = JSON.stringify(result.object);
204
224
 
@@ -210,8 +230,8 @@ export async function serializeObjectResult(
210
230
  messages: [
211
231
  {
212
232
  message,
213
- model: metadata.model,
214
- provider: metadata.provider,
233
+ model: model ? getModelName(model) : undefined,
234
+ provider: model ? getProviderName(model) : undefined,
215
235
  providerMetadata: result.providerMetadata,
216
236
  finishReason: result.finishReason,
217
237
  text,
@@ -347,6 +367,63 @@ export async function serializeContent(
347
367
  };
348
368
  }
349
369
 
370
+ export function fromModelMessageContent(content: Content): Message["content"] {
371
+ if (typeof content === "string") {
372
+ return content;
373
+ }
374
+ const metadata: {
375
+ providerOptions?: ProviderOptions;
376
+ providerMetadata?: ProviderMetadata;
377
+ } = {};
378
+ if ("providerOptions" in content) {
379
+ metadata.providerOptions = content.providerOptions as ProviderOptions;
380
+ }
381
+ if ("providerMetadata" in content) {
382
+ metadata.providerMetadata = content.providerMetadata as ProviderMetadata;
383
+ }
384
+ return content.map((part) => {
385
+ switch (part.type) {
386
+ case "text":
387
+ return part satisfies Infer<typeof vTextPart>;
388
+ case "image":
389
+ return {
390
+ type: part.type,
391
+ mimeType: getMimeOrMediaType(part),
392
+ ...metadata,
393
+ image: serializeDataOrUrl(part.image),
394
+ } satisfies Infer<typeof vImagePart>;
395
+ case "file":
396
+ return {
397
+ type: part.type,
398
+ data: serializeDataOrUrl(part.data),
399
+ filename: part.filename,
400
+ mimeType: getMimeOrMediaType(part)!,
401
+ ...metadata,
402
+ } satisfies Infer<typeof vFilePart>;
403
+ case "tool-call":
404
+ return {
405
+ type: part.type,
406
+ args: part.input ?? null,
407
+ toolCallId: part.toolCallId,
408
+ toolName: part.toolName,
409
+ providerExecuted: part.providerExecuted,
410
+ ...metadata,
411
+ } satisfies Infer<typeof vToolCallPart>;
412
+ case "tool-result":
413
+ return normalizeToolResult(part, metadata);
414
+ case "reasoning":
415
+ return {
416
+ type: part.type,
417
+ text: part.text,
418
+ ...metadata,
419
+ } satisfies Infer<typeof vReasoningPart>;
420
+ // Not in current generation output, but could be in historical messages
421
+ default:
422
+ return part satisfies Infer<typeof vContent>;
423
+ }
424
+ }) as Message["content"];
425
+ }
426
+
350
427
  export function deserializeContent(
351
428
  content: SerializedContent | ModelMessage["content"],
352
429
  ): Content {
@@ -0,0 +1 @@
1
+ {"version":"3.2.4","results":[[":client/index.test.ts",{"duration":129.9694999999997,"failed":false}],[":component/messages.test.ts",{"duration":191.73391600000014,"failed":false}],[":component/users.test.ts",{"duration":545.7272500000001,"failed":false}],[":mapping.test.ts",{"duration":9.238791999999876,"failed":false}],[":react/deltas.test.ts",{"duration":5.8497500000000855,"failed":false}],[":react/toUIMessages.test.ts",{"duration":6.495208000000048,"failed":false}],[":validators.test.ts",{"duration":1.0037499999999682,"failed":false}],[":deltas.test.ts",{"duration":0,"failed":false}],[":client/streaming.test.ts",{"duration":177.87866699695587,"failed":false}]]}
@@ -583,6 +583,7 @@ export function createStreamingMessage(
583
583
  _id: `${streamId}-${index}`,
584
584
  _creationTime: Date.now(),
585
585
  status: statusFromStreamStatus(message.status),
586
+ stepOrder: message.stepOrder + index,
586
587
  threadId,
587
588
  tool: false,
588
589
  };