@convex-dev/agent 0.1.18 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/client/createTool.d.ts +30 -11
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +24 -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 +68 -23
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/definePlaygroundAPI.js +7 -20
  12. package/dist/client/definePlaygroundAPI.js.map +1 -1
  13. package/dist/client/files.d.ts +14 -4
  14. package/dist/client/files.d.ts.map +1 -1
  15. package/dist/client/files.js +66 -11
  16. package/dist/client/files.js.map +1 -1
  17. package/dist/client/index.d.ts +323 -780
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +445 -543
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/messages.d.ts +256 -0
  22. package/dist/client/messages.d.ts.map +1 -0
  23. package/dist/client/messages.js +97 -0
  24. package/dist/client/messages.js.map +1 -0
  25. package/dist/client/search.d.ts +35 -12
  26. package/dist/client/search.d.ts.map +1 -1
  27. package/dist/client/search.js +21 -4
  28. package/dist/client/search.js.map +1 -1
  29. package/dist/client/streaming.d.ts +21 -21
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js +35 -40
  32. package/dist/client/streaming.js.map +1 -1
  33. package/dist/client/threads.d.ts +46 -0
  34. package/dist/client/threads.d.ts.map +1 -0
  35. package/dist/client/threads.js +49 -0
  36. package/dist/client/threads.js.map +1 -0
  37. package/dist/client/types.d.ts +159 -75
  38. package/dist/client/types.d.ts.map +1 -1
  39. package/dist/component/_generated/api.d.ts +261 -209
  40. package/dist/component/messages.d.ts +314 -105
  41. package/dist/component/messages.d.ts.map +1 -1
  42. package/dist/component/messages.js +141 -83
  43. package/dist/component/messages.js.map +1 -1
  44. package/dist/component/schema.d.ts +631 -870
  45. package/dist/component/schema.d.ts.map +1 -1
  46. package/dist/component/schema.js +11 -16
  47. package/dist/component/schema.js.map +1 -1
  48. package/dist/component/streams.d.ts +29 -149
  49. package/dist/component/streams.d.ts.map +1 -1
  50. package/dist/component/streams.js +104 -60
  51. package/dist/component/streams.js.map +1 -1
  52. package/dist/component/threads.d.ts +4 -4
  53. package/dist/component/users.d.ts +3 -3
  54. package/dist/mapping.d.ts +15 -16
  55. package/dist/mapping.d.ts.map +1 -1
  56. package/dist/mapping.js +208 -62
  57. package/dist/mapping.js.map +1 -1
  58. package/dist/parts.d.ts +3 -0
  59. package/dist/parts.d.ts.map +1 -0
  60. package/dist/parts.js +34 -0
  61. package/dist/parts.js.map +1 -0
  62. package/dist/react/SmoothText.d.ts +5 -0
  63. package/dist/react/SmoothText.d.ts.map +1 -0
  64. package/dist/react/SmoothText.js +6 -0
  65. package/dist/react/SmoothText.js.map +1 -0
  66. package/dist/react/deltas.d.ts +3 -5
  67. package/dist/react/deltas.d.ts.map +1 -1
  68. package/dist/react/deltas.js +89 -53
  69. package/dist/react/deltas.js.map +1 -1
  70. package/dist/react/index.d.ts +1 -0
  71. package/dist/react/index.d.ts.map +1 -1
  72. package/dist/react/index.js +31 -11
  73. package/dist/react/index.js.map +1 -1
  74. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  75. package/dist/react/optimisticallySendMessage.js +2 -8
  76. package/dist/react/optimisticallySendMessage.js.map +1 -1
  77. package/dist/react/toUIMessages.d.ts +5 -4
  78. package/dist/react/toUIMessages.d.ts.map +1 -1
  79. package/dist/react/toUIMessages.js +103 -40
  80. package/dist/react/toUIMessages.js.map +1 -1
  81. package/dist/react/useSmoothText.d.ts +13 -12
  82. package/dist/react/useSmoothText.d.ts.map +1 -1
  83. package/dist/react/useSmoothText.js +1 -1
  84. package/dist/react/useSmoothText.js.map +1 -1
  85. package/dist/validators.d.ts +751 -1464
  86. package/dist/validators.d.ts.map +1 -1
  87. package/dist/validators.js +39 -80
  88. package/dist/validators.js.map +1 -1
  89. package/package.json +34 -30
  90. package/src/client/createTool.ts +66 -40
  91. package/src/client/defaultComponent.ts +17 -0
  92. package/src/client/definePlaygroundAPI.ts +17 -31
  93. package/src/client/files.ts +94 -17
  94. package/src/client/index.test.ts +28 -38
  95. package/src/client/index.ts +785 -977
  96. package/src/client/messages.ts +205 -0
  97. package/src/client/search.ts +37 -10
  98. package/src/client/streaming.ts +53 -56
  99. package/src/client/threads.ts +78 -0
  100. package/src/client/types.ts +239 -131
  101. package/src/component/_generated/api.d.ts +261 -209
  102. package/src/component/messages.test.ts +182 -40
  103. package/src/component/messages.ts +165 -108
  104. package/src/component/schema.ts +11 -17
  105. package/src/component/streams.ts +180 -69
  106. package/src/mapping.ts +253 -96
  107. package/src/parts.ts +39 -0
  108. package/src/react/SmoothText.tsx +9 -0
  109. package/src/react/deltas.test.ts +38 -42
  110. package/src/react/deltas.ts +116 -65
  111. package/src/react/index.ts +35 -13
  112. package/src/react/optimisticallySendMessage.ts +2 -8
  113. package/src/react/toUIMessages.test.ts +154 -36
  114. package/src/react/toUIMessages.ts +136 -57
  115. package/src/react/useSmoothText.ts +14 -15
  116. package/src/validators.test.ts +1 -117
  117. package/src/validators.ts +57 -107
  118. package/dist/client/listMessages.d.ts +0 -22
  119. package/dist/client/listMessages.d.ts.map +0 -1
  120. package/dist/client/listMessages.js +0 -25
  121. package/dist/client/listMessages.js.map +0 -1
  122. package/src/client/listMessages.ts +0 -38
@@ -1,22 +1,26 @@
1
- import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
2
1
  import type {
3
- AssistantContent,
4
- CoreMessage,
5
- DeepPartial,
6
- FilePart,
2
+ FlexibleSchema,
3
+ IdGenerator,
4
+ InferSchema,
5
+ } from "@ai-sdk/provider-utils";
6
+ import type {
7
+ CallSettings,
8
+ EmbeddingModel,
7
9
  GenerateObjectResult,
8
10
  GenerateTextResult,
9
- ImagePart,
11
+ LanguageModel,
12
+ ModelMessage,
10
13
  StepResult,
11
- StreamObjectResult,
14
+ StopCondition,
12
15
  StreamTextResult,
16
+ ToolChoice,
13
17
  ToolSet,
14
- UserContent,
15
18
  } from "ai";
16
19
  import {
17
20
  embedMany,
18
21
  generateObject,
19
22
  generateText,
23
+ stepCountIs,
20
24
  streamObject,
21
25
  streamText,
22
26
  } from "ai";
@@ -24,13 +28,12 @@ import { assert, omit, pick } from "convex-helpers";
24
28
  import {
25
29
  internalActionGeneric,
26
30
  internalMutationGeneric,
27
- type GenericActionCtx,
28
31
  type GenericDataModel,
29
32
  type PaginationOptions,
30
33
  type PaginationResult,
31
34
  type WithoutSystemFields,
32
35
  } from "convex/server";
33
- import { v } from "convex/values";
36
+ import { convexToJson, v, type Value } from "convex/values";
34
37
  import type { MessageDoc, ThreadDoc } from "../component/schema.js";
35
38
  import type { threadFieldsSupportingPatch } from "../component/threads.js";
