@convex-dev/agent 0.2.6-alpha.0 → 0.2.6

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 (98) hide show
  1. package/dist/client/definePlaygroundAPI.d.ts +6 -4
  2. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  3. package/dist/client/definePlaygroundAPI.js +15 -6
  4. package/dist/client/definePlaygroundAPI.js.map +1 -1
  5. package/dist/client/index.d.ts +26 -120
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +48 -373
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/messages.d.ts +1 -1
  10. package/dist/client/messages.d.ts.map +1 -1
  11. package/dist/client/mockModel.d.ts +3 -3
  12. package/dist/client/mockModel.d.ts.map +1 -1
  13. package/dist/client/mockModel.js +22 -17
  14. package/dist/client/mockModel.js.map +1 -1
  15. package/dist/client/saveInputMessages.d.ts +20 -0
  16. package/dist/client/saveInputMessages.d.ts.map +1 -0
  17. package/dist/client/saveInputMessages.js +57 -0
  18. package/dist/client/saveInputMessages.js.map +1 -0
  19. package/dist/client/search.d.ts +110 -9
  20. package/dist/client/search.d.ts.map +1 -1
  21. package/dist/client/search.js +271 -39
  22. package/dist/client/search.js.map +1 -1
  23. package/dist/client/start.d.ts +83 -0
  24. package/dist/client/start.d.ts.map +1 -0
  25. package/dist/client/start.js +171 -0
  26. package/dist/client/start.js.map +1 -0
  27. package/dist/client/streaming.d.ts +8 -8
  28. package/dist/client/streaming.d.ts.map +1 -1
  29. package/dist/client/streaming.js +2 -1
  30. package/dist/client/streaming.js.map +1 -1
  31. package/dist/client/textStreamParts.d.ts.map +1 -1
  32. package/dist/client/textStreamParts.js +2 -9
  33. package/dist/client/textStreamParts.js.map +1 -1
  34. package/dist/client/threads.d.ts +1 -1
  35. package/dist/client/threads.d.ts.map +1 -1
  36. package/dist/client/types.d.ts +137 -5
  37. package/dist/client/types.d.ts.map +1 -1
  38. package/dist/component/_generated/api.d.ts +11 -3
  39. package/dist/component/messages.d.ts +13 -4
  40. package/dist/component/messages.d.ts.map +1 -1
  41. package/dist/component/messages.js +67 -25
  42. package/dist/component/messages.js.map +1 -1
  43. package/dist/component/schema.d.ts +2 -1643
  44. package/dist/component/schema.d.ts.map +1 -1
  45. package/dist/component/schema.js +0 -24
  46. package/dist/component/schema.js.map +1 -1
  47. package/dist/mapping.d.ts +7 -9
  48. package/dist/mapping.d.ts.map +1 -1
  49. package/dist/mapping.js +73 -7
  50. package/dist/mapping.js.map +1 -1
  51. package/dist/react/deltas.d.ts.map +1 -1
  52. package/dist/react/deltas.js +15 -5
  53. package/dist/react/deltas.js.map +1 -1
  54. package/dist/react/fromUIMessages.d.ts +13 -0
  55. package/dist/react/fromUIMessages.d.ts.map +1 -0
  56. package/dist/react/fromUIMessages.js +70 -0
  57. package/dist/react/fromUIMessages.js.map +1 -0
  58. package/dist/react/toUIMessages.d.ts +5 -2
  59. package/dist/react/toUIMessages.d.ts.map +1 -1
  60. package/dist/react/toUIMessages.js +3 -0
  61. package/dist/react/toUIMessages.js.map +1 -1
  62. package/dist/shared.d.ts +10 -0
  63. package/dist/shared.d.ts.map +1 -1
  64. package/dist/shared.js +26 -0
  65. package/dist/shared.js.map +1 -1
  66. package/dist/validators.d.ts +1640 -0
  67. package/dist/validators.d.ts.map +1 -1
  68. package/dist/validators.js +41 -0
  69. package/dist/validators.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/client/definePlaygroundAPI.ts +16 -7
  72. package/src/client/index.test.ts +11 -46
  73. package/src/client/index.ts +99 -558
  74. package/src/client/messages.ts +1 -1
  75. package/src/client/mock.json +68 -0
  76. package/src/client/mockModel.ts +34 -23
  77. package/src/client/saveInputMessages.test.ts +576 -0
  78. package/src/client/saveInputMessages.ts +100 -0
  79. package/src/client/search.test.ts +1017 -0
  80. package/src/client/search.ts +446 -68
  81. package/src/client/start.ts +313 -0
  82. package/src/client/stream.json +48 -0
  83. package/src/client/streaming.ts +3 -3
  84. package/src/client/textStreamParts.ts +2 -11
  85. package/src/client/threads.ts +1 -1
  86. package/src/client/types.ts +143 -3
  87. package/src/component/_generated/api.d.ts +11 -3
  88. package/src/component/messages.ts +73 -27
  89. package/src/component/schema.ts +1 -29
  90. package/src/mapping.ts +84 -7
  91. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  92. package/src/react/deltas.ts +18 -5
  93. package/src/react/fromUIMessages.test.ts +427 -0
  94. package/src/react/fromUIMessages.ts +85 -0
  95. package/src/react/toUIMessages.ts +21 -13
  96. package/src/shared.ts +33 -0
  97. package/src/validators.test.ts +13 -2
  98. package/src/validators.ts +48 -0
