@convex-dev/agent 0.2.0-alpha.2 → 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 (96) hide show
  1. package/dist/client/createTool.d.ts +3 -5
  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 +8 -1
  7. package/dist/client/files.d.ts.map +1 -1
  8. package/dist/client/files.js +60 -5
  9. package/dist/client/files.js.map +1 -1
  10. package/dist/client/index.d.ts +223 -239
  11. package/dist/client/index.d.ts.map +1 -1
  12. package/dist/client/index.js +368 -382
  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 +21 -22
  23. package/dist/client/streaming.d.ts.map +1 -1
  24. package/dist/client/streaming.js +35 -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/validators.d.ts +109 -1590
  71. package/dist/validators.d.ts.map +1 -1
  72. package/dist/validators.js +4 -81
  73. package/dist/validators.js.map +1 -1
  74. package/package.json +3 -3
  75. package/src/client/createTool.ts +4 -9
  76. package/src/client/files.ts +88 -10
  77. package/src/client/index.test.ts +9 -21
  78. package/src/client/index.ts +664 -650
  79. package/src/client/messages.ts +16 -2
  80. package/src/client/search.ts +10 -7
  81. package/src/client/streaming.ts +53 -57
  82. package/src/client/types.ts +227 -97
  83. package/src/component/_generated/api.d.ts +47 -357
  84. package/src/component/messages.ts +157 -96
  85. package/src/component/schema.ts +4 -4
  86. package/src/component/streams.ts +180 -69
  87. package/src/mapping.ts +23 -2
  88. package/src/parts.ts +39 -0
  89. package/src/react/SmoothText.tsx +9 -0
  90. package/src/react/deltas.test.ts +38 -42
  91. package/src/react/deltas.ts +24 -86
  92. package/src/react/index.ts +35 -13
  93. package/src/react/optimisticallySendMessage.ts +2 -8
  94. package/src/react/useSmoothText.ts +14 -15
  95. package/src/validators.test.ts +0 -17
  96. 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 { fetchContextMessages, filterOutOrphanedToolMessages, } from "./search.js";
25
+ export { abortStream, listStreams, syncStreams } from "./streaming.js";
24
26
  export { createThread, getThreadMetadata } 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,78 @@ 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
+ return Object.assign(result, metadata);
273
336
  }
274
337
  /**
275
338
  * This behaves like {@link generateObject} from the "ai" package except that
@@ -278,72 +341,30 @@ export class Agent {
278
341
  * Use {@link continueThread} to get a version of this function already scoped
279
342
  * to a thread (and optionally userId).
280
343
  */
281
- async generateObject(ctx, { userId: argsUserId, threadId, ...usageHandler },
344
+ async generateObject(ctx, threadOpts,
282
345
  /**
283
346
  * The arguments to the generateObject function, similar to the ai.generateObject function.
284
347
  */
285
- args,
348
+ generateObjectArgs,
286
349
  /**
287
350
  * The {@link ContextOptions} and {@link StorageOptions}
288
351
  * options to use for fetching contextual messages and saving input/output messages.
289
352
  */
290
353
  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";
354
+ const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
300
355
  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;
356
+ const result = (await generateObject(args));
357
+ await save({ object: result });
358
+ const metadata = {
359
+ promptMessageId,
360
+ order,
361
+ savedMessages: getSavedMessages(),
362
+ messageId: promptMessageId,
363
+ };
364
+ return Object.assign(result, metadata);
339
365
  }
340
366
  catch (error) {
341
- if (threadId && messageId) {
342
- await ctx.runMutation(this.component.messages.rollbackMessage, {
343
- messageId,
344
- error: error.message,
345
- });
346
- }
367
+ await fail(errorToString(error));
347
368
  throw error;
348
369
  }
349
370
  }
