@convex-dev/agent 0.1.18-alpha.1 → 0.5.0-alpha.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 (71) hide show
  1. package/dist/client/createTool.d.ts +30 -9
  2. package/dist/client/createTool.d.ts.map +1 -1
  3. package/dist/client/createTool.js +24 -9
  4. package/dist/client/createTool.js.map +1 -1
  5. package/dist/client/definePlaygroundAPI.d.ts +174 -199
  6. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  7. package/dist/client/definePlaygroundAPI.js +12 -19
  8. package/dist/client/definePlaygroundAPI.js.map +1 -1
  9. package/dist/client/files.js +4 -4
  10. package/dist/client/files.js.map +1 -1
  11. package/dist/client/index.d.ts +166 -63
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +75 -45
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/search.d.ts +27 -6
  16. package/dist/client/search.d.ts.map +1 -1
  17. package/dist/client/search.js.map +1 -1
  18. package/dist/client/streaming.d.ts +3 -2
  19. package/dist/client/streaming.d.ts.map +1 -1
  20. package/dist/client/streaming.js.map +1 -1
  21. package/dist/client/types.d.ts +27 -41
  22. package/dist/client/types.d.ts.map +1 -1
  23. package/dist/component/_generated/api.d.ts +436 -73
  24. package/dist/component/messages.d.ts +246 -43
  25. package/dist/component/messages.d.ts.map +1 -1
  26. package/dist/component/messages.js +2 -2
  27. package/dist/component/messages.js.map +1 -1
  28. package/dist/component/schema.d.ts +1453 -152
  29. package/dist/component/schema.d.ts.map +1 -1
  30. package/dist/component/schema.js +2 -2
  31. package/dist/component/schema.js.map +1 -1
  32. package/dist/component/streams.d.ts +180 -6
  33. package/dist/component/streams.d.ts.map +1 -1
  34. package/dist/mapping.d.ts +12 -14
  35. package/dist/mapping.d.ts.map +1 -1
  36. package/dist/mapping.js +187 -47
  37. package/dist/mapping.js.map +1 -1
  38. package/dist/react/deltas.d.ts +0 -3
  39. package/dist/react/deltas.d.ts.map +1 -1
  40. package/dist/react/deltas.js +140 -44
  41. package/dist/react/deltas.js.map +1 -1
  42. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  43. package/dist/react/optimisticallySendMessage.js +2 -1
  44. package/dist/react/optimisticallySendMessage.js.map +1 -1
  45. package/dist/react/toUIMessages.d.ts +5 -4
  46. package/dist/react/toUIMessages.d.ts.map +1 -1
  47. package/dist/react/toUIMessages.js +103 -40
  48. package/dist/react/toUIMessages.js.map +1 -1
  49. package/dist/validators.d.ts +1798 -259
  50. package/dist/validators.d.ts.map +1 -1
  51. package/dist/validators.js +79 -15
  52. package/dist/validators.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/client/createTool.ts +68 -37
  55. package/src/client/definePlaygroundAPI.ts +24 -25
  56. package/src/client/files.ts +4 -4
  57. package/src/client/index.test.ts +14 -12
  58. package/src/client/index.ts +134 -94
  59. package/src/client/search.ts +3 -2
  60. package/src/client/streaming.ts +4 -3
  61. package/src/client/types.ts +34 -70
  62. package/src/component/_generated/api.d.ts +436 -73
  63. package/src/component/messages.ts +2 -2
  64. package/src/component/schema.ts +2 -2
  65. package/src/mapping.ts +228 -75
  66. package/src/react/deltas.ts +165 -52
  67. package/src/react/optimisticallySendMessage.ts +4 -1
  68. package/src/react/toUIMessages.test.ts +154 -36
  69. package/src/react/toUIMessages.ts +136 -57
  70. package/src/validators.test.ts +2 -99
  71. package/src/validators.ts +95 -17
