@convex-dev/agent 0.1.18-alpha.1 → 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/client/createTool.d.ts +31 -10
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +25 -10
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/defaultComponent.d.ts +11 -0
  6. package/dist/client/defaultComponent.d.ts.map +1 -0
  7. package/dist/client/defaultComponent.js +7 -0
  8. package/dist/client/defaultComponent.js.map +1 -0
  9. package/dist/client/definePlaygroundAPI.d.ts +174 -199
  10. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  11. package/dist/client/definePlaygroundAPI.js +17 -35
  12. package/dist/client/definePlaygroundAPI.js.map +1 -1
  13. package/dist/client/files.d.ts +6 -3
  14. package/dist/client/files.d.ts.map +1 -1
  15. package/dist/client/files.js +7 -7
  16. package/dist/client/files.js.map +1 -1
  17. package/dist/client/index.d.ts +152 -589
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +138 -222
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/client/messages.d.ts +538 -0
  22. package/dist/client/messages.d.ts.map +1 -0
  23. package/dist/client/messages.js +91 -0
  24. package/dist/client/messages.js.map +1 -0
  25. package/dist/client/search.d.ts +30 -7
  26. package/dist/client/search.d.ts.map +1 -1
  27. package/dist/client/search.js +20 -3
  28. package/dist/client/search.js.map +1 -1
  29. package/dist/client/streaming.d.ts +3 -2
  30. package/dist/client/streaming.d.ts.map +1 -1
  31. package/dist/client/streaming.js.map +1 -1
  32. package/dist/client/threads.d.ts +46 -0
  33. package/dist/client/threads.d.ts.map +1 -0
  34. package/dist/client/threads.js +49 -0
  35. package/dist/client/threads.js.map +1 -0
  36. package/dist/client/types.d.ts +39 -42
  37. package/dist/client/types.d.ts.map +1 -1
  38. package/dist/component/_generated/api.d.ts +437 -75
  39. package/dist/component/messages.d.ts +254 -55
  40. package/dist/component/messages.d.ts.map +1 -1
  41. package/dist/component/messages.js +29 -25
  42. package/dist/component/messages.js.map +1 -1
  43. package/dist/component/schema.d.ts +1459 -158
  44. package/dist/component/schema.d.ts.map +1 -1
  45. package/dist/component/schema.js +8 -14
  46. package/dist/component/schema.js.map +1 -1
  47. package/dist/component/streams.d.ts +180 -6
  48. package/dist/component/streams.d.ts.map +1 -1
  49. package/dist/mapping.d.ts +11 -15
  50. package/dist/mapping.d.ts.map +1 -1
  51. package/dist/mapping.js +191 -61
  52. package/dist/mapping.js.map +1 -1
  53. package/dist/react/deltas.d.ts +0 -3
  54. package/dist/react/deltas.d.ts.map +1 -1
  55. package/dist/react/deltas.js +140 -44
  56. package/dist/react/deltas.js.map +1 -1
  57. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  58. package/dist/react/optimisticallySendMessage.js +2 -1
  59. package/dist/react/optimisticallySendMessage.js.map +1 -1
  60. package/dist/react/toUIMessages.d.ts +5 -4
  61. package/dist/react/toUIMessages.d.ts.map +1 -1
  62. package/dist/react/toUIMessages.js +103 -40
  63. package/dist/react/toUIMessages.js.map +1 -1
  64. package/dist/validators.d.ts +1978 -1210
  65. package/dist/validators.d.ts.map +1 -1
  66. package/dist/validators.js +90 -54
  67. package/dist/validators.js.map +1 -1
  68. package/package.json +32 -28
  69. package/src/client/createTool.ts +69 -38
  70. package/src/client/defaultComponent.ts +17 -0
  71. package/src/client/definePlaygroundAPI.ts +29 -43
  72. package/src/client/files.ts +7 -8
  73. package/src/client/index.test.ts +20 -18
  74. package/src/client/index.ts +228 -434
  75. package/src/client/messages.ts +191 -0
  76. package/src/client/search.ts +30 -6
  77. package/src/client/streaming.ts +4 -3
  78. package/src/client/threads.ts +78 -0
  79. package/src/client/types.ts +50 -72
  80. package/src/component/_generated/api.d.ts +437 -75
  81. package/src/component/messages.test.ts +182 -40
  82. package/src/component/messages.ts +30 -32
  83. package/src/component/schema.ts +8 -14
  84. package/src/mapping.ts +230 -94
  85. package/src/react/deltas.ts +165 -52
  86. package/src/react/optimisticallySendMessage.ts +4 -1
  87. package/src/react/toUIMessages.test.ts +154 -36
  88. package/src/react/toUIMessages.ts +136 -57
  89. package/src/validators.test.ts +2 -101
  90. package/src/validators.ts +111 -68
  91. package/dist/client/listMessages.d.ts +0 -22
  92. package/dist/client/listMessages.d.ts.map +0 -1
  93. package/dist/client/listMessages.js +0 -25
  94. package/dist/client/listMessages.js.map +0 -1
  95. package/src/client/listMessages.ts +0 -38
  96. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,24 +1,28 @@
