@convex-dev/agent 0.1.18-alpha.1 → 0.2.0-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 (96) hide show
  1. package/dist/client/createTool.d.ts +31 -10
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +25 -10
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/defaultComponent.d.ts +11 -0
  6. package/dist/client/defaultComponent.d.ts.map +1 -0
  7. package/dist/client/defaultComponent.js +7 -0
  8. package/dist/client/defaultComponent.js.map +1 -0
  9. package/dist/client/definePlaygroundAPI.d.ts +174 -199
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/definePlaygroundAPI.js +17 -35
  12. package/dist/client/definePlaygroundAPI.js.map +1 -1
  13. package/dist/client/files.d.ts +6 -3
  14. package/dist/client/files.d.ts.map +1 -1
  15. package/dist/client/files.js +7 -7
  16. package/dist/client/files.js.map +1 -1
  17. package/dist/client/index.d.ts +152 -589
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +138 -222
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/messages.d.ts +538 -0
  22. package/dist/client/messages.d.ts.map +1 -0
  23. package/dist/client/messages.js +91 -0
  24. package/dist/client/messages.js.map +1 -0
  25. package/dist/client/search.d.ts +30 -7
  26. package/dist/client/search.d.ts.map +1 -1
  27. package/dist/client/search.js +20 -3
  28. package/dist/client/search.js.map +1 -1
  29. package/dist/client/streaming.d.ts +3 -2
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js.map +1 -1
  32. package/dist/client/threads.d.ts +46 -0
  33. package/dist/client/threads.d.ts.map +1 -0
  34. package/dist/client/threads.js +49 -0
  35. package/dist/client/threads.js.map +1 -0
  36. package/dist/client/types.d.ts +39 -42
  37. package/dist/client/types.d.ts.map +1 -1
  38. package/dist/component/_generated/api.d.ts +437 -75
  39. package/dist/component/messages.d.ts +254 -55
  40. package/dist/component/messages.d.ts.map +1 -1
  41. package/dist/component/messages.js +29 -25
  42. package/dist/component/messages.js.map +1 -1
  43. package/dist/component/schema.d.ts +1459 -158
  44. package/dist/component/schema.d.ts.map +1 -1
  45. package/dist/component/schema.js +8 -14
  46. package/dist/component/schema.js.map +1 -1
  47. package/dist/component/streams.d.ts +180 -6
  48. package/dist/component/streams.d.ts.map +1 -1
  49. package/dist/mapping.d.ts +11 -15
  50. package/dist/mapping.d.ts.map +1 -1
  51. package/dist/mapping.js +191 -61
  52. package/dist/mapping.js.map +1 -1
  53. package/dist/react/deltas.d.ts +0 -3
  54. package/dist/react/deltas.d.ts.map +1 -1
  55. package/dist/react/deltas.js +140 -44
  56. package/dist/react/deltas.js.map +1 -1
  57. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  58. package/dist/react/optimisticallySendMessage.js +2 -1
  59. package/dist/react/optimisticallySendMessage.js.map +1 -1
  60. package/dist/react/toUIMessages.d.ts +5 -4
  61. package/dist/react/toUIMessages.d.ts.map +1 -1
  62. package/dist/react/toUIMessages.js +103 -40
  63. package/dist/react/toUIMessages.js.map +1 -1
  64. package/dist/validators.d.ts +1978 -1210
  65. package/dist/validators.d.ts.map +1 -1
  66. package/dist/validators.js +90 -54
  67. package/dist/validators.js.map +1 -1
  68. package/package.json +32 -28
  69. package/src/client/createTool.ts +69 -38
  70. package/src/client/defaultComponent.ts +17 -0
  71. package/src/client/definePlaygroundAPI.ts +29 -43
  72. package/src/client/files.ts +7 -8
  73. package/src/client/index.test.ts +20 -18
  74. package/src/client/index.ts +228 -434
  75. package/src/client/messages.ts +191 -0
  76. package/src/client/search.ts +30 -6
  77. package/src/client/streaming.ts +4 -3
  78. package/src/client/threads.ts +78 -0
  79. package/src/client/types.ts +50 -72
  80. package/src/component/_generated/api.d.ts +437 -75
  81. package/src/component/messages.test.ts +182 -40
  82. package/src/component/messages.ts +30 -32
  83. package/src/component/schema.ts +8 -14
  84. package/src/mapping.ts +230 -94
  85. package/src/react/deltas.ts +165 -52
  86. package/src/react/optimisticallySendMessage.ts +4 -1
  87. package/src/react/toUIMessages.test.ts +154 -36
  88. package/src/react/toUIMessages.ts +136 -57
  89. package/src/validators.test.ts +2 -101
  90. package/src/validators.ts +111 -68
  91. package/dist/client/listMessages.d.ts +0 -22
  92. package/dist/client/listMessages.d.ts.map +0 -1
  93. package/dist/client/listMessages.js +0 -25
  94. package/dist/client/listMessages.js.map +0 -1
  95. package/src/client/listMessages.ts +0 -38
  96. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,7 +1,7 @@