36
39
  import {
@@ -38,57 +41,77 @@ import {
38
41
  type VectorDimension,
39
42
  } from "../component/vector/tables.js";
40
43
  import {
41
- type AIMessageWithoutId,
42
44
  deserializeMessage,
43
- promptOrMessagesToCoreMessages,
44
45
  serializeMessage,
45
46
  serializeNewMessagesInStep,
46
47
  serializeObjectResult,
47
48
  } from "../mapping.js";
48
49
  import { extractText, isTool } from "../shared.js";
49
50
  import {
50
- type MessageEmbeddings,
51
+ vMessageEmbeddings,
52
+ vMessageWithMetadata,
53
+ vSafeObjectArgs,
54
+ vTextArgs,
55
+ type Message,
51
56
  type MessageStatus,
52
57
  type MessageWithMetadata,
53
58
  type ProviderMetadata,
54
59
  type StreamArgs,
55
60
  type Usage,
56
- vMessageWithMetadata,
57
- vSafeObjectArgs,
58
- vTextArgs,
59
61
  } from "../validators.js";
60
- import { createTool, wrapTools } from "./createTool.js";
61
- import { listMessages } from "./listMessages.js";
62
- import { fetchContextMessages } from "./search.js";
62
+ import { createTool, wrapTools, type ToolCtx } from "./createTool.js";
63
+ import {
64
+ listMessages,
65
+ saveMessages,
66
+ type SaveMessageArgs,
67
+ type SaveMessagesArgs,
68
+ } from "./messages.js";
69
+ import {
70
+ fetchContextMessages,
71
+ getModelName,
72
+ getProviderName,
73
+ } from "./search.js";
63
74
  import {
64
75
  DeltaStreamer,
65
76
  mergeTransforms,
66
- type StreamingOptions,
67
77
  syncStreams,
78
+ type StreamingOptions,
68
79
  } from "./streaming.js";
80
+ import { createThread, getThreadMetadata } from "./threads.js";
69
81
  import type {
70
82
  ActionCtx,
71
83
  AgentComponent,
72
84
  ContextOptions,
85
+ DefaultObjectSchema,
73
86
  GenerationOutputMetadata,
87
+ MaybeCustomCtx,
88
+ GenerateObjectArgs,
89
+ ObjectMode,
90
+ ObjectSchema,
74
91
  Options,
75
- OurObjectArgs,
76
- OurStreamObjectArgs,
77
92
  RawRequestResponseHandler,
78
93
  RunActionCtx,
79
94
  RunMutationCtx,
80
95
  RunQueryCtx,
81
96
  StorageOptions,
82
97
  StreamingTextArgs,
98
+ StreamObjectArgs,
83
99
  SyncStreamsReturnValue,
84
100
  TextArgs,
85
101
  Thread,
86
102
  UsageHandler,
87
103
  UserActionCtx,
88
104
  } from "./types.js";
105
+ import { inlineMessagesFiles } from "./files.js";
106
+ import type { DataModel } from "../component/_generated/dataModel.js";
89
107
 
108
+ export { stepCountIs } from "ai";
90
109
  export { vMessageDoc, vThreadDoc } from "../component/schema.js";
91
- export { serializeDataOrUrl } from "../mapping.js";
110
+ export {
111
+ deserializeMessage,
112
+ serializeDataOrUrl,
113
+ serializeMessage,
114
+ } from "../mapping.js";
92
115
  // NOTE: these are also exported via @convex-dev/agent/validators
93
116
  // a future version may put them all here or move these over there
94
117
  export {
@@ -105,22 +128,26 @@ export {
105
128
  vUserMessage,
106
129
  } from "../validators.js";
107
130
  export type { ToolCtx } from "./createTool.js";
108
- export { getFile, storeFile } from "./files.js";
109
- export { filterOutOrphanedToolMessages } from "./search.js";
110
- export { abortStream, listStreams } from "./streaming.js";
111
- export {
112
- createTool,
113
- extractText,
114
- fetchContextMessages,
115
- isTool,
116
- listMessages,
117
- syncStreams,
118
- };
119
131
  export {
120
132
  definePlaygroundAPI,
121
- type PlaygroundAPI,
122
133
  type AgentsFn,
134
+ type PlaygroundAPI,
123
135
  } from "./definePlaygroundAPI.js";
136
+ export { getFile, storeFile } from "./files.js";
137
+ export {
138
+ listMessages,
139
+ saveMessage,
140
+ saveMessages,
141
+ type SaveMessageArgs,
142
+ type SaveMessagesArgs,
143
+ } from "./messages.js";
144
+ export {
145
+ fetchContextMessages,
146
+ filterOutOrphanedToolMessages,
147
+ } from "./search.js";
148
+ export { abortStream, listStreams, syncStreams } from "./streaming.js";
149
+ export { createThread, getThreadMetadata } from "./threads.js";
150
+ export { createTool, extractText, isTool };
124
151
  export type {
125
152
  AgentComponent,
126
153
  ContextOptions,
@@ -136,6 +163,55 @@ export type {
136
163
  UsageHandler,
137
164
  };
138
165
 
166
+ export type Config = {
167
+ /**
168
+ * The LLM model to use for generating / streaming text and objects.
169
+ * e.g.
170
+ * import { openai } from "@ai-sdk/openai"
171
+ * const myAgent = new Agent(components.agent, {
172
+ * languageModel: openai.chat("gpt-4o-mini"),
173
+ */
174
+ languageModel?: LanguageModel;
175
+ /**
176
+ * The model to use for text embeddings. Optional.
177
+ * If specified, it will use this for generating vector embeddings
178
+ * of chats, and can opt-in to doing vector search for automatic context
179
+ * on generateText, etc.
180
+ * e.g.
181
+ * import { openai } from "@ai-sdk/openai"
182
+ * const myAgent = new Agent(components.agent, {
183
+ * ...
184
+ * textEmbeddingModel: openai.embedding("text-embedding-3-small")
185
+ */
186
+ textEmbeddingModel?: EmbeddingModel<string>;
187
+ /**
188
+ * Options to determine what messages are included as context in message
189
+ * generation. To disable any messages automatically being added, pass:
190
+ * { recentMessages: 0 }
191
+ */
192
+ contextOptions?: ContextOptions;
193
+ /**
194
+ * Determines whether messages are automatically stored when passed as
195
+ * arguments or generated.
196
+ */
197
+ storageOptions?: StorageOptions;
198
+ /**
199
+ * The default settings to use for the LLM calls.
200
+ * This can be overridden at each generate/stream callsite on a per-field
201
+ * basis. To clear a default setting, you'll need to pass `undefined`.
202
+ */
203
+ callSettings?: CallSettings;
204
+ /**
205
+ * The usage handler to use for this agent.
206
+ */
207
+ usageHandler?: UsageHandler;
208
+ /**
209
+ * Called for each LLM request/response, so you can do things like
210
+ * log the raw request body or response headers to a table, or logs.
211
+ */
212
+ rawRequestResponseHandler?: RawRequestResponseHandler;
213
+ };
214
+
139
215
  export class Agent<
140
216
  /**
141
217
  * You can require that all `ctx` args to generateText & streamText
@@ -162,7 +238,7 @@ export class Agent<
162
238
  > {
163
239
  constructor(
164
240
  public component: AgentComponent,
165
- public options: {
241
+ public options: Config & {
166
242
  /**
167
243
  * The name for the agent. This will be attributed on each message
168
244
  * created by this agent.
@@ -173,20 +249,9 @@ export class Agent<
173
249
  * e.g.
174
250
  * import { openai } from "@ai-sdk/openai"
175
251
  * const myAgent = new Agent(components.agent, {
176
- * chat: openai.chat("gpt-4o-mini"),
177
- */
178
- chat: LanguageModelV1;
179
- /**
180
- * The model to use for text embeddings. Optional.
181
- * If specified, it will use this for generating vector embeddings
182
- * of chats, and can opt-in to doing vector search for automatic context
183
- * on generateText, etc.
184
- * e.g.
185
- * import { openai } from "@ai-sdk/openai"
186
- * const myAgent = new Agent(components.agent, {
187
- * textEmbedding: openai.embedding("text-embedding-3-small")
252
+ * languageModel: openai.chat("gpt-4o-mini"),
188
253
  */
189
- textEmbedding?: EmbeddingModelV1<string>;
254
+ languageModel: LanguageModel;
190
255
  /**
191
256
  * The default system prompt to put in each request.
192
257
  * Override per-prompt by passing the "system" parameter.
@@ -199,36 +264,15 @@ export class Agent<
199
264
  * (import { createTool } from "@convex-dev/agent")
200
265
  */
201
266
  tools?: AgentTools;
202
- /**
203
- * Options to determine what messages are included as context in message
204
- * generation. To disable any messages automatically being added, pass:
205
- * { recentMessages: 0 }
206
- */
207
- contextOptions?: ContextOptions;
208
- /**
209
- * Determines whether messages are automatically stored when passed as
210
- * arguments or generated.
211
- */
212
- storageOptions?: StorageOptions;
213
267
  /**
214
268
  * When generating or streaming text with tools available, this
215
- * determines the default max number of iterations.
216
- */
217
- maxSteps?: number;
218
- /**
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.
269
+ * determines when to stop. Defaults to stepCountIs(1).
221
270
  */
222
- maxRetries?: number;
271
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
223
272
  /**
224
- * The usage handler to use for this agent.
273
+ * @deprecated Use `languageEmbeddingModel` instead.
225
274
  */
226
- usageHandler?: UsageHandler;
227
- /**
228
- * Called for each LLM request/response, so you can do things like
229
- * log the raw request body or response headers to a table, or logs.
230
- */
231
- rawRequestResponseHandler?: RawRequestResponseHandler;
275
+ chat?: LanguageModel;
232
276
  },
233
277
  ) {}
234
278
 
@@ -242,7 +286,7 @@ export class Agent<
242
286
  * @param args The thread metadata.
243
287
  * @returns The threadId of the new thread and the thread object.
244
288
  */
245
- async createThread<ThreadTools extends ToolSet | undefined = undefined>(
289
+ async createThread(
246
290
  ctx: RunActionCtx & CustomCtx,
247
291
  args?: {
248
292
  /**
@@ -258,21 +302,8 @@ export class Agent<
258
302
  * The summary of the thread. Not currently used for anything.
259
303
  */
260
304
  summary?: string;
261
- /**
262
- * The usage handler to use for this thread. Overrides any handler
263
- * set in the agent constructor.
264
- */
265
- usageHandler?: UsageHandler;
266
- /**
267
- * The tools to use for this thread.
268
- * Overrides any tools passed in the agent constructor.
269
- */
270
- tools?: ThreadTools;
271
305
  },
272
- ): Promise<{
273
- threadId: string;
274
- thread: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
275
- }>;
306
+ ): Promise<{ threadId: string; thread: Thread<AgentTools> }>;
276
307
  /**
277
308
  * Start a new thread with the agent. This will have a fresh history, though if
278
309
  * you pass in a userId you can have it search across other threads for relevant
@@ -282,7 +313,7 @@ export class Agent<
282
313
  * @param args The thread metadata.
283
314
  * @returns The threadId of the new thread.
284
315
  */
285
- async createThread<ThreadTools extends ToolSet | undefined = undefined>(
316
+ async createThread(
286
317
  ctx: RunMutationCtx,
287
318
  args?: {
288
319
  /**
@@ -298,33 +329,12 @@ export class Agent<
298
329
  * The summary of the thread. Not currently used for anything.
299
330
  */
300
331
  summary?: string;
301
- /**
302
- * The usage handler to use for this thread. Overrides any handler
303
- * set in the agent constructor.
304
- */
305
- usageHandler?: UsageHandler;
306
- /**
307
- * The tools to use for this thread.
308
- * Overrides any tools passed in the agent constructor.
309
- */
310
- tools?: ThreadTools;
311
332
  },
312
- ): Promise<{
313
- threadId: string;
314
- }>;
315
- async createThread<ThreadTools extends ToolSet | undefined = undefined>(
333
+ ): Promise<{ threadId: string }>;
334
+ async createThread(
316
335
  ctx: (ActionCtx & CustomCtx) | RunMutationCtx,
317
- args?: {
318
- userId: string | null;
319
- title?: string;
320
- summary?: string;
321
- usageHandler?: UsageHandler;
322
- tools?: ThreadTools;
323
- },
324
- ): Promise<{
325
- threadId: string;
326
- thread?: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
327
- }> {
336
+ args?: { userId: string | null; title?: string; summary?: string },
337
+ ): Promise<{ threadId: string; thread?: Thread<AgentTools> }> {
328
338
  const threadId = await createThread(ctx, this.component, args);
329
339
  if (!("runAction" in ctx) || "workflowId" in ctx) {
330
340
  return { threadId };
@@ -332,13 +342,8 @@ export class Agent<
332
342
  const { thread } = await this.continueThread(ctx, {
333
343
  threadId,
334
344
  userId: args?.userId,
335
- usageHandler: args?.usageHandler,
336
- tools: args?.tools,
337
345
  });
338
- return {
339
- threadId,
340
- thread,
341
- };
346
+ return { threadId, thread };
342
347
  }
343
348
 
344
349
  /**
@@ -349,7 +354,7 @@ export class Agent<
349
354
  * @param { threadId, userId }: the thread and user to associate the messages with.
350
355
  * @returns Functions bound to the userId and threadId on a `{thread}` object.
351
356
  */
352
- async continueThread<ThreadTools extends ToolSet | undefined = undefined>(
357
+ async continueThread(
353
358
  ctx: ActionCtx & CustomCtx,
354
359
  args: {
355
360
  /**
@@ -361,20 +366,8 @@ export class Agent<
361
366
  * relevant messages from the same user as context for the LLM calls.
362
367
  */
363
368
  userId?: string | null;
364
- /**
365
- * The usage handler to use for this thread. Overrides any handler
366
- * set in the agent constructor.
367
- */
368
- usageHandler?: UsageHandler;
369
- /**
370
- * The tools to use for this thread.
371
- * Overrides any tools passed in the agent constructor.
372
- */
373
- tools?: ThreadTools;
374
369
  },
375
- ): Promise<{
376
- thread: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
377
- }> {
370
+ ): Promise<{ thread: Thread<AgentTools> }> {
378
371
  return {
379
372
  thread: {
380
373
  threadId: args.threadId,
@@ -390,32 +383,251 @@ export class Agent<
390
383
  streamText: this.streamText.bind(this, ctx, args),
391
384
  generateObject: this.generateObject.bind(this, ctx, args),
392
385
  streamObject: this.streamObject.bind(this, ctx, args),
393
- } as Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>,
386
+ } as Thread<AgentTools>,
394
387
  };
395
388
  }
396
389
 
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;
390
+ async start<TOOLS extends ToolSet | undefined, T>(
391
+ ctx: ActionCtx & CustomCtx,
392
+ /**
393
+ * These are the arguments you'll pass to the LLM call such as
394
+ * `generateText` or `streamText`. This function will look up the context
395
+ * and provide functions to save the steps, abort the generation, and more.
396
+ * The type of the arguments returned infers from the type of the arguments
397
+ * you pass here.
398
+ */
399
+ args: T & {
400
+ /**
401
+ * If provided, this message will be used as the "prompt" for the LLM call,
402
+ * instead of the prompt or messages.
403
+ * This is useful if you want to first save a user message, then use it as
404
+ * the prompt for the LLM call in another call.
405
+ */
406
+ promptMessageId?: string;
407
+ /**
408
+ * The model to use for the LLM calls. This will override the model specified
409
+ * in the Agent constructor.
410
+ */
411
+ model?: LanguageModel;
412
+ /**
413
+ * The tools to use for the tool calls. This will override tools specified
414
+ * in the Agent constructor or createThread / continueThread.
415
+ */
416
+ tools?: TOOLS;
417
+ /**
418
+ * The single prompt message to use for the LLM call. This will be the
419
+ * last message in the context. If it's a string, it will be a user role.
420
+ */
421
+ prompt?: string | (ModelMessage | Message)[];
422
+ /**
423
+ * If provided alongside prompt, the ordering will be:
424
+ * 1. system prompt
425
+ * 2. search context
426
+ * 3. recent messages
427
+ * 4. these messages
428
+ * 5. prompt messages, including those already on the same `order` as
429
+ * the promptMessageId message, if provided.
430
+ */
431
+ messages?: (ModelMessage | Message)[];
432
+ /**
433
+ * This will be the first message in the context, and overrides the
434
+ * agent's instructions.
435
+ */
436
+ system?: string;
437
+ /**
438
+ * The abort signal to be passed to the LLM call. If triggered, it will
439
+ * mark the pending message as failed. If the generation is asynchronously
440
+ * aborted, it will trigger this signal when detected.
441
+ */
442
+ abortSignal?: AbortSignal;
443
+ // We optimistically override the generateId function to use the pending
444
+ // message id.
445
+ _internal?: { generateId?: IdGenerator };
412
446
  },
413
- ): Promise<ThreadDoc[]> {
414
- return ctx.runQuery(this.component.threads.searchThreadTitles, {
415
- userId,
416
- query,
417
- limit: limit ?? 10,
447
+ options?: Options & { userId?: string | null; threadId?: string },
448
+ ): Promise<{
449
+ args: T & {
450
+ system?: string;
451
+ model: LanguageModel;
452
+ messages: ModelMessage[];
453
+ // abortSignal?: AbortSignal;
454
+ tools?: TOOLS extends undefined ? AgentTools : TOOLS;
455
+ } & CallSettings;
456
+ order: number;
457
+ stepOrder: number;
458
+ userId: string | undefined;
459
+ promptMessageId: string | undefined;
460
+ updateModel: (model: LanguageModel | undefined) => void;
461
+ save: <TOOLS extends ToolSet>(
462
+ toSave:
463
+ | { step: StepResult<TOOLS> }
464
+ | { object: GenerateObjectResult<unknown> },
465
+ createPendingMessage?: boolean,
466
+ ) => Promise<void>;
467
+ fail: (reason: string) => Promise<void>;
468
+ getSavedMessages: () => MessageDoc[];
469
+ }> {
470
+ const { threadId, ...opts } = { ...this.options, ...options };
471
+ const context = await this._saveMessagesAndFetchContext(ctx, args, {
472
+ userId: options?.userId,
473
+ threadId: options?.threadId,
474
+ ...opts,
418
475
  });
476
+ let pendingMessageId = context.pendingMessageId;
477
+ // TODO: extract pending message if one exists
478
+ const { args: aiArgs, promptMessageId, order, stepOrder, userId } = context;
479
+ const messages = context.savedMessages ?? [];
480
+ if (pendingMessageId) {
481
+ if (!aiArgs._internal?.generateId) {
482
+ aiArgs._internal = {
483
+ ...aiArgs._internal,
484
+ generateId: () => pendingMessageId ?? crypto.randomUUID(),
485
+ };
486
+ }
487
+ }
488
+ const toolCtx = {
489
+ ...(ctx as UserActionCtx & CustomCtx),
490
+ userId,
491
+ threadId,
492
+ promptMessageId,
493
+ agent: this,
494
+ } satisfies ToolCtx;
495
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
496
+ const tools = wrapTools(toolCtx, args.tools ?? this.options.tools) as Tools;
497
+ const saveOutput = opts.storageOptions?.saveMessages !== "none";
498
+ const fail = async (reason: string) => {
499
+ if (threadId && promptMessageId) {
500
+ console.error("RollbackMessage", promptMessageId, reason);
501
+ }
502
+ if (pendingMessageId) {
503
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
504
+ messageId: pendingMessageId,
505
+ result: { status: "failed", error: reason },
506
+ });
507
+ }
508
+ };
509
+ let activeModel = aiArgs.model;
510
+ if (aiArgs.abortSignal) {
511
+ const abortSignal = aiArgs.abortSignal;
512
+ aiArgs.abortSignal.addEventListener(
513
+ "abort",
514
+ async () => {
515
+ await fail(abortSignal.reason ?? "Aborted");
516
+ },
517
+ { once: true },
518
+ );
519
+ }
520
+ return {
521
+ args: {
522
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
523
+ stopWhen: (args as any).stopWhen ?? this.options.stopWhen,
524
+ ...aiArgs,
525
+ tools,
526
+ // abortSignal: abortController.signal,
527
+ },
528
+ order: order ?? 0,
529
+ stepOrder: stepOrder ?? 0,
530
+ userId,
531
+ promptMessageId,
532
+ getSavedMessages: () => messages,
533
+ updateModel: (model: LanguageModel | undefined) => {
534
+ if (model) {
535
+ activeModel = model;
536
+ }
537
+ },
538
+ fail,
539
+ save: async <TOOLS extends ToolSet>(
540
+ toSave:
541
+ | { step: StepResult<TOOLS> }
542
+ | { object: GenerateObjectResult<unknown> },
543
+ createPendingMessage?: boolean,
544
+ ) => {
545
+ if (threadId && promptMessageId && saveOutput) {
546
+ const metadata = {
547
+ // TODO: get up to date one when user selects mid-generation
548
+ model: getModelName(activeModel),
549
+ provider: getProviderName(activeModel),
550
+ };
551
+ const serialized =
552
+ "object" in toSave
553
+ ? await serializeObjectResult(
554
+ ctx,
555
+ this.component,
556
+ toSave.object,
557
+ metadata,
558
+ )
559
+ : await serializeNewMessagesInStep(
560
+ ctx,
561
+ this.component,
562
+ toSave.step,
563
+ metadata,
564
+ );
565
+ const embeddings = await this.generateEmbeddings(
566
+ ctx,
567
+ { userId, threadId },
568
+ serialized.messages.map((m) => m.message),
569
+ );
570
+ if (createPendingMessage) {
571
+ serialized.messages.push({
572
+ message: { role: "assistant", content: [] },
573
+ status: "pending",
574
+ });
575
+ embeddings?.vectors.push(null);
576
+ }
577
+ const saved = await ctx.runMutation(
578
+ this.component.messages.addMessages,
579
+ {
580
+ userId,
581
+ threadId,
582
+ agentName: this.options.name,
583
+ promptMessageId,
584
+ pendingMessageId,
585
+ messages: serialized.messages,
586
+ embeddings,
587
+ failPendingSteps: false,
588
+ },
589
+ );
590
+ const lastMessage = saved.messages.at(-1)!;
591
+ if (createPendingMessage) {
592
+ if (lastMessage.status === "failed") {
593
+ pendingMessageId = undefined;
594
+ messages.push(...saved.messages);
595
+ await fail(
596
+ lastMessage.error ??
597
+ "Aborting - the pending message was marked as failed",
598
+ );
599
+ } else {
600
+ pendingMessageId = lastMessage._id;
601
+ messages.push(...saved.messages.slice(0, -1));
602
+ }
603
+ } else {
604
+ pendingMessageId = undefined;
605
+ messages.push(...saved.messages);
606
+ }
607
+ }
608
+ const output = "object" in toSave ? toSave.object : toSave.step;
609
+ if (this.options.rawRequestResponseHandler) {
610
+ await this.options.rawRequestResponseHandler(ctx, {
611
+ userId,
612
+ threadId,
613
+ agentName: this.options.name,
614
+ request: output.request,
615
+ response: output.response,
616
+ });
617
+ }
618
+ if (opts.usageHandler && output.usage) {
619
+ await opts.usageHandler(ctx, {
620
+ userId,
621
+ threadId,
622
+ agentName: this.options.name,
623
+ model: getModelName(activeModel),
624
+ provider: getProviderName(activeModel),
625
+ usage: output.usage,
626
+ providerMetadata: output.providerMetadata,
627
+ });
628
+ }
629
+ },
630
+ };
419
631
  }
420
632
 
421
633
  /**
@@ -426,7 +638,7 @@ export class Agent<
426
638
  * to a thread (and optionally userId).
427
639
  * @param ctx The context passed from the action function calling this.
428
640
  * @param { userId, threadId }: The user and thread to associate the message with
429
- * @param args The arguments to the generateText function, along with extra controls
641
+ * @param generateTextArgs The arguments to the generateText function, along with extra controls
430
642
  * for the {@link ContextOptions} and {@link StorageOptions}.
431
643
  * @returns The result of the generateText function.
432
644
  */
@@ -436,102 +648,44 @@ export class Agent<
436
648
  OUTPUT_PARTIAL = never,
437
649
  >(
438
650
  ctx: ActionCtx & CustomCtx,
439
- {
440
- userId: argsUserId,
441
- threadId,
442
- tools: threadTools,
443
- ...usageHandler
444
- }: {
445
- userId?: string | null;
446
- threadId?: string;
447
- /**
448
- * The usage handler to use for this thread. Overrides any handler
449
- * set in the agent constructor.
450
- */
451
- usageHandler?: UsageHandler;
452
- /** Note: to get better type inference, pass tools in the next arg */
453
- tools?: ToolSet;
454
- },
455
- args: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL>,
651
+ threadOpts: { userId?: string | null; threadId?: string },
652
+ generateTextArgs: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL>,
456
653
  options?: Options,
457
654
  ): Promise<
458
655
  GenerateTextResult<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT> &
459
656
  GenerationOutputMetadata
460
657
  > {
461
- const opts = { ...this.options, ...options, ...usageHandler };
462
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
463
- userId: argsUserId ?? undefined,
464
- threadId,
465
- ...opts,
466
- });
467
- const { args: aiArgs, messageId, order, userId } = context;
468
- const toolCtx = {
469
- ...(ctx as UserActionCtx & CustomCtx),
470
- userId,
471
- threadId,
472
- messageId,
473
- agent: this,
474
- };
475
- const tools = wrapTools(
476
- toolCtx,
477
- args.tools ?? threadTools ?? this.options.tools,
478
- ) as TOOLS extends undefined ? AgentTools : TOOLS;
479
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
658
+ const { args, promptMessageId, order, ...call } = await this.start(
659
+ ctx,
660
+ generateTextArgs,
661
+ { ...threadOpts, ...options },
662
+ );
663
+
664
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
665
+ const steps: StepResult<Tools>[] = [];
480
666
  try {
481
- const result = (await generateText({
482
- // Can be overridden
483
- maxSteps: this.options.maxSteps,
484
- ...aiArgs,
485
- tools,
667
+ const result = (await generateText<Tools, OUTPUT, OUTPUT_PARTIAL>({
668
+ ...args,
669
+ prepareStep: async (options) => {
670
+ const result = await generateTextArgs.prepareStep?.(options);
671
+ call.updateModel(result?.model ?? options.model);
672
+ return result;
673
+ },
486
674
  onStepFinish: async (step) => {
487
- if (threadId && messageId && saveOutput) {
488
- await this.saveStep(ctx, {
489
- userId,
490
- threadId,
491
- promptMessageId: messageId,
492
- model: aiArgs.model.modelId,
493
- provider: aiArgs.model.provider,
494
- step,
495
- });
496
- }
497
- if (this.options.rawRequestResponseHandler) {
498
- await this.options.rawRequestResponseHandler(ctx, {
499
- userId,
500
- threadId,
501
- agentName: this.options.name,
502
- request: step.request,
503
- response: step.response,
504
- });
505
- }
506
- if (opts.usageHandler && step.usage) {
507
- await opts.usageHandler(ctx, {
508
- userId,
509
- threadId,
510
- agentName: this.options.name,
511
- model: aiArgs.model.modelId,
512
- provider: aiArgs.model.provider,
513
- usage: step.usage,
514
- providerMetadata: step.providerMetadata,
515
- });
516
- }
517
- return args.onStepFinish?.(step);
675
+ steps.push(step);
676
+ await call.save({ step }, await willContinue(steps, args.stopWhen));
677
+ return generateTextArgs.onStepFinish?.(step);
518
678
  },
519
- })) as GenerateTextResult<
520
- TOOLS extends undefined ? AgentTools : TOOLS,
521
- OUTPUT
522
- > &
523
- GenerationOutputMetadata;
524
- result.messageId = messageId;
525
- result.order = order;
526
- return result;
679
+ })) as GenerateTextResult<Tools, OUTPUT>;
680
+ const metadata: GenerationOutputMetadata = {
681
+ promptMessageId,
682
+ order,
683
+ savedMessages: call.getSavedMessages(),
684
+ messageId: promptMessageId,
685
+ };
686
+ return Object.assign(result, metadata);
527
687
  } catch (error) {
528
- if (threadId && messageId) {
529
- console.error("RollbackMessage", messageId);
530
- await ctx.runMutation(this.component.messages.rollbackMessage, {
531
- messageId,
532
- error: (error as Error).message,
533
- });
534
- }
688
+ await call.fail(errorToString(error));
535
689
  throw error;
536
690
  }
537
691
  }
@@ -549,22 +703,16 @@ export class Agent<
549
703
  PARTIAL_OUTPUT = never,
550
704
  >(
551
705
  ctx: ActionCtx & CustomCtx,
552
- {
553
- userId: argsUserId,
554
- threadId,
555
- /** Note: to get better type inference, pass tools in the next arg */
556
- tools: threadTools,
557
- ...usageHandler
558
- }: {
559
- userId?: string | null;
560
- threadId?: string;
561
- usageHandler?: UsageHandler;
562
- tools?: ToolSet;
563
- },
706
+ threadOpts: { userId?: string | null; threadId?: string },
564
707
  /**
565
708
  * The arguments to the streamText function, similar to the ai `streamText` function.
566
709
  */
567
- args: StreamingTextArgs<AgentTools, TOOLS, OUTPUT, PARTIAL_OUTPUT>,
710
+ streamTextArgs: StreamingTextArgs<
711
+ AgentTools,
712
+ TOOLS,
713
+ OUTPUT,
714
+ PARTIAL_OUTPUT
715
+ >,
568
716
  /**
569
717
  * The {@link ContextOptions} and {@link StorageOptions}
570
718
  * options to use for fetching contextual messages and saving input/output messages.
@@ -589,109 +737,80 @@ export class Agent<
589
737
  > &
590
738
  GenerationOutputMetadata
591
739
  > {
592
- const opts = { ...this.options, ...options, ...usageHandler };
593
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
594
- userId: argsUserId ?? undefined,
595
- threadId,
596
- ...opts,
597
- });
598
- const { args: aiArgs, messageId, order, stepOrder, userId } = context;
599
- const toolCtx = {
600
- ...(ctx as UserActionCtx & CustomCtx),
601
- userId,
602
- threadId,
603
- messageId,
604
- agent: this,
605
- };
606
- const tools = wrapTools(
607
- toolCtx,
608
- args.tools ?? threadTools ?? this.options.tools,
609
- ) as TOOLS extends undefined ? AgentTools : TOOLS;
610
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
740
+ const { threadId } = threadOpts;
741
+ const { args, userId, order, stepOrder, promptMessageId, ...call } =
742
+ await this.start(ctx, streamTextArgs, { ...threadOpts, ...options });
743
+
744
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
745
+ const steps: StepResult<Tools>[] = [];
746
+
747
+ const opts = { ...this.options, ...options };
611
748
  const streamer =
612
749
  threadId && opts.saveStreamDeltas
613
750
  ? new DeltaStreamer(this.component, ctx, opts.saveStreamDeltas, {
614
751
  threadId,
615
752
  userId,
616
753
  agentName: this.options.name,
617
- model: aiArgs.model.modelId,
618
- provider: aiArgs.model.provider,
619
- providerOptions: aiArgs.providerOptions,
754
+ model: getModelName(args.model),
755
+ provider: getProviderName(args.model),
756
+ providerOptions: args.providerOptions,
620
757
  order,
621
758
  stepOrder,
622
- abortSignal: aiArgs.abortSignal,
759
+ abortSignal: args.abortSignal,
623
760
  })
624
761
  : undefined;
625
762
 
626
763
  const result = streamText({
627
- // Can be overridden
628
- maxSteps: this.options.maxSteps,
629
- ...aiArgs,
630
- tools,
631
- abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
764
+ ...args,
765
+ abortSignal: streamer?.abortController.signal ?? args.abortSignal,
766
+ // TODO: this is probably why reasoning isn't streaming
632
767
  experimental_transform: mergeTransforms(
633
768
  options?.saveStreamDeltas,
634
- args.experimental_transform,
769
+ streamTextArgs.experimental_transform,
635
770
  ),
636
771
  onChunk: async (event) => {
637
772
  await streamer?.addParts([event.chunk]);
638
773
  // console.log("onChunk", chunk);
639
- return args.onChunk?.(event);
774
+ return streamTextArgs.onChunk?.(event);
640
775
  },
641
776
  onError: async (error) => {
642
777
  console.error("onError", error);
643
- if (threadId && messageId && saveOutput) {
644
- await ctx.runMutation(this.component.messages.rollbackMessage, {
645
- messageId,
646
- error: (error.error as Error).message,
647
- });
778
+ await call.fail(errorToString(error.error));
779
+ await streamer?.fail(errorToString(error.error));
780
+ return streamTextArgs.onError?.(error);
781
+ },
782
+ // onFinish: async (event) => {
783
+ // return streamTextArgs.onFinish?.(event);
784
+ // },
785
+ prepareStep: async (options) => {
786
+ const result = await streamTextArgs.prepareStep?.(options);
787
+ if (result) {
788
+ const model = result.model ?? options.model;
789
+ call.updateModel(model);
790
+ return result;
648
791
  }
649
- // TODO: update the streamer to error state
650
- return args.onError?.(error);
792
+ return undefined;
651
793
  },
652
794
  onStepFinish: async (step) => {
653
- // console.log("onStepFinish", step);
654
- if (threadId && messageId && saveOutput) {
655
- const saved = await this.saveStep(ctx, {
656
- userId,
657
- threadId,
658
- model: aiArgs.model.modelId,
659
- provider: aiArgs.model.provider,
660
- promptMessageId: messageId,
661
- step,
662
- });
663
- await streamer?.finish(saved.messages);
664
- }
665
- if (this.options.rawRequestResponseHandler) {
666
- await this.options.rawRequestResponseHandler(ctx, {
667
- userId,
668
- threadId,
669
- agentName: this.options.name,
670
- request: step.request,
671
- response: step.response,
672
- });
673
- }
674
- if (opts.usageHandler && step.usage) {
675
- await opts.usageHandler(ctx, {
676
- userId,
677
- threadId,
678
- agentName: this.options.name,
679
- model: aiArgs.model.modelId,
680
- provider: aiArgs.model.provider,
681
- usage: step.usage,
682
- providerMetadata: step.providerMetadata,
683
- });
795
+ steps.push(step);
796
+ const createPendingMessage = await willContinue(steps, args.stopWhen);
797
+ await call.save({ step }, createPendingMessage);
798
+ if (!createPendingMessage) {
799
+ await streamer?.finish();
684
800
  }
685
801
  return args.onStepFinish?.(step);
686
802
  },
687
803
  }) as StreamTextResult<
688
804
  TOOLS extends undefined ? AgentTools : TOOLS,
689
805
  PARTIAL_OUTPUT
690
- > &
691
- GenerationOutputMetadata;
692
- result.messageId = messageId;
693
- result.order = order;
694
- return result;
806
+ >;
807
+ const metadata: GenerationOutputMetadata = {
808
+ promptMessageId,
809
+ order,
810
+ savedMessages: call.getSavedMessages(),
811
+ messageId: promptMessageId,
812
+ };
813
+ return Object.assign(result, metadata);
695
814
  }
696
815
 
697
816
  /**
@@ -701,81 +820,45 @@ export class Agent<
701
820
  * Use {@link continueThread} to get a version of this function already scoped
702
821
  * to a thread (and optionally userId).
703
822
  */
704
- async generateObject<T>(
705
- ctx: ActionCtx,
706
- {
707
- userId: argsUserId,
708
- threadId,
709
- ...usageHandler
710
- }: {
711
- userId?: string | null;
712
- threadId?: string;
713
- usageHandler?: UsageHandler;
714
- },
823
+ async generateObject<
824
+ SCHEMA extends ObjectSchema = DefaultObjectSchema,
825
+ OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string
826
+ ? "enum"
827
+ : "object",
828
+ RESULT = OUTPUT extends "array"
829
+ ? Array<InferSchema<SCHEMA>>
830
+ : InferSchema<SCHEMA>,
831
+ >(
832
+ ctx: ActionCtx & CustomCtx,
833
+ threadOpts: { userId?: string | null; threadId?: string },
715
834
  /**
716
835
  * The arguments to the generateObject function, similar to the ai.generateObject function.
717
836
  */
718
- args: OurObjectArgs<T>,
837
+ generateObjectArgs: GenerateObjectArgs<SCHEMA, OUTPUT, RESULT>,
719
838
  /**
720
839
  * The {@link ContextOptions} and {@link StorageOptions}
721
840
  * options to use for fetching contextual messages and saving input/output messages.
722
841
  */
723
842
  options?: Options,
724
- ): Promise<GenerateObjectResult<T> & GenerationOutputMetadata> {
725
- const opts = { ...this.options, ...options, ...usageHandler };
726
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
727
- userId: argsUserId ?? undefined,
728
- threadId,
729
- ...opts,
730
- });
731
- const { args: aiArgs, messageId, order, userId } = context;
732
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
843
+ ): Promise<GenerateObjectResult<RESULT> & GenerationOutputMetadata> {
844
+ const { args, promptMessageId, order, fail, save, getSavedMessages } =
845
+ await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
846
+
733
847
  try {
734
848
  const result = (await generateObject(
735
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
736
- aiArgs as any,
737
- )) as GenerateObjectResult<T> & GenerationOutputMetadata;
849
+ args,
850
+ )) as GenerateObjectResult<RESULT>;
738
851
 
739
- if (threadId && messageId && saveOutput) {
740
- await this.saveObject(ctx, {
741
- threadId,
742
- promptMessageId: messageId,
743
- result,
744
- userId,
745
- model: aiArgs.model.modelId,
746
- provider: aiArgs.model.provider,
747
- });
748
- }
749
- result.messageId = messageId;
750
- result.order = order;
751
- if (this.options.rawRequestResponseHandler) {
752
- await this.options.rawRequestResponseHandler(ctx, {
753
- userId,
754
- threadId,
755
- agentName: this.options.name,
756
- request: result.request,
757
- response: result.response,
758
- });
759
- }
760
- if (opts.usageHandler && result.usage) {
761
- await opts.usageHandler(ctx, {
762
- userId,
763
- threadId,
764
- agentName: this.options.name,
765
- model: aiArgs.model.modelId,
766
- provider: aiArgs.model.provider,
767
- usage: result.usage,
768
- providerMetadata: result.providerMetadata,
769
- });
770
- }
771
- return result;
852
+ await save({ object: result });
853
+ const metadata: GenerationOutputMetadata = {
854
+ promptMessageId,
855
+ order,
856
+ savedMessages: getSavedMessages(),
857
+ messageId: promptMessageId,
858
+ };
859
+ return Object.assign(result, metadata);
772
860
  } catch (error) {
773
- if (threadId && messageId) {
774
- await ctx.runMutation(this.component.messages.rollbackMessage, {
775
- messageId,
776
- error: (error as Error).message,
777
- });
778
- }
861
+ await fail(errorToString(error));
779
862
  throw error;
780
863
  }