1
- import { embedMany, generateObject, generateText, streamObject, streamText, } from "ai";
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
4
  import { v } from "convex/values";
5
5
  import { validateVectorDimension, } from "../component/vector/tables.js";
6
- import { deserializeMessage, promptOrMessagesToCoreMessages, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
6
+ import { deserializeMessage, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
7
7
  import { extractText, isTool } from "../shared.js";
8
- import { vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
8
+ import { vMessageEmbeddings, vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
9
9
  import { createTool, wrapTools } from "./createTool.js";
10
- import { listMessages } from "./listMessages.js";
11
- import { fetchContextMessages } from "./search.js";
10
+ import { listMessages, saveMessages, } from "./messages.js";
11
+ import { createThread, getThreadMetadata } from "./threads.js";
12
+ import { fetchContextMessages, getModelName, getProviderName, } from "./search.js";
12
13
  import { DeltaStreamer, mergeTransforms, syncStreams, } from "./streaming.js";
14
+ export { stepCountIs } from "ai";
13
15
  export { vMessageDoc, vThreadDoc } from "../component/schema.js";
14
- export { serializeDataOrUrl } from "../mapping.js";
16
+ export { serializeDataOrUrl, deserializeMessage, serializeMessage, } from "../mapping.js";
15
17
  // NOTE: these are also exported via @convex-dev/agent/validators
16
18
  // a future version may put them all here or move these over there
17
19
  export { vAssistantMessage, vContextOptions, vMessage, vPaginationResult, vProviderMetadata, vStorageOptions, vStreamArgs, vSystemMessage, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
18
20
  export { getFile, storeFile } from "./files.js";
19
- export { filterOutOrphanedToolMessages } from "./search.js";
20
- export { abortStream, listStreams } from "./streaming.js";
21
- export { createTool, extractText, fetchContextMessages, isTool, listMessages, syncStreams, };
21
+ export { filterOutOrphanedToolMessages, fetchContextMessages, } from "./search.js";
22
+ export { abortStream, listStreams, syncStreams } from "./streaming.js";
23
+ export { listMessages, saveMessage, saveMessages, } from "./messages.js";
24
+ export { createThread, getThreadMetadata } from "./threads.js";
25
+ export { createTool, extractText, isTool };
22
26
  export { definePlaygroundAPI, } from "./definePlaygroundAPI.js";
23
27
  export class Agent {
24
28
  component;
@@ -38,10 +42,7 @@ export class Agent {
38
42
  usageHandler: args?.usageHandler,
39
43
  tools: args?.tools,
40
44
  });
41
- return {
42
- threadId,
43
- thread,
44
- };
45
+ return { threadId, thread };
45
46
  }
46
47
  /**
47
48
  * Continues a thread using this agent. Note: threads can be continued
@@ -69,18 +70,6 @@ export class Agent {
69
70
  },
70
71
  };
71
72
  }
72
- /**
73
- * Search for threads by title, paginated.
74
- * @param ctx The context passed from the query/mutation/action.
75
- * @returns The threads matching the search, paginated.
76
- */
77
- async searchThreadTitles(ctx, { userId, query, limit, }) {
78
- return ctx.runQuery(this.component.threads.searchThreadTitles, {
79
- userId,
80
- query,
81
- limit: limit ?? 10,
82
- });
83
- }
84
73
  /**
85
74
  * This behaves like {@link generateText} from the "ai" package except that
86
75
  * it add context based on the userId and threadId and saves the input and
@@ -93,14 +82,15 @@ export class Agent {
93
82
  * for the {@link ContextOptions} and {@link StorageOptions}.
94
83
  * @returns The result of the generateText function.
95
84
  */
96
- async generateText(ctx, { userId: argsUserId, threadId, usageHandler, tools: threadTools, }, args, options) {
97
- const opts = { ...this.options, ...options, usageHandler };
85
+ async generateText(ctx, { userId: argsUserId, threadId, tools: threadTools, ...usageHandler }, args, options) {
86
+ const opts = { ...this.options, ...options, ...usageHandler };
98
87
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
99
88
  userId: argsUserId ?? undefined,
100
89
  threadId,
101
90
  ...opts,
102
91
  });
103
92
  const { args: aiArgs, messageId, order, userId } = context;
93
+ const messages = context.savedMessages ?? [];
104
94
  const toolCtx = {
105
95
  ...ctx,
106
96
  userId,
@@ -113,12 +103,12 @@ export class Agent {
113
103
  try {
114
104
  const result = (await generateText({
115
105
  // Can be overridden
116
- maxSteps: this.options.maxSteps,
106
+ stopWhen: this.options.stopWhen,
117
107
  ...aiArgs,
118
108
  tools,
119
109
  onStepFinish: async (step) => {
120
110
  if (threadId && messageId && saveOutput) {
121
- await this.saveStep(ctx, {
111
+ const saved = await this.saveStep(ctx, {
122
112
  userId,
123
113
  threadId,
124
114
  promptMessageId: messageId,
@@ -126,6 +116,7 @@ export class Agent {
126
116
  provider: aiArgs.model.provider,
127
117
  step,
128
118
  });
119
+ messages.push(...saved.messages);
129
120
  }
130
121
  if (this.options.rawRequestResponseHandler) {
131
122
  await this.options.rawRequestResponseHandler(ctx, {
@@ -152,6 +143,7 @@ export class Agent {
152
143
  }));
153
144
  result.messageId = messageId;
154
145
  result.order = order;
146
+ result.messages = messages;
155
147
  return result;
156
148
  }
157
149
  catch (error) {
@@ -172,9 +164,9 @@ export class Agent {
172
164
  * Use {@link continueThread} to get a version of this function already scoped
173
165
  * to a thread (and optionally userId).
174
166
  */
175
- async streamText(ctx, { userId: argsUserId, threadId, usageHandler,
167
+ async streamText(ctx, { userId: argsUserId, threadId,
176
168
  /** Note: to get better type inference, pass tools in the next arg */
177
- tools: threadTools, },
169
+ tools: threadTools, ...usageHandler },
178
170
  /**
179
171
  * The arguments to the streamText function, similar to the ai `streamText` function.
180
172
  */
@@ -184,13 +176,14 @@ export class Agent {
184
176
  * options to use for fetching contextual messages and saving input/output messages.
185
177
  */
186
178
  options) {
187
- const opts = { ...this.options, ...options, usageHandler };
179
+ const opts = { ...this.options, ...options, ...usageHandler };
188
180
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
189
181
  userId: argsUserId ?? undefined,
190
182
  threadId,
191
183
  ...opts,
192
184
  });
193
185
  const { args: aiArgs, messageId, order, stepOrder, userId } = context;
186
+ const messages = context.savedMessages ?? [];
194
187
  const toolCtx = {
195
188
  ...ctx,
196
189
  userId,
@@ -214,8 +207,8 @@ export class Agent {
214
207
  })
215
208
  : undefined;
216
209
  const result = streamText({
217
- // Can be overridden
218
- maxSteps: this.options.maxSteps,
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
+ stopWhen: this.options.stopWhen, // Can be overridden
219
212
  ...aiArgs,
220
213
  tools,
221
214
  abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
@@ -248,6 +241,7 @@ export class Agent {
248
241
  step,
249
242
  });
250
243
  await streamer?.finish(saved.messages);
244
+ messages.push(...saved.messages);
251
245
  }
252
246
  if (this.options.rawRequestResponseHandler) {
253
247
  await this.options.rawRequestResponseHandler(ctx, {
@@ -274,6 +268,7 @@ export class Agent {
274
268
  });
275
269
  result.messageId = messageId;
276
270
  result.order = order;
271
+ result.messages = messages;
277
272
  return result;
278
273
  }
279
274
  /**
@@ -283,7 +278,7 @@ export class Agent {
283
278
  * Use {@link continueThread} to get a version of this function already scoped
284
279
  * to a thread (and optionally userId).
285
280
  */
286
- async generateObject(ctx, { userId: argsUserId, threadId, usageHandler, },
281
+ async generateObject(ctx, { userId: argsUserId, threadId, ...usageHandler },
287
282
  /**
288
283
  * The arguments to the generateObject function, similar to the ai.generateObject function.
289
284
  */
@@ -293,20 +288,21 @@ export class Agent {
293
288
  * options to use for fetching contextual messages and saving input/output messages.
294
289
  */
295
290
  options) {
296
- const opts = { ...this.options, ...options, usageHandler };
291
+ const opts = { ...this.options, ...options, ...usageHandler };
297
292
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
298
293
  userId: argsUserId ?? undefined,
299
294
  threadId,
300
295
  ...opts,
301
296
  });
302
297
  const { args: aiArgs, messageId, order, userId } = context;
298
+ const messages = context.savedMessages ?? [];
303
299
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
304
300
  try {
305
301
  const result = (await generateObject(
306
302
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
303
  aiArgs));
308
304
  if (threadId && messageId && saveOutput) {
309
- await this.saveObject(ctx, {
305
+ const saved = await this.saveObject(ctx, {
310
306
  threadId,
311
307
  promptMessageId: messageId,
312
308
  result,
@@ -314,9 +310,11 @@ export class Agent {
314
310
  model: aiArgs.model.modelId,
315
311
  provider: aiArgs.model.provider,
316
312
  });
313
+ messages.push(...saved.messages);
317
314
  }
318
315
  result.messageId = messageId;
319
316
  result.order = order;
317
+ result.messages = messages;
320
318
  if (this.options.rawRequestResponseHandler) {
321
319
  await this.options.rawRequestResponseHandler(ctx, {
322
320
  userId,
@@ -356,7 +354,7 @@ export class Agent {
356
354
  * Use {@link continueThread} to get a version of this function already scoped
357
355
  * to a thread (and optionally userId).
358
356
  */
359
- async streamObject(ctx, { userId: argsUserId, threadId, usageHandler, },
357
+ async streamObject(ctx, { userId: argsUserId, threadId, ...usageHandler },
360
358
  /**
361
359
  * The arguments to the streamObject function, similar to the ai `streamObject` function.
362
360
  */
@@ -367,14 +365,16 @@ export class Agent {
367
365
  */
368
366
  options) {
369
367
  // TODO: unify all this shared code between all the generate* and stream* functions
370
- const opts = { ...this.options, ...options, usageHandler };
368
+ const opts = { ...this.options, ...options, ...usageHandler };
371
369
  const context = await this._saveMessagesAndFetchContext(ctx, args, {
372
370
  userId: argsUserId ?? undefined,
373
371
  threadId,
374
372
  ...opts,
375
373
  });
376
374
  const { args: aiArgs, messageId, order, userId } = context;
375
+ const messages = context.savedMessages ?? [];
377
376
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
377
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
378
378
  const stream = streamObject({
379
379
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
380
  ...aiArgs,
@@ -384,7 +384,7 @@ export class Agent {
384
384
  },
385
385
  onFinish: async (result) => {
386
386
  if (threadId && messageId && saveOutput) {
387
- await this.saveObject(ctx, {
387
+ const saved = await this.saveObject(ctx, {
388
388
  userId,
389
389
  threadId,
390
390
  promptMessageId: messageId,
@@ -396,13 +396,12 @@ export class Agent {
396
396
  request: await stream.request,
397
397
  response: result.response,
398
398
  providerMetadata: result.providerMetadata,
399
- experimental_providerMetadata: result.experimental_providerMetadata,
400
- logprobs: undefined,
401
399
  toJsonResponse: stream.toTextStreamResponse,
402
400
  },
403
401
  model: aiArgs.model.modelId,
404
402
  provider: aiArgs.model.provider,
405
403
  });
404
+ messages.push(...saved.messages);
406
405
  }
407
406
  if (opts.usageHandler && result.usage) {
408
407
  await opts.usageHandler(ctx, {
@@ -430,6 +429,7 @@ export class Agent {
430
429
  });
431
430
  stream.messageId = messageId;
432
431
  stream.order = order;
432
+ stream.messages = messages;
433
433
  return stream;
434
434
  }
435
435
  /**
@@ -440,14 +440,11 @@ export class Agent {
440
440
  * @returns The messageId of the saved message.
441
441
  */
442
442
  async saveMessage(ctx, args) {
443
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
443
+ const { messages } = await this.saveMessages(ctx, {
444
444
  threadId: args.threadId,
445
445
  userId: args.userId,
446
446
  embeddings: args.embedding
447
- ? {
448
- model: args.embedding.model,
449
- vectors: [args.embedding.vector],
450
- }
447
+ ? { model: args.embedding.model, vectors: [args.embedding.vector] }
451
448
  : undefined,
452
449
  messages: args.prompt !== undefined
453
450
  ? [{ role: "user", content: args.prompt }]
@@ -455,7 +452,8 @@ export class Agent {
455
452
  metadata: args.metadata ? [args.metadata] : undefined,
456
453
  skipEmbeddings: args.skipEmbeddings,
457
454
  });
458
- return { messageId: lastMessageId, message: messages.at(-1) };
455
+ const message = messages.at(-1);
456
+ return { messageId: message._id, message };
459
457
  }
460
458
  /**
461
459
  * Explicitly save messages associated with the thread (& user if provided)
@@ -485,10 +483,7 @@ export class Agent {
485
483
  "You can explicitly generate them asynchronously by using the scheduler to run an action later that calls `agent.generateAndSaveEmbeddings`.");
486
484
  }
487
485
  else {
488
- embeddings = await this.generateEmbeddings(ctx, {
489
- userId: args.userId ?? undefined,
490
- threadId: args.threadId,
491
- }, args.messages);
486
+ embeddings = await this.generateEmbeddings(ctx, { userId: args.userId ?? undefined, threadId: args.threadId }, args.messages);
492
487
  }
493
488
  }
494
489
  return saveMessages(ctx, this.component, {
@@ -547,7 +542,7 @@ export class Agent {
547
542
  threadId: args.threadId,
548
543
  values: [text],
549
544
  })).embeddings[0],
550
- embeddingModel: this.options.textEmbedding.modelId,
545
+ embeddingModel: this.options.textEmbedding,
551
546
  };
552
547
  },
553
548
  });
@@ -596,7 +591,6 @@ export class Agent {
596
591
  threadId,
597
592
  values: messageTexts,
598
593
  });
599
- // TODO: record usage of embeddings
600
594
  // Then assemble the embeddings into a single array with nulls for the messages without text.
601
595
  const embeddingsOrNull = Array(messages.length).fill(null);
602
596
  textIndexes.forEach((i, j) => {
@@ -605,11 +599,8 @@ export class Agent {
605
599
  if (textEmbeddings.embeddings.length > 0) {
606
600
  const dimension = textEmbeddings.embeddings[0].length;
607
601
  validateVectorDimension(dimension);
608
- embeddings = {
609
- vectors: embeddingsOrNull,
610
- dimension,
611
- model: this.options.textEmbedding.modelId,
612
- };
602
+ const model = getModelName(this.options.textEmbedding);
603
+ embeddings = { vectors: embeddingsOrNull, dimension, model };
613
604
  }
614
605
  return embeddings;
615
606
  }
@@ -647,7 +638,7 @@ export class Agent {
647
638
  const embeddings = await this.generateEmbeddings(ctx, {
648
639
  userId: messagesMissingEmbeddings[0].userId,
649
640
  threadId: messagesMissingEmbeddings[0].threadId,
650
- }, messagesMissingEmbeddings.map((m) => m.message));
641
+ }, messagesMissingEmbeddings.map((m) => deserializeMessage(m.message)));
651
642
  if (!embeddings) {
652
643
  if (!this.options.textEmbedding) {
653
644
  throw new Error("No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.");
@@ -676,11 +667,11 @@ export class Agent {
676
667
  */
677
668
  async saveStep(ctx, args) {
678
669
  const messages = await serializeNewMessagesInStep(ctx, this.component, args.step, {
679
- provider: args.provider ?? this.options.chat.provider,
680
- model: args.model ?? this.options.chat.modelId,
670
+ provider: args.provider ?? getProviderName(this.options.chat),
671
+ model: args.model ?? getModelName(this.options.chat),
681
672
  });
682
673
  const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
683
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
674
+ return ctx.runMutation(this.component.messages.addMessages, {
684
675
  userId: args.userId,
685
676
  threadId: args.threadId,
686
677
  agentName: this.options.name,
@@ -689,7 +680,6 @@ export class Agent {
689
680
  embeddings,
690
681
  failPendingSteps: false,
691
682
  });
692
- return saved;
693
683
  }
694
684
  /**
695
685
  * Manually save the result of a generateObject call to the thread.
@@ -700,11 +690,11 @@ export class Agent {
700
690
  */
701
691
  async saveObject(ctx, args) {
702
692
  const { messages } = await serializeObjectResult(ctx, this.component, args.result, {
703
- model: args.model ?? this.options.chat.modelId,
704
- provider: args.provider ?? this.options.chat.provider,
693
+ model: args.model ?? getModelName(this.options.chat),
694
+ provider: args.provider ?? getProviderName(this.options.chat),
705
695
  });
706
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
707
- await ctx.runMutation(this.component.messages.addMessages, {
696
+ const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => deserializeMessage(m.message)));
697
+ return ctx.runMutation(this.component.messages.addMessages, {
708
698
  userId: args.userId,
709
699
  threadId: args.threadId,
710
700
  promptMessageId: args.promptMessageId,
@@ -712,7 +702,6 @@ export class Agent {
712
702
  messages,
713
703
  embeddings,
714
704
  agentName: this.options.name,
715
- pending: false,
716
705
  });
717
706
  }
718
707
  /**
@@ -814,7 +803,7 @@ export class Agent {
814
803
  * @param args The range of messages to delete.
815
804
  */
816
805
  async deleteMessageRange(ctx, args) {
817
- await ctx.runMutation(this.component.messages.deleteByOrder, {
806
+ return ctx.runMutation(this.component.messages.deleteByOrder, {
818
807
  threadId: args.threadId,
819
808
  startOrder: args.startOrder,
820
809
  startStepOrder: args.startStepOrder,
@@ -850,7 +839,12 @@ export class Agent {
850
839
  }
851
840
  async _saveMessagesAndFetchContext(ctx, args, { userId: argsUserId, threadId, contextOptions, storageOptions, }) {
852
841
  // If only a promptMessageId is provided, this will be empty.
853
- const messages = promptOrMessagesToCoreMessages(args);
842
+ const messages = args.messages ?? [];
843
+ const prompt = !args.prompt
844
+ ? []
845
+ : Array.isArray(args.prompt)
846
+ ? args.prompt
847
+ : [{ role: "user", content: args.prompt }];
854
848
  const userId = argsUserId ??
855
849
  (threadId &&
856
850
  (await ctx.runQuery(this.component.threads.getThread, { threadId }))
@@ -865,40 +859,42 @@ export class Agent {
865
859
  });
866
860
  // If it was a promptMessageId, pop it off context messages
867
861
  // and add to the end of messages.
868
- // TODO: slice it from the prompt message, to append all of them
869
- const promptMessage = !!args.promptMessageId &&
870
- contextMessages.at(-1)?._id === args.promptMessageId
871
- ? contextMessages.pop()
862
+ const promptMessageIndex = args.promptMessageId
863
+ ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
864
+ : -1;
865
+ const promptMessage = promptMessageIndex !== -1
866
+ ? contextMessages.splice(promptMessageIndex, 1)[0]
872
867
  : undefined;
873
- if (promptMessage && args.prompt) {
874
- // If they specify both a promptMessageId and a prompt, we prefer
875
- // the prompt to stand in for the promptMessageId message.
876
- promptMessage.message = { role: "user", content: args.prompt };
877
- }
878
868
  let messageId = promptMessage?._id;
879
869
  let order = promptMessage?.order;
880
870
  let stepOrder = promptMessage?.stepOrder;
871
+ let savedMessages = undefined;
881
872
  if (threadId &&
882
- messages.length &&
873
+ messages.length + prompt.length &&
883
874
  storageOptions?.saveMessages !== "none" &&
884
875
  // If it was a promptMessageId, we don't want to save it again.
885
876
  (!args.promptMessageId || storageOptions?.saveMessages === "all")) {
886
877
  const saveAll = storageOptions?.saveMessages === "all";
887
- const coreMessages = saveAll ? messages : messages.slice(-1);
878
+ const coreMessages = [...messages, ...prompt];
879
+ const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
880
+ const metadata = Array.from({ length: toSave.length }, () => ({}));
888
881
  const saved = await this.saveMessages(ctx, {
889
882
  threadId,
890
883
  userId,
891
- messages: coreMessages,
892
- metadata: coreMessages.map((_, i) => i === coreMessages.length - 1 ? { id: args.id } : {}),
884
+ messages: toSave,
885
+ metadata,
893
886
  failPendingSteps: true,
894
887
  });
895
- messageId = saved.lastMessageId;
896
- order = saved.messages.at(-1)?.order;
897
- stepOrder = saved.messages.at(-1)?.stepOrder;
888
+ messageId = saved.messages.at(-1)._id;
889
+ order = saved.messages.at(-1).order;
890
+ stepOrder = saved.messages.at(-1).stepOrder;
891
+ savedMessages = saved.messages;
898
892
  }
899
893
  if (promptMessage?.message) {
900
- // Add the message after saving the messages, so it's not saved again.
901
- messages.push(deserializeMessage(promptMessage.message));
894
+ if (!args.prompt) {
895
+ // If they override the prompt, we skip the existing prompt message.
896
+ messages.push(deserializeMessage(promptMessage.message));
897
+ }
902
898
  // Lazily generate embeddings for the prompt message, if it doesn't have
903
899
  // embeddings yet. This can happen if the message was saved in a mutation
904
900
  // where the LLM is not available.
@@ -906,10 +902,26 @@ export class Agent {
906
902
  await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
907
903
  }
908
904
  }
905
+ const prePrompt = contextMessages.map((m) => deserializeMessage(m.message));
906
+ let existingResponses = [];
907
+ if (promptMessageIndex !== -1) {
908
+ // pull any messages that already responded to the prompt off
909
+ // and add them after the prompt
910
+ existingResponses = prePrompt.splice(promptMessageIndex);
911
+ }
909
912
  let processedMessages = [
910
- ...contextMessages.map((m) => deserializeMessage(m.message)),
913
+ ...prePrompt,
911
914
  ...messages,
915
+ ...prompt,
916
+ ...existingResponses,
912
917
  ];
918
+ if (promptMessageIndex === -1) {
919
+ processedMessages.push(...prompt);
920
+ }
921
+ else {
922
+ // We add the prompt where the prompt message was
923
+ processedMessages.splice(promptMessageIndex, 0, ...prompt);
924
+ }
913
925
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
914
926
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
915
927
  processedMessages = await this._inlineMessagesFiles(processedMessages);
@@ -917,14 +929,15 @@ export class Agent {
917
929
  const { prompt: _, model, ...rest } = args;
918
930
  return {
919
931
  args: {
932
+ ...this.options.callSettings,
920
933
  ...rest,
921
- maxRetries: args.maxRetries ?? this.options.maxRetries,
922
934
  model: model ?? this.options.chat,
923
935
  system: args.system ?? this.options.instructions,
924
936
  messages: processedMessages,
925
937
  },
926
938
  userId,
927
939
  messageId,
940
+ savedMessages,
928
941
  order,
929
942
  stepOrder,
930
943
  };
@@ -933,23 +946,23 @@ export class Agent {
933
946
  const embeddingModel = this.options.textEmbedding;
934
947
  assert(embeddingModel, "a textEmbedding model is required to be set on the Agent that you're doing vector search with");
935
948
  const result = await embedMany({
949
+ ...this.options.callSettings,
936
950
  model: embeddingModel,
937
951
  values: options.values,
938
952
  abortSignal: options.abortSignal,
939
953
  headers: options.headers,
940
- maxRetries: this.options.maxRetries,
941
954
  });
942
955
  if (this.options.usageHandler && result.usage) {
943
956
  await this.options.usageHandler(ctx, {
944
957
  userId: options.userId,
945
958
  threadId: options.threadId,
946
959
  agentName: this.options.name,
947
- model: embeddingModel.modelId,
948
- provider: embeddingModel.provider,
960
+ model: getModelName(embeddingModel),
961
+ provider: getProviderName(embeddingModel),
949
962
  providerMetadata: undefined,
950
963
  usage: {
951
- promptTokens: result.usage.tokens,
952
- completionTokens: 0,
964
+ inputTokens: result.usage.tokens,
965
+ outputTokens: 0,
953
966
  totalTokens: result.usage.tokens,
954
967
  },
955
968
  });
@@ -974,35 +987,23 @@ export class Agent {
974
987
  assert(message.role === "user", "Images can only be in user messages");
975
988
  if (this._isLocalhostUrl(part.image)) {
976
989
  const imageData = await this._downloadFile(part.image);
977
- return {
978
- ...part,
979
- image: imageData,
980
- };
990
+ return { ...part, image: imageData };
981
991
  }
982
992
  }
983
993
  // Handle file parts
984
994
  if (part.type === "file" && part.data instanceof URL) {
985
995
  if (this._isLocalhostUrl(part.data)) {
986
996
  const fileData = await this._downloadFile(part.data);
987
- return {
988
- ...part,
989
- data: fileData,
990
- };
997
+ return { ...part, data: fileData };
991
998
  }
992
999
  }
993
1000
  return part;
994
1001
  }));
995
1002
  if (message.role === "user") {
996
- return {
997
- ...message,
998
- content: processedContent,
999
- };
1003
+ return { ...message, content: processedContent };
1000
1004
  }
1001
1005
  else {
1002
- return {
1003
- ...message,
1004
- content: processedContent,
1005
- };
1006
+ return { ...message, content: processedContent };
1006
1007
  }
1007
1008
  }));
1008
1009
  }
@@ -1064,19 +1065,27 @@ export class Agent {
1064
1065
  * Create an action out of this agent so you can call it from workflows or other actions
1065
1066
  * without a wrapping function.
1066
1067
  * @param spec Configuration for the agent acting as an action, including
1067
- * {@link ContextOptions}, {@link StorageOptions}, and maxSteps.
1068
+ * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1068
1069
  */
1069
1070
  asTextAction(spec) {
1070
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1071
+ const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1071
1072
  return internalActionGeneric({
1072
1073
  args: vTextArgs,
1073
1074
  handler: async (ctx_, args) => {
1074
- const stream = args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1075
+ const stream = args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1075
1076
  const targetArgs = { userId: args.userId, threadId: args.threadId };
1076
1077
  const llmArgs = {
1077
- maxSteps,
1078
+ stopWhen,
1078
1079
  ...omit(args, ["storageOptions", "contextOptions"]),
1080
+ messages: args.messages?.map(deserializeMessage),
1081
+ prompt: Array.isArray(args.prompt)
1082
+ ? args.prompt.map(deserializeMessage)
1083
+ : args.prompt,
1084
+ toolChoice: args.toolChoice,
1079
1085
  };
1086
+ if (args.maxSteps) {
1087
+ llmArgs.stopWhen = stepCountIs(args.maxSteps);
1088
+ }
1080
1089
  const opts = {
1081
1090
  ...this.options,
1082
1091
  ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
@@ -1087,6 +1096,7 @@ export class Agent {
1087
1096
  ? { ...ctx_, ...spec.customCtx(ctx_, targetArgs, llmArgs) }
1088
1097
  : ctx_);
1089
1098
  if (stream) {
1099
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1090
1100
  const result = await this.streamText(ctx, targetArgs, llmArgs, opts);
1091
1101
  await result.consumeStream();
1092
1102
  return {
@@ -1098,6 +1108,7 @@ export class Agent {
1098
1108
  };
1099
1109
  }
1100
1110
  else {
1111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1101
1112
  const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1102
1113
  return {
1103
1114
  text: res.text,
@@ -1115,18 +1126,17 @@ export class Agent {
1115
1126
  * it from workflows or other actions without a wrapping function.
1116
1127
  * @param spec Configuration for the agent acting as an action, including
1117
1128
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1118
- * and maxSteps.
1129
+ * and stopWhen.
1119
1130
  */
1120
1131
  asObjectAction(spec, options) {
1121
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1122
1132
  return internalActionGeneric({
1123
1133
  args: vSafeObjectArgs,
1124
1134
  handler: async (ctx, args) => {
1125
- const overrides = pick(args, ["userId", "threadId"]);
1126
- const value = await this.generateObject(ctx, { userId: args.userId, threadId: args.threadId }, {
1135
+ const { userId, threadId, ...rest } = args;
1136
+ const overrides = pick(rest, ["contextOptions", "storageOptions"]);
1137
+ const value = await this.generateObject(ctx, { userId, threadId }, {
1127
1138
  ...spec,
1128
- maxSteps,
1129
- ...omit(args, ["userId", "threadId"]),
1139
+ ...omit(rest, ["contextOptions", "storageOptions"]),
1130
1140
  }, { ...this.options, ...options, ...overrides });
1131
1141
  return {
1132
1142
  object: value.object,
@@ -1165,116 +1175,22 @@ export class Agent {
1165
1175
  userId: v.optional(v.string()),
1166
1176
  promptMessageId: v.optional(v.string()),
1167
1177
  messages: v.array(vMessageWithMetadata),
1168
- pending: v.optional(v.boolean()),
1169
1178
  failPendingSteps: v.optional(v.boolean()),
1179
+ embeddings: v.optional(vMessageEmbeddings),
1170
1180
  },
1171
1181
  handler: async (ctx, args) => {
1172
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
1182
+ const { messages } = await this.saveMessages(ctx, {
1173
1183
  ...args,
1174
- messages: args.messages.map((m) => m.message),
1184
+ messages: args.messages.map((m) => deserializeMessage(m.message)),
1175
1185
  metadata: args.messages.map(({ message: _, ...m }) => m),
1186
+ skipEmbeddings: true,
1176
1187
  });
1177
1188
  return {
1178
- lastMessageId,
1179
- messageIds: messages.map((m) => m._id),
1189
+ lastMessageId: messages.at(-1)._id,
1190
+ messages: messages.map((m) => pick(m, ["_id", "order", "stepOrder"])),
1180
1191
  };
1181
1192
  },
1182
1193
  });
1183
1194
  }
1184
1195
  }
1185
- /**
1186
- * Create a thread to store messages with an Agent.
1187
- * @param ctx The context from a mutation or action.
1188
- * @param component The Agent component, usually `components.agent`.
1189
- * @param args The associated thread metadata.
1190
- * @returns The id of the created thread.
1191
- */
1192
- export async function createThread(ctx, component, args) {
1193
- const { _id: threadId } = await ctx.runMutation(component.threads.createThread, {
1194
- userId: args?.userId ?? undefined,
1195
- title: args?.title,
1196
- summary: args?.summary,
1197
- });
1198
- return threadId;
1199
- }
1200
- /**
1201
- * Get the metadata for a thread.
1202
- * @param ctx A ctx object from a query, mutation, or action.
1203
- * @param args.threadId The thread to get the metadata for.
1204
- * @returns The metadata for the thread.
1205
- */
1206
- export async function getThreadMetadata(ctx, component, args) {
1207
- const thread = await ctx.runQuery(component.threads.getThread, {
1208
- threadId: args.threadId,
1209
- });
1210
- if (!thread) {
1211
- throw new Error("Thread not found");
1212
- }
1213
- return thread;
1214
- }
1215
- /**
1216
- * Explicitly save messages associated with the thread (& user if provided)
1217
- */
1218
- export async function saveMessages(ctx, component, args) {
1219
- let embeddings;
1220
- if (args.embeddings) {
1221
- const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
1222
- if (dimension) {
1223
- validateVectorDimension(dimension);
1224
- embeddings = {
1225
- model: args.embeddings.model,
1226
- dimension,
1227
- vectors: args.embeddings.vectors,
1228
- };
1229
- }
1230
- }
1231
- const result = await ctx.runMutation(component.messages.addMessages, {
1232
- threadId: args.threadId,
1233
- userId: args.userId ?? undefined,
1234
- agentName: args.agentName,
1235
- promptMessageId: args.promptMessageId,
1236
- embeddings,
1237
- messages: await Promise.all(args.messages.map(async (m, i) => {
1238
- const { message, fileIds } = await serializeMessage(ctx, component, m);
1239
- return {
1240
- ...args.metadata?.[i],
1241
- message,
1242
- fileIds,
1243
- };
1244
- })),
1245
- failPendingSteps: args.failPendingSteps ?? false,
1246
- pending: args.pending ?? false,
1247
- });
1248
- return {
1249
- lastMessageId: result.messages.at(-1)._id,
1250
- messages: result.messages,
1251
- };
1252
- }
1253
- /**
1254
- * Save a message to the thread.
1255
- * @param ctx A ctx object from a mutation or action.
1256
- * @param args The message and what to associate it with (user / thread)
1257
- * You can pass extra metadata alongside the message, e.g. associated fileIds.
1258
- * @returns The messageId of the saved message.
1259
- */
1260
- export async function saveMessage(ctx, component, args) {
1261
- let embeddings;
1262
- if (args.embedding && args.embedding.vector) {
1263
- embeddings = {
1264
- model: args.embedding.model,
1265
- vectors: [args.embedding.vector],
1266
- };
1267
- }
1268
- const { lastMessageId, messages } = await saveMessages(ctx, component, {
1269
- threadId: args.threadId,
1270
- userId: args.userId ?? undefined,
1271
- agentName: args.agentName,
1272
- messages: args.prompt !== undefined
1273
- ? [{ role: "user", content: args.prompt }]
1274
- : [args.message],
1275
- metadata: args.metadata ? [args.metadata] : undefined,
1276
- embeddings,
1277
- });
1278
- return { messageId: lastMessageId, message: messages.at(-1) };
1279
- }
1280
1196
  //# sourceMappingURL=index.js.map