@@ -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 {
@@ -433,7 +510,7 @@ export function deserializeContent(
433
510
  }) as Content;
434
511
  }
435
512
 
436
- function normalizeToolOutput(
513
+ export function normalizeToolOutput(
437
514
  result: string | JSONValue | undefined,
438
515
  ): ToolResultPart["output"] {
439
516
  if (typeof result === "string") {
@@ -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}]]}
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ JSONValue,
2
3
  ProviderMetadata,
3
4
  TextStreamPart,
4
5
  ToolSet,
@@ -18,7 +19,7 @@ import {
18
19
  type vToolResultPart,
19
20
  } from "../validators.js";
20
21
  import type { Infer } from "convex/values";
21
- import { serializeWarnings } from "../mapping.js";
22
+ import { normalizeToolOutput, serializeWarnings } from "../mapping.js";
22
23
  import { parse } from "convex-helpers/validators";
23
24
  import { sorted } from "../shared.js";
24
25
 
@@ -267,7 +268,9 @@ export function applyDeltasToStreamMessage(
267
268
  lastContent?.type === "tool-result" &&
268
269
  lastContent.toolCallId === part.toolCallId
269
270
  ) {
270
- lastContent.result = part.output;
271
+ lastContent.output = normalizeToolOutput(
272
+ part.output as string | JSONValue | undefined,
273
+ );
271
274
  lastContent.providerExecuted = part.providerExecuted;
272
275
  } else {
273
276
  console.warn(
@@ -376,7 +379,10 @@ export function applyDeltasToStreamMessage(
376
379
  lastContent.toolCallId === part.toolCallId
377
380
  ) {
378
381
  lastContent.isError = true;
379
- lastContent.result = part.errorText;
382
+ lastContent.output = {
383
+ type: "error-text",
384
+ value: part.errorText,
385
+ };
380
386
  lastContent.providerExecuted = part.providerExecuted;
381
387
  } else if (
382
388
  lastContent?.type === "tool-call" &&
@@ -391,7 +397,10 @@ export function applyDeltasToStreamMessage(
391
397
  toolCallId: part.toolCallId,
392
398
  toolName: lastContent.toolName,
393
399
  args: lastContent.args,
394
- result: part.errorText,
400
+ output: {
401
+ type: "error-text",
402
+ value: part.errorText,
403
+ },
395
404
  providerExecuted: part.providerExecuted,
396
405
  isError: true,
397
406
  } satisfies Infer<typeof vToolResultPart>;
@@ -408,7 +417,10 @@ export function applyDeltasToStreamMessage(
408
417
  lastContent.toolCallId === part.toolCallId
409
418
  ) {
410
419
  lastContent.isError = true;
411
- lastContent.result = part.error;
420
+ lastContent.output = {
421
+ type: "error-json",
422
+ value: part.error,
423
+ };
412
424
  lastContent.providerExecuted = part.providerExecuted;
413
425
  } else {
414
426
  if (
@@ -571,6 +583,7 @@ export function createStreamingMessage(
571
583
  _id: `${streamId}-${index}`,
572
584
  _creationTime: Date.now(),
573
585
  status: statusFromStreamStatus(message.status),
586
+ stepOrder: message.stepOrder + index,
574
587
  threadId,
575
588
  tool: false,
576
589
  };