@convex-dev/agent 0.2.0-alpha.2 → 0.2.1

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 (100) hide show
  1. package/dist/client/createTool.d.ts +4 -6
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +0 -1
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/definePlaygroundAPI.d.ts +28 -28
  6. package/dist/client/files.d.ts +9 -2
  7. package/dist/client/files.d.ts.map +1 -1
  8. package/dist/client/files.js +61 -6
  9. package/dist/client/files.js.map +1 -1
  10. package/dist/client/index.d.ts +232 -242
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +375 -383
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/client/messages.d.ts +91 -373
  15. package/dist/client/messages.d.ts.map +1 -1
  16. package/dist/client/messages.js +7 -1
  17. package/dist/client/messages.js.map +1 -1
  18. package/dist/client/search.d.ts +15 -15
  19. package/dist/client/search.d.ts.map +1 -1
  20. package/dist/client/search.js +3 -3
  21. package/dist/client/search.js.map +1 -1
  22. package/dist/client/streaming.d.ts +30 -22
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +36 -40
  25. package/dist/client/streaming.js.map +1 -1
  26. package/dist/client/types.d.ts +148 -61
  27. package/dist/client/types.d.ts.map +1 -1
  28. package/dist/component/_generated/api.d.ts +47 -357
  29. package/dist/component/messages.d.ts +157 -150
  30. package/dist/component/messages.d.ts.map +1 -1
  31. package/dist/component/messages.js +124 -70
  32. package/dist/component/messages.js.map +1 -1
  33. package/dist/component/schema.d.ts +1147 -2687
  34. package/dist/component/schema.d.ts.map +1 -1
  35. package/dist/component/schema.js +5 -4
  36. package/dist/component/schema.js.map +1 -1
  37. package/dist/component/streams.d.ts +29 -323
  38. package/dist/component/streams.d.ts.map +1 -1
  39. package/dist/component/streams.js +104 -60
  40. package/dist/component/streams.js.map +1 -1
  41. package/dist/component/threads.d.ts +4 -4
  42. package/dist/component/users.d.ts +3 -3
  43. package/dist/mapping.d.ts +4 -1
  44. package/dist/mapping.d.ts.map +1 -1
  45. package/dist/mapping.js +17 -1
  46. package/dist/mapping.js.map +1 -1
  47. package/dist/parts.d.ts +3 -0
  48. package/dist/parts.d.ts.map +1 -0
  49. package/dist/parts.js +34 -0
  50. package/dist/parts.js.map +1 -0
  51. package/dist/react/SmoothText.d.ts +5 -0
  52. package/dist/react/SmoothText.d.ts.map +1 -0
  53. package/dist/react/SmoothText.js +6 -0
  54. package/dist/react/SmoothText.js.map +1 -0
  55. package/dist/react/deltas.d.ts +3 -2
  56. package/dist/react/deltas.d.ts.map +1 -1
  57. package/dist/react/deltas.js +19 -79
  58. package/dist/react/deltas.js.map +1 -1
  59. package/dist/react/index.d.ts +1 -0
  60. package/dist/react/index.d.ts.map +1 -1
  61. package/dist/react/index.js +31 -11
  62. package/dist/react/index.js.map +1 -1
  63. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  64. package/dist/react/optimisticallySendMessage.js +2 -8
  65. package/dist/react/optimisticallySendMessage.js.map +1 -1
  66. package/dist/react/useSmoothText.d.ts +13 -12
  67. package/dist/react/useSmoothText.d.ts.map +1 -1
  68. package/dist/react/useSmoothText.js +1 -1
  69. package/dist/react/useSmoothText.js.map +1 -1
  70. package/dist/shared.d.ts +3 -3
  71. package/dist/shared.d.ts.map +1 -1
  72. package/dist/shared.js.map +1 -1
  73. package/dist/validators.d.ts +109 -1590
  74. package/dist/validators.d.ts.map +1 -1
  75. package/dist/validators.js +4 -81
  76. package/dist/validators.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/client/createTool.ts +5 -10
  79. package/src/client/files.ts +89 -11
  80. package/src/client/index.test.ts +25 -35
  81. package/src/client/index.ts +686 -652
  82. package/src/client/messages.ts +16 -2
  83. package/src/client/search.ts +10 -7
  84. package/src/client/streaming.ts +62 -57
  85. package/src/client/types.ts +227 -97
  86. package/src/component/_generated/api.d.ts +47 -357
  87. package/src/component/messages.ts +157 -96
  88. package/src/component/schema.ts +4 -4
  89. package/src/component/streams.ts +180 -69
  90. package/src/mapping.ts +23 -2
  91. package/src/parts.ts +39 -0
  92. package/src/react/SmoothText.tsx +9 -0
  93. package/src/react/deltas.test.ts +38 -42
  94. package/src/react/deltas.ts +24 -86
  95. package/src/react/index.ts +35 -13
  96. package/src/react/optimisticallySendMessage.ts +2 -8
  97. package/src/react/useSmoothText.ts +14 -15
  98. package/src/shared.ts +3 -3
  99. package/src/validators.test.ts +0 -17
  100. package/src/validators.ts +8 -101
@@ -1,29 +1,30 @@
1
1
  import { embedMany, generateObject, generateText, stepCountIs, streamObject, streamText, } from "ai";
2
2
  import { assert, omit, pick } from "convex-helpers";
3
3
  import { internalActionGeneric, internalMutationGeneric, } from "convex/server";
4
- import { v } from "convex/values";
4
+ import { convexToJson, v } from "convex/values";
5
5
  import { validateVectorDimension, } from "../component/vector/tables.js";