@@ -354,83 +375,50 @@ export class Agent {
354
375
  * Use {@link continueThread} to get a version of this function already scoped
355
376
  * to a thread (and optionally userId).
356
377
  */
357
- async streamObject(ctx, { userId: argsUserId, threadId, ...usageHandler },
378
+ async streamObject(ctx, threadOpts,
358
379
  /**
359
380
  * The arguments to the streamObject function, similar to the ai `streamObject` function.
360
381
  */
361
- args,
382
+ streamObjectArgs,
362
383
  /**
363
384
  * The {@link ContextOptions} and {@link StorageOptions}
364
385
  * options to use for fetching contextual messages and saving input/output messages.
365
386
  */
366
387
  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
388
+ const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
378
389
  const stream = streamObject({
379
390
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
- ...aiArgs,
391
+ ...args,
381
392
  onError: async (error) => {
382
- console.error("onError", error);
393
+ console.error(" streamObject onError", error);
394
+ // TODO: content that we have so far
395
+ // content: stream.fullStream.
396
+ await fail(errorToString(error.error));
383
397
  return args.onError?.(error);
384
398
  },
385
399
  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,
400
+ await save({
401
+ object: {
402
+ object: result.object,
403
+ finishReason: result.error ? "error" : "stop",
413
404
  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,
405
+ warnings: result.warnings,
422
406
  request: await stream.request,
423
407
  response: result.response,
424
- });
425
- }
426
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
408
+ providerMetadata: result.providerMetadata,
409
+ toJsonResponse: stream.toTextStreamResponse,
410
+ },
411
+ });
427
412
  return args.onFinish?.(result);
428
413
  },
429
414
  });
430
- stream.messageId = messageId;
431
- stream.order = order;
432
- stream.messages = messages;
433
- return stream;
415
+ const metadata = {
416
+ promptMessageId,
417
+ order,
418
+ savedMessages: getSavedMessages(),
419
+ messageId: promptMessageId,
420
+ };
421
+ return Object.assign(stream, metadata);
434
422
  }
435
423
  /**
436
424
  * Save a message to the thread.
@@ -451,6 +439,7 @@ export class Agent {
451
439
  : [args.message],
452
440
  metadata: args.metadata ? [args.metadata] : undefined,
453
441
  skipEmbeddings: args.skipEmbeddings,
442
+ pendingMessageId: args.pendingMessageId,
454
443
  });
455
444
  const message = messages.at(-1);
456
445
  return { messageId: message._id, message };
@@ -469,7 +458,7 @@ export class Agent {
469
458
  if (args.embeddings) {
470
459
  embeddings = args.embeddings;
471
460
  }
472
- else if (!skipEmbeddings && this.options.textEmbedding) {
461
+ else if (!skipEmbeddings && this.options.textEmbeddingModel) {
473
462
  if (!("runAction" in ctx)) {
474
463
  console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
475
464
  "Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
@@ -535,14 +524,14 @@ export class Agent {
535
524
  contextOptions,
536
525
  getEmbedding: async (text) => {
537
526
  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");
527
+ assert(this.options.textEmbeddingModel, "A textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
539
528
  return {
540
529
  embedding: (await this.doEmbed(ctx, {
541
530
  userId: args.userId,
542
531
  threadId: args.threadId,
543
532
  values: [text],
544
533
  })).embeddings[0],
545
- embeddingModel: this.options.textEmbedding,
534
+ textEmbeddingModel: this.options.textEmbeddingModel,
546
535
  };
547
536
  },
548
537
  });
@@ -573,7 +562,7 @@ export class Agent {
573
562
  * @returns The embeddings for the messages.
574
563
  */