781
864
  }
@@ -787,96 +870,83 @@ export class Agent<
787
870
  * Use {@link continueThread} to get a version of this function already scoped
788
871
  * to a thread (and optionally userId).
789
872
  */
790
- async streamObject<T>(
791
- ctx: ActionCtx,
792
- {
793
- userId: argsUserId,
794
- threadId,
795
- ...usageHandler
796
- }: {
797
- userId?: string | null;
798
- threadId?: string;
799
- usageHandler?: UsageHandler;
800
- },
873
+ async streamObject<
874
+ SCHEMA extends ObjectSchema = DefaultObjectSchema,
875
+ OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string
876
+ ? "enum"
877
+ : "object",
878
+ RESULT = OUTPUT extends "array"
879
+ ? Array<InferSchema<SCHEMA>>
880
+ : InferSchema<SCHEMA>,
881
+ >(
882
+ ctx: ActionCtx & CustomCtx,
883
+ threadOpts: { userId?: string | null; threadId?: string },
801
884
  /**
802
885
  * The arguments to the streamObject function, similar to the ai `streamObject` function.
803
886
  */
804
- args: OurStreamObjectArgs<T>,
887
+ streamObjectArgs: StreamObjectArgs<SCHEMA, OUTPUT, RESULT> & {
888
+ /**
889
+ * If provided, this message will be used as the "prompt" for the LLM call,
890
+ * instead of the prompt or messages.
891
+ * This is useful if you want to first save a user message, then use it as
892
+ * the prompt for the LLM call in another call.
893
+ */
894
+ promptMessageId?: string;
895
+ /**
896
+ * The model to use for the LLM calls. This will override the model specified
897
+ * in the Agent constructor.
898
+ */
899
+ model?: LanguageModel;
900
+ /**
901
+ * The tools to use for the tool calls. This will override tools specified
902
+ * in the Agent constructor or createThread / continueThread.
903
+ */
904
+ },
805
905
  /**
806
906
  * The {@link ContextOptions} and {@link StorageOptions}
807
907
  * options to use for fetching contextual messages and saving input/output messages.
808
908
  */
809
909
  options?: Options,
810
910
  ): Promise<
811
- StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
911
+ ReturnType<typeof streamObject<SCHEMA, OUTPUT, RESULT>> &
912
+ GenerationOutputMetadata
812
913
  > {
813
- // TODO: unify all this shared code between all the generate* and stream* functions
814
- const opts = { ...this.options, ...options, ...usageHandler };
815
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
816
- userId: argsUserId ?? undefined,
817
- threadId,
818
- ...opts,
819
- });
820
- const { args: aiArgs, messageId, order, userId } = context;
821
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
822
- const stream = streamObject<T>({
914
+ const { args, promptMessageId, order, fail, save, getSavedMessages } =
915
+ await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
916
+
917
+ const stream = streamObject<SCHEMA, OUTPUT, RESULT>({
823
918
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
824
- ...(aiArgs as any),
919
+ ...(args as any),
825
920
  onError: async (error) => {
826
- console.error("onError", error);
921
+ console.error(" streamObject onError", error);
922
+ // TODO: content that we have so far
923
+ // content: stream.fullStream.
924
+ await fail(errorToString(error.error));
827
925
  return args.onError?.(error);
828
926
  },
829
927
  onFinish: async (result) => {
830
- if (threadId && messageId && saveOutput) {
831
- await this.saveObject(ctx, {
832
- userId,
833
- threadId,
834
- promptMessageId: messageId,
835
- result: {
836
- object: result.object,
837
- finishReason: "stop",
838
- usage: result.usage,
839
- warnings: result.warnings,
840
- request: await stream.request,
841
- response: result.response,
842
- providerMetadata: result.providerMetadata,
843
- experimental_providerMetadata:
844
- result.experimental_providerMetadata,
845
- logprobs: undefined,
846
- toJsonResponse: stream.toTextStreamResponse,
847
- },
848
- model: aiArgs.model.modelId,
849
- provider: aiArgs.model.provider,
850
- });
851
- }
852
- if (opts.usageHandler && result.usage) {
853
- await opts.usageHandler(ctx, {
854
- userId,
855
- threadId,
856
- agentName: this.options.name,
857
- model: aiArgs.model.modelId,
858
- provider: aiArgs.model.provider,
928
+ await save({
929
+ object: {
930
+ object: result.object,
931
+ finishReason: result.error ? "error" : "stop",
859
932
  usage: result.usage,
860
- providerMetadata: result.providerMetadata,
861
- });
862
- }
863
- if (this.options.rawRequestResponseHandler) {
864
- await this.options.rawRequestResponseHandler(ctx, {
865
- userId,
866
- threadId,
867
- agentName: this.options.name,
933
+ warnings: result.warnings,
868
934
  request: await stream.request,
869
935
  response: result.response,
870
- });
871
- }
872
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
873
- return args.onFinish?.(result as any);
936
+ providerMetadata: result.providerMetadata,
937
+ toJsonResponse: stream.toTextStreamResponse,
938
+ },
939
+ });
940
+ return args.onFinish?.(result);
874
941
  },
875
- }) as StreamObjectResult<DeepPartial<T>, T, never> &
876
- GenerationOutputMetadata;
877
- stream.messageId = messageId;
878
- stream.order = order;
879
- return stream;
942
+ });
943
+ const metadata: GenerationOutputMetadata = {
944
+ promptMessageId,
945
+ order,
946
+ savedMessages: getSavedMessages(),
947
+ messageId: promptMessageId,
948
+ };
949
+ return Object.assign(stream, metadata);
880
950
  }