6
6
  import { deserializeMessage, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
7
7
  import { extractText, isTool } from "../shared.js";
8
8
  import { vMessageEmbeddings, vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
9
9
  import { createTool, wrapTools } from "./createTool.js";
10
10
  import { listMessages, saveMessages, } from "./messages.js";
11
- import { createThread, getThreadMetadata } from "./threads.js";
12
11
  import { fetchContextMessages, getModelName, getProviderName, } from "./search.js";
13
12
  import { DeltaStreamer, mergeTransforms, syncStreams, } from "./streaming.js";
13
+ import { createThread, getThreadMetadata } from "./threads.js";
14
+ import { inlineMessagesFiles } from "./files.js";
14
15
  export { stepCountIs } from "ai";
15
16
  export { vMessageDoc, vThreadDoc } from "../component/schema.js";
16
- export { serializeDataOrUrl, deserializeMessage, serializeMessage, } from "../mapping.js";
17
+ export { deserializeMessage, serializeDataOrUrl, serializeMessage, } from "../mapping.js";
17
18
  // NOTE: these are also exported via @convex-dev/agent/validators
18
19
  // a future version may put them all here or move these over there
19
20
  export { vAssistantMessage, vContextOptions, vMessage, vPaginationResult, vProviderMetadata, vStorageOptions, vStreamArgs, vSystemMessage, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
21
+ export { definePlaygroundAPI, } from "./definePlaygroundAPI.js";
20
22
  export { getFile, storeFile } from "./files.js";
21
- export { filterOutOrphanedToolMessages, fetchContextMessages, } from "./search.js";
22
- export { abortStream, listStreams, syncStreams } from "./streaming.js";
23
23
  export { listMessages, saveMessage, saveMessages, } from "./messages.js";
24
- export { createThread, getThreadMetadata } from "./threads.js";
24
+ export { fetchContextMessages, filterOutOrphanedToolMessages, } from "./search.js";
25
+ export { abortStream, listStreams, syncStreams } from "./streaming.js";
26
+ export { createThread, getThreadMetadata, updateThreadMetadata, searchThreadTitles, } from "./threads.js";
25
27
  export { createTool, extractText, isTool };
26
- export { definePlaygroundAPI, } from "./definePlaygroundAPI.js";
27
28
  export class Agent {
28
29
  component;
29
30
  options;
@@ -39,8 +40,6 @@ export class Agent {
39
40
  const { thread } = await this.continueThread(ctx, {
40
41
  threadId,
41
42
  userId: args?.userId,
42
- usageHandler: args?.usageHandler,
43
- tools: args?.tools,
44
43
  });
45
44
  return { threadId, thread };
46
45
  }
@@ -70,90 +69,188 @@ export class Agent {
70
69
  },
71
70
  };
72
71
  }
72
+ async start(ctx,
73
73
  /**
74
- * This behaves like {@link generateText} from the "ai" package except that
75
- * it add context based on the userId and threadId and saves the input and
76
- * resulting messages to the thread, if specified.
77
- * Use {@link continueThread} to get a version of this function already scoped
78
- * to a thread (and optionally userId).
79
- * @param ctx The context passed from the action function calling this.
80
- * @param { userId, threadId }: The user and thread to associate the message with
81
- * @param args The arguments to the generateText function, along with extra controls
82
- * for the {@link ContextOptions} and {@link StorageOptions}.
83
- * @returns The result of the generateText function.
74
+ * These are the arguments you'll pass to the LLM call such as
75
+ * `generateText` or `streamText`. This function will look up the context
76
+ * and provide functions to save the steps, abort the generation, and more.
77
+ * The type of the arguments returned infers from the type of the arguments
78
+ * you pass here.
84
79
  */
85
- async generateText(ctx, { userId: argsUserId, threadId, tools: threadTools, ...usageHandler }, args, options) {
86
- const opts = { ...this.options, ...options, ...usageHandler };
80
+ args, options) {
81
+ const { threadId, ...opts } = { ...this.options, ...options };
87
82
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
88
- userId: argsUserId ?? undefined,
89
- threadId,
83
+ userId: options?.userId,
84
+ threadId: options?.threadId,
90
85
  ...opts,
91
86
  });
92
- const { args: aiArgs, messageId, order, userId } = context;
87
+ let pendingMessageId = context.pendingMessageId;
88
+ // TODO: extract pending message if one exists
89
+ const { args: aiArgs, promptMessageId, order, stepOrder, userId } = context;
93
90
  const messages = context.savedMessages ?? [];
91
+ if (pendingMessageId) {
92
+ if (!aiArgs._internal?.generateId) {
93
+ aiArgs._internal = {
94
+ ...aiArgs._internal,
95
+ generateId: () => pendingMessageId ?? crypto.randomUUID(),
96
+ };
97
+ }
98
+ }
94
99
  const toolCtx = {
95
100
  ...ctx,
96
101
  userId,
97
102
  threadId,
98
- messageId,
103
+ promptMessageId,
99
104
  agent: this,
100
105
  };
101
- const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
106
+ const tools = wrapTools(toolCtx, args.tools ?? this.options.tools);
102
107
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
103
- try {
104
- const result = (await generateText({
105
- // Can be overridden
106
- stopWhen: this.options.stopWhen,
108
+ const fail = async (reason) => {
109
+ if (threadId && promptMessageId) {
110
+ console.error("RollbackMessage", promptMessageId, reason);
111
+ }
112
+ if (pendingMessageId) {
113
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
114
+ messageId: pendingMessageId,
115
+ result: { status: "failed", error: reason },
116
+ });
117
+ }
118
+ };
119
+ let activeModel = aiArgs.model;
120
+ if (aiArgs.abortSignal) {
121
+ const abortSignal = aiArgs.abortSignal;
122
+ aiArgs.abortSignal.addEventListener("abort", async () => {
123
+ await fail(abortSignal.reason ?? "Aborted");
124
+ }, { once: true });
125
+ }
126
+ return {
127
+ args: {
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
+ stopWhen: args.stopWhen ?? this.options.stopWhen,
107
130
  ...aiArgs,
108
131
  tools,
109
- onStepFinish: async (step) => {
110
- if (threadId && messageId && saveOutput) {
111
- const saved = await this.saveStep(ctx, {
112
- userId,
113
- threadId,
114
- promptMessageId: messageId,
115
- model: aiArgs.model.modelId,
116
- provider: aiArgs.model.provider,
117
- step,
132
+ // abortSignal: abortController.signal,
133
+ },
134
+ order: order ?? 0,
135
+ stepOrder: stepOrder ?? 0,
136
+ userId,
137
+ promptMessageId,
138
+ getSavedMessages: () => messages,
139
+ updateModel: (model) => {
140
+ if (model) {
141
+ activeModel = model;
142
+ }
143
+ },
144
+ fail,
145
+ save: async (toSave, createPendingMessage) => {
146
+ if (threadId && promptMessageId && saveOutput) {
147
+ const metadata = {
148
+ // TODO: get up to date one when user selects mid-generation
149
+ model: getModelName(activeModel),
150
+ provider: getProviderName(activeModel),
151
+ };
152
+ const serialized = "object" in toSave
153
+ ? await serializeObjectResult(ctx, this.component, toSave.object, metadata)
154
+ : await serializeNewMessagesInStep(ctx, this.component, toSave.step, metadata);
155
+ const embeddings = await this.generateEmbeddings(ctx, { userId, threadId }, serialized.messages.map((m) => m.message));
156
+ if (createPendingMessage) {
157
+ serialized.messages.push({
158
+ message: { role: "assistant", content: [] },
159
+ status: "pending",
118
160
  });
119
- messages.push(...saved.messages);
161
+ embeddings?.vectors.push(null);
120
162
  }
121
- if (this.options.rawRequestResponseHandler) {
122
- await this.options.rawRequestResponseHandler(ctx, {
123
- userId,
124
- threadId,
125
- agentName: this.options.name,
126
- request: step.request,
127
- response: step.response,
128
- });
163
+ const saved = await ctx.runMutation(this.component.messages.addMessages, {
164
+ userId,
165
+ threadId,
166
+ agentName: this.options.name,
167
+ promptMessageId,
168
+ pendingMessageId,
169
+ messages: serialized.messages,
170
+ embeddings,
171
+ failPendingSteps: false,
172
+ });
173
+ const lastMessage = saved.messages.at(-1);
174
+ if (createPendingMessage) {
175
+ if (lastMessage.status === "failed") {
176
+ pendingMessageId = undefined;
177
+ messages.push(...saved.messages);
178
+ await fail(lastMessage.error ??
179
+ "Aborting - the pending message was marked as failed");
180
+ }
181
+ else {
182
+ pendingMessageId = lastMessage._id;
183
+ messages.push(...saved.messages.slice(0, -1));
184
+ }
129
185
  }
130
- if (opts.usageHandler && step.usage) {
131
- await opts.usageHandler(ctx, {
132
- userId,
133
- threadId,
134
- agentName: this.options.name,
135
- model: aiArgs.model.modelId,
136
- provider: aiArgs.model.provider,
137
- usage: step.usage,
138
- providerMetadata: step.providerMetadata,
139
- });
186
+ else {
187
+ pendingMessageId = undefined;
188
+ messages.push(...saved.messages);
140
189
  }
141
- return args.onStepFinish?.(step);
190
+ }
191
+ const output = "object" in toSave ? toSave.object : toSave.step;
192
+ if (this.options.rawRequestResponseHandler) {
193
+ await this.options.rawRequestResponseHandler(ctx, {
194
+ userId,
195
+ threadId,
196
+ agentName: this.options.name,
197
+ request: output.request,
198
+ response: output.response,
199
+ });
200
+ }
201
+ if (opts.usageHandler && output.usage) {
202
+ await opts.usageHandler(ctx, {
203
+ userId,
204
+ threadId,
205
+ agentName: this.options.name,
206
+ model: getModelName(activeModel),
207
+ provider: getProviderName(activeModel),
208
+ usage: output.usage,
209
+ providerMetadata: output.providerMetadata,
210
+ });
211
+ }
212
+ },
213
+ };
214
+ }
215
+ /**
216
+ * This behaves like {@link generateText} from the "ai" package except that
217
+ * it add context based on the userId and threadId and saves the input and
218
+ * resulting messages to the thread, if specified.
219
+ * Use {@link continueThread} to get a version of this function already scoped
220
+ * to a thread (and optionally userId).
221
+ * @param ctx The context passed from the action function calling this.
222
+ * @param { userId, threadId }: The user and thread to associate the message with
223
+ * @param generateTextArgs The arguments to the generateText function, along with extra controls
224
+ * for the {@link ContextOptions} and {@link StorageOptions}.
225
+ * @returns The result of the generateText function.
226
+ */
227
+ async generateText(ctx, threadOpts, generateTextArgs, options) {
228
+ const { args, promptMessageId, order, ...call } = await this.start(ctx, generateTextArgs, { ...threadOpts, ...options });
229
+ const steps = [];
230
+ try {
231
+ const result = (await generateText({
232
+ ...args,
233
+ prepareStep: async (options) => {
234
+ const result = await generateTextArgs.prepareStep?.(options);
235
+ call.updateModel(result?.model ?? options.model);
236
+ return result;
237
+ },
238
+ onStepFinish: async (step) => {
239
+ steps.push(step);
240
+ await call.save({ step }, await willContinue(steps, args.stopWhen));
241
+ return generateTextArgs.onStepFinish?.(step);
142
242
  },
143
243
  }));
144
- result.messageId = messageId;
145
- result.order = order;
146
- result.messages = messages;
147
- return result;
244
+ const metadata = {
245
+ promptMessageId,
246
+ order,
247
+ savedMessages: call.getSavedMessages(),
248
+ messageId: promptMessageId,
249
+ };
250
+ return Object.assign(result, metadata);
148
251
  }
149
252
  catch (error) {
150
- if (threadId && messageId) {
151
- console.error("RollbackMessage", messageId);
152
- await ctx.runMutation(this.component.messages.rollbackMessage, {
153
- messageId,
154
- error: error.message,
155
- });
156
- }
253
+ await call.fail(errorToString(error));
157
254
  throw error;
158
255
  }
159
256
  }
@@ -164,112 +261,83 @@ export class Agent {
164
261
  * Use {@link continueThread} to get a version of this function already scoped
165
262
  * to a thread (and optionally userId).
166
263
  */
167
- async streamText(ctx, { userId: argsUserId, threadId,
168
- /** Note: to get better type inference, pass tools in the next arg */
169
- tools: threadTools, ...usageHandler },
264
+ async streamText(ctx, threadOpts,
170
265
  /**
171
266
  * The arguments to the streamText function, similar to the ai `streamText` function.
172
267
  */
173
- args,
268
+ streamTextArgs,
174
269
  /**
175
270
  * The {@link ContextOptions} and {@link StorageOptions}
176
271
  * options to use for fetching contextual messages and saving input/output messages.
177
272
  */
178
273
  options) {
179
- const opts = { ...this.options, ...options, ...usageHandler };
180
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
181
- userId: argsUserId ?? undefined,
182
- threadId,
183
- ...opts,
184
- });
185
- const { args: aiArgs, messageId, order, stepOrder, userId } = context;
186
- const messages = context.savedMessages ?? [];
187
- const toolCtx = {
188
- ...ctx,
189
- userId,
190
- threadId,
191
- messageId,
192
- agent: this,
193
- };
194
- const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
195
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
274
+ const { threadId } = threadOpts;
275
+ const { args, userId, order, stepOrder, promptMessageId, ...call } = await this.start(ctx, streamTextArgs, { ...threadOpts, ...options });
276
+ const steps = [];
277
+ const opts = { ...this.options, ...options };
196
278
  const streamer = threadId && opts.saveStreamDeltas
197
279
  ? new DeltaStreamer(this.component, ctx, opts.saveStreamDeltas, {
198
280
  threadId,
199
281
  userId,
200
282
  agentName: this.options.name,
201
- model: aiArgs.model.modelId,
202
- provider: aiArgs.model.provider,
203
- providerOptions: aiArgs.providerOptions,
283
+ model: getModelName(args.model),
284
+ provider: getProviderName(args.model),
285
+ providerOptions: args.providerOptions,
204
286
  order,
205
287
  stepOrder,
206
- abortSignal: aiArgs.abortSignal,
288
+ abortSignal: args.abortSignal,
207
289
  })
208
290
  : undefined;
209
291
  const result = streamText({
210
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
- stopWhen: this.options.stopWhen, // Can be overridden
212
- ...aiArgs,
213
- tools,
214
- abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
215
- experimental_transform: mergeTransforms(options?.saveStreamDeltas, args.experimental_transform),
292
+ ...args,
293
+ abortSignal: streamer?.abortController.signal ?? args.abortSignal,
294
+ // TODO: this is probably why reasoning isn't streaming
295
+ experimental_transform: mergeTransforms(options?.saveStreamDeltas, streamTextArgs.experimental_transform),
216
296
  onChunk: async (event) => {
217
297
  await streamer?.addParts([event.chunk]);
218
298
  // console.log("onChunk", chunk);
219
- return args.onChunk?.(event);
299
+ return streamTextArgs.onChunk?.(event);
220
300
  },
221
301
  onError: async (error) => {
222
302
  console.error("onError", error);
223
- if (threadId && messageId && saveOutput) {
224
- await ctx.runMutation(this.component.messages.rollbackMessage, {
225
- messageId,
226
- error: error.error.message,
227
- });
303
+ await call.fail(errorToString(error.error));
304
+ await streamer?.fail(errorToString(error.error));
305
+ return streamTextArgs.onError?.(error);
306
+ },
307
+ // onFinish: async (event) => {
308
+ // return streamTextArgs.onFinish?.(event);
309
+ // },
310
+ prepareStep: async (options) => {
311
+ const result = await streamTextArgs.prepareStep?.(options);
312
+ if (result) {
313
+ const model = result.model ?? options.model;
314
+ call.updateModel(model);
315
+ return result;
228
316
  }
229
- // TODO: update the streamer to error state
230
- return args.onError?.(error);
317
+ return undefined;
231
318
  },
232
319
  onStepFinish: async (step) => {
233
- // console.log("onStepFinish", step);
234
- if (threadId && messageId && saveOutput) {
235
- const saved = await this.saveStep(ctx, {
236
- userId,
237
- threadId,
238
- model: aiArgs.model.modelId,
239
- provider: aiArgs.model.provider,
240
- promptMessageId: messageId,
241
- step,
242
- });
243
- await streamer?.finish(saved.messages);
244
- messages.push(...saved.messages);
245
- }
246
- if (this.options.rawRequestResponseHandler) {
247
- await this.options.rawRequestResponseHandler(ctx, {
248
- userId,
249
- threadId,
250
- agentName: this.options.name,
251
- request: step.request,
252
- response: step.response,
253
- });
254
- }
255
- if (opts.usageHandler && step.usage) {
256
- await opts.usageHandler(ctx, {
257
- userId,
258
- threadId,
259
- agentName: this.options.name,
260
- model: aiArgs.model.modelId,
261
- provider: aiArgs.model.provider,
262
- usage: step.usage,
263
- providerMetadata: step.providerMetadata,
264
- });
320
+ steps.push(step);
321
+ const createPendingMessage = await willContinue(steps, args.stopWhen);
322
+ await call.save({ step }, createPendingMessage);
323
+ if (!createPendingMessage) {
324
+ await streamer?.finish();
265
325
  }
266
326
  return args.onStepFinish?.(step);
267
327
  },
268
328
  });
269
- result.messageId = messageId;
270
- result.order = order;
271
- result.messages = messages;
272
- return result;
329
+ const metadata = {
330
+ promptMessageId,
331
+ order,
332
+ savedMessages: call.getSavedMessages(),
333
+ messageId: promptMessageId,
334
+ };
335
+ if ((typeof options?.saveStreamDeltas === "object" &&
336
+ !options.saveStreamDeltas.returnImmediately) ||
337
+ options?.saveStreamDeltas === true) {
338
+ await result.consumeStream();
339
+ }
340
+ return Object.assign(result, metadata);
273
341
  }
274
342
  /**
275
343
  * This behaves like {@link generateObject} from the "ai" package except that
@@ -278,72 +346,30 @@ export class Agent {
278
346
  * Use {@link continueThread} to get a version of this function already scoped
279
347
  * to a thread (and optionally userId).
280
348
  */
281
- async generateObject(ctx, { userId: argsUserId, threadId, ...usageHandler },
349
+ async generateObject(ctx, threadOpts,
282
350
  /**
283
351
  * The arguments to the generateObject function, similar to the ai.generateObject function.
284
352
  */
285
- args,
353
+ generateObjectArgs,
286
354
  /**
287
355
  * The {@link ContextOptions} and {@link StorageOptions}
288
356
  * options to use for fetching contextual messages and saving input/output messages.
289
357
  */
290
358
  options) {
291
- const opts = { ...this.options, ...options, ...usageHandler };
292
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
293
- userId: argsUserId ?? undefined,
294
- threadId,
295
- ...opts,
296
- });
297
- const { args: aiArgs, messageId, order, userId } = context;
298
- const messages = context.savedMessages ?? [];
299
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
359
+ const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
300
360
  try {
301
- const result = (await generateObject(
302
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
- aiArgs));
304
- if (threadId && messageId && saveOutput) {
305
- const saved = await this.saveObject(ctx, {
306
- threadId,
307
- promptMessageId: messageId,
308
- result,
309
- userId,
310
- model: aiArgs.model.modelId,
311
- provider: aiArgs.model.provider,
312
- });
313
- messages.push(...saved.messages);
314
- }
315
- result.messageId = messageId;
316
- result.order = order;
317
- result.messages = messages;
318
- if (this.options.rawRequestResponseHandler) {
319
- await this.options.rawRequestResponseHandler(ctx, {
320
- userId,
321
- threadId,
322
- agentName: this.options.name,
323
- request: result.request,
324
- response: result.response,
325
- });
326
- }
327
- if (opts.usageHandler && result.usage) {
328
- await opts.usageHandler(ctx, {
329
- userId,
330
- threadId,
331
- agentName: this.options.name,
332
- model: aiArgs.model.modelId,
333
- provider: aiArgs.model.provider,
334
- usage: result.usage,
335
- providerMetadata: result.providerMetadata,
336
- });
337
- }
338
- return result;
361
+ const result = (await generateObject(args));
362
+ await save({ object: result });
363
+ const metadata = {
364
+ promptMessageId,
365
+ order,
366
+ savedMessages: getSavedMessages(),
367
+ messageId: promptMessageId,
368
+ };
369
+ return Object.assign(result, metadata);
339
370
  }
340
371
  catch (error) {
341
- if (threadId && messageId) {
342
- await ctx.runMutation(this.component.messages.rollbackMessage, {
343
- messageId,
344
- error: error.message,
345
- });
346
- }
372
+ await fail(errorToString(error));
347
373
  throw error;
348
374
  }
349
375
  }
@@ -354,83 +380,50 @@ export class Agent {
354
380
  * Use {@link continueThread} to get a version of this function already scoped
355
381
  * to a thread (and optionally userId).
356
382
  */
357
- async streamObject(ctx, { userId: argsUserId, threadId, ...usageHandler },
383
+ async streamObject(ctx, threadOpts,
358
384
  /**
359
385
  * The arguments to the streamObject function, similar to the ai `streamObject` function.
360
386
  */
361
- args,
387
+ streamObjectArgs,
362
388
  /**
363
389
  * The {@link ContextOptions} and {@link StorageOptions}
364
390
  * options to use for fetching contextual messages and saving input/output messages.
365
391
  */
366
392
  options) {
367
- // TODO: unify all this shared code between all the generate* and stream* functions
368
- const opts = { ...this.options, ...options, ...usageHandler };
369
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
370
- userId: argsUserId ?? undefined,
371
- threadId,
372
- ...opts,
373
- });
374
- const { args: aiArgs, messageId, order, userId } = context;
375
- const messages = context.savedMessages ?? [];
376
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
377
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
378
394
  const stream = streamObject({
379
395
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
- ...aiArgs,
396
+ ...args,
381
397
  onError: async (error) => {
382
- console.error("onError", error);
398
+ console.error(" streamObject onError", error);
399
+ // TODO: content that we have so far
400
+ // content: stream.fullStream.
401
+ await fail(errorToString(error.error));
383
402
  return args.onError?.(error);
384
403
  },
385
404
  onFinish: async (result) => {
386
- if (threadId && messageId && saveOutput) {
387
- const saved = await this.saveObject(ctx, {
388
- userId,
389
- threadId,
390
- promptMessageId: messageId,
391
- result: {
392
- object: result.object,
393
- finishReason: "stop",
394
- usage: result.usage,
395
- warnings: result.warnings,
396
- request: await stream.request,
397
- response: result.response,
398
- providerMetadata: result.providerMetadata,
399
- toJsonResponse: stream.toTextStreamResponse,
400
- },
401
- model: aiArgs.model.modelId,
402
- provider: aiArgs.model.provider,
403
- });
404
- messages.push(...saved.messages);
405
- }
406
- if (opts.usageHandler && result.usage) {
407
- await opts.usageHandler(ctx, {
408
- userId,
409
- threadId,
410
- agentName: this.options.name,
411
- model: aiArgs.model.modelId,
412
- provider: aiArgs.model.provider,
405
+ await save({
406
+ object: {
407
+ object: result.object,
408
+ finishReason: result.error ? "error" : "stop",
413
409
  usage: result.usage,
414
- providerMetadata: result.providerMetadata,
415
- });
416
- }
417
- if (this.options.rawRequestResponseHandler) {
418
- await this.options.rawRequestResponseHandler(ctx, {
419
- userId,
420
- threadId,
421
- agentName: this.options.name,
410
+ warnings: result.warnings,
422
411
  request: await stream.request,
423
412
  response: result.response,
424
- });
425
- }
426
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
413
+ providerMetadata: result.providerMetadata,
414
+ toJsonResponse: stream.toTextStreamResponse,
415
+ },
416
+ });
427
417
  return args.onFinish?.(result);
428
418
  },
429
419
  });
430
- stream.messageId = messageId;
431
- stream.order = order;
432
- stream.messages = messages;
433
- return stream;
420
+ const metadata = {
421
+ promptMessageId,
422
+ order,
423
+ savedMessages: getSavedMessages(),
424
+ messageId: promptMessageId,
425
+ };
426
+ return Object.assign(stream, metadata);
434
427
  }
435
428
  /**
436
429
  * Save a message to the thread.
@@ -451,6 +444,7 @@ export class Agent {
451
444
  : [args.message],
452
445
  metadata: args.metadata ? [args.metadata] : undefined,
453
446
  skipEmbeddings: args.skipEmbeddings,
447
+ pendingMessageId: args.pendingMessageId,
454
448
  });
455
449
  const message = messages.at(-1);
456
450
  return { messageId: message._id, message };
@@ -469,7 +463,7 @@ export class Agent {
469
463
  if (args.embeddings) {
470
464
  embeddings = args.embeddings;
471
465
  }
472
- else if (!skipEmbeddings && this.options.textEmbedding) {
466
+ else if (!skipEmbeddings && this.options.textEmbeddingModel) {
473
467
  if (!("runAction" in ctx)) {
474
468
  console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
475
469
  "Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
@@ -535,14 +529,14 @@ export class Agent {
535
529
  contextOptions,
536
530
  getEmbedding: async (text) => {
537
531
  assert("runAction" in ctx);
538
- assert(this.options.textEmbedding, "A textEmbedding model is required to be set on the Agent that you're doing vector search with");
532
+ assert(this.options.textEmbeddingModel, "A textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
539
533
  return {
540
534
  embedding: (await this.doEmbed(ctx, {
541
535
  userId: args.userId,
542
536
  threadId: args.threadId,
543
537
  values: [text],
544
538
  })).embeddings[0],
545
- embeddingModel: this.options.textEmbedding,
539
+ textEmbeddingModel: this.options.textEmbeddingModel,
546
540
  };
547
541
  },
548
542
  });
@@ -573,7 +567,7 @@ export class Agent {
573
567
  * @returns The embeddings for the messages.
574
568
  */
575
569
  async generateEmbeddings(ctx, { userId, threadId, }, messages) {
576
- if (!this.options.textEmbedding) {
570
+ if (!this.options.textEmbeddingModel) {
577
571
  return undefined;
578
572
  }
579
573
  let embeddings;
@@ -585,11 +579,12 @@ export class Agent {
585
579
  if (textIndexes.length === 0) {
586
580
  return undefined;
587
581
  }
582
+ const values = messageTexts.filter((t) => !!t);
588
583
  // Then embed those messages.
589
584
  const textEmbeddings = await this.doEmbed(ctx, {
590
585
  userId,
591
586
  threadId,
592
- values: messageTexts,
587
+ values,
593
588
  });
594
589
  // Then assemble the embeddings into a single array with nulls for the messages without text.
595
590
  const embeddingsOrNull = Array(messages.length).fill(null);
@@ -599,7 +594,7 @@ export class Agent {
599
594
  if (textEmbeddings.embeddings.length > 0) {
600
595
  const dimension = textEmbeddings.embeddings[0].length;
601
596
  validateVectorDimension(dimension);
602
- const model = getModelName(this.options.textEmbedding);
597
+ const model = getModelName(this.options.textEmbeddingModel);
603
598
  embeddings = { vectors: embeddingsOrNull, dimension, model };
604
599
  }
605
600
  return embeddings;
@@ -640,8 +635,8 @@ export class Agent {
640
635
  threadId: messagesMissingEmbeddings[0].threadId,
641
636
  }, messagesMissingEmbeddings.map((m) => deserializeMessage(m.message)));
642
637
  if (!embeddings) {
643
- if (!this.options.textEmbedding) {
644
- throw new Error("No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.");
638
+ if (!this.options.textEmbeddingModel) {
639
+ throw new Error("No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.");
645
640
  }
646
641
  throw new Error("No embeddings were generated for these messages: " +
647
642
  messagesMissingEmbeddings.map((m) => m._id).join(", "));
@@ -666,9 +661,9 @@ export class Agent {
666
661
  * @param args The Step generated by the AI SDK.
667
662
  */
668
663
  async saveStep(ctx, args) {
669
- const messages = await serializeNewMessagesInStep(ctx, this.component, args.step, {
670
- provider: args.provider ?? getProviderName(this.options.chat),
671
- model: args.model ?? getModelName(this.options.chat),
664
+ const { messages } = await serializeNewMessagesInStep(ctx, this.component, args.step, {
665
+ provider: args.provider ?? getProviderName(this.options.languageModel),
666
+ model: args.model ?? getModelName(this.options.languageModel),
672
667
  });
673
668
  const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
674
669
  return ctx.runMutation(this.component.messages.addMessages, {
@@ -690,10 +685,14 @@ export class Agent {
690
685
  */
691
686
  async saveObject(ctx, args) {
692
687
  const { messages } = await serializeObjectResult(ctx, this.component, args.result, {
693
- model: args.model ?? getModelName(this.options.chat),
694
- provider: args.provider ?? getProviderName(this.options.chat),
688
+ model: args.model ??
689
+ args.metadata?.model ??
690
+ getModelName(this.options.languageModel),
691
+ provider: args.provider ??
692
+ args.metadata?.provider ??
693
+ getProviderName(this.options.languageModel),
695
694
  });
696
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => deserializeMessage(m.message)));
695
+ const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
697
696
  return ctx.runMutation(this.component.messages.addMessages, {
698
697
  userId: args.userId,
699
698
  threadId: args.threadId,
@@ -712,19 +711,11 @@ export class Agent {
712
711
  * @param args What message to save. Generally the parent message sent into
713
712
  * the generateText call.
714
713
  */
715
- async completeMessage(ctx, args) {
716
- const result = args.result;
717
- if (result.kind === "success") {
718
- await ctx.runMutation(this.component.messages.commitMessage, {
719
- messageId: args.messageId,
720
- });
721
- }
722
- else {
723
- await ctx.runMutation(this.component.messages.rollbackMessage, {
724
- messageId: args.messageId,
725
- error: result.error,
726
- });
727
- }
714
+ async finalizeMessage(ctx, args) {
715
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
716
+ messageId: args.messageId,
717
+ result: args.result,
718
+ });
728
719
  }
729
720
  /**
730
721
  * Update a message by its id.
@@ -848,7 +839,8 @@ export class Agent {
848
839
  const userId = argsUserId ??
849
840
  (threadId &&
850
841
  (await ctx.runQuery(this.component.threads.getThread, { threadId }))
851
- ?.userId);
842
+ ?.userId) ??
843
+ undefined;
852
844
  // If only a messageId is provided, this will add that message to the end.
853
845
  const contextMessages = await this.fetchContextMessages(ctx, {
854
846
  userId,
@@ -865,30 +857,45 @@ export class Agent {
865
857
  const promptMessage = promptMessageIndex !== -1
866
858
  ? contextMessages.splice(promptMessageIndex, 1)[0]
867
859
  : undefined;
868
- let messageId = promptMessage?._id;
860
+ let promptMessageId = promptMessage?._id;
869
861
  let order = promptMessage?.order;
870
862
  let stepOrder = promptMessage?.stepOrder;
871
863
  let savedMessages = undefined;
872
- if (threadId &&
873
- messages.length + prompt.length &&
874
- storageOptions?.saveMessages !== "none" &&
875
- // If it was a promptMessageId, we don't want to save it again.
876
- (!args.promptMessageId || storageOptions?.saveMessages === "all")) {
877
- const saveAll = storageOptions?.saveMessages === "all";
878
- const coreMessages = [...messages, ...prompt];
879
- const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
880
- const metadata = Array.from({ length: toSave.length }, () => ({}));
881
- const saved = await this.saveMessages(ctx, {
882
- threadId,
883
- userId,
884
- messages: toSave,
885
- metadata,
886
- failPendingSteps: true,
887
- });
888
- messageId = saved.messages.at(-1)._id;
864
+ let pendingMessageId = undefined;
865
+ if (threadId && storageOptions?.saveMessages !== "none") {
866
+ let saved;
867
+ if (messages.length + prompt.length &&
868
+ // If it was a promptMessageId, we don't want to save it again.
869
+ (!args.promptMessageId || storageOptions?.saveMessages === "all")) {
870
+ const saveAll = storageOptions?.saveMessages === "all";
871
+ const coreMessages = [...messages, ...prompt];
872
+ const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
873
+ const metadata = Array.from({ length: toSave.length }, () => ({}));
874
+ saved = await this.saveMessages(ctx, {
875
+ threadId,
876
+ userId,
877
+ messages: [...toSave, { role: "assistant", content: [] }],
878
+ metadata: [...metadata, { status: "pending" }],
879
+ failPendingSteps: true,
880
+ pendingMessageId: args.pendingMessageId,
881
+ });
882
+ promptMessageId = saved.messages.at(-2)._id;
883
+ }
884
+ else {
885
+ saved = await this.saveMessages(ctx, {
886
+ threadId,
887
+ userId,
888
+ messages: [{ role: "assistant", content: [] }],
889
+ metadata: [{ status: "pending" }],
890
+ failPendingSteps: true,
891
+ pendingMessageId: args.pendingMessageId,
892
+ });
893
+ }
894
+ pendingMessageId = saved.messages.at(-1)._id;
889
895
  order = saved.messages.at(-1).order;
890
896
  stepOrder = saved.messages.at(-1).stepOrder;
891
- savedMessages = saved.messages;
897
+ // Don't return the pending message
898
+ savedMessages = saved.messages.slice(0, -1);
892
899
  }
893
900
  if (promptMessage?.message) {
894
901
  if (!args.prompt) {
@@ -898,7 +905,7 @@ export class Agent {
898
905
  // Lazily generate embeddings for the prompt message, if it doesn't have
899
906
  // embeddings yet. This can happen if the message was saved in a mutation
900
907
  // where the LLM is not available.
901
- if (!promptMessage.embeddingId && this.options.textEmbedding) {
908
+ if (!promptMessage.embeddingId && this.options.textEmbeddingModel) {
902
909
  await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
903
910
  }
904
911
  }
@@ -924,27 +931,29 @@ export class Agent {
924
931
  }
925
932
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
926
933
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
927
- processedMessages = await this._inlineMessagesFiles(processedMessages);
934
+ processedMessages = await inlineMessagesFiles(processedMessages);
928
935
  }
929
936
  const { prompt: _, model, ...rest } = args;
930
937
  return {
931
938
  args: {
932
939
  ...this.options.callSettings,
940
+ ...this.options.providerOptions,
933
941
  ...rest,
934
- model: model ?? this.options.chat,
942
+ model: model ?? this.options.languageModel,
935
943
  system: args.system ?? this.options.instructions,
936
944
  messages: processedMessages,
937
945
  },
938
946
  userId,
939
- messageId,
947
+ promptMessageId,
948
+ pendingMessageId,
940
949
  savedMessages,
941
950
  order,
942
951
  stepOrder,
943
952
  };
944
953
  }
945
954
  async doEmbed(ctx, options) {
946
- const embeddingModel = this.options.textEmbedding;
947
- assert(embeddingModel, "a textEmbedding model is required to be set on the Agent that you're doing vector search with");
955
+ const embeddingModel = this.options.textEmbeddingModel;
956
+ assert(embeddingModel, "a textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
948
957
  const result = await embedMany({
949
958
  ...this.options.callSettings,
950
959
  model: embeddingModel,
@@ -969,64 +978,6 @@ export class Agent {
969
978
  }
970
979
  return { embeddings: result.embeddings };
971
980
  }
972
- /**
973
- * Process messages to inline file and image URLs that point to localhost
974
- * by converting them to base64. This solves the problem of LLMs not being
975
- * able to access localhost URLs.
976
- */
977
- async _inlineMessagesFiles(messages) {
978
- // Process each message to convert localhost URLs to base64
979
- return Promise.all(messages.map(async (message) => {
980
- if ((message.role !== "user" && message.role !== "assistant") ||
981
- typeof message.content === "string" ||
982
- !Array.isArray(message.content)) {
983
- return message;
984
- }
985
- const processedContent = await Promise.all(message.content.map(async (part) => {
986
- if (part.type === "image" && part.image instanceof URL) {
987
- assert(message.role === "user", "Images can only be in user messages");
988
- if (this._isLocalhostUrl(part.image)) {
989
- const imageData = await this._downloadFile(part.image);
990
- return { ...part, image: imageData };
991
- }
992
- }
993
- // Handle file parts
994
- if (part.type === "file" && part.data instanceof URL) {
995
- if (this._isLocalhostUrl(part.data)) {
996
- const fileData = await this._downloadFile(part.data);
997
- return { ...part, data: fileData };
998
- }
999
- }
1000
- return part;
1001
- }));
1002
- if (message.role === "user") {
1003
- return { ...message, content: processedContent };
1004
- }
1005
- else {
1006
- return { ...message, content: processedContent };
1007
- }
1008
- }));
1009
- }
1010
- /**
1011
- * Check if a URL points to localhost
1012
- */
1013
- _isLocalhostUrl(url) {
1014
- return (url.hostname === "localhost" ||
1015
- url.hostname === "127.0.0.1" ||
1016
- url.hostname === "::1" ||
1017
- url.hostname === "0.0.0.0");
1018
- }
1019
- /**
1020
- * Download a file from a URL
1021
- */
1022
- async _downloadFile(url) {
1023
- // Fetch the file
1024
- const response = await fetch(url);
1025
- if (!response.ok) {
1026
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
1027
- }
1028
- return await response.arrayBuffer();
1029
- }
1030
981
  /**
1031
982
  * WORKFLOW UTILITIES
1032
983
  */
@@ -1067,15 +1018,15 @@ export class Agent {
1067
1018
  * @param spec Configuration for the agent acting as an action, including
1068
1019
  * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1069
1020
  */
1070
- asTextAction(spec) {
1071
- const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1021
+ asTextAction(spec, overrides) {
1072
1022
  return internalActionGeneric({
1073
1023
  args: vTextArgs,
1074
1024
  handler: async (ctx_, args) => {
1075
1025
  const stream = args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1076
1026
  const targetArgs = { userId: args.userId, threadId: args.threadId };
1077
1027
  const llmArgs = {
1078
- stopWhen,
1028
+ stopWhen: spec?.stopWhen ?? this.options.stopWhen,
1029
+ ...overrides,
1079
1030
  ...omit(args, ["storageOptions", "contextOptions"]),
1080
1031
  messages: args.messages?.map(deserializeMessage),
1081
1032
  prompt: Array.isArray(args.prompt)
@@ -1088,7 +1039,7 @@ export class Agent {
1088
1039
  }
1089
1040
  const opts = {
1090
1041
  ...this.options,
1091
- ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
1042
+ ...pick(spec, ["contextOptions", "storageOptions"]),
1092
1043
  ...pick(args, ["contextOptions", "storageOptions"]),
1093
1044
  saveStreamDeltas: stream,
1094
1045
  };
@@ -1101,10 +1052,11 @@ export class Agent {
1101
1052
  await result.consumeStream();
1102
1053
  return {
1103
1054
  text: await result.text,
1104
- messageId: result.messageId,
1055
+ promptMessageId: result.promptMessageId,
1105
1056
  order: result.order,
1106
1057
  finishReason: await result.finishReason,
1107
1058
  warnings: result.warnings,
1059
+ savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1108
1060
  };
1109
1061
  }
1110
1062
  else {
@@ -1112,10 +1064,11 @@ export class Agent {
1112
1064
  const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1113
1065
  return {
1114
1066
  text: res.text,
1115
- messageId: res.messageId,
1067
+ promptMessageId: res.promptMessageId,
1116
1068
  order: res.order,
1117
1069
  finishReason: res.finishReason,
1118
1070
  warnings: res.warnings,
1071
+ savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
1119
1072
  };
1120
1073
  }
1121
1074
  },
@@ -1128,22 +1081,37 @@ export class Agent {
1128
1081
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1129
1082
  * and stopWhen.
1130
1083
  */
1131
- asObjectAction(spec, options) {
1084
+ asObjectAction(objectArgs, options) {
1132
1085
  return internalActionGeneric({
1133
1086
  args: vSafeObjectArgs,
1134
- handler: async (ctx, args) => {
1135
- const { userId, threadId, ...rest } = args;
1087
+ handler: async (ctx_, args) => {
1088
+ const { userId, threadId, callSettings, ...rest } = args;
1136
1089
  const overrides = pick(rest, ["contextOptions", "storageOptions"]);
1137
- const value = await this.generateObject(ctx, { userId, threadId }, {
1138
- ...spec,
1139
- ...omit(rest, ["contextOptions", "storageOptions"]),
1140
- }, { ...this.options, ...options, ...overrides });
1090
+ const targetArgs = { userId, threadId };
1091
+ const llmArgs = {
1092
+ ...objectArgs,
1093
+ ...callSettings,
1094
+ ...omit(rest, ["storageOptions", "contextOptions"]),
1095
+ messages: args.messages?.map(deserializeMessage),
1096
+ prompt: Array.isArray(args.prompt)
1097
+ ? args.prompt.map(deserializeMessage)
1098
+ : args.prompt,
1099
+ };
1100
+ const ctx = (options?.customCtx
1101
+ ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }
1102
+ : ctx_);
1103
+ const value = await this.generateObject(ctx, targetArgs, llmArgs, {
1104
+ ...this.options,
1105
+ ...options,
1106
+ ...overrides,
1107
+ });
1141
1108
  return {
1142
- object: value.object,
1143
- messageId: value.messageId,
1109
+ object: convexToJson(value.object),
1110
+ promptMessageId: value.promptMessageId,
1144
1111
  order: value.order,
1145
1112
  finishReason: value.finishReason,
1146
1113
  warnings: value.warnings,
1114
+ savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
1147
1115
  };
1148
1116
  },
1149
1117
  });
@@ -1193,4 +1161,28 @@ export class Agent {
1193
1161
  });
1194
1162
  }
1195
1163
  }
1164
+ async function willContinue(
1165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1166
+ steps,
1167
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1168
+ stopWhen) {
1169
+ const step = steps.at(-1);
1170
+ // we aren't doing another round after a tool result
1171
+ // TODO: whether to handle continuing after too much context used..
1172
+ if (step.finishReason !== "tool-calls")
1173
+ return false;
1174
+ // we don't have a tool result, so we'll wait for more
1175
+ if (step.toolCalls.length > step.toolResults.length)
1176
+ return false;
1177
+ if (Array.isArray(stopWhen)) {
1178
+ return (await Promise.all(stopWhen.map(async (s) => s({ steps })))).every((stop) => !stop);
1179
+ }
1180
+ return !(await stopWhen?.({ steps }));
1181
+ }
1182
+ function errorToString(error) {
1183
+ if (error instanceof Error) {
1184
+ return error.message;
1185
+ }
1186
+ return String(error);
1187
+ }
1196
1188
  //# sourceMappingURL=index.js.map