575
564
  async generateEmbeddings(ctx, { userId, threadId, }, messages) {
576
- if (!this.options.textEmbedding) {
565
+ if (!this.options.textEmbeddingModel) {
577
566
  return undefined;
578
567
  }
579
568
  let embeddings;
@@ -585,11 +574,12 @@ export class Agent {
585
574
  if (textIndexes.length === 0) {
586
575
  return undefined;
587
576
  }
577
+ const values = messageTexts.filter((t) => !!t);
588
578
  // Then embed those messages.
589
579
  const textEmbeddings = await this.doEmbed(ctx, {
590
580
  userId,
591
581
  threadId,
592
- values: messageTexts,
582
+ values,
593
583
  });
594
584
  // Then assemble the embeddings into a single array with nulls for the messages without text.
595
585
  const embeddingsOrNull = Array(messages.length).fill(null);
@@ -599,7 +589,7 @@ export class Agent {
599
589
  if (textEmbeddings.embeddings.length > 0) {
600
590
  const dimension = textEmbeddings.embeddings[0].length;
601
591
  validateVectorDimension(dimension);
602
- const model = getModelName(this.options.textEmbedding);
592
+ const model = getModelName(this.options.textEmbeddingModel);
603
593
  embeddings = { vectors: embeddingsOrNull, dimension, model };
604
594
  }
605
595
  return embeddings;
@@ -640,8 +630,8 @@ export class Agent {
640
630
  threadId: messagesMissingEmbeddings[0].threadId,
641
631
  }, messagesMissingEmbeddings.map((m) => deserializeMessage(m.message)));
642
632
  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.");
633
+ if (!this.options.textEmbeddingModel) {
634
+ throw new Error("No embeddings were generated for the messages. You must pass a textEmbeddingModel to the agent constructor.");
645
635
  }
646
636
  throw new Error("No embeddings were generated for these messages: " +
647
637
  messagesMissingEmbeddings.map((m) => m._id).join(", "));
@@ -666,9 +656,9 @@ export class Agent {
666
656
  * @param args The Step generated by the AI SDK.
667
657
  */
668
658
  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),
659
+ const { messages } = await serializeNewMessagesInStep(ctx, this.component, args.step, {
660
+ provider: args.provider ?? getProviderName(this.options.languageModel),
661
+ model: args.model ?? getModelName(this.options.languageModel),
672
662
  });
673
663
  const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
674
664
  return ctx.runMutation(this.component.messages.addMessages, {
@@ -690,10 +680,14 @@ export class Agent {
690
680
  */
691
681
  async saveObject(ctx, args) {
692
682
  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),
683
+ model: args.model ??
684
+ args.metadata?.model ??
685
+ getModelName(this.options.languageModel),
686
+ provider: args.provider ??
687
+ args.metadata?.provider ??
688
+ getProviderName(this.options.languageModel),
695
689
  });
696
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => deserializeMessage(m.message)));
690
+ const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
697
691
  return ctx.runMutation(this.component.messages.addMessages, {
698
692
  userId: args.userId,
699
693
  threadId: args.threadId,
@@ -712,19 +706,11 @@ export class Agent {
712
706
  * @param args What message to save. Generally the parent message sent into
713
707
  * the generateText call.
714
708
  */
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
- }
709
+ async finalizeMessage(ctx, args) {
710
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
711
+ messageId: args.messageId,
712
+ result: args.result,
713
+ });
728
714
  }
729
715
  /**
730
716
  * Update a message by its id.
@@ -848,7 +834,8 @@ export class Agent {
848
834
  const userId = argsUserId ??
849
835
  (threadId &&
850
836
  (await ctx.runQuery(this.component.threads.getThread, { threadId }))
851
- ?.userId);
837
+ ?.userId) ??
838
+ undefined;
852
839
  // If only a messageId is provided, this will add that message to the end.
853
840
  const contextMessages = await this.fetchContextMessages(ctx, {
854
841
  userId,
@@ -865,30 +852,45 @@ export class Agent {
865
852
  const promptMessage = promptMessageIndex !== -1
866
853
  ? contextMessages.splice(promptMessageIndex, 1)[0]
867
854
  : undefined;
868
- let messageId = promptMessage?._id;
855
+ let promptMessageId = promptMessage?._id;
869
856
  let order = promptMessage?.order;
870
857
  let stepOrder = promptMessage?.stepOrder;
871
858
  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;
859
+ let pendingMessageId = undefined;
860
+ if (threadId && storageOptions?.saveMessages !== "none") {
861
+ let saved;
862
+ if (messages.length + prompt.length &&
863
+ // If it was a promptMessageId, we don't want to save it again.
864
+ (!args.promptMessageId || storageOptions?.saveMessages === "all")) {
865
+ const saveAll = storageOptions?.saveMessages === "all";
866
+ const coreMessages = [...messages, ...prompt];
867
+ const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
868
+ const metadata = Array.from({ length: toSave.length }, () => ({}));
869
+ saved = await this.saveMessages(ctx, {
870
+ threadId,
871
+ userId,
872
+ messages: [...toSave, { role: "assistant", content: [] }],
873
+ metadata: [...metadata, { status: "pending" }],
874
+ failPendingSteps: true,
875
+ pendingMessageId: args.pendingMessageId,
876
+ });
877
+ promptMessageId = saved.messages.at(-2)._id;
878
+ }
879
+ else {
880
+ saved = await this.saveMessages(ctx, {
881
+ threadId,
882
+ userId,
883
+ messages: [{ role: "assistant", content: [] }],
884
+ metadata: [{ status: "pending" }],
885
+ failPendingSteps: true,
886
+ pendingMessageId: args.pendingMessageId,
887
+ });
888
+ }
889
+ pendingMessageId = saved.messages.at(-1)._id;
889
890
  order = saved.messages.at(-1).order;
890
891
  stepOrder = saved.messages.at(-1).stepOrder;
891
- savedMessages = saved.messages;
892
+ // Don't return the pending message
893
+ savedMessages = saved.messages.slice(0, -1);
892
894
  }
893
895
  if (promptMessage?.message) {
894
896
  if (!args.prompt) {
@@ -898,7 +900,7 @@ export class Agent {
898
900
  // Lazily generate embeddings for the prompt message, if it doesn't have
899
901
  // embeddings yet. This can happen if the message was saved in a mutation
900
902
  // where the LLM is not available.
901
- if (!promptMessage.embeddingId && this.options.textEmbedding) {
903
+ if (!promptMessage.embeddingId && this.options.textEmbeddingModel) {
902
904
  await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
903
905
  }
904
906
  }
@@ -924,27 +926,28 @@ export class Agent {
924
926
  }
925
927
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
926
928
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
927
- processedMessages = await this._inlineMessagesFiles(processedMessages);
929
+ processedMessages = await inlineMessagesFiles(processedMessages);
928
930
  }
929
931
  const { prompt: _, model, ...rest } = args;
930
932
  return {
931
933
  args: {
932
934
  ...this.options.callSettings,
933
935
  ...rest,
934
- model: model ?? this.options.chat,
936
+ model: model ?? this.options.languageModel,
935
937
  system: args.system ?? this.options.instructions,
936
938
  messages: processedMessages,
937
939
  },
938
940
  userId,
939
- messageId,
941
+ promptMessageId,
942
+ pendingMessageId,
940
943
  savedMessages,
941
944
  order,
942
945
  stepOrder,
943
946
  };
944
947
  }
945
948
  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");
949
+ const embeddingModel = this.options.textEmbeddingModel;
950
+ assert(embeddingModel, "a textEmbeddingModel is required to be set on the Agent that you're doing vector search with");
948
951
  const result = await embedMany({
949
952
  ...this.options.callSettings,
950
953
  model: embeddingModel,
@@ -969,64 +972,6 @@ export class Agent {
969
972
  }
970
973
  return { embeddings: result.embeddings };
971
974
  }
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
975
  /**
1031
976
  * WORKFLOW UTILITIES
1032
977
  */
@@ -1067,15 +1012,15 @@ export class Agent {
1067
1012
  * @param spec Configuration for the agent acting as an action, including
1068
1013
  * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1069
1014
  */
1070
- asTextAction(spec) {
1071
- const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1015
+ asTextAction(spec, overrides) {
1072
1016
  return internalActionGeneric({
1073
1017
  args: vTextArgs,
1074
1018
  handler: async (ctx_, args) => {
1075
1019
  const stream = args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1076
1020
  const targetArgs = { userId: args.userId, threadId: args.threadId };
1077
1021
  const llmArgs = {
1078
- stopWhen,
1022
+ stopWhen: spec?.stopWhen ?? this.options.stopWhen,
1023
+ ...overrides,
1079
1024
  ...omit(args, ["storageOptions", "contextOptions"]),
1080
1025
  messages: args.messages?.map(deserializeMessage),
1081
1026
  prompt: Array.isArray(args.prompt)
@@ -1088,7 +1033,7 @@ export class Agent {
1088
1033
  }
1089
1034
  const opts = {
1090
1035
  ...this.options,
1091
- ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
1036
+ ...pick(spec, ["contextOptions", "storageOptions"]),
1092
1037
  ...pick(args, ["contextOptions", "storageOptions"]),
1093
1038
  saveStreamDeltas: stream,
1094
1039
  };
@@ -1101,10 +1046,11 @@ export class Agent {
1101
1046
  await result.consumeStream();
1102
1047
  return {
1103
1048
  text: await result.text,
1104
- messageId: result.messageId,
1049
+ promptMessageId: result.promptMessageId,
1105
1050
  order: result.order,
1106
1051
  finishReason: await result.finishReason,
1107
1052
  warnings: result.warnings,
1053
+ savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1108
1054
  };
1109
1055
  }
1110
1056
  else {
@@ -1112,10 +1058,11 @@ export class Agent {
1112
1058
  const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1113
1059
  return {
1114
1060
  text: res.text,
1115
- messageId: res.messageId,
1061
+ promptMessageId: res.promptMessageId,
1116
1062
  order: res.order,
1117
1063
  finishReason: res.finishReason,
1118
1064
  warnings: res.warnings,
1065
+ savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
1119
1066
  };
1120
1067
  }
1121
1068
  },
@@ -1128,22 +1075,37 @@ export class Agent {
1128
1075
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1129
1076
  * and stopWhen.
1130
1077
  */
1131
- asObjectAction(spec, options) {
1078
+ asObjectAction(objectArgs, options) {
1132
1079
  return internalActionGeneric({
1133
1080
  args: vSafeObjectArgs,
1134
- handler: async (ctx, args) => {
1135
- const { userId, threadId, ...rest } = args;
1081
+ handler: async (ctx_, args) => {
1082
+ const { userId, threadId, callSettings, ...rest } = args;
1136
1083
  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 });
1084
+ const targetArgs = { userId, threadId };
1085
+ const llmArgs = {
1086
+ ...objectArgs,
1087
+ ...callSettings,
1088
+ ...omit(rest, ["storageOptions", "contextOptions"]),
1089
+ messages: args.messages?.map(deserializeMessage),
1090
+ prompt: Array.isArray(args.prompt)
1091
+ ? args.prompt.map(deserializeMessage)
1092
+ : args.prompt,
1093
+ };
1094
+ const ctx = (options?.customCtx
1095
+ ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }
1096
+ : ctx_);
1097
+ const value = await this.generateObject(ctx, targetArgs, llmArgs, {
1098
+ ...this.options,
1099
+ ...options,
1100
+ ...overrides,
1101
+ });
1141
1102
  return {
1142
- object: value.object,
1143
- messageId: value.messageId,
1103
+ object: convexToJson(value.object),
1104
+ promptMessageId: value.promptMessageId,
1144
1105
  order: value.order,
1145
1106
  finishReason: value.finishReason,
1146
1107
  warnings: value.warnings,
1108
+ savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
1147
1109
  };
1148
1110
  },
1149
1111
  });
@@ -1193,4 +1155,28 @@ export class Agent {
1193
1155
  });
1194
1156
  }
1195
1157
  }
1158
+ async function willContinue(
1159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1160
+ steps,
1161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1162
+ stopWhen) {
1163
+ const step = steps.at(-1);
1164
+ // we aren't doing another round after a tool result
1165
+ // TODO: whether to handle continuing after too much context used..
1166
+ if (step.finishReason !== "tool-calls")
1167
+ return false;
1168
+ // we don't have a tool result, so we'll wait for more
1169
+ if (step.toolCalls.length > step.toolResults.length)
1170
+ return false;
1171
+ if (Array.isArray(stopWhen)) {
1172
+ return (await Promise.all(stopWhen.map(async (s) => s({ steps })))).every((stop) => !stop);
1173
+ }
1174
+ return !(await stopWhen?.({ steps }));
1175
+ }
1176
+ function errorToString(error) {
1177
+ if (error instanceof Error) {
1178
+ return error.message;
1179
+ }
1180
+ return String(error);
1181
+ }
1196
1182
  //# sourceMappingURL=index.js.map