1
- import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
1
+ import type { LanguageModelV2 } from "@ai-sdk/provider";
2
2
  import type {
3
3
  AssistantContent,
4
- CoreMessage,
4
+ ModelMessage,
5
5
  DeepPartial,
6
6
  FilePart,
7
7
  GenerateObjectResult,
@@ -12,11 +12,18 @@ import type {
12
12
  StreamTextResult,
13
13
  ToolSet,
14
14
  UserContent,
15
+ StopCondition,
16
+ Schema,
17
+ ToolChoice,
18
+ LanguageModel,
19
+ EmbeddingModel,
20
+ CallSettings,
15
21
  } from "ai";
16
22
  import {
17
23
  embedMany,
18
24
  generateObject,
19
25
  generateText,
26
+ stepCountIs,
20
27
  streamObject,
21
28
  streamText,
22
29
  } from "ai";
@@ -38,28 +45,37 @@ import {
38
45
  type VectorDimension,
39
46
  } from "../component/vector/tables.js";
40
47
  import {
41
- type AIMessageWithoutId,
42
48
  deserializeMessage,
43
- promptOrMessagesToCoreMessages,
44
49
  serializeMessage,
45
50
  serializeNewMessagesInStep,
46
51
  serializeObjectResult,
47
52
  } from "../mapping.js";
48
53
  import { extractText, isTool } from "../shared.js";
49
54
  import {
50
- type MessageEmbeddings,
55
+ type Message,
51
56
  type MessageStatus,
52
57
  type MessageWithMetadata,
53
58
  type ProviderMetadata,
54
59
  type StreamArgs,
55
60
  type Usage,
61
+ vMessageEmbeddings,
56
62
  vMessageWithMetadata,
57
63
  vSafeObjectArgs,
58
64
  vTextArgs,
59
65
  } from "../validators.js";
60
- import { createTool, wrapTools } from "./createTool.js";
61
- import { listMessages } from "./listMessages.js";
62
- import { fetchContextMessages } from "./search.js";
66
+ import { createTool, wrapTools, type ToolCtx } from "./createTool.js";
67
+ import {
68
+ listMessages,
69
+ saveMessages,
70
+ type SaveMessageArgs,
71
+ type SaveMessagesArgs,
72
+ } from "./messages.js";
73
+ import { createThread, getThreadMetadata } from "./threads.js";
74
+ import {
75
+ fetchContextMessages,
76
+ getModelName,
77
+ getProviderName,
78
+ } from "./search.js";
63
79
  import {
64
80
  DeltaStreamer,
65
81
  mergeTransforms,
@@ -86,9 +102,15 @@ import type {
86
102
  UsageHandler,
87
103
  UserActionCtx,
88
104
  } from "./types.js";
105
+ import type z from "zod/v3";
89
106
 
107
+ export { stepCountIs } from "ai";
90
108
  export { vMessageDoc, vThreadDoc } from "../component/schema.js";
91
- export { serializeDataOrUrl } from "../mapping.js";
109
+ export {
110
+ serializeDataOrUrl,
111
+ deserializeMessage,
112
+ serializeMessage,
113
+ } from "../mapping.js";
92
114
  // NOTE: these are also exported via @convex-dev/agent/validators
93
115
  // a future version may put them all here or move these over there
94
116
  export {
@@ -106,16 +128,20 @@ export {
106
128
  } from "../validators.js";
107
129
  export type { ToolCtx } from "./createTool.js";
108
130
  export { getFile, storeFile } from "./files.js";
109
- export { filterOutOrphanedToolMessages } from "./search.js";
110
- export { abortStream, listStreams } from "./streaming.js";
111
131
  export {
112
- createTool,
113
- extractText,
132
+ filterOutOrphanedToolMessages,
114
133
  fetchContextMessages,
115
- isTool,
134
+ } from "./search.js";
135
+ export { abortStream, listStreams, syncStreams } from "./streaming.js";
136
+ export {
116
137
  listMessages,
117
- syncStreams,
118
- };
138
+ saveMessage,
139
+ saveMessages,
140
+ type SaveMessageArgs,
141
+ type SaveMessagesArgs,
142
+ } from "./messages.js";
143
+ export { createThread, getThreadMetadata } from "./threads.js";
144
+ export { createTool, extractText, isTool };
119
145
  export {
120
146
  definePlaygroundAPI,
121
147
  type PlaygroundAPI,
@@ -175,7 +201,7 @@ export class Agent<
175
201
  * const myAgent = new Agent(components.agent, {
176
202
  * chat: openai.chat("gpt-4o-mini"),
177
203
  */
178
- chat: LanguageModelV1;
204
+ chat: LanguageModel;
179
205
  /**
180
206
  * The model to use for text embeddings. Optional.
181
207
  * If specified, it will use this for generating vector embeddings
@@ -186,7 +212,7 @@ export class Agent<
186
212
  * const myAgent = new Agent(components.agent, {
187
213
  * textEmbedding: openai.embedding("text-embedding-3-small")
188
214
  */
189
- textEmbedding?: EmbeddingModelV1<string>;
215
+ textEmbedding?: EmbeddingModel<string>;
190
216
  /**
191
217
  * The default system prompt to put in each request.
192
218
  * Override per-prompt by passing the "system" parameter.
@@ -212,14 +238,15 @@ export class Agent<
212
238
  storageOptions?: StorageOptions;
213
239
  /**
214
240
  * When generating or streaming text with tools available, this
215
- * determines the default max number of iterations.
241
+ * determines when to stop. Defaults to stepCountIs(1).
216
242
  */
217
- maxSteps?: number;
243
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
218
244
  /**
219
- * The maximum number of calls to make to an LLM in case it fails.
220
- * This can be overridden at each generate/stream callsite.
245
+ * The default settings to use for the LLM calls.
246
+ * This can be overridden at each generate/stream callsite on a per-field
247
+ * basis. To clear a default setting, you'll need to pass `undefined`.
221
248
  */
222
- maxRetries?: number;
249
+ callSettings?: CallSettings;
223
250
  /**
224
251
  * The usage handler to use for this agent.
225
252
  */
@@ -309,9 +336,7 @@ export class Agent<
309
336
  */
310
337
  tools?: ThreadTools;
311
338
  },
312
- ): Promise<{
313
- threadId: string;
314
- }>;
339
+ ): Promise<{ threadId: string }>;
315
340
  async createThread<ThreadTools extends ToolSet | undefined = undefined>(
316
341
  ctx: (ActionCtx & CustomCtx) | RunMutationCtx,
317
342
  args?: {
@@ -335,10 +360,7 @@ export class Agent<
335
360
  usageHandler: args?.usageHandler,
336
361
  tools: args?.tools,
337
362
  });
338
- return {
339
- threadId,
340
- thread,
341
- };
363
+ return { threadId, thread };
342
364
  }
343
365
 
344
366
  /**
@@ -394,30 +416,6 @@ export class Agent<
394
416
  };
395
417
  }
396
418
 
397
- /**
398
- * Search for threads by title, paginated.
399
- * @param ctx The context passed from the query/mutation/action.
400
- * @returns The threads matching the search, paginated.
401
- */
402
- async searchThreadTitles(
403
- ctx: RunQueryCtx,
404
- {
405
- userId,
406
- query,
407
- limit,
408
- }: {
409
- userId?: string | undefined;
410
- query: string;
411
- limit?: number;
412
- },
413
- ): Promise<ThreadDoc[]> {
414
- return ctx.runQuery(this.component.threads.searchThreadTitles, {
415
- userId,
416
- query,
417
- limit: limit ?? 10,
418
- });
419
- }
420
-
421
419
  /**
422
420
  * This behaves like {@link generateText} from the "ai" package except that
423
421
  * it add context based on the userId and threadId and saves the input and
@@ -439,8 +437,8 @@ export class Agent<
439
437
  {
440
438
  userId: argsUserId,
441
439
  threadId,
442
- usageHandler,
443
440
  tools: threadTools,
441
+ ...usageHandler
444
442
  }: {
445
443
  userId?: string | null;
446
444
  threadId?: string;
@@ -458,34 +456,36 @@ export class Agent<
458
456
  GenerateTextResult<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT> &
459
457
  GenerationOutputMetadata
460
458
  > {
461
- const opts = { ...this.options, ...options, usageHandler };
459
+ const opts = { ...this.options, ...options, ...usageHandler };
462
460
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
463
461
  userId: argsUserId ?? undefined,
464
462
  threadId,
465
463
  ...opts,
466
464
  });
467
465
  const { args: aiArgs, messageId, order, userId } = context;
466
+ const messages = context.savedMessages ?? [];
468
467
  const toolCtx = {
469
468
  ...(ctx as UserActionCtx & CustomCtx),
470
469
  userId,
471
470
  threadId,
472
471
  messageId,
473
472
  agent: this,
474
- };
473
+ } satisfies ToolCtx;
474
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
475
475
  const tools = wrapTools(
476
476
  toolCtx,
477
477
  args.tools ?? threadTools ?? this.options.tools,
478
- ) as TOOLS extends undefined ? AgentTools : TOOLS;
478
+ ) as Tools;
479
479
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
480
480
  try {
481
- const result = (await generateText({
481
+ const result = (await generateText<Tools, OUTPUT, OUTPUT_PARTIAL>({
482
482
  // Can be overridden
483
- maxSteps: this.options.maxSteps,
483
+ stopWhen: this.options.stopWhen as StopCondition<Tools> | undefined,
484
484
  ...aiArgs,
485
485
  tools,
486
486
  onStepFinish: async (step) => {
487
487
  if (threadId && messageId && saveOutput) {
488
- await this.saveStep(ctx, {
488
+ const saved = await this.saveStep(ctx, {
489
489
  userId,
490
490
  threadId,
491
491
  promptMessageId: messageId,
@@ -493,6 +493,7 @@ export class Agent<
493
493
  provider: aiArgs.model.provider,
494
494
  step,
495
495
  });
496
+ messages.push(...saved.messages);
496
497
  }
497
498
  if (this.options.rawRequestResponseHandler) {
498
499
  await this.options.rawRequestResponseHandler(ctx, {
@@ -516,13 +517,10 @@ export class Agent<
516
517
  }
517
518
  return args.onStepFinish?.(step);
518
519
  },
519
- })) as GenerateTextResult<
520
- TOOLS extends undefined ? AgentTools : TOOLS,
521
- OUTPUT
522
- > &
523
- GenerationOutputMetadata;
520
+ })) as GenerateTextResult<Tools, OUTPUT> & GenerationOutputMetadata;
524
521
  result.messageId = messageId;
525
522
  result.order = order;
523
+ result.messages = messages;
526
524
  return result;
527
525
  } catch (error) {
528
526
  if (threadId && messageId) {
@@ -552,9 +550,9 @@ export class Agent<
552
550
  {
553
551
  userId: argsUserId,
554
552
  threadId,
555
- usageHandler,
556
553
  /** Note: to get better type inference, pass tools in the next arg */
557
554
  tools: threadTools,
555
+ ...usageHandler
558
556
  }: {
559
557
  userId?: string | null;
560
558
  threadId?: string;
@@ -589,13 +587,14 @@ export class Agent<
589
587
  > &
590
588
  GenerationOutputMetadata
591
589
  > {
592
- const opts = { ...this.options, ...options, usageHandler };
590
+ const opts = { ...this.options, ...options, ...usageHandler };
593
591
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
594
592
  userId: argsUserId ?? undefined,
595
593
  threadId,
596
594
  ...opts,
597
595
  });