881
951
 
882
952
  /**
@@ -898,14 +968,11 @@ export class Agent<
898
968
  skipEmbeddings?: boolean;
899
969
  },
900
970
  ) {
901
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
971
+ const { messages } = await this.saveMessages(ctx, {
902
972
  threadId: args.threadId,
903
973
  userId: args.userId,
904
974
  embeddings: args.embedding
905
- ? {
906
- model: args.embedding.model,
907
- vectors: [args.embedding.vector],
908
- }
975
+ ? { model: args.embedding.model, vectors: [args.embedding.vector] }
909
976
  : undefined,
910
977
  messages:
911
978
  args.prompt !== undefined
@@ -913,8 +980,10 @@ export class Agent<
913
980
  : [args.message],
914
981
  metadata: args.metadata ? [args.metadata] : undefined,
915
982
  skipEmbeddings: args.skipEmbeddings,
983
+ pendingMessageId: args.pendingMessageId,
916
984
  });
917
- return { messageId: lastMessageId, message: messages.at(-1)! };
985
+ const message = messages.at(-1)!;
986
+ return { messageId: message._id, message };
918
987
  }
919
988
 
920
989
  /**
@@ -936,20 +1005,12 @@ export class Agent<
936
1005
  */
937
1006
  skipEmbeddings?: boolean;