@@ -1,7 +1,7 @@
1
- import type { EmbeddingModelV1, LanguageModelV1 } from "@ai-sdk/provider";
1
+ import type { LanguageModelV2, EmbeddingModelV2 } from "@ai-sdk/provider";
2
2
  import type {
3
3
  AssistantContent,
4
- CoreMessage,
4
+ ModelMessage,
5
5
  DeepPartial,
6
6
  FilePart,
7
7
  GenerateObjectResult,
@@ -12,11 +12,15 @@ import type {
12
12
  StreamTextResult,
13
13
  ToolSet,
14
14
  UserContent,
15
+ StopCondition,
16
+ Schema,
17
+ ToolChoice,
15
18
  } from "ai";
16
19
  import {
17
20
  embedMany,
18
21
  generateObject,
19
22
  generateText,
23
+ stepCountIs,
20
24
  streamObject,
21
25
  streamText,
22
26
  } from "ai";
@@ -38,15 +42,14 @@ import {
38
42
  type VectorDimension,
39
43
  } from "../component/vector/tables.js";
40
44
  import {
41
- type AIMessageWithoutId,
42
45
  deserializeMessage,
43
- promptOrMessagesToCoreMessages,
44
46
  serializeMessage,
45
47
  serializeNewMessagesInStep,
46
48
  serializeObjectResult,
47
49
  } from "../mapping.js";
48
50
  import { extractText, isTool } from "../shared.js";
49
51
  import {
52
+ type Message,
50
53
  type MessageEmbeddings,
51
54
  type MessageStatus,
52
55
  type MessageWithMetadata,
@@ -57,7 +60,7 @@ import {
57
60
  vSafeObjectArgs,
58
61
  vTextArgs,
59
62
  } from "../validators.js";
60
- import { createTool, wrapTools } from "./createTool.js";
63
+ import { createTool, wrapTools, type ToolCtx } from "./createTool.js";
61
64
  import { listMessages } from "./listMessages.js";
62
65
  import { fetchContextMessages } from "./search.js";
63
66
  import {
@@ -86,9 +89,15 @@ import type {
86
89
  UsageHandler,
87
90
  UserActionCtx,
88
91
  } from "./types.js";
92
+ import type z from "zod";
89
93
 
94
+ export { stepCountIs } from "ai";
90
95
  export { vMessageDoc, vThreadDoc } from "../component/schema.js";
91
- export { serializeDataOrUrl } from "../mapping.js";
96
+ export {
97
+ serializeDataOrUrl,
98
+ deserializeMessage,
99
+ serializeMessage,
100
+ } from "../mapping.js";
92
101
  // NOTE: these are also exported via @convex-dev/agent/validators
93
102
  // a future version may put them all here or move these over there
94
103
  export {
@@ -106,16 +115,12 @@ export {
106
115
  } from "../validators.js";
107
116
  export type { ToolCtx } from "./createTool.js";
108
117
  export { getFile, storeFile } from "./files.js";
109
- export { filterOutOrphanedToolMessages } from "./search.js";
110
- export { abortStream, listStreams } from "./streaming.js";
111
118
  export {
112
- createTool,
113
- extractText,
119
+ filterOutOrphanedToolMessages,
114
120
  fetchContextMessages,
115
- isTool,
116
- listMessages,
117
- syncStreams,
118
- };
121
+ } from "./search.js";
122
+ export { abortStream, listStreams, syncStreams } from "./streaming.js";
123
+ export { createTool, extractText, isTool, listMessages };
119
124
  export {
120
125
  definePlaygroundAPI,
121
126
  type PlaygroundAPI,
@@ -175,7 +180,7 @@ export class Agent<
175
180
  * const myAgent = new Agent(components.agent, {
176
181
  * chat: openai.chat("gpt-4o-mini"),
177
182
  */
178
- chat: LanguageModelV1;
183
+ chat: LanguageModelV2;
179
184
  /**
180
185
  * The model to use for text embeddings. Optional.
181
186
  * If specified, it will use this for generating vector embeddings
@@ -186,7 +191,7 @@ export class Agent<
186
191
  * const myAgent = new Agent(components.agent, {
187
192
  * textEmbedding: openai.embedding("text-embedding-3-small")
188
193
  */
189
- textEmbedding?: EmbeddingModelV1<string>;
194
+ textEmbedding?: EmbeddingModelV2<string>;
190
195
  /**
191
196
  * The default system prompt to put in each request.
192
197
  * Override per-prompt by passing the "system" parameter.
@@ -212,9 +217,9 @@ export class Agent<
212
217
  storageOptions?: StorageOptions;
213
218
  /**
214
219
  * When generating or streaming text with tools available, this
215
- * determines the default max number of iterations.
220
+ * determines when to stop. Defaults to stepCountIs(1).
216
221
  */
217
- maxSteps?: number;
222
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
218
223
  /**
219
224
  * The maximum number of calls to make to an LLM in case it fails.
220
225
  * This can be overridden at each generate/stream callsite.
@@ -471,16 +476,17 @@ export class Agent<
471
476
  threadId,
472
477
  messageId,
473
478
  agent: this,
474
- };
479
+ } satisfies ToolCtx;
480
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
475
481
  const tools = wrapTools(
476
482
  toolCtx,
477
483
  args.tools ?? threadTools ?? this.options.tools,
478
- ) as TOOLS extends undefined ? AgentTools : TOOLS;
484
+ ) as Tools;
479
485
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
480
486
  try {
481
- const result = (await generateText({
487
+ const result = (await generateText<Tools, OUTPUT, OUTPUT_PARTIAL>({
482
488
  // Can be overridden
483
- maxSteps: this.options.maxSteps,
489
+ stopWhen: this.options.stopWhen as StopCondition<Tools> | undefined,
484
490
  ...aiArgs,
485
491
  tools,
486
492
  onStepFinish: async (step) => {
@@ -516,11 +522,7 @@ export class Agent<
516
522
  }
517
523
  return args.onStepFinish?.(step);
518
524
  },
519
- })) as GenerateTextResult<
520
- TOOLS extends undefined ? AgentTools : TOOLS,
521
- OUTPUT
522
- > &
523
- GenerationOutputMetadata;
525
+ })) as GenerateTextResult<Tools, OUTPUT> & GenerationOutputMetadata;
524
526
  result.messageId = messageId;
525
527
  result.order = order;
526
528
  return result;
@@ -624,8 +626,8 @@ export class Agent<
624
626
  : undefined;
625
627
 
626
628
  const result = streamText({
627
- // Can be overridden
628
- maxSteps: this.options.maxSteps,
629
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
630
+ stopWhen: this.options.stopWhen as any, // Can be overridden
629
631
  ...aiArgs,
630
632
  tools,
631
633
  abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
@@ -787,7 +789,7 @@ export class Agent<
787
789
  * Use {@link continueThread} to get a version of this function already scoped
788
790
  * to a thread (and optionally userId).
789
791
  */
790
- async streamObject<T>(
792
+ async streamObject<T extends z.Schema | Schema>(
791
793
  ctx: ActionCtx,
792
794
  {
793
795
  userId: argsUserId,
@@ -819,7 +821,8 @@ export class Agent<
819
821
  });
820
822
  const { args: aiArgs, messageId, order, userId } = context;
821
823
  const saveOutput = opts.storageOptions?.saveMessages !== "none";
822
- const stream = streamObject<T>({
824
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
825
+ const stream = streamObject<any>({
823
826
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
824
827
  ...(aiArgs as any),
825
828
  onError: async (error) => {
@@ -840,9 +843,6 @@ export class Agent<
840
843
  request: await stream.request,
841
844
  response: result.response,
842
845
  providerMetadata: result.providerMetadata,
843
- experimental_providerMetadata:
844
- result.experimental_providerMetadata,
845
- logprobs: undefined,
846
846
  toJsonResponse: stream.toTextStreamResponse,
847
847
  },
848
848
  model: aiArgs.model.modelId,
@@ -1037,7 +1037,7 @@ export class Agent<
1037
1037
  args: {
1038
1038
  userId: string | undefined;
1039
1039
  threadId: string | undefined;
1040
- messages: CoreMessage[];
1040
+ messages: (ModelMessage | Message)[];
1041
1041
  /**
1042
1042
  * If provided, it will search for messages up to and including this message.
1043
1043
  * Note: if this is far in the past, text and vector search results may be more
@@ -1125,7 +1125,7 @@ export class Agent<
1125
1125
  userId: string | undefined;
1126
1126
  threadId: string | undefined;
1127
1127
  },
1128
- messages: CoreMessage[],
1128
+ messages: (ModelMessage | Message)[],
1129
1129
  ) {
1130
1130
  if (!this.options.textEmbedding) {
1131
1131
  return undefined;
@@ -1151,7 +1151,6 @@ export class Agent<
1151
1151
  threadId,
1152
1152
  values: messageTexts as string[],
1153
1153
  });
1154
- // TODO: record usage of embeddings
1155
1154
  // Then assemble the embeddings into a single array with nulls for the messages without text.
1156
1155
  const embeddingsOrNull = Array(messages.length).fill(null);
1157
1156
  textIndexes.forEach((i, j) => {
@@ -1218,7 +1217,7 @@ export class Agent<
1218
1217
  userId: messagesMissingEmbeddings[0]!.userId,
1219
1218
  threadId: messagesMissingEmbeddings[0]!.threadId,
1220
1219
  },
1221
- messagesMissingEmbeddings.map((m) => m!.message!),
1220
+ messagesMissingEmbeddings.map((m) => deserializeMessage(m!.message!)),
1222
1221
  );
1223
1222
  if (!embeddings) {
1224
1223
  if (!this.options.textEmbedding) {
@@ -1291,7 +1290,7 @@ export class Agent<
1291
1290
  const embeddings = await this.generateEmbeddings(
1292
1291
  ctx,
1293
1292
  { userId: args.userId, threadId: args.threadId },
1294
- messages.map((m) => m.message),
1293
+ messages.map((m) => deserializeMessage(m.message)),
1295
1294
  );
1296
1295
  const saved = await ctx.runMutation(this.component.messages.addMessages, {
1297
1296
  userId: args.userId,
@@ -1336,7 +1335,7 @@ export class Agent<
1336
1335
  const embeddings = await this.generateEmbeddings(
1337
1336
  ctx,
1338
1337
  { userId: args.userId, threadId: args.threadId },
1339
- messages.map((m) => m.message),
1338
+ messages.map((m) => deserializeMessage(m.message)),
1340
1339
  );
1341
1340
 
1342
1341
  await ctx.runMutation(this.component.messages.addMessages, {
@@ -1392,7 +1391,7 @@ export class Agent<
1392
1391
  messageId: string;
1393
1392
  patch: {
1394
1393
  /** The message to replace the existing message. */
1395
- message: CoreMessage & { id?: string };
1394
+ message: (ModelMessage & { id?: string }) | Message;
1396
1395
  /** The status to set on the message. */
1397
1396
  status: "success" | "error";
1398
1397
  /** The error message to set on the message. */
@@ -1507,8 +1506,8 @@ export class Agent<
1507
1506
  endOrder: number;
1508
1507
  endStepOrder?: number;
1509
1508
  },
1510
- ): Promise<void> {
1511
- await ctx.runMutation(this.component.messages.deleteByOrder, {
1509
+ ): Promise<{ isDone: boolean; lastOrder?: number; lastStepOrder?: number }> {
1510
+ return ctx.runMutation(this.component.messages.deleteByOrder, {
1512
1511
  threadId: args.threadId,
1513
1512
  startOrder: args.startOrder,
1514
1513
  startStepOrder: args.startStepOrder,
@@ -1560,11 +1559,11 @@ export class Agent<
1560
1559
  async _saveMessagesAndFetchContext<
1561
1560
  T extends {
1562
1561
  id?: string;
1563
- prompt?: string;
1564
- messages?: CoreMessage[] | AIMessageWithoutId[];
1562
+ prompt?: string | (ModelMessage | Message)[];
1563
+ messages?: (ModelMessage | Message)[];
1565
1564
  system?: string;
1566
1565
  promptMessageId?: string;
1567
- model?: LanguageModelV1;
1566
+ model?: LanguageModelV2;
1568
1567
  maxRetries?: number;
1569
1568
  },
1570
1569
  >(
@@ -1580,14 +1579,19 @@ export class Agent<
1580
1579
  threadId: string | undefined;
1581
1580
  } & Options,
1582
1581
  ): Promise<{
1583
- args: T & { model: LanguageModelV1 };
1582
+ args: T & { model: LanguageModelV2 };
1584
1583
  userId: string | undefined;
1585
1584
  messageId: string | undefined;
1586
1585
  order: number | undefined;
1587
1586
  stepOrder: number | undefined;
1588
1587
  }> {
1589
1588
  // If only a promptMessageId is provided, this will be empty.
1590
- const messages = promptOrMessagesToCoreMessages(args);
1589
+ const messages = args.messages ?? [];
1590
+ const prompt: (ModelMessage | Message)[] = !args.prompt
1591
+ ? []
1592
+ : Array.isArray(args.prompt)
1593
+ ? args.prompt
1594
+ : [{ role: "user", content: args.prompt }];
1591
1595
  const userId =
1592
1596
  argsUserId ??
1593
1597
  (threadId &&
@@ -1603,45 +1607,46 @@ export class Agent<
1603
1607
  });
1604
1608
  // If it was a promptMessageId, pop it off context messages
1605
1609
  // and add to the end of messages.
1606
- // TODO: slice it from the prompt message, to append all of them
1610
+ const promptMessageIndex = args.promptMessageId
1611
+ ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
1612
+ : -1;
1607
1613
  const promptMessage =
1608
- !!args.promptMessageId &&
1609
- contextMessages.at(-1)?._id === args.promptMessageId
1610
- ? contextMessages.pop()
1614
+ promptMessageIndex !== -1
1615
+ ? contextMessages.splice(promptMessageIndex, 1)[0]
1611
1616
  : undefined;
1612
- if (promptMessage && args.prompt) {
1613
- // If they specify both a promptMessageId and a prompt, we prefer
1614
- // the prompt to stand in for the promptMessageId message.
1615
- promptMessage.message = { role: "user", content: args.prompt };
1616
- }
1617
+
1617
1618
  let messageId = promptMessage?._id;
1618
1619
  let order = promptMessage?.order;
1619
1620
  let stepOrder = promptMessage?.stepOrder;
1620
1621
  if (
1621
1622
  threadId &&
1622
- messages.length &&
1623
+ messages.length + prompt.length &&
1623
1624
  storageOptions?.saveMessages !== "none" &&
1624
1625
  // If it was a promptMessageId, we don't want to save it again.
1625
1626
  (!args.promptMessageId || storageOptions?.saveMessages === "all")
1626
1627
  ) {
1627
1628
  const saveAll = storageOptions?.saveMessages === "all";
1628
- const coreMessages = saveAll ? messages : messages.slice(-1);
1629
+ const coreMessages = [...messages, ...prompt];
1630
+ const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
1631
+ const metadata = Array.from({ length: toSave.length }, () => ({}));
1632
+ metadata[metadata.length - 1] = { id: args.id };
1629
1633
  const saved = await this.saveMessages(ctx, {
1630
1634
  threadId,
1631
1635
  userId,
1632
- messages: coreMessages,
1633
- metadata: coreMessages.map((_, i) =>
1634
- i === coreMessages.length - 1 ? { id: args.id } : {},
1635
- ),
1636
+ messages: toSave,
1637
+ metadata,
1636
1638
  failPendingSteps: true,
1637
1639
  });
1638
1640
  messageId = saved.lastMessageId;
1639
1641
  order = saved.messages.at(-1)?.order;
1640
1642
  stepOrder = saved.messages.at(-1)?.stepOrder;
1641
1643
  }
1644
+
1642
1645
  if (promptMessage?.message) {
1643
- // Add the message after saving the messages, so it's not saved again.
1644
- messages.push(deserializeMessage(promptMessage.message));
1646
+ if (!args.prompt) {
1647
+ // If they override the prompt, we skip the existing prompt message.
1648
+ messages.push(deserializeMessage(promptMessage.message));
1649
+ }
1645
1650
  // Lazily generate embeddings for the prompt message, if it doesn't have
1646
1651
  // embeddings yet. This can happen if the message was saved in a mutation
1647
1652
  // where the LLM is not available.
@@ -1650,10 +1655,28 @@ export class Agent<
1650
1655
  }
1651
1656
  }
1652
1657
 
1658
+ const prePrompt = contextMessages.map((m) =>
1659
+ deserializeMessage(m.message!),
1660
+ );
1661
+ let existingResponses: ModelMessage[] = [];
1662
+ if (promptMessageIndex !== -1) {
1663
+ // pull any messages that already responded to the prompt off
1664
+ // and add them after the prompt
1665
+ existingResponses = prePrompt.splice(promptMessageIndex);
1666
+ }
1667
+
1653
1668
  let processedMessages = [
1654
- ...contextMessages.map((m) => deserializeMessage(m.message!)),
1669
+ ...prePrompt,
1655
1670
  ...messages,
1671
+ ...prompt,
1672
+ ...existingResponses,
1656
1673
  ];
1674
+ if (promptMessageIndex === -1) {
1675
+ processedMessages.push(...prompt);
1676
+ } else {
1677
+ // We add the prompt where the prompt message was
1678
+ processedMessages.splice(promptMessageIndex, 0, ...prompt);
1679
+ }
1657
1680
 
1658
1681
  // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
1659
1682
  if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
@@ -1668,7 +1691,7 @@ export class Agent<
1668
1691
  model: model ?? this.options.chat,
1669
1692
  system: args.system ?? this.options.instructions,
1670
1693
  messages: processedMessages,
1671
- } as T & { model: LanguageModelV1 },
1694
+ } as T & { model: LanguageModelV2 },
1672
1695
  userId,
1673
1696
  messageId,
1674
1697
  order,
@@ -1707,8 +1730,8 @@ export class Agent<
1707
1730
  provider: embeddingModel.provider,
1708
1731
  providerMetadata: undefined,
1709
1732
  usage: {
1710
- promptTokens: result.usage.tokens,
1711
- completionTokens: 0,
1733
+ inputTokens: result.usage.tokens,
1734
+ outputTokens: 0,
1712
1735
  totalTokens: result.usage.tokens,
1713
1736
  },
1714
1737
  });
@@ -1722,11 +1745,11 @@ export class Agent<
1722
1745
  * able to access localhost URLs.
1723
1746
  */
1724
1747
  private async _inlineMessagesFiles(
1725
- messages: CoreMessage[],
1726
- ): Promise<CoreMessage[]> {
1748
+ messages: (ModelMessage | Message)[],
1749
+ ): Promise<(ModelMessage | Message)[]> {
1727
1750
  // Process each message to convert localhost URLs to base64
1728
1751
  return Promise.all(
1729
- messages.map(async (message): Promise<CoreMessage> => {
1752
+ messages.map(async (message): Promise<ModelMessage | Message> => {
1730
1753
  if (
1731
1754
  (message.role !== "user" && message.role !== "assistant") ||
1732
1755
  typeof message.content === "string" ||
@@ -1845,15 +1868,15 @@ export class Agent<
1845
1868
  * Create an action out of this agent so you can call it from workflows or other actions
1846
1869
  * without a wrapping function.
1847
1870
  * @param spec Configuration for the agent acting as an action, including
1848
- * {@link ContextOptions}, {@link StorageOptions}, and maxSteps.
1871
+ * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1849
1872
  */
1850
1873
  asTextAction<DataModel extends GenericDataModel>(
1851
1874
  spec?: {
1852
1875
  /**
1853
- * The maximum number of steps to take in this action.
1854
- * Defaults to the {@link Agent.maxSteps} option.
1876
+ * When to stop generating text.
1877
+ * Defaults to the {@link Agent["options"].stopWhen} option.
1855
1878
  */
1856
- maxSteps?: number;
1879
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
1857
1880
  /**
1858
1881
  * The {@link ContextOptions} to use for fetching contextual messages and
1859
1882
  * saving input/output messages.
@@ -1902,7 +1925,7 @@ export class Agent<
1902
1925
  }
1903
1926
  : { customCtx?: never }),
1904
1927
  ) {
1905
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1928
+ const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1906
1929
  return internalActionGeneric({
1907
1930
  args: vTextArgs,
1908
1931
  handler: async (ctx_, args) => {
@@ -1910,9 +1933,17 @@ export class Agent<
1910
1933
  args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1911
1934
  const targetArgs = { userId: args.userId, threadId: args.threadId };
1912
1935
  const llmArgs = {
1913
- maxSteps,
1936
+ stopWhen,
1914
1937
  ...omit(args, ["storageOptions", "contextOptions"]),
1915
- };
1938
+ messages: args.messages?.map(deserializeMessage),
1939
+ prompt: Array.isArray(args.prompt)
1940
+ ? args.prompt.map(deserializeMessage)
1941
+ : args.prompt,
1942
+ toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1943
+ } satisfies StreamingTextArgs<AgentTools>;
1944
+ if (args.maxSteps) {
1945
+ llmArgs.stopWhen = stepCountIs(args.maxSteps);
1946
+ }
1916
1947
  const opts = {
1917
1948
  ...this.options,
1918
1949
  ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
@@ -1925,7 +1956,13 @@ export class Agent<
1925
1956
  : ctx_
1926
1957
  ) as UserActionCtx & CustomCtx;
1927
1958
  if (stream) {
1928
- const result = await this.streamText(ctx, targetArgs, llmArgs, opts);
1959
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1960
+ const result = await this.streamText<any>(
1961
+ ctx,
1962
+ targetArgs,
1963
+ llmArgs,
1964
+ opts,
1965
+ );
1929
1966
  await result.consumeStream();
1930
1967
  return {
1931
1968
  text: await result.text,
@@ -1935,7 +1972,13 @@ export class Agent<
1935
1972
  warnings: result.warnings,
1936
1973
  };
1937
1974
  } else {
1938
- const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1975
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1976
+ const res = await this.generateText<any>(
1977
+ ctx,
1978
+ targetArgs,
1979
+ llmArgs,
1980
+ opts,
1981
+ );
1939
1982
  return {
1940
1983
  text: res.text,
1941
1984
  messageId: res.messageId,
@@ -1952,28 +1995,27 @@ export class Agent<
1952
1995
  * it from workflows or other actions without a wrapping function.
1953
1996
  * @param spec Configuration for the agent acting as an action, including
1954
1997
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1955
- * and maxSteps.
1998
+ * and stopWhen.
1956
1999
  */
1957
2000
  asObjectAction<T>(
1958
- spec: OurObjectArgs<T> & { maxSteps?: number },
2001
+ spec: OurObjectArgs<T>,
1959
2002
  options?: {
1960
2003
  contextOptions?: ContextOptions;
1961
2004
  storageOptions?: StorageOptions;
1962
2005
  },
1963
2006
  ) {
1964
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1965
2007
  return internalActionGeneric({
1966
2008
  args: vSafeObjectArgs,
1967
2009
  handler: async (ctx, args) => {
1968
- const overrides = pick(args, ["userId", "threadId"]);
2010
+ const { userId, threadId, ...rest } = args;
2011
+ const overrides = pick(rest, ["contextOptions", "storageOptions"]);
1969
2012
  const value = await this.generateObject(
1970
2013
  ctx,
1971
- { userId: args.userId, threadId: args.threadId },
2014
+ { userId, threadId },
1972
2015
  {
1973
2016
  ...spec,
1974
- maxSteps,
1975
- ...omit(args, ["userId", "threadId"]),
1976
- } as unknown as OurObjectArgs<unknown>,
2017
+ ...omit(rest, ["contextOptions", "storageOptions"]),
2018
+ } as OurObjectArgs<unknown>,
1977
2019
  { ...this.options, ...options, ...overrides },
1978
2020
  );
1979
2021
  return {
@@ -2020,7 +2062,7 @@ export class Agent<
2020
2062
  handler: async (ctx, args) => {
2021
2063
  const { lastMessageId, messages } = await this.saveMessages(ctx, {
2022
2064
  ...args,
2023
- messages: args.messages.map((m) => m.message),
2065
+ messages: args.messages.map((m) => deserializeMessage(m.message)),
2024
2066
  metadata: args.messages.map(({ message: _, ...m }) => m),
2025
2067
  });
2026
2068
  return {
@@ -2032,8 +2074,6 @@ export class Agent<
2032
2074
  }
2033
2075
  }
2034
2076
 
2035
- type CoreMessageMaybeWithId = CoreMessage & { id?: string | undefined };
2036
-
2037
2077
  /**
2038
2078
  * Create a thread to store messages with an Agent.
2039
2079
  * @param ctx The context from a mutation or action.
@@ -2092,7 +2132,7 @@ type SaveMessagesArgs = {
2092
2132
  /**
2093
2133
  * The messages to save.
2094
2134
  */
2095
- messages: CoreMessageMaybeWithId[];
2135
+ messages: ((ModelMessage & { id?: string | undefined }) | Message)[];
2096
2136
  /**
2097
2137
  * Metadata to save with the messages. Each element corresponds to the
2098
2138
  * message at the same index.
@@ -2186,7 +2226,7 @@ type SaveMessageArgs = {
2186
2226
  /**
2187
2227
  * The message to save.
2188
2228
  */
2189
- message: CoreMessage;
2229
+ message: ModelMessage | Message;
2190
2230
  }
2191
2231
  | {
2192
2232
  /*
@@ -5,13 +5,14 @@ import type {
5
5
  RunQueryCtx,
6
6
  } from "./types.js";
7
7
  import type { MessageDoc } from "../component/schema.js";
8
- import type { CoreMessage } from "ai";
8
+ import type { ModelMessage } from "ai";
9
9
  import { assert } from "convex-helpers";
10
10
  import {
11
11
  DEFAULT_MESSAGE_RANGE,
12
12
  DEFAULT_RECENT_MESSAGES,
13
13
  extractText,
14
14
  } from "../shared.js";
15
+ import type { Message } from "../validators.js";
15
16
 
16
17
  const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
17
18
 
@@ -34,7 +35,7 @@ export async function fetchContextMessages(
34
35
  args: {
35
36
  userId: string | undefined;
36
37
  threadId: string | undefined;
37
- messages: CoreMessage[];
38
+ messages: (ModelMessage | Message)[];
38
39
  /**
39
40
  * If provided, it will search for messages up to and including this message.
40
41
  * Note: if this is far in the past, text and vector search results may be more
@@ -9,7 +9,7 @@ import type {
9
9
  StreamArgs,
10
10
  StreamDelta,
11
11
  StreamMessage,
12
- TextStreamPart,
12
+ vTextStreamPartV5,
13
13
  } from "../validators.js";
14
14
  import type { MessageDoc } from "../component/schema.js";
15
15
  import type {
@@ -20,6 +20,7 @@ import type {
20
20
  SyncStreamsReturnValue,
21
21
  } from "./types.js";
22
22
  import { omit } from "convex-helpers";
23
+ import type { Infer } from "convex/values";
23
24
 
24
25
  /**
25
26
  * A function that handles fetching stream deltas, used with the React hooks
@@ -158,7 +159,7 @@ export function mergeTransforms<TOOLS extends ToolSet>(
158
159
  export class DeltaStreamer {
159
160
  public streamId: string | undefined;
160
161
  public readonly options: Required<StreamingOptions>;
161
- #nextParts: TextStreamPart[] = [];
162
+ #nextParts: Infer<typeof vTextStreamPartV5>[] = [];
162
163
  #nextOrder: number;
163
164
  #nextStepOrder: number;
164
165
  #latestWrite: number = 0;
@@ -205,7 +206,7 @@ export class DeltaStreamer {
205
206
  });
206
207
  }
207
208
  }
208
- public async addParts(parts: TextStreamPart[]) {
209
+ public async addParts(parts: Infer<typeof vTextStreamPartV5>[]) {
209
210
  if (this.abortController.signal.aborted) {
210
211
  return;
211
212
  }