598
596
  const { args: aiArgs, messageId, order, stepOrder, userId } = context;
597
+ const messages = context.savedMessages ?? [];
599
598
  const toolCtx = {
600
599
  ...(ctx as UserActionCtx & CustomCtx),
601
600
  userId,
@@ -624,8 +623,8 @@ export class Agent<
624
623
  : undefined;
625
624
 
626
625
  const result = streamText({
627
- // Can be overridden
628
- maxSteps: this.options.maxSteps,
626
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
627
+ stopWhen: this.options.stopWhen as any, // Can be overridden
629
628
  ...aiArgs,
630
629
  tools,
631
630
  abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
@@ -661,6 +660,7 @@ export class Agent<
661
660
  step,
662
661
  });
663
662
  await streamer?.finish(saved.messages);
663
+ messages.push(...saved.messages);
664
664
  }
665
665
  if (this.options.rawRequestResponseHandler) {
666
666
  await this.options.rawRequestResponseHandler(ctx, {
@@ -691,6 +691,7 @@ export class Agent<
691
691
  GenerationOutputMetadata;
692
692
  result.messageId = messageId;
693
693
  result.order = order;
694
+ result.messages = messages;
694
695
  return result;
695
696
  }
696
697
 
@@ -706,7 +707,7 @@ export class Agent<
706
707
  {
707
708
  userId: argsUserId,
708
709
  threadId,
709
- usageHandler,
710
+ ...usageHandler
710
711
  }: {
711
712
  userId?: string | null;
712
713
  threadId?: string;
@@ -722,13 +723,14 @@ export class Agent<
722
723
  */
723
724
  options?: Options,
724
725
  ): Promise<GenerateObjectResult<T> & GenerationOutputMetadata> {
725
- const opts = { ...this.options, ...options, usageHandler };
726
+ const opts = { ...this.options, ...options, ...usageHandler };
726
727
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
727
728
  userId: argsUserId ?? undefined,
728
729
  threadId,
729
730
  ...opts,
730
731
  });
731
732
  const { args: aiArgs, messageId, order, userId } = context;
733
+ const messages = context.savedMessages ?? [];
732
734
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
733
735
  try {
734
736
  const result = (await generateObject(
@@ -737,7 +739,7 @@ export class Agent<
737
739
  )) as GenerateObjectResult<T> & GenerationOutputMetadata;
738
740
 
739
741
  if (threadId && messageId && saveOutput) {
740
- await this.saveObject(ctx, {
742
+ const saved = await this.saveObject(ctx, {
741
743
  threadId,
742
744
  promptMessageId: messageId,
743
745
  result,
@@ -745,9 +747,11 @@ export class Agent<
745
747
  model: aiArgs.model.modelId,
746
748
  provider: aiArgs.model.provider,
747
749
  });
750
+ messages.push(...saved.messages);
748
751
  }
749
752
  result.messageId = messageId;
750
753
  result.order = order;
754
+ result.messages = messages;
751
755
  if (this.options.rawRequestResponseHandler) {
752
756
  await this.options.rawRequestResponseHandler(ctx, {
753
757
  userId,
@@ -787,12 +791,12 @@ export class Agent<
787
791
  * Use {@link continueThread} to get a version of this function already scoped
788
792
  * to a thread (and optionally userId).
789
793
  */
790
- async streamObject<T>(
794
+ async streamObject<T extends z.Schema | Schema>(
791
795
  ctx: ActionCtx,
792
796
  {
793
797
  userId: argsUserId,
794
798
  threadId,
795
- usageHandler,
799
+ ...usageHandler
796
800
  }: {
797
801
  userId?: string | null;
798
802
  threadId?: string;
@@ -811,15 +815,17 @@ export class Agent<
811
815
  StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
812
816
  > {
813
817
  // TODO: unify all this shared code between all the generate* and stream* functions
814
- const opts = { ...this.options, ...options, usageHandler };
818
+ const opts = { ...this.options, ...options, ...usageHandler };
815
819
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
816
820
  userId: argsUserId ?? undefined,
817
821
  threadId,
818
822
  ...opts,
819
823
  });
820
824
  const { args: aiArgs, messageId, order, userId } = context;
825
+ const messages = context.savedMessages ?? [];
821
826
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
822
- const stream = streamObject<T>({
827
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
828
+ const stream = streamObject<any>({
823
829
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
824
830
  ...(aiArgs as any),
825
831
  onError: async (error) => {
@@ -828,7 +834,7 @@ export class Agent<
828
834
  },
829
835
  onFinish: async (result) => {
830
836
  if (threadId && messageId && saveOutput) {
831
- await this.saveObject(ctx, {
837
+ const saved = await this.saveObject(ctx, {
832
838
  userId,
833
839
  threadId,
834
840
  promptMessageId: messageId,
@@ -840,14 +846,12 @@ export class Agent<
840
846
  request: await stream.request,
841
847
  response: result.response,
842
848
  providerMetadata: result.providerMetadata,
843
- experimental_providerMetadata:
844
- result.experimental_providerMetadata,
845
- logprobs: undefined,
846
849
  toJsonResponse: stream.toTextStreamResponse,
847
850
  },
848
851
  model: aiArgs.model.modelId,
849
852
  provider: aiArgs.model.provider,
850
853
  });
854
+ messages.push(...saved.messages);
851
855
  }
852
856
  if (opts.usageHandler && result.usage) {
853
857
  await opts.usageHandler(ctx, {
@@ -876,6 +880,7 @@ export class Agent<
876
880
  GenerationOutputMetadata;
877
881
  stream.messageId = messageId;
878
882
  stream.order = order;
883
+ stream.messages = messages;
879
884
  return stream;
880
885
  }
881
886
 
@@ -898,14 +903,11 @@ export class Agent<
898
903
  skipEmbeddings?: boolean;
899
904
  },
900
905
  ) {
901
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
906
+ const { messages } = await this.saveMessages(ctx, {
902
907
  threadId: args.threadId,
903
908
  userId: args.userId,
904
909
  embeddings: args.embedding
905
- ? {
906
- model: args.embedding.model,
907
- vectors: [args.embedding.vector],
908
- }
910
+ ? { model: args.embedding.model, vectors: [args.embedding.vector] }
909
911
  : undefined,
910
912
  messages:
911
913
  args.prompt !== undefined
@@ -914,7 +916,8 @@ export class Agent<
914
916
  metadata: args.metadata ? [args.metadata] : undefined,
915
917
  skipEmbeddings: args.skipEmbeddings,
916
918
  });
917
- return { messageId: lastMessageId, message: messages.at(-1)! };
919
+ const message = messages.at(-1)!;
920
+ return { messageId: message._id, message };
918
921
  }
919
922
 
920
923
  /**
@@ -936,16 +939,8 @@ export class Agent<
936
939
  */
937
940
  skipEmbeddings?: boolean;
938
941
  },
939
- ): Promise<{
940
- lastMessageId: string;
941
- messages: MessageDoc[];
942
- }> {
943
- let embeddings:
944
- | {
945
- vectors: (number[] | null)[];
946
- model: string;
947
- }
948
- | undefined;
942
+ ): Promise<{ messages: MessageDoc[] }> {
943
+ let embeddings: { vectors: (number[] | null)[]; model: string } | undefined;
949
944
  const { skipEmbeddings, ...rest } = args;
950
945
  if (args.embeddings) {
951
946
  embeddings = args.embeddings;
@@ -967,10 +962,7 @@ export class Agent<
967
962
  } else {
968
963
  embeddings = await this.generateEmbeddings(
969
964
  ctx,
970
- {
971
- userId: args.userId ?? undefined,
972
- threadId: args.threadId,
973
- },
965
+ { userId: args.userId ?? undefined, threadId: args.threadId },
974
966
  args.messages,
975
967
  );
976
968
  }
@@ -1037,7 +1029,7 @@ export class Agent<
1037
1029
  args: {
1038
1030
  userId: string | undefined;
1039
1031
  threadId: string | undefined;
1040
- messages: CoreMessage[];
1032
+ messages: (ModelMessage | Message)[];
1041
1033
  /**
1042
1034
  * If provided, it will search for messages up to and including this message.
1043
1035
  * Note: if this is far in the past, text and vector search results may be more
@@ -1069,7 +1061,7 @@ export class Agent<
1069
1061
  values: [text],
1070
1062
  })
1071
1063
  ).embeddings[0],
1072
- embeddingModel: this.options.textEmbedding.modelId,
1064
+ embeddingModel: this.options.textEmbedding,
1073
1065
  };
1074
1066
  },
1075
1067
  });
@@ -1121,11 +1113,8 @@ export class Agent<
1121
1113
  {
1122
1114
  userId,
1123
1115
  threadId,
1124
- }: {
1125
- userId: string | undefined;
1126
- threadId: string | undefined;
1127
- },
1128
- messages: CoreMessage[],
1116
+ }: { userId: string | undefined; threadId: string | undefined },
1117
+ messages: (ModelMessage | Message)[],
1129
1118
  ) {
1130
1119
  if (!this.options.textEmbedding) {
1131
1120
  return undefined;
@@ -1151,7 +1140,6 @@ export class Agent<
1151
1140
  threadId,
1152
1141
  values: messageTexts as string[],
1153
1142
  });
1154
- // TODO: record usage of embeddings
1155
1143
  // Then assemble the embeddings into a single array with nulls for the messages without text.
1156
1144
  const embeddingsOrNull = Array(messages.length).fill(null);
1157
1145
  textIndexes.forEach((i, j) => {
@@ -1160,11 +1148,8 @@ export class Agent<
1160
1148
  if (textEmbeddings.embeddings.length > 0) {
1161
1149
  const dimension = textEmbeddings.embeddings[0].length;
1162
1150
  validateVectorDimension(dimension);
1163
- embeddings = {
1164
- vectors: embeddingsOrNull,
1165
- dimension,
1166
- model: this.options.textEmbedding.modelId,
1167
- };
1151
+ const model = getModelName(this.options.textEmbedding);
1152
+ embeddings = { vectors: embeddingsOrNull, dimension, model };
1168
1153
  }
1169
1154
  return embeddings;
1170
1155
  }
@@ -1178,9 +1163,7 @@ export class Agent<
1178
1163
  */
1179
1164
  async generateAndSaveEmbeddings(
1180
1165
  ctx: RunActionCtx,
1181
- args: {
1182
- messageIds: string[];
1183
- },
1166
+ args: { messageIds: string[] },
1184
1167
  ) {
1185
1168
  const messages = (
1186
1169
  await ctx.runQuery(this.component.messages.getMessagesByIds, {
@@ -1218,7 +1201,7 @@ export class Agent<
1218
1201
  userId: messagesMissingEmbeddings[0]!.userId,
1219
1202
  threadId: messagesMissingEmbeddings[0]!.threadId,
1220
1203
  },
1221
- messagesMissingEmbeddings.map((m) => m!.message!),
1204
+ messagesMissingEmbeddings.map((m) => deserializeMessage(m!.message!)),
1222
1205
  );
1223
1206
  if (!embeddings) {
1224
1207
  if (!this.options.textEmbedding) {
@@ -1278,14 +1261,14 @@ export class Agent<
1278
1261
  */
1279
1262
  provider?: string;
1280
1263
  },
1281
- ): Promise<{ messages: MessageDoc[]; pending?: MessageDoc }> {
1264
+ ): Promise<{ messages: MessageDoc[] }> {
1282
1265
  const messages = await serializeNewMessagesInStep(
1283
1266
  ctx,
1284
1267
  this.component,
1285
1268
  args.step,
1286
1269
  {
1287
- provider: args.provider ?? this.options.chat.provider,
1288
- model: args.model ?? this.options.chat.modelId,
1270
+ provider: args.provider ?? getProviderName(this.options.chat),
1271
+ model: args.model ?? getModelName(this.options.chat),
1289
1272
  },
1290
1273
  );
1291
1274
  const embeddings = await this.generateEmbeddings(
@@ -1293,7 +1276,7 @@ export class Agent<
1293
1276
  { userId: args.userId, threadId: args.threadId },
1294
1277
  messages.map((m) => m.message),
1295
1278
  );
1296
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
1279
+ return ctx.runMutation(this.component.messages.addMessages, {
1297
1280
  userId: args.userId,
1298
1281
  threadId: args.threadId,
1299
1282
  agentName: this.options.name,
@@ -1302,7 +1285,6 @@ export class Agent<
1302
1285
  embeddings,
1303
1286
  failPendingSteps: false,
1304
1287
  });
1305
- return saved;
1306
1288
  }
1307
1289
 
1308
1290
  /**
@@ -1323,23 +1305,23 @@ export class Agent<
1323
1305
  result: GenerateObjectResult<unknown>;
1324
1306
  metadata?: Omit<MessageWithMetadata, "message">;
1325
1307
  },
1326
- ): Promise<void> {
1308
+ ): Promise<{ messages: MessageDoc[] }> {
1327
1309
  const { messages } = await serializeObjectResult(
1328
1310
  ctx,
1329
1311
  this.component,
1330
1312
  args.result,
1331
1313
  {
1332
- model: args.model ?? this.options.chat.modelId,
1333
- provider: args.provider ?? this.options.chat.provider,
1314
+ model: args.model ?? getModelName(this.options.chat),
1315
+ provider: args.provider ?? getProviderName(this.options.chat),
1334
1316
  },
1335
1317
  );
1336
1318
  const embeddings = await this.generateEmbeddings(
1337
1319
  ctx,
1338
1320
  { userId: args.userId, threadId: args.threadId },
1339
- messages.map((m) => m.message),
1321
+ messages.map((m) => deserializeMessage(m.message)),
1340
1322
  );
1341
1323
 
1342
- await ctx.runMutation(this.component.messages.addMessages, {
1324
+ return ctx.runMutation(this.component.messages.addMessages, {
1343
1325
  userId: args.userId,
1344
1326
  threadId: args.threadId,
1345
1327
  promptMessageId: args.promptMessageId,
@@ -1347,7 +1329,6 @@ export class Agent<
1347
1329
  messages,
1348
1330
  embeddings,
1349
1331
  agentName: this.options.name,
1350
- pending: false,
1351
1332
  });
1352
1333
  }
1353
1334
 
@@ -1392,7 +1373,7 @@ export class Agent<
1392
1373
  messageId: string;
1393
1374
  patch: {
1394
1375
  /** The message to replace the existing message. */
1395
- message: CoreMessage & { id?: string };
1376
+ message: ModelMessage | Message;
1396
1377
  /** The status to set on the message. */
1397
1378
  status: "success" | "error";
1398
1379
  /** The error message to set on the message. */
@@ -1437,9 +1418,7 @@ export class Agent<
1437
1418
  */
1438
1419
  async deleteMessages(
1439
1420
  ctx: RunMutationCtx,
1440
- args: {
1441
- messageIds: string[];
1442
- },
1421
+ args: { messageIds: string[] },
1443
1422
  ): Promise<void> {
1444
1423
  await ctx.runMutation(this.component.messages.deleteByIds, args);
1445
1424
  }
@@ -1452,9 +1431,7 @@ export class Agent<
1452
1431
  */
1453
1432
  async deleteMessage(
1454
1433
  ctx: RunMutationCtx,
1455
- args: {
1456
- messageId: string;
1457
- },
1434
+ args: { messageId: string },
1458
1435
  ): Promise<void> {
1459
1436
  await ctx.runMutation(this.component.messages.deleteByIds, {
1460
1437
  messageIds: [args.messageId],
@@ -1507,8 +1484,8 @@ export class Agent<
1507
1484
  endOrder: number;
1508
1485
  endStepOrder?: number;
1509
1486
  },
1510
- ): Promise<void> {
1511
- await ctx.runMutation(this.component.messages.deleteByOrder, {
1487
+ ): Promise<{ isDone: boolean; lastOrder?: number; lastStepOrder?: number }> {
1488
+ return ctx.runMutation(this.component.messages.deleteByOrder, {
1512
1489
  threadId: args.threadId,
1513
1490
  startOrder: args.startOrder,
1514
1491
  startStepOrder: args.startStepOrder,
@@ -1526,10 +1503,7 @@ export class Agent<
1526
1503
  */
1527
1504
  async deleteThreadAsync(
1528
1505
  ctx: RunMutationCtx,
1529
- args: {
1530
- threadId: string;
1531
- pageSize?: number;
1532
- },
1506
+ args: { threadId: string; pageSize?: number },
1533
1507
  ): Promise<void> {
1534
1508
  await ctx.runMutation(this.component.threads.deleteAllForThreadIdAsync, {
1535
1509
  threadId: args.threadId,
@@ -1546,10 +1520,7 @@ export class Agent<
1546
1520
  */
1547
1521
  async deleteThreadSync(
1548
1522
  ctx: RunActionCtx,
1549
- args: {
1550
- threadId: string;
1551
- pageSize?: number;
1552
- },
1523
+ args: { threadId: string; pageSize?: number },
1553
1524
  ): Promise<void> {
1554
1525
  await ctx.runAction(this.component.threads.deleteAllForThreadIdSync, {
1555
1526
  threadId: args.threadId,
@@ -1560,12 +1531,11 @@ export class Agent<
1560
1531
  async _saveMessagesAndFetchContext<
1561
1532
  T extends {
1562
1533
  id?: string;
1563
- prompt?: string;
1564
- messages?: CoreMessage[] | AIMessageWithoutId[];
1534
+ prompt?: string | (ModelMessage | Message)[];
1535
+ messages?: (ModelMessage | Message)[];
1565
1536
  system?: string;
1566
1537
  promptMessageId?: string;
1567
- model?: LanguageModelV1;
1568
- maxRetries?: number;
1538
+ model?: LanguageModelV2;
1569
1539
  },
1570
1540
  >(
1571
1541
  ctx: RunActionCtx,
@@ -1575,19 +1545,22 @@ export class Agent<
1575
1545
  threadId,
1576
1546
  contextOptions,
1577
1547
  storageOptions,
1578
- }: {
1579
- userId: string | undefined;
1580
- threadId: string | undefined;
1581
- } & Options,
1548
+ }: { userId: string | undefined; threadId: string | undefined } & Options,
1582
1549
  ): Promise<{
1583
- args: T & { model: LanguageModelV1 };
1550
+ args: T & { model: LanguageModelV2 };
1584
1551
  userId: string | undefined;
1585
1552
  messageId: string | undefined;
1586
1553
  order: number | undefined;
1587
1554
  stepOrder: number | undefined;
1555
+ savedMessages: MessageDoc[] | undefined;
1588
1556
  }> {
1589
1557
  // If only a promptMessageId is provided, this will be empty.
1590
- const messages = promptOrMessagesToCoreMessages(args);
1558
+ const messages = args.messages ?? [];
1559
+ const prompt: (ModelMessage | Message)[] = !args.prompt
1560
+ ? []
1561
+ : Array.isArray(args.prompt)
1562
+ ? args.prompt
1563
+ : [{ role: "user", content: args.prompt }];
1591
1564
  const userId =
1592
1565
  argsUserId ??
1593
1566
  (threadId &&
@@ -1603,45 +1576,47 @@ export class Agent<
1603
1576
  });
1604
1577
  // If it was a promptMessageId, pop it off context messages
1605
1578
  // and add to the end of messages.
1606
- // TODO: slice it from the prompt message, to append all of them
1579
+ const promptMessageIndex = args.promptMessageId
1580
+ ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
1581
+ : -1;
1607
1582
  const promptMessage =
1608
- !!args.promptMessageId &&
1609
- contextMessages.at(-1)?._id === args.promptMessageId
1610
- ? contextMessages.pop()
1583
+ promptMessageIndex !== -1
1584
+ ? contextMessages.splice(promptMessageIndex, 1)[0]
1611
1585
  : undefined;
1612
- if (promptMessage && args.prompt) {
1613
- // If they specify both a promptMessageId and a prompt, we prefer
1614
- // the prompt to stand in for the promptMessageId message.
1615
- promptMessage.message = { role: "user", content: args.prompt };
1616
- }
1586
+
1617
1587
  let messageId = promptMessage?._id;
1618
1588
  let order = promptMessage?.order;
1619
1589
  let stepOrder = promptMessage?.stepOrder;
1590
+ let savedMessages = undefined;
1620
1591
  if (
1621
1592
  threadId &&
1622
- messages.length &&
1593
+ messages.length + prompt.length &&
1623
1594
  storageOptions?.saveMessages !== "none" &&
1624
1595
  // If it was a promptMessageId, we don't want to save it again.
1625
1596
  (!args.promptMessageId || storageOptions?.saveMessages === "all")
1626
1597
  ) {
1627
1598
  const saveAll = storageOptions?.saveMessages === "all";
1628
- const coreMessages = saveAll ? messages : messages.slice(-1);
1599
+ const coreMessages = [...messages, ...prompt];
1600
+ const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
1601
+ const metadata = Array.from({ length: toSave.length }, () => ({}));
1629
1602
  const saved = await this.saveMessages(ctx, {
1630
1603
  threadId,
1631
1604
  userId,
1632
- messages: coreMessages,
1633
- metadata: coreMessages.map((_, i) =>
1634
- i === coreMessages.length - 1 ? { id: args.id } : {},
1635
- ),
1605
+ messages: toSave,
1606
+ metadata,
1636
1607
  failPendingSteps: true,
1637
1608
  });
1638
- messageId = saved.lastMessageId;
1639
- order = saved.messages.at(-1)?.order;
1640
- stepOrder = saved.messages.at(-1)?.stepOrder;
1609
+ messageId = saved.messages.at(-1)!._id;
1610
+ order = saved.messages.at(-1)!.order;
1611
+ stepOrder = saved.messages.at(-1)!.stepOrder;
1612
+ savedMessages = saved.messages;
1641
1613
  }
1614
+
1642
1615
  if (promptMessage?.message) {
1643
- // Add the message after saving the messages, so it's not saved again.
1644
- messages.push(deserializeMessage(promptMessage.message));
1616
+ if (!args.prompt) {
1617
+ // If they override the prompt, we skip the existing prompt message.
1618
+ messages.push(deserializeMessage(promptMessage.message));
1619
+ }
1645
1620
  // Lazily generate embeddings for the prompt message, if it doesn't have
1646
1621
  // embeddings yet. This can happen if the message was saved in a mutation
1647
1622
  // where the LLM is not available.
@@ -1650,10 +1625,28 @@ export class Agent<
1650
1625
  }
1651
1626
  }
1652
1627
 
1628
+ const prePrompt = contextMessages.map((m) =>
1629
+ deserializeMessage(m.message!),
1630
+ );
1631
+ let existingResponses: ModelMessage[] = [];
1632
+ if (promptMessageIndex !== -1) {
1633
+ // pull any messages that already responded to the prompt off
1634
+ // and add them after the prompt
1635
+ existingResponses = prePrompt.splice(promptMessageIndex);
1636
+ }
1637
+
1653
1638
  let processedMessages = [
1654
- ...contextMessages.map((m) => deserializeMessage(m.message!)),
1639
+ ...prePrompt,
1655
1640
  ...messages,
1641
+ ...prompt,
1642
+ ...existingResponses,
1656
1643
  ];
1644
+ if (promptMessageIndex === -1) {
1645
+ processedMessages.push(...prompt);
1646
+ } else {
1647
+ // We add the prompt where the prompt message was
1648
+ processedMessages.splice(promptMessageIndex, 0, ...prompt);
1649
+ }
1657
1650
 
1658
1651
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
1659
1652
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
@@ -1663,14 +1656,15 @@ export class Agent<
1663
1656
  const { prompt: _, model, ...rest } = args;
1664
1657
  return {
1665
1658
  args: {
1659
+ ...this.options.callSettings,
1666
1660
  ...rest,
1667
- maxRetries: args.maxRetries ?? this.options.maxRetries,
1668
1661
  model: model ?? this.options.chat,
1669
1662
  system: args.system ?? this.options.instructions,
1670
1663
  messages: processedMessages,
1671
- } as T & { model: LanguageModelV1 },
1664
+ } as T & { model: LanguageModelV2 },
1672
1665
  userId,
1673
1666
  messageId,
1667
+ savedMessages,
1674
1668
  order,
1675
1669
  stepOrder,
1676
1670
  };
@@ -1692,23 +1686,23 @@ export class Agent<
1692
1686
  "a textEmbedding model is required to be set on the Agent that you're doing vector search with",
1693
1687
  );
1694
1688
  const result = await embedMany({
1689
+ ...this.options.callSettings,
1695
1690
  model: embeddingModel,
1696
1691
  values: options.values,
1697
1692
  abortSignal: options.abortSignal,
1698
1693
  headers: options.headers,
1699
- maxRetries: this.options.maxRetries,
1700
1694
  });
1701
1695
  if (this.options.usageHandler && result.usage) {
1702
1696
  await this.options.usageHandler(ctx, {
1703
1697
  userId: options.userId,
1704
1698
  threadId: options.threadId,
1705
1699
  agentName: this.options.name,
1706
- model: embeddingModel.modelId,
1707
- provider: embeddingModel.provider,
1700
+ model: getModelName(embeddingModel),
1701
+ provider: getProviderName(embeddingModel),
1708
1702
  providerMetadata: undefined,
1709
1703
  usage: {
1710
- promptTokens: result.usage.tokens,
1711
- completionTokens: 0,
1704
+ inputTokens: result.usage.tokens,
1705
+ outputTokens: 0,
1712
1706
  totalTokens: result.usage.tokens,
1713
1707
  },
1714
1708
  });
@@ -1722,11 +1716,11 @@ export class Agent<
1722
1716
  * able to access localhost URLs.
1723
1717
  */
1724
1718
  private async _inlineMessagesFiles(
1725
- messages: CoreMessage[],
1726
- ): Promise<CoreMessage[]> {
1719
+ messages: (ModelMessage | Message)[],
1720
+ ): Promise<(ModelMessage | Message)[]> {
1727
1721
  // Process each message to convert localhost URLs to base64
1728
1722
  return Promise.all(
1729
- messages.map(async (message): Promise<CoreMessage> => {
1723
+ messages.map(async (message): Promise<ModelMessage | Message> => {
1730
1724
  if (
1731
1725
  (message.role !== "user" && message.role !== "assistant") ||
1732
1726
  typeof message.content === "string" ||
@@ -1744,10 +1738,7 @@ export class Agent<
1744
1738
  );
1745
1739
  if (this._isLocalhostUrl(part.image)) {
1746
1740
  const imageData = await this._downloadFile(part.image);
1747
- return {
1748
- ...part,
1749
- image: imageData,
1750
- } as ImagePart;
1741
+ return { ...part, image: imageData } as ImagePart;
1751
1742
  }
1752
1743
  }
1753
1744
 
@@ -1755,10 +1746,7 @@ export class Agent<
1755
1746
  if (part.type === "file" && part.data instanceof URL) {
1756
1747
  if (this._isLocalhostUrl(part.data)) {
1757
1748
  const fileData = await this._downloadFile(part.data);
1758
- return {
1759
- ...part,
1760
- data: fileData,
1761
- } as FilePart;
1749
+ return { ...part, data: fileData } as FilePart;
1762
1750
  }
1763
1751
  }
1764
1752
 
@@ -1766,15 +1754,9 @@ export class Agent<
1766
1754
  }),
1767
1755
  );
1768
1756
  if (message.role === "user") {
1769
- return {
1770
- ...message,
1771
- content: processedContent as UserContent,
1772
- };
1757
+ return { ...message, content: processedContent as UserContent };
1773
1758
  } else {
1774
- return {
1775
- ...message,
1776
- content: processedContent as AssistantContent,
1777
- };
1759
+ return { ...message, content: processedContent as AssistantContent };
1778
1760
  }
1779
1761
  }),
1780
1762
  );
@@ -1845,15 +1827,15 @@ export class Agent<
1845
1827
  * Create an action out of this agent so you can call it from workflows or other actions
1846
1828
  * without a wrapping function.
1847
1829
  * @param spec Configuration for the agent acting as an action, including
1848
- * {@link ContextOptions}, {@link StorageOptions}, and maxSteps.
1830
+ * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1849
1831
  */
1850
1832
  asTextAction<DataModel extends GenericDataModel>(
1851
1833
  spec?: {
1852
1834
  /**
1853
- * The maximum number of steps to take in this action.
1854
- * Defaults to the {@link Agent.maxSteps} option.
1835
+ * When to stop generating text.
1836
+ * Defaults to the {@link Agent["options"].stopWhen} option.
1855
1837
  */
1856
- maxSteps?: number;
1838
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
1857
1839
  /**
1858
1840
  * The {@link ContextOptions} to use for fetching contextual messages and
1859
1841
  * saving input/output messages.
@@ -1902,17 +1884,25 @@ export class Agent<
1902
1884
  }
1903
1885
  : { customCtx?: never }),
1904
1886
  ) {
1905
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1887
+ const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1906
1888
  return internalActionGeneric({
1907
1889
  args: vTextArgs,
1908
1890
  handler: async (ctx_, args) => {
1909
1891
  const stream =
1910
- args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1892
+ args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1911
1893
  const targetArgs = { userId: args.userId, threadId: args.threadId };
1912
1894
  const llmArgs = {
1913
- maxSteps,
1895
+ stopWhen,
1914
1896
  ...omit(args, ["storageOptions", "contextOptions"]),
1915
- };
1897
+ messages: args.messages?.map(deserializeMessage),
1898
+ prompt: Array.isArray(args.prompt)
1899
+ ? args.prompt.map(deserializeMessage)
1900
+ : args.prompt,
1901
+ toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1902
+ } satisfies StreamingTextArgs<AgentTools>;
1903
+ if (args.maxSteps) {
1904
+ llmArgs.stopWhen = stepCountIs(args.maxSteps);
1905
+ }
1916
1906
  const opts = {
1917
1907
  ...this.options,
1918
1908
  ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
@@ -1925,7 +1915,13 @@ export class Agent<
1925
1915
  : ctx_
1926
1916
  ) as UserActionCtx & CustomCtx;
1927
1917
  if (stream) {
1928
- const result = await this.streamText(ctx, targetArgs, llmArgs, opts);
1918
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1919
+ const result = await this.streamText<any>(
1920
+ ctx,
1921
+ targetArgs,
1922
+ llmArgs,
1923
+ opts,
1924
+ );
1929
1925
  await result.consumeStream();
1930
1926
  return {
1931
1927
  text: await result.text,
@@ -1935,7 +1931,13 @@ export class Agent<
1935
1931
  warnings: result.warnings,
1936
1932
  };
1937
1933
  } else {
1938
- const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1934
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1935
+ const res = await this.generateText<any>(
1936
+ ctx,
1937
+ targetArgs,
1938
+ llmArgs,
1939
+ opts,
1940
+ );
1939
1941
  return {
1940
1942
  text: res.text,
1941
1943
  messageId: res.messageId,
@@ -1952,28 +1954,27 @@ export class Agent<
1952
1954
  * it from workflows or other actions without a wrapping function.
1953
1955
  * @param spec Configuration for the agent acting as an action, including
1954
1956
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1955
- * and maxSteps.
1957
+ * and stopWhen.
1956
1958
  */
1957
1959
  asObjectAction<T>(
1958
- spec: OurObjectArgs<T> & { maxSteps?: number },
1960
+ spec: OurObjectArgs<T>,
1959
1961
  options?: {
1960
1962
  contextOptions?: ContextOptions;
1961
1963
  storageOptions?: StorageOptions;
1962
1964
  },
1963
1965
  ) {
1964
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1965
1966
  return internalActionGeneric({
1966
1967
  args: vSafeObjectArgs,
1967
1968
  handler: async (ctx, args) => {
1968
- const overrides = pick(args, ["userId", "threadId"]);
1969
+ const { userId, threadId, ...rest } = args;
1970
+ const overrides = pick(rest, ["contextOptions", "storageOptions"]);
1969
1971
  const value = await this.generateObject(
1970
1972
  ctx,
1971
- { userId: args.userId, threadId: args.threadId },
1973
+ { userId, threadId },
1972
1974
  {
1973
1975
  ...spec,
1974
- maxSteps,
1975
- ...omit(args, ["userId", "threadId"]),
1976
- } as unknown as OurObjectArgs<unknown>,
1976
+ ...omit(rest, ["contextOptions", "storageOptions"]),
1977
+ } as OurObjectArgs<unknown>,
1977
1978
  { ...this.options, ...options, ...overrides },
1978
1979
  );
1979
1980
  return {
@@ -2014,228 +2015,21 @@ export class Agent<
2014
2015
  userId: v.optional(v.string()),
2015
2016
  promptMessageId: v.optional(v.string()),
2016
2017
  messages: v.array(vMessageWithMetadata),
2017
- pending: v.optional(v.boolean()),
2018
2018
  failPendingSteps: v.optional(v.boolean()),
2019
+ embeddings: v.optional(vMessageEmbeddings),
2019
2020
  },
2020
2021
  handler: async (ctx, args) => {
2021
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
2022
+ const { messages } = await this.saveMessages(ctx, {
2022
2023
  ...args,
2023
- messages: args.messages.map((m) => m.message),
2024
+ messages: args.messages.map((m) => deserializeMessage(m.message)),
2024
2025
  metadata: args.messages.map(({ message: _, ...m }) => m),
2026
+ skipEmbeddings: true,
2025
2027
  });
2026
2028
  return {
2027
- lastMessageId,
2028
- messageIds: messages.map((m) => m._id),
2029
+ lastMessageId: messages.at(-1)!._id,
2030
+ messages: messages.map((m) => pick(m, ["_id", "order", "stepOrder"])),
2029
2031
  };
2030
2032
  },
2031
2033
  });
2032
2034
  }
2033
2035
  }
2034
-
2035
- type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
2036
-
2037
- /**
2038
- * Create a thread to store messages with an Agent.
2039
- * @param ctx The context from a mutation or action.
2040
- * @param component The Agent component, usually `components.agent`.
2041
- * @param args The associated thread metadata.
2042
- * @returns The id of the created thread.
2043
- */
2044
- export async function createThread(
2045
- ctx: RunMutationCtx,
2046
- component: AgentComponent,
2047
- args?: {
2048
- userId?: string | null;
2049
- title?: string;
2050
- summary?: string;
2051
- },
2052
- ) {
2053
- const { _id: threadId } = await ctx.runMutation(
2054
- component.threads.createThread,
2055
- {
2056
- userId: args?.userId ?? undefined,
2057
- title: args?.title,
2058
- summary: args?.summary,
2059
- },
2060
- );
2061
- return threadId;
2062
- }
2063
-
2064
- /**
2065
- * Get the metadata for a thread.
2066
- * @param ctx A ctx object from a query, mutation, or action.
2067
- * @param args.threadId The thread to get the metadata for.
2068
- * @returns The metadata for the thread.
2069
- */
2070
- export async function getThreadMetadata(
2071
- ctx: RunQueryCtx,
2072
- component: AgentComponent,
2073
- args: { threadId: string },
2074
- ): Promise<ThreadDoc> {
2075
- const thread = await ctx.runQuery(component.threads.getThread, {
2076
- threadId: args.threadId,
2077
- });
2078
- if (!thread) {
2079
- throw new Error("Thread not found");
2080
- }
2081
- return thread;
2082
- }
2083
-
2084
- type SaveMessagesArgs = {
2085
- threadId: string;
2086
- userId?: string | null;
2087
- /**
2088
- * The message that these messages are in response to. They will be
2089
- * the same "order" as this message, at increasing stepOrder(s).
2090
- */
2091
- promptMessageId?: string;
2092
- /**
2093
- * The messages to save.
2094
- */
2095
- messages: CoreMessageMaybeWithId[];
2096
- /**
2097
- * Metadata to save with the messages. Each element corresponds to the
2098
- * message at the same index.
2099
- */
2100
- metadata?: Omit<MessageWithMetadata, "message">[];
2101
- /**
2102
- * If false, it will "commit" the messages immediately.
2103
- * If true, it will mark them as pending until the final step has finished.
2104
- * Defaults to false.
2105
- */
2106
- pending?: boolean;
2107
- /**
2108
- * If true, it will fail any pending steps.
2109
- * Defaults to false.
2110
- */
2111
- failPendingSteps?: boolean;
2112
- /**
2113
- * The embeddings to save with the messages.
2114
- */
2115
- embeddings?: Omit<MessageEmbeddings, "dimension">;
2116
- };
2117
-
2118
- /**
2119
- * Explicitly save messages associated with the thread (& user if provided)
2120
- */
2121
- export async function saveMessages(
2122
- ctx: RunMutationCtx,
2123
- component: AgentComponent,
2124
- args: SaveMessagesArgs & {
2125
- /**
2126
- * The agent name to associate with the messages.
2127
- */
2128
- agentName?: string;
2129
- },
2130
- ) {
2131
- let embeddings: MessageEmbeddings | undefined;
2132
- if (args.embeddings) {
2133
- const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
2134
- if (dimension) {
2135
- validateVectorDimension(dimension);
2136
- embeddings = {
2137
- model: args.embeddings.model,
2138
- dimension,
2139
- vectors: args.embeddings.vectors,
2140
- };
2141
- }
2142
- }
2143
- const result = await ctx.runMutation(component.messages.addMessages, {
2144
- threadId: args.threadId,
2145
- userId: args.userId ?? undefined,
2146
- agentName: args.agentName,
2147
- promptMessageId: args.promptMessageId,
2148
- embeddings,
2149
- messages: await Promise.all(
2150
- args.messages.map(async (m, i) => {
2151
- const { message, fileIds } = await serializeMessage(ctx, component, m);
2152
- return {
2153
- ...args.metadata?.[i],
2154
- message,
2155
- fileIds,
2156
- } as MessageWithMetadata;
2157
- }),
2158
- ),
2159
- failPendingSteps: args.failPendingSteps ?? false,
2160
- pending: args.pending ?? false,
2161
- });
2162
- return {
2163
- lastMessageId: result.messages.at(-1)!._id,
2164
- messages: result.messages,
2165
- };
2166
- }
2167
-
2168
- type SaveMessageArgs = {
2169
- threadId: string;
2170
- userId?: string | null;
2171
- /**
2172
- * Metadata to save with the messages. Each element corresponds to the
2173
- * message at the same index.
2174
- */
2175
- metadata?: Omit<MessageWithMetadata, "message">;
2176
- /**
2177
- * The embedding to save with the message.
2178
- */
2179
- embedding?: {
2180
- vector: number[];
2181
- model: string;
2182
- };
2183
- } & (
2184
- | {
2185
- prompt?: undefined;
2186
- /**
2187
- * The message to save.
2188
- */
2189
- message: CoreMessage;
2190
- }
2191
- | {
2192
- /*
2193
- * The prompt to save with the message.
2194
- */
2195
- prompt: string;
2196
- message?: undefined;
2197
- }
2198
- );
2199
-
2200
- /**
2201
- * Save a message to the thread.
2202
- * @param ctx A ctx object from a mutation or action.
2203
- * @param args The message and what to associate it with (user / thread)
2204
- * You can pass extra metadata alongside the message, e.g. associated fileIds.
2205
- * @returns The messageId of the saved message.
2206
- */
2207
- export async function saveMessage(
2208
- ctx: RunMutationCtx,
2209
- component: AgentComponent,
2210
- args: SaveMessageArgs & {
2211
- /**
2212
- * The agent name to associate with the message.
2213
- */
2214
- agentName?: string;
2215
- },
2216
- ) {
2217
- let embeddings:
2218
- | {
2219
- vectors: number[][];
2220
- model: string;
2221
- }
2222
- | undefined;
2223
- if (args.embedding && args.embedding.vector) {
2224
- embeddings = {
2225
- model: args.embedding.model,
2226
- vectors: [args.embedding.vector],
2227
- };
2228
- }
2229
- const { lastMessageId, messages } = await saveMessages(ctx, component, {
2230
- threadId: args.threadId,
2231
- userId: args.userId ?? undefined,
2232
- agentName: args.agentName,
2233
- messages:
2234
- args.prompt !== undefined
2235
- ? [{ role: "user", content: args.prompt }]
2236
- : [args.message],
2237
- metadata: args.metadata ? [args.metadata] : undefined,
2238
- embeddings,
2239
- });
2240
- return { messageId: lastMessageId, message: messages.at(-1)! };
2241
- }