938
1007
  },
939
- ): Promise<{
940
- lastMessageId: string;
941
- messages: MessageDoc[];
942
- }> {
943
- let embeddings:
944
- | {
945
- vectors: (number[] | null)[];
946
- model: string;
947
- }
948
- | undefined;
1008
+ ): Promise<{ messages: MessageDoc[] }> {
1009
+ let embeddings: { vectors: (number[] | null)[]; model: string } | undefined;
949
1010
  const { skipEmbeddings, ...rest } = args;
950
1011
  if (args.embeddings) {
951
1012
  embeddings = args.embeddings;
952
- } else if (!skipEmbeddings && this.options.textEmbedding) {
1013
+ } else if (!skipEmbeddings && this.options.textEmbeddingModel) {
953
1014
  if (!("runAction" in ctx)) {
954
1015
  console.warn(
955
1016
  "You're trying to save messages and generate embeddings, but you're in a mutation. " +
@@ -967,10 +1028,7 @@ export class Agent<
967
1028
  } else {
968
1029
  embeddings = await this.generateEmbeddings(
969
1030
  ctx,
970
- {
971
- userId: args.userId ?? undefined,
972
- threadId: args.threadId,
973
- },
1031
+ { userId: args.userId ?? undefined, threadId: args.threadId },
974
1032
  args.messages,
975
1033
  );
976
1034
  }
@@ -1037,7 +1095,7 @@ export class Agent<
1037
1095
  args: {
1038
1096
  userId: string | undefined;
1039
1097
  threadId: string | undefined;
1040
- messages: CoreMessage[];
1098
+ messages: (ModelMessage | Message)[];
1041
1099
  /**
1042
1100
  * If provided, it will search for messages up to and including this message.
1043
1101
  * Note: if this is far in the past, text and vector search results may be more
@@ -1058,8 +1116,8 @@ export class Agent<
1058
1116
  getEmbedding: async (text) => {
1059
1117
  assert("runAction" in ctx);
1060
1118
  assert(
1061
- this.options.textEmbedding,
1062
- "A textEmbedding model is required to be set on the Agent that you're doing vector search with",
1119
+ this.options.textEmbeddingModel,
1120
+ "A textEmbeddingModel is required to be set on the Agent that you're doing vector search with",
1063
1121
  );
1064
1122
  return {
1065
1123
  embedding: (
@@ -1069,7 +1127,7 @@ export class Agent<
1069
1127
  values: [text],
1070
1128
  })
1071
1129
  ).embeddings[0],
1072
- embeddingModel: this.options.textEmbedding.modelId,
1130
+ textEmbeddingModel: this.options.textEmbeddingModel,
1073
1131
  };
1074
1132
  },
1075
1133
  });
@@ -1121,13 +1179,10 @@ export class Agent<
1121
1179
  {
1122
1180
  userId,
1123
1181
  threadId,
1124
- }: {
1125
- userId: string | undefined;
1126
- threadId: string | undefined;
1127
- },
1128
- messages: CoreMessage[],
1182
+ }: { userId: string | undefined; threadId: string | undefined },
1183
+ messages: (ModelMessage | Message)[],
1129
1184
  ) {
1130
- if (!this.options.textEmbedding) {
1185
+ if (!this.options.textEmbeddingModel) {
1131
1186
  return undefined;
1132
1187
  }
1133
1188
  let embeddings:
@@ -1145,13 +1200,13 @@ export class Agent<
1145
1200
  if (textIndexes.length === 0) {
1146
1201
  return undefined;
1147
1202
  }
1203
+ const values = messageTexts.filter((t): t is string => !!t);
1148
1204
  // Then embed those messages.
1149
1205
  const textEmbeddings = await this.doEmbed(ctx, {
1150
1206
  userId,
1151
1207
  threadId,
1152
- values: messageTexts as string[],
1208
+ values,
1153
1209
  });
1154
- // TODO: record usage of embeddings
1155
1210
  // Then assemble the embeddings into a single array with nulls for the messages without text.
1156
1211
  const embeddingsOrNull = Array(messages.length).fill(null);
1157
1212
  textIndexes.forEach((i, j) => {
@@ -1160,11 +1215,8 @@ export class Agent<
1160
1215
  if (textEmbeddings.embeddings.length > 0) {
1161
1216
  const dimension = textEmbeddings.embeddings[0].length;
1162
1217
  validateVectorDimension(dimension);
1163
- embeddings = {
1164
- vectors: embeddingsOrNull,
1165
- dimension,
1166
- model: this.options.textEmbedding.modelId,
1167
- };
1218
+ const model = getModelName(this.options.textEmbeddingModel);
1219
+ embeddings = { vectors: embeddingsOrNull, dimension, model };
1168
1220
  }
1169
1221
  return embeddings;
1170
1222
  }
@@ -1178,9 +1230,7 @@ export class Agent<
1178
1230
  */
1179
1231
  async generateAndSaveEmbeddings(
1180
1232
  ctx: RunActionCtx,
1181
- args: {
1182
- messageIds: string[];
1183
- },
1233
+ args: { messageIds: string[] },
1184
1234
  ) {
1185
1235
  const messages = (
1186
1236
  await ctx.runQuery(this.component.messages.getMessagesByIds, {
@@ -1218,12 +1268,12 @@ export class Agent<
1218
1268
  userId: messagesMissingEmbeddings[0]!.userId,
1219
1269
  threadId: messagesMissingEmbeddings[0]!.threadId,
1220
1270
  },
1221
- messagesMissingEmbeddings.map((m) => m!.message!),
1271
+ messagesMissingEmbeddings.map((m) => deserializeMessage(m!.message!)),
1222
1272
  );
1223
1273
  if (!embeddings) {
1224
- if (!this.options.textEmbedding) {
1274
+ if (!this.options.textEmbeddingModel) {
1225
1275
  throw new Error(
1226
- "No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.",
1276
+ "No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.",
1227
1277
  );
1228
1278
  }
1229
1279
  throw new Error(
@@ -1278,14 +1328,14 @@ export class Agent<
1278
1328
  */
1279
1329
  provider?: string;
1280
1330
  },
1281
- ): Promise<{ messages: MessageDoc[]; pending?: MessageDoc }> {
1282
- const messages = await serializeNewMessagesInStep(
1331
+ ): Promise<{ messages: MessageDoc[] }> {
1332
+ const { messages } = await serializeNewMessagesInStep(
1283
1333
  ctx,
1284
1334
  this.component,
1285
1335
  args.step,
1286
1336
  {
1287
- provider: args.provider ?? this.options.chat.provider,
1288
- model: args.model ?? this.options.chat.modelId,
1337
+ provider: args.provider ?? getProviderName(this.options.languageModel),
1338
+ model: args.model ?? getModelName(this.options.languageModel),
1289
1339
  },
1290
1340
  );
1291
1341
  const embeddings = await this.generateEmbeddings(
@@ -1293,7 +1343,7 @@ export class Agent<
1293
1343
  { userId: args.userId, threadId: args.threadId },
1294
1344
  messages.map((m) => m.message),
1295
1345
  );
1296
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
1346
+ return ctx.runMutation(this.component.messages.addMessages, {
1297
1347
  userId: args.userId,
1298
1348
  threadId: args.threadId,
1299
1349
  agentName: this.options.name,
@@ -1302,7 +1352,6 @@ export class Agent<
1302
1352
  embeddings,
1303
1353
  failPendingSteps: false,
1304
1354
  });
1305
- return saved;
1306
1355
  }
1307
1356
 
1308
1357
  /**
@@ -1323,14 +1372,20 @@ export class Agent<
1323
1372
  result: GenerateObjectResult<unknown>;
1324
1373
  metadata?: Omit<MessageWithMetadata, "message">;
1325
1374
  },
1326
- ): Promise<void> {
1375
+ ): Promise<{ messages: MessageDoc[] }> {
1327
1376
  const { messages } = await serializeObjectResult(
1328
1377
  ctx,
1329
1378
  this.component,
1330
1379
  args.result,
1331
1380
  {
1332
- model: args.model ?? this.options.chat.modelId,
1333
- provider: args.provider ?? this.options.chat.provider,
1381
+ model:
1382
+ args.model ??
1383
+ args.metadata?.model ??
1384
+ getModelName(this.options.languageModel),
1385
+ provider:
1386
+ args.provider ??
1387
+ args.metadata?.provider ??
1388
+ getProviderName(this.options.languageModel),
1334
1389
  },
1335
1390
  );
1336
1391
  const embeddings = await this.generateEmbeddings(
@@ -1339,7 +1394,7 @@ export class Agent<
1339
1394
  messages.map((m) => m.message),
1340
1395
  );
1341
1396
 
1342
- await ctx.runMutation(this.component.messages.addMessages, {
1397
+ return ctx.runMutation(this.component.messages.addMessages, {
1343
1398
  userId: args.userId,
1344
1399
  threadId: args.threadId,
1345
1400
  promptMessageId: args.promptMessageId,
@@ -1347,7 +1402,6 @@ export class Agent<
1347
1402
  messages,
1348
1403
  embeddings,
1349
1404
  agentName: this.options.name,
1350
- pending: false,
1351
1405
  });
1352
1406
  }
1353
1407
 
@@ -1359,25 +1413,17 @@ export class Agent<
1359
1413
  * @param args What message to save. Generally the parent message sent into
1360
1414
  * the generateText call.
1361
1415
  */
1362
- async completeMessage(
1416
+ async finalizeMessage(
1363
1417
  ctx: RunMutationCtx,
1364
1418
  args: {
1365
- threadId: string;
1366
1419
  messageId: string;
1367
- result: { kind: "error"; error: string } | { kind: "success" };
1420
+ result: { status: "failed"; error: string } | { status: "success" };
1368
1421
  },
1369
1422
  ): Promise<void> {
1370
- const result = args.result;
1371
- if (result.kind === "success") {
1372
- await ctx.runMutation(this.component.messages.commitMessage, {
1373
- messageId: args.messageId,
1374
- });
1375
- } else {
1376
- await ctx.runMutation(this.component.messages.rollbackMessage, {
1377
- messageId: args.messageId,
1378
- error: result.error,
1379
- });
1380
- }
1423
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
1424
+ messageId: args.messageId,
1425
+ result: args.result,
1426
+ });
1381
1427
  }
1382
1428
 
1383
1429
  /**
@@ -1392,7 +1438,7 @@ export class Agent<
1392
1438
  messageId: string;
1393
1439
  patch: {
1394
1440
  /** The message to replace the existing message. */
1395
- message: CoreMessage & { id?: string };
1441
+ message: ModelMessage | Message;
1396
1442
  /** The status to set on the message. */
1397
1443
  status: "success" | "error";
1398
1444
  /** The error message to set on the message. */
@@ -1437,9 +1483,7 @@ export class Agent<
1437
1483
  */
1438
1484
  async deleteMessages(
1439
1485
  ctx: RunMutationCtx,
1440
- args: {
1441
- messageIds: string[];
1442
- },
1486
+ args: { messageIds: string[] },
1443
1487
  ): Promise<void> {
1444
1488
  await ctx.runMutation(this.component.messages.deleteByIds, args);
1445
1489
  }
@@ -1452,9 +1496,7 @@ export class Agent<
1452
1496
  */
1453
1497
  async deleteMessage(
1454
1498
  ctx: RunMutationCtx,
1455
- args: {
1456
- messageId: string;
1457
- },
1499
+ args: { messageId: string },
1458
1500
  ): Promise<void> {
1459
1501
  await ctx.runMutation(this.component.messages.deleteByIds, {
1460
1502
  messageIds: [args.messageId],
@@ -1526,10 +1568,7 @@ export class Agent<
1526
1568
  */
1527
1569
  async deleteThreadAsync(
1528
1570
  ctx: RunMutationCtx,
1529
- args: {
1530
- threadId: string;
1531
- pageSize?: number;
1532
- },
1571
+ args: { threadId: string; pageSize?: number },
1533
1572
  ): Promise<void> {
1534
1573
  await ctx.runMutation(this.component.threads.deleteAllForThreadIdAsync, {
1535
1574
  threadId: args.threadId,
@@ -1546,10 +1585,7 @@ export class Agent<
1546
1585
  */
1547
1586
  async deleteThreadSync(
1548
1587
  ctx: RunActionCtx,
1549
- args: {
1550
- threadId: string;
1551
- pageSize?: number;
1552
- },
1588
+ args: { threadId: string; pageSize?: number },
1553
1589
  ): Promise<void> {
1554
1590
  await ctx.runAction(this.component.threads.deleteAllForThreadIdSync, {
1555
1591
  threadId: args.threadId,
@@ -1559,13 +1595,12 @@ export class Agent<
1559
1595
 
1560
1596
  async _saveMessagesAndFetchContext<
1561
1597
  T extends {
1562
- id?: string;
1563
- prompt?: string;
1564
- messages?: CoreMessage[] | AIMessageWithoutId[];
1598
+ prompt?: string | (ModelMessage | Message)[];
1599
+ messages?: (ModelMessage | Message)[];
1565
1600
  system?: string;
1566
1601
  promptMessageId?: string;
1567
- model?: LanguageModelV1;
1568
- maxRetries?: number;
1602
+ pendingMessageId?: string;
1603
+ model?: LanguageModel;
1569
1604
  },
1570
1605
  >(
1571
1606
  ctx: RunActionCtx,
@@ -1576,23 +1611,32 @@ export class Agent<
1576
1611
  contextOptions,
1577
1612
  storageOptions,
1578
1613
  }: {
1579
- userId: string | undefined;
1614
+ userId: string | null | undefined;
1580
1615
  threadId: string | undefined;
1581
1616
  } & Options,
1582
1617
  ): Promise<{
1583
- args: T & { model: LanguageModelV1 };
1618
+ args: Extract<T, { model: LanguageModel; messages: ModelMessage[] }> &
1619
+ CallSettings;
1584
1620
  userId: string | undefined;
1585
- messageId: string | undefined;
1621
+ promptMessageId: string | undefined;
1622
+ pendingMessageId: string | undefined;
1586
1623
  order: number | undefined;
1587
1624
  stepOrder: number | undefined;
1625
+ savedMessages: MessageDoc[] | undefined;
1588
1626
  }> {
1589
1627
  // If only a promptMessageId is provided, this will be empty.
1590
- const messages = promptOrMessagesToCoreMessages(args);
1628
+ const messages = args.messages ?? [];
1629
+ const prompt: (ModelMessage | Message)[] = !args.prompt
1630
+ ? []
1631
+ : Array.isArray(args.prompt)
1632
+ ? args.prompt
1633
+ : [{ role: "user", content: args.prompt }];
1591
1634
  const userId =
1592
1635
  argsUserId ??
1593
1636
  (threadId &&
1594
1637
  (await ctx.runQuery(this.component.threads.getThread, { threadId }))
1595
- ?.userId);
1638
+ ?.userId) ??
1639
+ undefined;
1596
1640
  // If only a messageId is provided, this will add that message to the end.
1597
1641
  const contextMessages = await this.fetchContextMessages(ctx, {
1598
1642
  userId,
@@ -1603,74 +1647,111 @@ export class Agent<
1603
1647
  });
1604
1648
  // If it was a promptMessageId, pop it off context messages
1605
1649
  // and add to the end of messages.
1606
- // TODO: slice it from the prompt message, to append all of them
1650
+ const promptMessageIndex = args.promptMessageId
1651
+ ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
1652
+ : -1;
1607
1653
  const promptMessage =
1608
- !!args.promptMessageId &&
1609
- contextMessages.at(-1)?._id === args.promptMessageId
1610
- ? contextMessages.pop()
1654
+ promptMessageIndex !== -1
1655
+ ? contextMessages.splice(promptMessageIndex, 1)[0]
1611
1656
  : 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
- }
1617
- let messageId = promptMessage?._id;
1657
+
1658
+ let promptMessageId = promptMessage?._id;
1618
1659
  let order = promptMessage?.order;
1619
1660
  let stepOrder = promptMessage?.stepOrder;
1620
- if (
1621
- threadId &&
1622
- messages.length &&
1623
- storageOptions?.saveMessages !== "none" &&
1624
- // If it was a promptMessageId, we don't want to save it again.
1625
- (!args.promptMessageId || storageOptions?.saveMessages === "all")
1626
- ) {
1627
- const saveAll = storageOptions?.saveMessages === "all";
1628
- const coreMessages = saveAll ? messages : messages.slice(-1);
1629
- const saved = await this.saveMessages(ctx, {
1630
- threadId,
1631
- userId,
1632
- messages: coreMessages,
1633
- metadata: coreMessages.map((_, i) =>
1634
- i === coreMessages.length - 1 ? { id: args.id } : {},
1635
- ),
1636
- failPendingSteps: true,
1637
- });
1638
- messageId = saved.lastMessageId;
1639
- order = saved.messages.at(-1)?.order;
1640
- stepOrder = saved.messages.at(-1)?.stepOrder;
1661
+ let savedMessages = undefined;
1662
+ let pendingMessageId = undefined;
1663
+ if (threadId && storageOptions?.saveMessages !== "none") {
1664
+ let saved: { messages: MessageDoc[] };
1665
+ if (
1666
+ messages.length + prompt.length &&
1667
+ // If it was a promptMessageId, we don't want to save it again.
1668
+ (!args.promptMessageId || storageOptions?.saveMessages === "all")
1669
+ ) {
1670
+ const saveAll = storageOptions?.saveMessages === "all";
1671
+ const coreMessages = [...messages, ...prompt];
1672
+ const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
1673
+ const metadata = Array.from({ length: toSave.length }, () => ({}));
1674
+ saved = await this.saveMessages(ctx, {
1675
+ threadId,
1676
+ userId,
1677
+ messages: [...toSave, { role: "assistant", content: [] }],
1678
+ metadata: [...metadata, { status: "pending" }],
1679
+ failPendingSteps: true,
1680
+ pendingMessageId: args.pendingMessageId,
1681
+ });
1682
+ promptMessageId = saved.messages.at(-2)!._id;
1683
+ } else {
1684
+ saved = await this.saveMessages(ctx, {
1685
+ threadId,
1686
+ userId,
1687
+ messages: [{ role: "assistant", content: [] }],
1688
+ metadata: [{ status: "pending" }],
1689
+ failPendingSteps: true,
1690
+ pendingMessageId: args.pendingMessageId,
1691
+ });
1692
+ }
1693
+ pendingMessageId = saved.messages.at(-1)!._id;
1694
+ order = saved.messages.at(-1)!.order;
1695
+ stepOrder = saved.messages.at(-1)!.stepOrder;
1696
+ // Don't return the pending message
1697
+ savedMessages = saved.messages.slice(0, -1);
1641
1698
  }
1699
+
1642
1700
  if (promptMessage?.message) {
1643
- // Add the message after saving the messages, so it's not saved again.
1644
- messages.push(deserializeMessage(promptMessage.message));
1701
+ if (!args.prompt) {
1702
+ // If they override the prompt, we skip the existing prompt message.
1703
+ messages.push(deserializeMessage(promptMessage.message));
1704
+ }
1645
1705
  // Lazily generate embeddings for the prompt message, if it doesn't have
1646
1706
  // embeddings yet. This can happen if the message was saved in a mutation
1647
1707
  // where the LLM is not available.
1648
- if (!promptMessage.embeddingId && this.options.textEmbedding) {
1708
+ if (!promptMessage.embeddingId && this.options.textEmbeddingModel) {
1649
1709
  await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
1650
1710
  }
1651
1711
  }
1652
1712
 
1713
+ const prePrompt = contextMessages.map((m) =>
1714
+ deserializeMessage(m.message!),
1715
+ );
1716
+ let existingResponses: ModelMessage[] = [];
1717
+ if (promptMessageIndex !== -1) {
1718
+ // pull any messages that already responded to the prompt off
1719
+ // and add them after the prompt
1720
+ existingResponses = prePrompt.splice(promptMessageIndex);
1721
+ }
1722
+
1653
1723
  let processedMessages = [
1654
- ...contextMessages.map((m) => deserializeMessage(m.message!)),
1724
+ ...prePrompt,
1655
1725
  ...messages,
1726
+ ...prompt,
1727
+ ...existingResponses,
1656
1728
  ];
1729
+ if (promptMessageIndex === -1) {
1730
+ processedMessages.push(...prompt);
1731
+ } else {
1732
+ // We add the prompt where the prompt message was
1733
+ processedMessages.splice(promptMessageIndex, 0, ...prompt);
1734
+ }
1657
1735
 
1658
1736
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
1659
1737
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
1660
- processedMessages = await this._inlineMessagesFiles(processedMessages);
1738
+ processedMessages = await inlineMessagesFiles(processedMessages);
1661
1739
  }
1662
1740
 
1663
1741
  const { prompt: _, model, ...rest } = args;
1664
1742
  return {
1665
1743
  args: {
1744
+ ...this.options.callSettings,
1666
1745
  ...rest,
1667
- maxRetries: args.maxRetries ?? this.options.maxRetries,
1668
- model: model ?? this.options.chat,
1746
+ model: model ?? this.options.languageModel,
1669
1747
  system: args.system ?? this.options.instructions,
1670
1748
  messages: processedMessages,
1671
- } as T & { model: LanguageModelV1 },
1749
+ } as Extract<T, { model: LanguageModel; messages: ModelMessage[] }> &
1750
+ CallSettings,
1672
1751
  userId,
1673
- messageId,
1752
+ promptMessageId,
1753
+ pendingMessageId,
1754
+ savedMessages,
1674
1755
  order,
1675
1756
  stepOrder,
1676
1757
  };
@@ -1686,29 +1767,29 @@ export class Agent<
1686
1767
  headers?: Record<string, string>;
1687
1768
  },
1688
1769
  ): Promise<{ embeddings: number[][] }> {
1689
- const embeddingModel = this.options.textEmbedding;
1770
+ const embeddingModel = this.options.textEmbeddingModel;
1690
1771
  assert(
1691
1772
  embeddingModel,
1692
- "a textEmbedding model is required to be set on the Agent that you're doing vector search with",
1773
+ "a textEmbeddingModel is required to be set on the Agent that you're doing vector search with",
1693
1774
  );
1694
1775
  const result = await embedMany({
1776
+ ...this.options.callSettings,
1695
1777
  model: embeddingModel,
1696
1778
  values: options.values,
1697
1779
  abortSignal: options.abortSignal,
1698
1780
  headers: options.headers,
1699
- maxRetries: this.options.maxRetries,
1700
1781
  });
1701
1782
  if (this.options.usageHandler && result.usage) {
1702
1783
  await this.options.usageHandler(ctx, {
1703
1784
  userId: options.userId,
1704
1785
  threadId: options.threadId,
1705
1786
  agentName: this.options.name,
1706
- model: embeddingModel.modelId,
1707
- provider: embeddingModel.provider,
1787
+ model: getModelName(embeddingModel),
1788
+ provider: getProviderName(embeddingModel),
1708
1789
  providerMetadata: undefined,
1709
1790
  usage: {
1710
- promptTokens: result.usage.tokens,
1711
- completionTokens: 0,
1791
+ inputTokens: result.usage.tokens,
1792
+ outputTokens: 0,
1712
1793
  totalTokens: result.usage.tokens,
1713
1794
  },
1714
1795
  });
@@ -1716,95 +1797,6 @@ export class Agent<
1716
1797
  return { embeddings: result.embeddings };
1717
1798
  }
1718
1799
 
1719
- /**
1720
- * Process messages to inline file and image URLs that point to localhost
1721
- * by converting them to base64. This solves the problem of LLMs not being
1722
- * able to access localhost URLs.
1723
- */
1724
- private async _inlineMessagesFiles(
1725
- messages: CoreMessage[],
1726
- ): Promise<CoreMessage[]> {
1727
- // Process each message to convert localhost URLs to base64
1728
- return Promise.all(
1729
- messages.map(async (message): Promise<CoreMessage> => {
1730
- if (
1731
- (message.role !== "user" && message.role !== "assistant") ||
1732
- typeof message.content === "string" ||
1733
- !Array.isArray(message.content)
1734
- ) {
1735
- return message;
1736
- }
1737
-
1738
- const processedContent = await Promise.all(
1739
- message.content.map(async (part) => {
1740
- if (part.type === "image" && part.image instanceof URL) {
1741
- assert(
1742
- message.role === "user",
1743
- "Images can only be in user messages",
1744
- );
1745
- if (this._isLocalhostUrl(part.image)) {
1746
- const imageData = await this._downloadFile(part.image);
1747
- return {
1748
- ...part,
1749
- image: imageData,
1750
- } as ImagePart;
1751
- }
1752
- }
1753
-
1754
- // Handle file parts
1755
- if (part.type === "file" && part.data instanceof URL) {
1756
- if (this._isLocalhostUrl(part.data)) {
1757
- const fileData = await this._downloadFile(part.data);
1758
- return {
1759
- ...part,
1760
- data: fileData,
1761
- } as FilePart;
1762
- }
1763
- }
1764
-
1765
- return part;
1766
- }),
1767
- );
1768
- if (message.role === "user") {
1769
- return {
1770
- ...message,
1771
- content: processedContent as UserContent,
1772
- };
1773
- } else {
1774
- return {
1775
- ...message,
1776
- content: processedContent as AssistantContent,
1777
- };
1778
- }
1779
- }),
1780
- );
1781
- }
1782
-
1783
- /**
1784
- * Check if a URL points to localhost
1785
- */
1786
- private _isLocalhostUrl(url: URL): boolean {
1787
- return (
1788
- url.hostname === "localhost" ||
1789
- url.hostname === "127.0.0.1" ||
1790
- url.hostname === "::1" ||
1791
- url.hostname === "0.0.0.0"
1792
- );
1793
- }
1794
-
1795
- /**
1796
- * Download a file from a URL
1797
- */
1798
- private async _downloadFile(url: URL): Promise<ArrayBuffer> {
1799
- // Fetch the file
1800
- const response = await fetch(url);
1801
- if (!response.ok) {
1802
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
1803
- }
1804
-
1805
- return await response.arrayBuffer();
1806
- }
1807
-
1808
1800
  /**
1809
1801
  * WORKFLOW UTILITIES
1810
1802
  */
@@ -1845,26 +1837,10 @@ export class Agent<
1845
1837
  * Create an action out of this agent so you can call it from workflows or other actions
1846
1838
  * without a wrapping function.
1847
1839
  * @param spec Configuration for the agent acting as an action, including
1848
- * {@link ContextOptions}, {@link StorageOptions}, and maxSteps.
1840
+ * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1849
1841
  */
1850
1842
  asTextAction<DataModel extends GenericDataModel>(
1851
- spec?: {
1852
- /**
1853
- * The maximum number of steps to take in this action.
1854
- * Defaults to the {@link Agent.maxSteps} option.
1855
- */
1856
- maxSteps?: number;
1857
- /**
1858
- * The {@link ContextOptions} to use for fetching contextual messages and
1859
- * saving input/output messages.
1860
- * Defaults to the {@link Agent.contextOptions} option.
1861
- */
1862
- contextOptions?: ContextOptions;
1863
- /**
1864
- * The {@link StorageOptions} to use for saving input/output messages.
1865
- * Defaults to the {@link Agent.storageOptions} option.
1866
- */
1867
- storageOptions?: StorageOptions;
1843
+ spec: MaybeCustomCtx<CustomCtx, DataModel, AgentTools> & {
1868
1844
  /**
1869
1845
  * Whether to stream the text.
1870
1846
  * If false, it will generate the text in a single call. (default)
@@ -1873,49 +1849,36 @@ export class Agent<
1873
1849
  * defaults if you pass true.
1874
1850
  */
1875
1851
  stream?: boolean | StreamingOptions;
1876
- } & (CustomCtx extends Record<string, unknown>
1877
- ? {
1878
- /**
1879
- * If you have a custom ctx that you use with the Agent
1880
- * (e.g. new Agent<{ orgId: string }>(...))
1881
- * you need to provide this function to add any extra fields.
1882
- * e.g.
1883
- * ```ts
1884
- * const myAgent = new Agent<{ orgId: string }>(...);
1885
- * const myAction = myAgent.asTextAction({
1886
- * customCtx: (ctx: ActionCtx, target, llmArgs) => {
1887
- * const orgId = await lookupOrgId(ctx, target.threadId);
1888
- * return { orgId };
1889
- * },
1890
- * });
1891
- * ```
1892
- * Then, in your tools, you can
1893
- */
1894
- customCtx: (
1895
- ctx: GenericActionCtx<DataModel>,
1896
- target: {
1897
- userId?: string | undefined;
1898
- threadId?: string | undefined;
1899
- },
1900
- llmArgs: TextArgs<AgentTools>,
1901
- ) => CustomCtx;
1902
- }
1903
- : { customCtx?: never }),
1852
+ /**
1853
+ * When to stop generating text.
1854
+ * Defaults to the {@link Agent["options"].stopWhen} option.
1855
+ */
1856
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
1857
+ } & Options,
1858
+ overrides?: CallSettings,
1904
1859
  ) {
1905
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1906
1860
  return internalActionGeneric({
1907
1861
  args: vTextArgs,
1908
1862
  handler: async (ctx_, args) => {
1909
1863
  const stream =
1910
- args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1864
+ args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1911
1865
  const targetArgs = { userId: args.userId, threadId: args.threadId };
1912
1866
  const llmArgs = {
1913
- maxSteps,
1867
+ stopWhen: spec?.stopWhen ?? this.options.stopWhen,
1868
+ ...overrides,
1914
1869
  ...omit(args, ["storageOptions", "contextOptions"]),
1915
- };
1870
+ messages: args.messages?.map(deserializeMessage),
1871
+ prompt: Array.isArray(args.prompt)
1872
+ ? args.prompt.map(deserializeMessage)
1873
+ : args.prompt,
1874
+ toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1875
+ } satisfies StreamingTextArgs<AgentTools>;
1876
+ if (args.maxSteps) {
1877
+ llmArgs.stopWhen = stepCountIs(args.maxSteps);
1878
+ }
1916
1879
  const opts = {
1917
1880
  ...this.options,
1918
- ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
1881
+ ...pick(spec, ["contextOptions", "storageOptions"]),
1919
1882
  ...pick(args, ["contextOptions", "storageOptions"]),
1920
1883
  saveStreamDeltas: stream,
1921
1884
  };
@@ -1925,23 +1888,37 @@ export class Agent<
1925
1888
  : ctx_
1926
1889
  ) as UserActionCtx & CustomCtx;
1927
1890
  if (stream) {
1928
- const result = await this.streamText(ctx, targetArgs, llmArgs, opts);
1891
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1892
+ const result = await this.streamText<any>(
1893
+ ctx,
1894
+ targetArgs,
1895
+ llmArgs,
1896
+ opts,
1897
+ );
1929
1898
  await result.consumeStream();
1930
1899
  return {
1931
1900
  text: await result.text,
1932
- messageId: result.messageId,
1901
+ promptMessageId: result.promptMessageId,
1933
1902
  order: result.order,
1934
1903
  finishReason: await result.finishReason,
1935
1904
  warnings: result.warnings,
1905
+ savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1936
1906
  };
1937
1907
  } else {
1938
- const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1908
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1909
+ const res = await this.generateText<any>(
1910
+ ctx,
1911
+ targetArgs,
1912
+ llmArgs,
1913
+ opts,
1914
+ );
1939
1915
  return {
1940
1916
  text: res.text,
1941
- messageId: res.messageId,
1917
+ promptMessageId: res.promptMessageId,
1942
1918
  order: res.order,
1943
1919
  finishReason: res.finishReason,
1944
1920
  warnings: res.warnings,
1921
+ savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
1945
1922
  };
1946
1923
  }
1947
1924
  },
@@ -1952,36 +1929,47 @@ export class Agent<
1952
1929
  * it from workflows or other actions without a wrapping function.
1953
1930
  * @param spec Configuration for the agent acting as an action, including
1954
1931
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1955
- * and maxSteps.
1932
+ * and stopWhen.
1956
1933
  */
1957
1934
  asObjectAction<T>(
1958
- spec: OurObjectArgs<T> & { maxSteps?: number },
1959
- options?: {
1960
- contextOptions?: ContextOptions;
1961
- storageOptions?: StorageOptions;
1962
- },
1935
+ objectArgs: Omit<
1936
+ Parameters<typeof generateObject<FlexibleSchema<T>>>[0],
1937
+ "model"
1938
+ >,
1939
+ options?: Options & MaybeCustomCtx<CustomCtx, DataModel, AgentTools>,
1963
1940
  ) {
1964
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1965
1941
  return internalActionGeneric({
1966
1942
  args: vSafeObjectArgs,
1967
- handler: async (ctx, args) => {
1968
- const overrides = pick(args, ["userId", "threadId"]);
1969
- const value = await this.generateObject(
1970
- ctx,
1971
- { userId: args.userId, threadId: args.threadId },
1972
- {
1973
- ...spec,
1974
- maxSteps,
1975
- ...omit(args, ["userId", "threadId"]),
1976
- } as unknown as OurObjectArgs<unknown>,
1977
- { ...this.options, ...options, ...overrides },
1978
- );
1943
+ handler: async (ctx_, args) => {
1944
+ const { userId, threadId, callSettings, ...rest } = args;
1945
+ const overrides = pick(rest, ["contextOptions", "storageOptions"]);
1946
+ const targetArgs = { userId, threadId };
1947
+ const llmArgs = {
1948
+ ...objectArgs,
1949
+ ...callSettings,
1950
+ ...omit(rest, ["storageOptions", "contextOptions"]),
1951
+ messages: args.messages?.map(deserializeMessage),
1952
+ prompt: Array.isArray(args.prompt)
1953
+ ? args.prompt.map(deserializeMessage)
1954
+ : args.prompt,
1955
+ } as Omit<Parameters<typeof generateObject>[0], "model">;
1956
+ const ctx = (
1957
+ options?.customCtx
1958
+ ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }
1959
+ : ctx_
1960
+ ) as UserActionCtx & CustomCtx;
1961
+ const value = await this.generateObject(ctx, targetArgs, llmArgs, {
1962
+ ...this.options,
1963
+ ...options,
1964
+ ...overrides,
1965
+ });
1979
1966
  return {
1980
- object: value.object as T,
1981
- messageId: value.messageId,
1967
+ object: convexToJson(value.object as Value) as T,
1968
+ promptMessageId: value.promptMessageId,
1982
1969
  order: value.order,
1983
1970
  finishReason: value.finishReason,
1984
1971
  warnings: value.warnings,
1972
+ savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
1985
1973
  };
1986
1974
  },
1987
1975
  });
@@ -2014,228 +2002,48 @@ export class Agent<
2014
2002
  userId: v.optional(v.string()),
2015
2003
  promptMessageId: v.optional(v.string()),
2016
2004
  messages: v.array(vMessageWithMetadata),
2017
- pending: v.optional(v.boolean()),
2018
2005
  failPendingSteps: v.optional(v.boolean()),
2006
+ embeddings: v.optional(vMessageEmbeddings),
2019
2007
  },
2020
2008
  handler: async (ctx, args) => {
2021
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
2009
+ const { messages } = await this.saveMessages(ctx, {
2022
2010
  ...args,
2023
- messages: args.messages.map((m) => m.message),
2011
+ messages: args.messages.map((m) => deserializeMessage(m.message)),
2024
2012
  metadata: args.messages.map(({ message: _, ...m }) => m),
2013
+ skipEmbeddings: true,
2025
2014
  });
2026
2015
  return {
2027
- lastMessageId,
2028
- messageIds: messages.map((m) => m._id),
2016
+ lastMessageId: messages.at(-1)!._id,
2017
+ messages: messages.map((m) => pick(m, ["_id", "order", "stepOrder"])),
2029
2018
  };
2030
2019
  },
2031
2020
  });
2032
2021
  }
2033
2022
  }
2034
2023
 
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
- }
2024
+ async function willContinue(
2025
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2026
+ steps: StepResult<any>[],
2027
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2028
+ stopWhen: StopCondition<any> | Array<StopCondition<any>> | undefined,
2029
+ ): Promise<boolean> {
2030
+ const step = steps.at(-1)!;
2031
+ // we aren't doing another round after a tool result
2032
+ // TODO: whether to handle continuing after too much context used..
2033
+ if (step.finishReason !== "tool-calls") return false;
2034
+ // we don't have a tool result, so we'll wait for more
2035
+ if (step.toolCalls.length > step.toolResults.length) return false;
2036
+ if (Array.isArray(stopWhen)) {
2037
+ return (await Promise.all(stopWhen.map(async (s) => s({ steps })))).every(
2038
+ (stop) => !stop,
2039
+ );
2142
2040
  }
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
- };
2041
+ return !(await stopWhen?.({ steps }));
2166
2042
  }
2167
2043
 
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
- };
2044
+ function errorToString(error: unknown): string {
2045
+ if (error instanceof Error) {
2046
+ return error.message;
2228
2047
  }
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)! };
2048
+ return String(error);
2241
2049
  }