@convex-dev/agent 0.5.0-alpha.1 → 0.6.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 (235) hide show
  1. package/MIGRATION.md +153 -0
  2. package/README.md +32 -27
  3. package/dist/UIMessages.d.ts +46 -0
  4. package/dist/UIMessages.d.ts.map +1 -0
  5. package/dist/UIMessages.js +546 -0
  6. package/dist/UIMessages.js.map +1 -0
  7. package/dist/client/createTool.d.ts +126 -27
  8. package/dist/client/createTool.d.ts.map +1 -1
  9. package/dist/client/createTool.js +67 -12
  10. package/dist/client/createTool.js.map +1 -1
  11. package/dist/client/defaultComponent.d.ts +11 -0
  12. package/dist/client/defaultComponent.d.ts.map +1 -0
  13. package/dist/client/defaultComponent.js +7 -0
  14. package/dist/client/defaultComponent.js.map +1 -0
  15. package/dist/client/definePlaygroundAPI.d.ts +1335 -204
  16. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  17. package/dist/client/definePlaygroundAPI.js +52 -28
  18. package/dist/client/definePlaygroundAPI.js.map +1 -1
  19. package/dist/client/files.d.ts +20 -7
  20. package/dist/client/files.d.ts.map +1 -1
  21. package/dist/client/files.js +68 -11
  22. package/dist/client/files.js.map +1 -1
  23. package/dist/client/index.d.ts +1116 -978
  24. package/dist/client/index.d.ts.map +1 -1
  25. package/dist/client/index.js +332 -747
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/client/messages.d.ts +461 -0
  28. package/dist/client/messages.d.ts.map +1 -0
  29. package/dist/client/messages.js +106 -0
  30. package/dist/client/messages.js.map +1 -0
  31. package/dist/client/mockModel.d.ts +42 -0
  32. package/dist/client/mockModel.d.ts.map +1 -0
  33. package/dist/client/mockModel.js +182 -0
  34. package/dist/client/mockModel.js.map +1 -0
  35. package/dist/client/saveInputMessages.d.ts +20 -0
  36. package/dist/client/saveInputMessages.d.ts.map +1 -0
  37. package/dist/client/saveInputMessages.js +58 -0
  38. package/dist/client/saveInputMessages.js.map +1 -0
  39. package/dist/client/search.d.ts +350 -39
  40. package/dist/client/search.d.ts.map +1 -1
  41. package/dist/client/search.js +350 -39
  42. package/dist/client/search.js.map +1 -1
  43. package/dist/client/start.d.ts +84 -0
  44. package/dist/client/start.d.ts.map +1 -0
  45. package/dist/client/start.js +185 -0
  46. package/dist/client/start.js.map +1 -0
  47. package/dist/client/streamText.d.ts +46 -0
  48. package/dist/client/streamText.d.ts.map +1 -0
  49. package/dist/client/streamText.js +117 -0
  50. package/dist/client/streamText.js.map +1 -0
  51. package/dist/client/streaming.d.ts +3716 -32
  52. package/dist/client/streaming.d.ts.map +1 -1
  53. package/dist/client/streaming.js +161 -59
  54. package/dist/client/streaming.js.map +1 -1
  55. package/dist/client/threads.d.ts +46 -0
  56. package/dist/client/threads.d.ts.map +1 -0
  57. package/dist/client/threads.js +49 -0
  58. package/dist/client/threads.js.map +1 -0
  59. package/dist/client/types.d.ts +266 -128
  60. package/dist/client/types.d.ts.map +1 -1
  61. package/dist/client/utils.d.ts +4 -0
  62. package/dist/client/utils.d.ts.map +1 -0
  63. package/dist/client/utils.js +21 -0
  64. package/dist/client/utils.js.map +1 -0
  65. package/dist/component/_generated/api.d.ts +24 -2178
  66. package/dist/component/_generated/api.d.ts.map +1 -1
  67. package/dist/component/_generated/api.js +10 -1
  68. package/dist/component/_generated/api.js.map +1 -1
  69. package/dist/component/_generated/component.d.ts +3120 -0
  70. package/dist/component/_generated/component.d.ts.map +1 -0
  71. package/dist/component/_generated/component.js +11 -0
  72. package/dist/component/_generated/component.js.map +1 -0
  73. package/dist/component/_generated/dataModel.d.ts +4 -18
  74. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  75. package/dist/component/_generated/dataModel.js +11 -0
  76. package/dist/component/_generated/dataModel.js.map +1 -0
  77. package/dist/component/_generated/server.d.ts +10 -38
  78. package/dist/component/_generated/server.d.ts.map +1 -1
  79. package/dist/component/_generated/server.js +9 -5
  80. package/dist/component/_generated/server.js.map +1 -1
  81. package/dist/component/files.d.ts +16 -10
  82. package/dist/component/files.d.ts.map +1 -1
  83. package/dist/component/files.js +10 -2
  84. package/dist/component/files.js.map +1 -1
  85. package/dist/component/messages.d.ts +2578 -366
  86. package/dist/component/messages.d.ts.map +1 -1
  87. package/dist/component/messages.js +397 -154
  88. package/dist/component/messages.js.map +1 -1
  89. package/dist/component/schema.d.ts +5697 -3584
  90. package/dist/component/schema.d.ts.map +1 -1
  91. package/dist/component/schema.js +18 -41
  92. package/dist/component/schema.js.map +1 -1
  93. package/dist/component/streams.d.ts +39 -339
  94. package/dist/component/streams.d.ts.map +1 -1
  95. package/dist/component/streams.js +114 -73
  96. package/dist/component/streams.js.map +1 -1
  97. package/dist/component/threads.d.ts +13 -13
  98. package/dist/component/users.d.ts +7 -7
  99. package/dist/component/vector/index.d.ts +1 -1
  100. package/dist/component/vector/index.d.ts.map +1 -1
  101. package/dist/component/vector/index.js +1 -3
  102. package/dist/component/vector/index.js.map +1 -1
  103. package/dist/deltas.d.ts +43 -0
  104. package/dist/deltas.d.ts.map +1 -0
  105. package/dist/deltas.js +446 -0
  106. package/dist/deltas.js.map +1 -0
  107. package/dist/mapping.d.ts +38 -20
  108. package/dist/mapping.d.ts.map +1 -1
  109. package/dist/mapping.js +365 -97
  110. package/dist/mapping.js.map +1 -1
  111. package/dist/react/SmoothText.d.ts +5 -0
  112. package/dist/react/SmoothText.d.ts.map +1 -0
  113. package/dist/react/SmoothText.js +6 -0
  114. package/dist/react/SmoothText.js.map +1 -0
  115. package/dist/react/index.d.ts +5 -77
  116. package/dist/react/index.d.ts.map +1 -1
  117. package/dist/react/index.js +6 -160
  118. package/dist/react/index.js.map +1 -1
  119. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  120. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  121. package/dist/react/optimisticallySendMessage.js +35 -9
  122. package/dist/react/optimisticallySendMessage.js.map +1 -1
  123. package/dist/react/types.d.ts +4 -18
  124. package/dist/react/types.d.ts.map +1 -1
  125. package/dist/react/useDeltaStreams.d.ts +10 -0
  126. package/dist/react/useDeltaStreams.d.ts.map +1 -0
  127. package/dist/react/useDeltaStreams.js +106 -0
  128. package/dist/react/useDeltaStreams.js.map +1 -0
  129. package/dist/react/useSmoothText.d.ts +13 -12
  130. package/dist/react/useSmoothText.d.ts.map +1 -1
  131. package/dist/react/useSmoothText.js +32 -15
  132. package/dist/react/useSmoothText.js.map +1 -1
  133. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  134. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  135. package/dist/react/useStreamingUIMessages.js +92 -0
  136. package/dist/react/useStreamingUIMessages.js.map +1 -0
  137. package/dist/react/useThreadMessages.d.ts +104 -0
  138. package/dist/react/useThreadMessages.d.ts.map +1 -0
  139. package/dist/react/useThreadMessages.js +148 -0
  140. package/dist/react/useThreadMessages.js.map +1 -0
  141. package/dist/react/useUIMessages.d.ts +96 -0
  142. package/dist/react/useUIMessages.d.ts.map +1 -0
  143. package/dist/react/useUIMessages.js +108 -0
  144. package/dist/react/useUIMessages.js.map +1 -0
  145. package/dist/shared.d.ts +20 -4
  146. package/dist/shared.d.ts.map +1 -1
  147. package/dist/shared.js +45 -8
  148. package/dist/shared.js.map +1 -1
  149. package/dist/validators.d.ts +22981 -5666
  150. package/dist/validators.d.ts.map +1 -1
  151. package/dist/validators.js +245 -137
  152. package/dist/validators.js.map +1 -1
  153. package/package.json +101 -51
  154. package/src/UIMessages.combineUIMessages.test.ts +239 -0
  155. package/src/UIMessages.test.ts +273 -0
  156. package/src/UIMessages.ts +739 -0
  157. package/src/client/approval.test.ts +350 -0
  158. package/src/client/createTool.ts +291 -76
  159. package/src/client/defaultComponent.ts +17 -0
  160. package/src/client/definePlaygroundAPI.ts +67 -31
  161. package/src/client/files.ts +100 -20
  162. package/src/client/index.test.ts +40 -85
  163. package/src/client/index.ts +638 -1289
  164. package/src/client/messages.ts +237 -0
  165. package/src/client/mockModel.ts +252 -0
  166. package/src/client/saveInputMessages.test.ts +583 -0
  167. package/src/client/saveInputMessages.ts +101 -0
  168. package/src/client/search.test.ts +1207 -0
  169. package/src/client/search.ts +581 -70
  170. package/src/client/start.ts +327 -0
  171. package/src/client/streamText.ts +187 -0
  172. package/src/client/streaming.test.ts +186 -0
  173. package/src/client/streaming.ts +241 -97
  174. package/src/client/threads.ts +83 -0
  175. package/src/client/types.ts +370 -219
  176. package/src/client/utils.ts +27 -0
  177. package/src/component/_generated/api.ts +64 -0
  178. package/src/component/_generated/component.ts +4902 -0
  179. package/src/component/_generated/{server.d.ts → server.ts} +33 -21
  180. package/src/component/files.ts +11 -2
  181. package/src/component/messages.test.ts +195 -51
  182. package/src/component/messages.ts +500 -201
  183. package/src/component/schema.ts +20 -46
  184. package/src/component/setup.test.ts +7 -0
  185. package/src/component/streams.ts +184 -83
  186. package/src/component/users.test.ts +0 -1
  187. package/src/component/vector/index.ts +1 -3
  188. package/src/deltas.test.ts +626 -0
  189. package/src/deltas.ts +569 -0
  190. package/src/fromUIMessages.test.ts +497 -0
  191. package/src/mapping.test.ts +180 -6
  192. package/src/mapping.ts +479 -162
  193. package/src/react/SmoothText.tsx +9 -0
  194. package/src/react/index.ts +10 -230
  195. package/src/react/optimisticallySendMessage.ts +55 -12
  196. package/src/react/types.ts +6 -39
  197. package/src/react/useDeltaStreams.ts +160 -0
  198. package/src/react/useSmoothText.ts +56 -36
  199. package/src/react/useStreamingUIMessages.ts +143 -0
  200. package/src/react/useThreadMessages.ts +262 -0
  201. package/src/react/useUIMessages.test.ts +255 -0
  202. package/src/react/useUIMessages.ts +195 -0
  203. package/src/shared.ts +88 -12
  204. package/src/test.ts +18 -0
  205. package/src/toUIMessages.test.ts +1269 -0
  206. package/src/validators.test.ts +18 -19
  207. package/src/validators.ts +325 -185
  208. package/dist/client/_generated/_ignore.d.ts +0 -1
  209. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  210. package/dist/client/_generated/_ignore.js +0 -3
  211. package/dist/client/_generated/_ignore.js.map +0 -1
  212. package/dist/client/listMessages.d.ts +0 -22
  213. package/dist/client/listMessages.d.ts.map +0 -1
  214. package/dist/client/listMessages.js +0 -25
  215. package/dist/client/listMessages.js.map +0 -1
  216. package/dist/package.json +0 -3
  217. package/dist/react/deltas.d.ts +0 -26
  218. package/dist/react/deltas.d.ts.map +0 -1
  219. package/dist/react/deltas.js +0 -384
  220. package/dist/react/deltas.js.map +0 -1
  221. package/dist/react/toUIMessages.d.ts +0 -15
  222. package/dist/react/toUIMessages.d.ts.map +0 -1
  223. package/dist/react/toUIMessages.js +0 -211
  224. package/dist/react/toUIMessages.js.map +0 -1
  225. package/src/client/listMessages.ts +0 -38
  226. package/src/component/_generated/api.d.ts +0 -2202
  227. package/src/component/_generated/api.js +0 -23
  228. package/src/component/_generated/server.js +0 -90
  229. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  230. package/src/react/deltas.test.ts +0 -315
  231. package/src/react/deltas.ts +0 -478
  232. package/src/react/toUIMessages.test.ts +0 -420
  233. package/src/react/toUIMessages.ts +0 -253
  234. package/src/vitest.config.ts +0 -7
  235. /package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +0 -0
@@ -1,29 +1,32 @@
1
- import type { LanguageModelV2, EmbeddingModelV2 } from "@ai-sdk/provider";
1
+ import type { JSONValue } from "@ai-sdk/provider";
2
2
  import type {
3
- AssistantContent,
4
- ModelMessage,
5
- DeepPartial,
6
- FilePart,
3
+ FlexibleSchema,
4
+ IdGenerator,
5
+ InferSchema,
6
+ } from "@ai-sdk/provider-utils";
7
+ import type {
8
+ CallSettings,
9
+ EmbeddingModel,
7
10
  GenerateObjectResult,
8
11
  GenerateTextResult,
9
- ImagePart,
12
+ LanguageModel,
13
+ ModelMessage,
10
14
  StepResult,
11
- StreamObjectResult,
12
- StreamTextResult,
13
- ToolSet,
14
- UserContent,
15
15
  StopCondition,
16
- Schema,
16
+ StreamTextResult,
17
17
  ToolChoice,
18
+ ToolSet,
18
19
  } from "ai";
19
- import {
20
- embedMany,
21
- generateObject,
22
- generateText,
23
- stepCountIs,
24
- streamObject,
25
- streamText,
26
- } from "ai";
20
+ import { generateObject, generateText, stepCountIs, streamObject } from "ai";
21
+
22
+ const MIGRATION_URL = "node_modules/@convex-dev/agent/MIGRATION.md";
23
+ const warnedDeprecations = new Set<string>();
24
+ function warnDeprecation(key: string, message: string) {
25
+ if (!warnedDeprecations.has(key)) {
26
+ warnedDeprecations.add(key);
27
+ console.warn(`[@convex-dev/agent] ${message}\n See: ${MIGRATION_URL}`);
28
+ }
29
+ }
27
30
  import { assert, omit, pick } from "convex-helpers";
28
31
  import {
29
32
  internalActionGeneric,
@@ -34,110 +37,159 @@ import {
34
37
  type PaginationResult,
35
38
  type WithoutSystemFields,
36
39
  } from "convex/server";
37
- import { v } from "convex/values";
38
- import type { MessageDoc, ThreadDoc } from "../component/schema.js";
40
+ import { convexToJson, v, type Value } from "convex/values";
39
41
  import type { threadFieldsSupportingPatch } from "../component/threads.js";
42
+ import { type VectorDimension } from "../component/vector/tables.js";
40
43
  import {
41
- validateVectorDimension,
42
- type VectorDimension,
43
- } from "../component/vector/tables.js";
44
- import {
45
- deserializeMessage,
44
+ toModelMessage,
46
45
  serializeMessage,
47
46
  serializeNewMessagesInStep,
48
47
  serializeObjectResult,
49
48
  } from "../mapping.js";
50
- import { extractText, isTool } from "../shared.js";
49
+ import { getModelName, getProviderName } from "../shared.js";
51
50
  import {
51
+ vMessageEmbeddings,
52
+ vMessageWithMetadata,
53
+ vSafeObjectArgs,
54
+ vTextArgs,
52
55
  type Message,
53
- type MessageEmbeddings,
56
+ type MessageDoc,
54
57
  type MessageStatus,
55
58
  type MessageWithMetadata,
56
59
  type ProviderMetadata,
57
60
  type StreamArgs,
58
- type Usage,
59
- vMessageWithMetadata,
60
- vSafeObjectArgs,
61
- vTextArgs,
61
+ type ThreadDoc,
62
62
  } from "../validators.js";
63
- import { createTool, wrapTools, type ToolCtx } from "./createTool.js";
64
- import { listMessages } from "./listMessages.js";
65
- import { fetchContextMessages } from "./search.js";
66
63
  import {
67
- DeltaStreamer,
68
- mergeTransforms,
69
- type StreamingOptions,
70
- syncStreams,
71
- } from "./streaming.js";
64
+ listMessages,
65
+ saveMessages,
66
+ type SaveMessageArgs,
67
+ type SaveMessagesArgs,
68
+ } from "./messages.js";
69
+ import {
70
+ embedMany,
71
+ embedMessages,
72
+ fetchContextMessages,
73
+ generateAndSaveEmbeddings,
74
+ } from "./search.js";
75
+ import { startGeneration } from "./start.js";
76
+ import { syncStreams, type StreamingOptions } from "./streaming.js";
77
+ import { createThread, getThreadMetadata } from "./threads.js";
72
78
  import type {
73
79
  ActionCtx,
74
80
  AgentComponent,
81
+ Config,
75
82
  ContextOptions,
83
+ GenerateObjectArgs,
76
84
  GenerationOutputMetadata,
85
+ MaybeCustomCtx,
86
+ ObjectMode,
77
87
  Options,
78
- OurObjectArgs,
79
- OurStreamObjectArgs,
80
88
  RawRequestResponseHandler,
81
- RunActionCtx,
82
- RunMutationCtx,
83
- RunQueryCtx,
89
+ MutationCtx,
84
90
  StorageOptions,
85
91
  StreamingTextArgs,
92
+ StreamObjectArgs,
86
93
  SyncStreamsReturnValue,
87
94
  TextArgs,
88
95
  Thread,
89
96
  UsageHandler,
90
- UserActionCtx,
97
+ QueryCtx,
98
+ AgentPrompt,
99
+ Output,
91
100
  } from "./types.js";
92
- import type z from "zod";
101
+ import { streamText } from "./streamText.js";
102
+ import { errorToString, willContinue } from "./utils.js";
93
103
 
94
104
  export { stepCountIs } from "ai";
95
- export { vMessageDoc, vThreadDoc } from "../component/schema.js";
96
105
  export {
106
+ docsToModelMessages,
107
+ toModelMessage,
108
+ //** @deprecated use toModelMessage instead */
109
+ toModelMessage as deserializeMessage,
110
+ guessMimeType,
97
111
  serializeDataOrUrl,
98
- deserializeMessage,
99
112
  serializeMessage,
113
+ toUIFilePart,
100
114
  } from "../mapping.js";
101
115
  // NOTE: these are also exported via @convex-dev/agent/validators
102
116
  // a future version may put them all here or move these over there
117
+ export { extractText, isTool, sorted } from "../shared.js";
103
118
  export {
104
119
  vAssistantMessage,
120
+ vContent,
105
121
  vContextOptions,
106
122
  vMessage,
123
+ vMessageDoc,
107
124
  vPaginationResult,
108
125
  vProviderMetadata,
126
+ vSource,
109
127
  vStorageOptions,
110
128
  vStreamArgs,
111
129
  vSystemMessage,
130
+ vThreadDoc,
112
131
  vToolMessage,
113
132
  vUsage,
114
133
  vUserMessage,
134
+ type Message,
135
+ type MessageDoc,
136
+ type SourcePart,
137
+ type ThreadDoc,
138
+ type Usage,
115
139
  } from "../validators.js";
116
- export type { ToolCtx } from "./createTool.js";
140
+ export { createTool, type ToolCtx } from "./createTool.js";
141
+ export {
142
+ definePlaygroundAPI,
143
+ type AgentsFn,
144
+ type PlaygroundAPI,
145
+ } from "./definePlaygroundAPI.js";
117
146
  export { getFile, storeFile } from "./files.js";
118
147
  export {
119
- filterOutOrphanedToolMessages,
148
+ listMessages,
149
+ listUIMessages,
150
+ saveMessage,
151
+ saveMessages,
152
+ type SaveMessageArgs,
153
+ type SaveMessagesArgs,
154
+ } from "./messages.js";
155
+ export { mockModel } from "./mockModel.js";
156
+ export {
120
157
  fetchContextMessages,
158
+ filterOutOrphanedToolMessages,
159
+ fetchContextWithPrompt,
160
+ generateAndSaveEmbeddings,
161
+ embedMessages,
162
+ embedMany,
121
163
  } from "./search.js";
122
- export { abortStream, listStreams, syncStreams } from "./streaming.js";
123
- export { createTool, extractText, isTool, listMessages };
164
+ export { startGeneration } from "./start.js";
124
165
  export {
125
- definePlaygroundAPI,
126
- type PlaygroundAPI,
127
- type AgentsFn,
128
- } from "./definePlaygroundAPI.js";
166
+ DEFAULT_STREAMING_OPTIONS,
167
+ DeltaStreamer,
168
+ abortStream,
169
+ compressUIMessageChunks,
170
+ listStreams,
171
+ syncStreams,
172
+ vStreamMessagesReturnValue,
173
+ } from "./streaming.js";
174
+ export {
175
+ createThread,
176
+ getThreadMetadata,
177
+ searchThreadTitles,
178
+ updateThreadMetadata,
179
+ } from "./threads.js";
180
+ export type { ContextHandler } from "./types.js";
181
+ export { toUIMessages, fromUIMessages, type UIMessage } from "../UIMessages.js";
182
+
129
183
  export type {
130
184
  AgentComponent,
185
+ Config,
131
186
  ContextOptions,
132
- MessageDoc,
133
187
  ProviderMetadata,
134
188
  RawRequestResponseHandler,
135
189
  StorageOptions,
136
190
  StreamArgs,
137
191
  SyncStreamsReturnValue,
138
192
  Thread,
139
- ThreadDoc,
140
- Usage,
141
193
  UsageHandler,
142
194
  };
143
195
 
@@ -162,36 +214,24 @@ export class Agent<
162
214
  * });
163
215
  */
164
216
  CustomCtx extends object = object,
165
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
217
  AgentTools extends ToolSet = any,
167
218
  > {
168
219
  constructor(
169
220
  public component: AgentComponent,
170
- public options: {
221
+ public options: Config & {
171
222
  /**
172
223
  * The name for the agent. This will be attributed on each message
173
224
  * created by this agent.
174
225
  */
175
- name?: string;
226
+ name: string;
176
227
  /**
177
228
  * The LLM model to use for generating / streaming text and objects.
178
229
  * e.g.
179
230
  * import { openai } from "@ai-sdk/openai"
180
231
  * const myAgent = new Agent(components.agent, {
181
- * chat: openai.chat("gpt-4o-mini"),
182
- */
183
- chat: LanguageModelV2;
184
- /**
185
- * The model to use for text embeddings. Optional.
186
- * If specified, it will use this for generating vector embeddings
187
- * of chats, and can opt-in to doing vector search for automatic context
188
- * on generateText, etc.
189
- * e.g.
190
- * import { openai } from "@ai-sdk/openai"
191
- * const myAgent = new Agent(components.agent, {
192
- * textEmbedding: openai.embedding("text-embedding-3-small")
232
+ * languageModel: openai.chat("gpt-4o-mini"),
193
233
  */
194
- textEmbedding?: EmbeddingModelV2<string>;
234
+ languageModel: LanguageModel;
195
235
  /**
196
236
  * The default system prompt to put in each request.
197
237
  * Override per-prompt by passing the "system" parameter.
@@ -204,38 +244,30 @@ export class Agent<
204
244
  * (import { createTool } from "@convex-dev/agent")
205
245
  */
206
246
  tools?: AgentTools;
207
- /**
208
- * Options to determine what messages are included as context in message
209
- * generation. To disable any messages automatically being added, pass:
210
- * { recentMessages: 0 }
211
- */
212
- contextOptions?: ContextOptions;
213
- /**
214
- * Determines whether messages are automatically stored when passed as
215
- * arguments or generated.
216
- */
217
- storageOptions?: StorageOptions;
218
247
  /**
219
248
  * When generating or streaming text with tools available, this
220
- * determines when to stop. Defaults to stepCountIs(1).
221
- */
222
- stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
223
- /**
224
- * The maximum number of calls to make to an LLM in case it fails.
225
- * This can be overridden at each generate/stream callsite.
249
+ * determines when to stop. Defaults to the AI SDK default.
226
250
  */
227
- maxRetries?: number;
228
- /**
229
- * The usage handler to use for this agent.
230
- */
231
- usageHandler?: UsageHandler;
232
- /**
233
- * Called for each LLM request/response, so you can do things like
234
- * log the raw request body or response headers to a table, or logs.
235
- */
236
- rawRequestResponseHandler?: RawRequestResponseHandler;
251
+ stopWhen?:
252
+ | StopCondition<NoInfer<AgentTools>>
253
+ | Array<StopCondition<NoInfer<AgentTools>>>;
237
254
  },
238
- ) {}
255
+ ) {
256
+ if (this.options.textEmbeddingModel && !this.options.embeddingModel) {
257
+ warnDeprecation(
258
+ "textEmbeddingModel",
259
+ "textEmbeddingModel is deprecated. Use embeddingModel instead.",
260
+ );
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Get the embedding model, prioritizing embeddingModel over textEmbeddingModel.
266
+ * @private
267
+ */
268
+ private getEmbeddingModel(): EmbeddingModel | undefined {
269
+ return this.options.embeddingModel ?? this.options.textEmbeddingModel;
270
+ }
239
271
 
240
272
  /**
241
273
  * Start a new thread with the agent. This will have a fresh history, though if
@@ -247,8 +279,8 @@ export class Agent<
247
279
  * @param args The thread metadata.
248
280
  * @returns The threadId of the new thread and the thread object.
249
281
  */
250
- async createThread<ThreadTools extends ToolSet | undefined = undefined>(
251
- ctx: RunActionCtx & CustomCtx,
282
+ async createThread(
283
+ ctx: ActionCtx & CustomCtx,
252
284
  args?: {
253
285
  /**
254
286
  * The userId to associate with the thread. If not provided, the thread will be
@@ -263,21 +295,8 @@ export class Agent<
263
295
  * The summary of the thread. Not currently used for anything.
264
296
  */
265
297
  summary?: string;
266
- /**
267
- * The usage handler to use for this thread. Overrides any handler
268
- * set in the agent constructor.
269
- */
270
- usageHandler?: UsageHandler;
271
- /**
272
- * The tools to use for this thread.
273
- * Overrides any tools passed in the agent constructor.
274
- */
275
- tools?: ThreadTools;
276
298
  },
277
- ): Promise<{
278
- threadId: string;
279
- thread: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
280
- }>;
299
+ ): Promise<{ threadId: string; thread: Thread<AgentTools> }>;
281
300
  /**
282
301
  * Start a new thread with the agent. This will have a fresh history, though if
283
302
  * you pass in a userId you can have it search across other threads for relevant
@@ -287,8 +306,8 @@ export class Agent<
287
306
  * @param args The thread metadata.
288
307
  * @returns The threadId of the new thread.
289
308
  */
290
- async createThread<ThreadTools extends ToolSet | undefined = undefined>(
291
- ctx: RunMutationCtx,
309
+ async createThread(
310
+ ctx: MutationCtx,
292
311
  args?: {
293
312
  /**
294
313
  * The userId to associate with the thread. If not provided, the thread will be
@@ -303,33 +322,12 @@ export class Agent<
303
322
  * The summary of the thread. Not currently used for anything.
304
323
  */
305
324
  summary?: string;
306
- /**
307
- * The usage handler to use for this thread. Overrides any handler
308
- * set in the agent constructor.
309
- */
310
- usageHandler?: UsageHandler;
311
- /**
312
- * The tools to use for this thread.
313
- * Overrides any tools passed in the agent constructor.
314
- */
315
- tools?: ThreadTools;
316
325
  },
317
- ): Promise<{
318
- threadId: string;
319
- }>;
320
- async createThread<ThreadTools extends ToolSet | undefined = undefined>(
321
- ctx: (ActionCtx & CustomCtx) | RunMutationCtx,
322
- args?: {
323
- userId: string | null;
324
- title?: string;
325
- summary?: string;
326
- usageHandler?: UsageHandler;
327
- tools?: ThreadTools;
328
- },
329
- ): Promise<{
330
- threadId: string;
331
- thread?: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
332
- }> {
326
+ ): Promise<{ threadId: string }>;
327
+ async createThread(
328
+ ctx: (ActionCtx & CustomCtx) | MutationCtx,
329
+ args?: { userId: string | null; title?: string; summary?: string },
330
+ ): Promise<{ threadId: string; thread?: Thread<AgentTools> }> {
333
331
  const threadId = await createThread(ctx, this.component, args);
334
332
  if (!("runAction" in ctx) || "workflowId" in ctx) {
335
333
  return { threadId };
@@ -337,13 +335,8 @@ export class Agent<
337
335
  const { thread } = await this.continueThread(ctx, {
338
336
  threadId,
339
337
  userId: args?.userId,
340
- usageHandler: args?.usageHandler,
341
- tools: args?.tools,
342
338
  });
343
- return {
344
- threadId,
345
- thread,
346
- };
339
+ return { threadId, thread };
347
340
  }
348
341
 
349
342
  /**
@@ -354,7 +347,7 @@ export class Agent<
354
347
  * @param { threadId, userId }: the thread and user to associate the messages with.
355
348
  * @returns Functions bound to the userId and threadId on a `{thread}` object.
356
349
  */
357
- async continueThread<ThreadTools extends ToolSet | undefined = undefined>(
350
+ async continueThread(
358
351
  ctx: ActionCtx & CustomCtx,
359
352
  args: {
360
353
  /**
@@ -366,20 +359,8 @@ export class Agent<
366
359
  * relevant messages from the same user as context for the LLM calls.
367
360
  */
368
361
  userId?: string | null;
369
- /**
370
- * The usage handler to use for this thread. Overrides any handler
371
- * set in the agent constructor.
372
- */
373
- usageHandler?: UsageHandler;
374
- /**
375
- * The tools to use for this thread.
376
- * Overrides any tools passed in the agent constructor.
377
- */
378
- tools?: ThreadTools;
379
362
  },
380
- ): Promise<{
381
- thread: Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>;
382
- }> {
363
+ ): Promise<{ thread: Thread<AgentTools> }> {
383
364
  return {
384
365
  thread: {
385
366
  threadId: args.threadId,
@@ -395,32 +376,81 @@ export class Agent<
395
376
  streamText: this.streamText.bind(this, ctx, args),
396
377
  generateObject: this.generateObject.bind(this, ctx, args),
397
378
  streamObject: this.streamObject.bind(this, ctx, args),
398
- } as Thread<ThreadTools extends undefined ? AgentTools : ThreadTools>,
379
+ } as Thread<AgentTools>,
399
380
  };
400
381
  }
401
382
 
402
- /**
403
- * Search for threads by title, paginated.
404
- * @param ctx The context passed from the query/mutation/action.
405
- * @returns The threads matching the search, paginated.
406
- */
407
- async searchThreadTitles(
408
- ctx: RunQueryCtx,
409
- {
410
- userId,
411
- query,
412
- limit,
413
- }: {
414
- userId?: string | undefined;
415
- query: string;
416
- limit?: number;
383
+ async start<
384
+ TOOLS extends ToolSet | undefined,
385
+ T extends {
386
+ _internal?: { generateId?: IdGenerator };
417
387
  },
418
- ): Promise<ThreadDoc[]> {
419
- return ctx.runQuery(this.component.threads.searchThreadTitles, {
420
- userId,
421
- query,
422
- limit: limit ?? 10,
423
- });
388
+ >(
389
+ ctx: ActionCtx & CustomCtx,
390
+ /**
391
+ * These are the arguments you'll pass to the LLM call such as
392
+ * `generateText` or `streamText`. This function will look up the context
393
+ * and provide functions to save the steps, abort the generation, and more.
394
+ * The type of the arguments returned infers from the type of the arguments
395
+ * you pass here.
396
+ */
397
+ args: T &
398
+ AgentPrompt & {
399
+ /**
400
+ * The tools to use for the tool calls. This will override tools specified
401
+ * in the Agent constructor or createThread / continueThread.
402
+ */
403
+ tools?: TOOLS;
404
+ /**
405
+ * The abort signal to be passed to the LLM call. If triggered, it will
406
+ * mark the pending message as failed. If the generation is asynchronously
407
+ * aborted, it will trigger this signal when detected.
408
+ */
409
+ abortSignal?: AbortSignal;
410
+ stopWhen?:
411
+ | StopCondition<TOOLS extends undefined ? AgentTools : TOOLS>
412
+ | Array<StopCondition<TOOLS extends undefined ? AgentTools : TOOLS>>;
413
+ },
414
+ options?: Options & { userId?: string | null; threadId?: string },
415
+ ): Promise<{
416
+ args: T & {
417
+ system?: string;
418
+ model: LanguageModel;
419
+ prompt?: never;
420
+ messages: ModelMessage[];
421
+ tools?: TOOLS extends undefined ? AgentTools : TOOLS;
422
+ } & CallSettings;
423
+ order: number;
424
+ stepOrder: number;
425
+ userId: string | undefined;
426
+ promptMessageId: string | undefined;
427
+ updateModel: (model: LanguageModel | undefined) => void;
428
+ save: <TOOLS extends ToolSet>(
429
+ toSave:
430
+ | { step: StepResult<TOOLS> }
431
+ | { object: GenerateObjectResult<unknown> },
432
+ createPendingMessage?: boolean,
433
+ ) => Promise<void>;
434
+ fail: (reason: string) => Promise<void>;
435
+ getSavedMessages: () => MessageDoc[];
436
+ }> {
437
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
438
+ return startGeneration<T, Tools, CustomCtx>(
439
+ ctx,
440
+ this.component,
441
+ {
442
+ ...args,
443
+ tools: (args.tools ?? this.options.tools) as Tools,
444
+ system: args.system ?? this.options.instructions,
445
+ stopWhen: (args.stopWhen ?? this.options.stopWhen) as any,
446
+ },
447
+ {
448
+ ...this.options,
449
+ ...options,
450
+ agentName: this.options.name,
451
+ agentForToolCtx: this,
452
+ },
453
+ );
424
454
  }
425
455
 
426
456
  /**
@@ -430,110 +460,60 @@ export class Agent<
430
460
  * Use {@link continueThread} to get a version of this function already scoped
431
461
  * to a thread (and optionally userId).
432
462
  * @param ctx The context passed from the action function calling this.
433
- * @param { userId, threadId }: The user and thread to associate the message with
434
- * @param args The arguments to the generateText function, along with extra controls
435
- * for the {@link ContextOptions} and {@link StorageOptions}.
463
+ * @param scope: The user and thread to associate the message with
464
+ * @param generateTextArgs The arguments to the generateText function, along
465
+ * with {@link AgentPrompt} options, such as promptMessageId.
466
+ * @param options Extra controls for the {@link ContextOptions} and {@link StorageOptions}.
436
467
  * @returns The result of the generateText function.
437
468
  */
438
469
  async generateText<
439
470
  TOOLS extends ToolSet | undefined = undefined,
440
- OUTPUT = never,
441
- OUTPUT_PARTIAL = never,
471
+ OUTPUT extends Output<any, any, any> = never,
442
472
  >(
443
473
  ctx: ActionCtx & CustomCtx,
444
- {
445
- userId: argsUserId,
446
- threadId,
447
- usageHandler,
448
- tools: threadTools,
449
- }: {
450
- userId?: string | null;
451
- threadId?: string;
452
- /**
453
- * The usage handler to use for this thread. Overrides any handler
454
- * set in the agent constructor.
455
- */
456
- usageHandler?: UsageHandler;
457
- /** Note: to get better type inference, pass tools in the next arg */
458
- tools?: ToolSet;
459
- },
460
- args: TextArgs<AgentTools, TOOLS, OUTPUT, OUTPUT_PARTIAL>,
474
+ threadOpts: { userId?: string | null; threadId?: string },
475
+ /**
476
+ * The arguments to the generateText function, similar to the ai sdk's
477
+ * {@link generateText} function, along with Agent prompt options.
478
+ */
479
+ generateTextArgs: AgentPrompt &
480
+ TextArgs<AgentTools, TOOLS, OUTPUT>,
461
481
  options?: Options,
462
482
  ): Promise<
463
483
  GenerateTextResult<TOOLS extends undefined ? AgentTools : TOOLS, OUTPUT> &
464
484
  GenerationOutputMetadata
465
485
  > {
466
- const opts = { ...this.options, ...options, usageHandler };
467
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
468
- userId: argsUserId ?? undefined,
469
- threadId,
470
- ...opts,
471
- });
472
- const { args: aiArgs, messageId, order, userId } = context;
473
- const toolCtx = {
474
- ...(ctx as UserActionCtx & CustomCtx),
475
- userId,
476
- threadId,
477
- messageId,
478
- agent: this,
479
- } satisfies ToolCtx;
486
+ const { args, promptMessageId, order, ...call } = await this.start(
487
+ ctx,
488
+ generateTextArgs,
489
+ { ...threadOpts, ...options },
490
+ );
491
+
480
492
  type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
481
- const tools = wrapTools(
482
- toolCtx,
483
- args.tools ?? threadTools ?? this.options.tools,
484
- ) as Tools;
485
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
493
+ const steps: StepResult<Tools>[] = [];
486
494
  try {
487
- const result = (await generateText<Tools, OUTPUT, OUTPUT_PARTIAL>({
488
- // Can be overridden
489
- stopWhen: this.options.stopWhen as StopCondition<Tools> | undefined,
490
- ...aiArgs,
491
- tools,
495
+ const result = (await generateText<Tools, OUTPUT>({
496
+ ...args,
497
+ prepareStep: async (options) => {
498
+ const result = await generateTextArgs.prepareStep?.(options);
499
+ call.updateModel(result?.model ?? options.model);
500
+ return result;
501
+ },
492
502
  onStepFinish: async (step) => {
493
- if (threadId && messageId && saveOutput) {
494
- await this.saveStep(ctx, {
495
- userId,
496
- threadId,
497
- promptMessageId: messageId,
498
- model: aiArgs.model.modelId,
499
- provider: aiArgs.model.provider,
500
- step,
501
- });
502
- }
503
- if (this.options.rawRequestResponseHandler) {
504
- await this.options.rawRequestResponseHandler(ctx, {
505
- userId,
506
- threadId,
507
- agentName: this.options.name,
508
- request: step.request,
509
- response: step.response,
510
- });
511
- }
512
- if (opts.usageHandler && step.usage) {
513
- await opts.usageHandler(ctx, {
514
- userId,
515
- threadId,
516
- agentName: this.options.name,
517
- model: aiArgs.model.modelId,
518
- provider: aiArgs.model.provider,
519
- usage: step.usage,
520
- providerMetadata: step.providerMetadata,
521
- });
522
- }
523
- return args.onStepFinish?.(step);
503
+ steps.push(step);
504
+ await call.save({ step }, await willContinue(steps, args.stopWhen));
505
+ return generateTextArgs.onStepFinish?.(step);
524
506
  },
525
- })) as GenerateTextResult<Tools, OUTPUT> & GenerationOutputMetadata;
526
- result.messageId = messageId;
527
- result.order = order;
528
- return result;
507
+ })) as GenerateTextResult<Tools, OUTPUT>;
508
+ const metadata: GenerationOutputMetadata = {
509
+ promptMessageId,
510
+ order,
511
+ savedMessages: call.getSavedMessages(),
512
+ messageId: promptMessageId,
513
+ };
514
+ return Object.assign(result, metadata);
529
515
  } catch (error) {
530
- if (threadId && messageId) {
531
- console.error("RollbackMessage", messageId);
532
- await ctx.runMutation(this.component.messages.rollbackMessage, {
533
- messageId,
534
- error: (error as Error).message,
535
- });
536
- }
516
+ await call.fail(errorToString(error));
537
517
  throw error;
538
518
  }
539
519
  }
@@ -547,26 +527,16 @@ export class Agent<
547
527
  */
548
528
  async streamText<
549
529
  TOOLS extends ToolSet | undefined = undefined,
550
- OUTPUT = never,
551
- PARTIAL_OUTPUT = never,
530
+ OUTPUT extends Output<any, any, any> = never,
552
531
  >(
553
532
  ctx: ActionCtx & CustomCtx,
554
- {
555
- userId: argsUserId,
556
- threadId,
557
- usageHandler,
558
- /** Note: to get better type inference, pass tools in the next arg */
559
- tools: threadTools,
560
- }: {
561
- userId?: string | null;
562
- threadId?: string;
563
- usageHandler?: UsageHandler;
564
- tools?: ToolSet;
565
- },
533
+ threadOpts: { userId?: string | null; threadId?: string },
566
534
  /**
567
- * The arguments to the streamText function, similar to the ai `streamText` function.
535
+ * The arguments to the streamText function, similar to the ai sdk's
536
+ * {@link streamText} function, along with Agent prompt options.
568
537
  */
569
- args: StreamingTextArgs<AgentTools, TOOLS, OUTPUT, PARTIAL_OUTPUT>,
538
+ streamTextArgs: AgentPrompt &
539
+ StreamingTextArgs<AgentTools, TOOLS, OUTPUT>,
570
540
  /**
571
541
  * The {@link ContextOptions} and {@link StorageOptions}
572
542
  * options to use for fetching contextual messages and saving input/output messages.
@@ -587,113 +557,29 @@ export class Agent<
587
557
  ): Promise<
588
558
  StreamTextResult<
589
559
  TOOLS extends undefined ? AgentTools : TOOLS,
590
- PARTIAL_OUTPUT
560
+ OUTPUT
591
561
  > &
592
562
  GenerationOutputMetadata
593
563
  > {
594
- const opts = { ...this.options, ...options, usageHandler };
595
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
596
- userId: argsUserId ?? undefined,
597
- threadId,
598
- ...opts,
599
- });
600
- const { args: aiArgs, messageId, order, stepOrder, userId } = context;
601
- const toolCtx = {
602
- ...(ctx as UserActionCtx & CustomCtx),
603
- userId,
604
- threadId,
605
- messageId,
606
- agent: this,
607
- };
608
- const tools = wrapTools(
609
- toolCtx,
610
- args.tools ?? threadTools ?? this.options.tools,
611
- ) as TOOLS extends undefined ? AgentTools : TOOLS;
612
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
613
- const streamer =
614
- threadId && opts.saveStreamDeltas
615
- ? new DeltaStreamer(this.component, ctx, opts.saveStreamDeltas, {
616
- threadId,
617
- userId,
618
- agentName: this.options.name,
619
- model: aiArgs.model.modelId,
620
- provider: aiArgs.model.provider,
621
- providerOptions: aiArgs.providerOptions,
622
- order,
623
- stepOrder,
624
- abortSignal: aiArgs.abortSignal,
625
- })
626
- : undefined;
627
-
628
- const result = streamText({
629
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
630
- stopWhen: this.options.stopWhen as any, // Can be overridden
631
- ...aiArgs,
632
- tools,
633
- abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
634
- experimental_transform: mergeTransforms(
635
- options?.saveStreamDeltas,
636
- args.experimental_transform,
637
- ),
638
- onChunk: async (event) => {
639
- await streamer?.addParts([event.chunk]);
640
- // console.log("onChunk", chunk);
641
- return args.onChunk?.(event);
642
- },
643
- onError: async (error) => {
644
- console.error("onError", error);
645
- if (threadId && messageId && saveOutput) {
646
- await ctx.runMutation(this.component.messages.rollbackMessage, {
647
- messageId,
648
- error: (error.error as Error).message,
649
- });
650
- }
651
- // TODO: update the streamer to error state
652
- return args.onError?.(error);
564
+ type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
565
+ return streamText<Tools, OUTPUT>(
566
+ ctx,
567
+ this.component,
568
+ {
569
+ ...streamTextArgs,
570
+ model: streamTextArgs.model ?? this.options.languageModel,
571
+ tools: (streamTextArgs.tools ?? this.options.tools) as Tools,
572
+ system: streamTextArgs.system ?? this.options.instructions,
573
+ stopWhen: (streamTextArgs.stopWhen ?? this.options.stopWhen) as any,
653
574
  },
654
- onStepFinish: async (step) => {
655
- // console.log("onStepFinish", step);
656
- if (threadId && messageId && saveOutput) {
657
- const saved = await this.saveStep(ctx, {
658
- userId,
659
- threadId,
660
- model: aiArgs.model.modelId,
661
- provider: aiArgs.model.provider,
662
- promptMessageId: messageId,
663
- step,
664
- });
665
- await streamer?.finish(saved.messages);
666
- }
667
- if (this.options.rawRequestResponseHandler) {
668
- await this.options.rawRequestResponseHandler(ctx, {
669
- userId,
670
- threadId,
671
- agentName: this.options.name,
672
- request: step.request,
673
- response: step.response,
674
- });
675
- }
676
- if (opts.usageHandler && step.usage) {
677
- await opts.usageHandler(ctx, {
678
- userId,
679
- threadId,
680
- agentName: this.options.name,
681
- model: aiArgs.model.modelId,
682
- provider: aiArgs.model.provider,
683
- usage: step.usage,
684
- providerMetadata: step.providerMetadata,
685
- });
686
- }
687
- return args.onStepFinish?.(step);
575
+ {
576
+ ...threadOpts,
577
+ ...this.options,
578
+ agentName: this.options.name,
579
+ agentForToolCtx: this,
580
+ ...options,
688
581
  },
689
- }) as StreamTextResult<
690
- TOOLS extends undefined ? AgentTools : TOOLS,
691
- PARTIAL_OUTPUT
692
- > &
693
- GenerationOutputMetadata;
694
- result.messageId = messageId;
695
- result.order = order;
696
- return result;
582
+ );
697
583
  }
698
584
 
699
585
  /**
@@ -703,81 +589,47 @@ export class Agent<
703
589
  * Use {@link continueThread} to get a version of this function already scoped
704
590
  * to a thread (and optionally userId).
705
591
  */
706
- async generateObject<T>(
707
- ctx: ActionCtx,
708
- {
709
- userId: argsUserId,
710
- threadId,
711
- usageHandler,
712
- }: {
713
- userId?: string | null;
714
- threadId?: string;
715
- usageHandler?: UsageHandler;
716
- },
592
+ async generateObject<
593
+ SCHEMA extends FlexibleSchema<unknown> = FlexibleSchema<JSONValue>,
594
+ OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string
595
+ ? "enum"
596
+ : "object",
597
+ RESULT = OUTPUT extends "array"
598
+ ? Array<InferSchema<SCHEMA>>
599
+ : InferSchema<SCHEMA>,
600
+ >(
601
+ ctx: ActionCtx & CustomCtx,
602
+ threadOpts: { userId?: string | null; threadId?: string },
717
603
  /**
718
- * The arguments to the generateObject function, similar to the ai.generateObject function.
604
+ * The arguments to the generateObject function, similar to the ai sdk's
605
+ * {@link generateObject} function, along with Agent prompt options.
719
606
  */
720
- args: OurObjectArgs<T>,
607
+ generateObjectArgs: AgentPrompt &
608
+ GenerateObjectArgs<SCHEMA, OUTPUT, RESULT>,
721
609
  /**
722
610
  * The {@link ContextOptions} and {@link StorageOptions}
723
611
  * options to use for fetching contextual messages and saving input/output messages.
724
612
  */
725
613
  options?: Options,
726
- ): Promise<GenerateObjectResult<T> & GenerationOutputMetadata> {
727
- const opts = { ...this.options, ...options, usageHandler };
728
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
729
- userId: argsUserId ?? undefined,
730
- threadId,
731
- ...opts,
732
- });
733
- const { args: aiArgs, messageId, order, userId } = context;
734
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
614
+ ): Promise<GenerateObjectResult<RESULT> & GenerationOutputMetadata> {
615
+ const { args, promptMessageId, order, fail, save, getSavedMessages } =
616
+ await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
617
+
735
618
  try {
736
619
  const result = (await generateObject(
737
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
738
- aiArgs as any,
739
- )) as GenerateObjectResult<T> & GenerationOutputMetadata;
620
+ args,
621
+ )) as GenerateObjectResult<RESULT>;
740
622
 
741
- if (threadId && messageId && saveOutput) {
742
- await this.saveObject(ctx, {
743
- threadId,
744
- promptMessageId: messageId,
745
- result,
746
- userId,
747
- model: aiArgs.model.modelId,
748
- provider: aiArgs.model.provider,
749
- });
750
- }
751
- result.messageId = messageId;
752
- result.order = order;
753
- if (this.options.rawRequestResponseHandler) {
754
- await this.options.rawRequestResponseHandler(ctx, {
755
- userId,
756
- threadId,
757
- agentName: this.options.name,
758
- request: result.request,
759
- response: result.response,
760
- });
761
- }
762
- if (opts.usageHandler && result.usage) {
763
- await opts.usageHandler(ctx, {
764
- userId,
765
- threadId,
766
- agentName: this.options.name,
767
- model: aiArgs.model.modelId,
768
- provider: aiArgs.model.provider,
769
- usage: result.usage,
770
- providerMetadata: result.providerMetadata,
771
- });
772
- }
773
- return result;
623
+ await save({ object: result });
624
+ const metadata: GenerationOutputMetadata = {
625
+ promptMessageId,
626
+ order,
627
+ savedMessages: getSavedMessages(),
628
+ messageId: promptMessageId,
629
+ };
630
+ return Object.assign(result, metadata);
774
631
  } catch (error) {
775
- if (threadId && messageId) {
776
- await ctx.runMutation(this.component.messages.rollbackMessage, {
777
- messageId,
778
- error: (error as Error).message,
779
- });
780
- }
632
+ await fail(errorToString(error));
781
633
  throw error;
782
634
  }
783
635
  }
@@ -789,94 +641,67 @@ export class Agent<
789
641
  * Use {@link continueThread} to get a version of this function already scoped
790
642
  * to a thread (and optionally userId).
791
643
  */
792
- async streamObject<T extends z.Schema | Schema>(
793
- ctx: ActionCtx,
794
- {
795
- userId: argsUserId,
796
- threadId,
797
- usageHandler,
798
- }: {
799
- userId?: string | null;
800
- threadId?: string;
801
- usageHandler?: UsageHandler;
802
- },
644
+ async streamObject<
645
+ SCHEMA extends FlexibleSchema<unknown> = FlexibleSchema<JSONValue>,
646
+ OUTPUT extends ObjectMode = InferSchema<SCHEMA> extends string
647
+ ? "enum"
648
+ : "object",
649
+ RESULT = OUTPUT extends "array"
650
+ ? Array<InferSchema<SCHEMA>>
651
+ : InferSchema<SCHEMA>,
652
+ >(
653
+ ctx: ActionCtx & CustomCtx,
654
+ threadOpts: { userId?: string | null; threadId?: string },
803
655
  /**
804
- * The arguments to the streamObject function, similar to the ai `streamObject` function.
656
+ * The arguments to the streamObject function, similar to the ai sdk's
657
+ * {@link streamObject} function, along with Agent prompt options.
805
658
  */
806
- args: OurStreamObjectArgs<T>,
659
+ streamObjectArgs: AgentPrompt & StreamObjectArgs<SCHEMA, OUTPUT, RESULT>,
807
660
  /**
808
661
  * The {@link ContextOptions} and {@link StorageOptions}
809
662
  * options to use for fetching contextual messages and saving input/output messages.
810
663
  */
811
664
  options?: Options,
812
665
  ): Promise<
813
- StreamObjectResult<DeepPartial<T>, T, never> & GenerationOutputMetadata
666
+ ReturnType<typeof streamObject<SCHEMA, OUTPUT, RESULT>> &
667
+ GenerationOutputMetadata
814
668
  > {
815
- // TODO: unify all this shared code between all the generate* and stream* functions
816
- const opts = { ...this.options, ...options, usageHandler };
817
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
818
- userId: argsUserId ?? undefined,
819
- threadId,
820
- ...opts,
821
- });
822
- const { args: aiArgs, messageId, order, userId } = context;
823
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
824
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
825
- const stream = streamObject<any>({
826
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
827
- ...(aiArgs as any),
669
+ const { args, promptMessageId, order, fail, save, getSavedMessages } =
670
+ await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
671
+
672
+ const stream = streamObject<SCHEMA, OUTPUT, RESULT>({
673
+ ...(args as any),
828
674
  onError: async (error) => {
829
- console.error("onError", error);
675
+ console.error(" streamObject onError", error);
676
+ // TODO: content that we have so far
677
+ // content: stream.fullStream.
678
+ await fail(errorToString(error.error));
830
679
  return args.onError?.(error);
831
680
  },
832
681
  onFinish: async (result) => {
833
- if (threadId && messageId && saveOutput) {
834
- await this.saveObject(ctx, {
835
- userId,
836
- threadId,
837
- promptMessageId: messageId,
838
- result: {
839
- object: result.object,
840
- finishReason: "stop",
841
- usage: result.usage,
842
- warnings: result.warnings,
843
- request: await stream.request,
844
- response: result.response,
845
- providerMetadata: result.providerMetadata,
846
- toJsonResponse: stream.toTextStreamResponse,
847
- },
848
- model: aiArgs.model.modelId,
849
- provider: aiArgs.model.provider,
850
- });
851
- }
852
- if (opts.usageHandler && result.usage) {
853
- await opts.usageHandler(ctx, {
854
- userId,
855
- threadId,
856
- agentName: this.options.name,
857
- model: aiArgs.model.modelId,
858
- provider: aiArgs.model.provider,
682
+ await save({
683
+ object: {
684
+ object: result.object,
685
+ finishReason: result.error ? "error" : "stop",
859
686
  usage: result.usage,
860
- providerMetadata: result.providerMetadata,
861
- });
862
- }
863
- if (this.options.rawRequestResponseHandler) {
864
- await this.options.rawRequestResponseHandler(ctx, {
865
- userId,
866
- threadId,
867
- agentName: this.options.name,
687
+ warnings: result.warnings,
868
688
  request: await stream.request,
869
689
  response: result.response,
870
- });
871
- }
872
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
873
- return args.onFinish?.(result as any);
690
+ providerMetadata: result.providerMetadata,
691
+ toJsonResponse: stream.toTextStreamResponse,
692
+ reasoning: undefined,
693
+ },
694
+ });
695
+ return args.onFinish?.(result);
874
696
  },
875
- }) as StreamObjectResult<DeepPartial<T>, T, never> &
876
- GenerationOutputMetadata;
877
- stream.messageId = messageId;
878
- stream.order = order;
879
- return stream;
697
+ });
698
+ const metadata: GenerationOutputMetadata = {
699
+ promptMessageId,
700
+ order,
701
+ savedMessages: getSavedMessages(),
702
+ messageId: promptMessageId,
703
+ };
704
+ return Object.assign(stream, metadata);
880
705
  }
881
706
 
882
707
  /**
@@ -887,7 +712,7 @@ export class Agent<
887
712
  * @returns The messageId of the saved message.
888
713
  */
889
714
  async saveMessage(
890
- ctx: RunMutationCtx,
715
+ ctx: MutationCtx | ActionCtx,
891
716
  args: SaveMessageArgs & {
892
717
  /**
893
718
  * If true, it will not generate embeddings for the message.
@@ -898,14 +723,11 @@ export class Agent<
898
723
  skipEmbeddings?: boolean;
899
724
  },
900
725
  ) {
901
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
726
+ const { messages } = await this.saveMessages(ctx, {
902
727
  threadId: args.threadId,
903
728
  userId: args.userId,
904
729
  embeddings: args.embedding
905
- ? {
906
- model: args.embedding.model,
907
- vectors: [args.embedding.vector],
908
- }
730
+ ? { model: args.embedding.model, vectors: [args.embedding.vector] }
909
731
  : undefined,
910
732
  messages:
911
733
  args.prompt !== undefined
@@ -913,8 +735,11 @@ export class Agent<
913
735
  : [args.message],
914
736
  metadata: args.metadata ? [args.metadata] : undefined,
915
737
  skipEmbeddings: args.skipEmbeddings,
738
+ promptMessageId: args.promptMessageId,
739
+ pendingMessageId: args.pendingMessageId,
916
740
  });
917
- return { messageId: lastMessageId, message: messages.at(-1)! };
741
+ const message = messages.at(-1)!;
742
+ return { messageId: message._id, message };
918
743
  }
919
744
 
920
745
  /**
@@ -926,7 +751,7 @@ export class Agent<
926
751
  * @returns
927
752
  */
928
753
  async saveMessages(
929
- ctx: RunMutationCtx | RunActionCtx,
754
+ ctx: MutationCtx | ActionCtx,
930
755
  args: SaveMessagesArgs & {
931
756
  /**
932
757
  * Skip generating embeddings for the messages. Useful if you're
@@ -936,20 +761,12 @@ export class Agent<
936
761
  */
937
762
  skipEmbeddings?: boolean;
938
763
  },
939
- ): Promise<{
940
- lastMessageId: string;
941
- messages: MessageDoc[];
942
- }> {
943
- let embeddings:
944
- | {
945
- vectors: (number[] | null)[];
946
- model: string;
947
- }
948
- | undefined;
764
+ ): Promise<{ messages: MessageDoc[] }> {
765
+ let embeddings: { vectors: (number[] | null)[]; model: string } | undefined;
949
766
  const { skipEmbeddings, ...rest } = args;
950
767
  if (args.embeddings) {
951
768
  embeddings = args.embeddings;
952
- } else if (!skipEmbeddings && this.options.textEmbedding) {
769
+ } else if (!skipEmbeddings && this.getEmbeddingModel()) {
953
770
  if (!("runAction" in ctx)) {
954
771
  console.warn(
955
772
  "You're trying to save messages and generate embeddings, but you're in a mutation. " +
@@ -967,10 +784,7 @@ export class Agent<
967
784
  } else {
968
785
  embeddings = await this.generateEmbeddings(
969
786
  ctx,
970
- {
971
- userId: args.userId ?? undefined,
972
- threadId: args.threadId,
973
- },
787
+ { userId: args.userId ?? undefined, threadId: args.threadId },
974
788
  args.messages,
975
789
  );
976
790
  }
@@ -993,7 +807,7 @@ export class Agent<
993
807
  * @returns The MessageDoc's in a format compatible with usePaginatedQuery.
994
808
  */
995
809
  async listMessages(
996
- ctx: RunQueryCtx,
810
+ ctx: QueryCtx | MutationCtx | ActionCtx,
997
811
  args: {
998
812
  threadId: string;
999
813
  paginationOpts: PaginationOptions;
@@ -1013,7 +827,7 @@ export class Agent<
1013
827
  * @returns The deltas for each stream from their existing cursor.
1014
828
  */
1015
829
  async syncStreams(
1016
- ctx: RunQueryCtx,
830
+ ctx: QueryCtx | MutationCtx | ActionCtx,
1017
831
  args: {
1018
832
  threadId: string;
1019
833
  streamArgs: StreamArgs | undefined;
@@ -1033,15 +847,26 @@ export class Agent<
1033
847
  * @returns
1034
848
  */
1035
849
  async fetchContextMessages(
1036
- ctx: RunQueryCtx | RunActionCtx,
850
+ ctx: QueryCtx | MutationCtx | ActionCtx,
1037
851
  args: {
1038
852
  userId: string | undefined;
1039
853
  threadId: string | undefined;
1040
- messages: (ModelMessage | Message)[];
1041
854
  /**
1042
- * If provided, it will search for messages up to and including this message.
1043
- * Note: if this is far in the past, text and vector search results may be more
1044
- * limited, as it's post-filtering the results.
855
+ * If targetMessageId is not provided, this text will be used
856
+ * for text and vector search
857
+ */
858
+ searchText?: string;
859
+ /**
860
+ * If provided, it will use this message for text/vector search (if enabled)
861
+ * and will only fetch messages up to (and including) this message's "order"
862
+ */
863
+ targetMessageId?: string;
864
+ /**
865
+ * @deprecated use searchText and targetMessageId instead
866
+ */
867
+ messages?: (ModelMessage | Message)[];
868
+ /**
869
+ * @deprecated use targetMessageId instead
1045
870
  */
1046
871
  upToAndIncludingMessageId?: string;
1047
872
  contextOptions: ContextOptions | undefined;
@@ -1057,19 +882,22 @@ export class Agent<
1057
882
  contextOptions,
1058
883
  getEmbedding: async (text) => {
1059
884
  assert("runAction" in ctx);
885
+ const embeddingModel = this.getEmbeddingModel();
1060
886
  assert(
1061
- this.options.textEmbedding,
1062
- "A textEmbedding model is required to be set on the Agent that you're doing vector search with",
887
+ embeddingModel,
888
+ "An embeddingModel (or textEmbeddingModel) is required to be set on the Agent that you're doing vector search with",
1063
889
  );
1064
890
  return {
1065
891
  embedding: (
1066
- await this.doEmbed(ctx, {
892
+ await embedMany(ctx, {
893
+ ...this.options,
894
+ agentName: this.options.name,
1067
895
  userId: args.userId,
1068
896
  threadId: args.threadId,
1069
897
  values: [text],
1070
898
  })
1071
899
  ).embeddings[0],
1072
- embeddingModel: this.options.textEmbedding.modelId,
900
+ embeddingModel: embeddingModel,
1073
901
  };
1074
902
  },
1075
903
  });
@@ -1082,7 +910,7 @@ export class Agent<
1082
910
  * @returns The metadata for the thread.
1083
911
  */
1084
912
  async getThreadMetadata(
1085
- ctx: RunQueryCtx,
913
+ ctx: QueryCtx | MutationCtx | ActionCtx,
1086
914
  args: { threadId: string },
1087
915
  ): Promise<ThreadDoc> {
1088
916
  return getThreadMetadata(ctx, this.component, args);
@@ -1096,7 +924,7 @@ export class Agent<
1096
924
  * @returns The updated thread metadata.
1097
925
  */
1098
926
  async updateThreadMetadata(
1099
- ctx: RunMutationCtx,
927
+ ctx: MutationCtx | ActionCtx,
1100
928
  args: {
1101
929
  threadId: string;
1102
930
  patch: Partial<
@@ -1117,55 +945,22 @@ export class Agent<
1117
945
  * @returns The embeddings for the messages.
1118
946
  */
1119
947
  async generateEmbeddings(
1120
- ctx: RunActionCtx,
1121
- {
1122
- userId,
1123
- threadId,
1124
- }: {
1125
- userId: string | undefined;
1126
- threadId: string | undefined;
1127
- },
948
+ ctx: ActionCtx,
949
+ args: { userId: string | undefined; threadId: string | undefined },
1128
950
  messages: (ModelMessage | Message)[],
1129
- ) {
1130
- if (!this.options.textEmbedding) {
1131
- return undefined;
1132
- }
1133
- let embeddings:
1134
- | {
1135
- vectors: (number[] | null)[];
1136
- dimension: VectorDimension;
1137
- model: string;
1138
- }
1139
- | undefined;
1140
- const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
1141
- // Find the indexes of the messages that have text.
1142
- const textIndexes = messageTexts
1143
- .map((t, i) => (t ? i : undefined))
1144
- .filter((i) => i !== undefined);
1145
- if (textIndexes.length === 0) {
1146
- return undefined;
1147
- }
1148
- // Then embed those messages.
1149
- const textEmbeddings = await this.doEmbed(ctx, {
1150
- userId,
1151
- threadId,
1152
- values: messageTexts as string[],
1153
- });
1154
- // Then assemble the embeddings into a single array with nulls for the messages without text.
1155
- const embeddingsOrNull = Array(messages.length).fill(null);
1156
- textIndexes.forEach((i, j) => {
1157
- embeddingsOrNull[i] = textEmbeddings.embeddings[j];
1158
- });
1159
- if (textEmbeddings.embeddings.length > 0) {
1160
- const dimension = textEmbeddings.embeddings[0].length;
1161
- validateVectorDimension(dimension);
1162
- embeddings = {
1163
- vectors: embeddingsOrNull,
1164
- dimension,
1165
- model: this.options.textEmbedding.modelId,
1166
- };
1167
- }
1168
- return embeddings;
951
+ ): Promise<
952
+ | {
953
+ vectors: (number[] | null)[];
954
+ dimension: VectorDimension;
955
+ model: string;
956
+ }
957
+ | undefined
958
+ > {
959
+ return embedMessages(
960
+ ctx,
961
+ { ...args, ...this.options, agentName: this.options.name },
962
+ messages,
963
+ );
1169
964
  }
1170
965
 
1171
966
  /**
@@ -1176,10 +971,8 @@ export class Agent<
1176
971
  * @param args The messageIds to generate embeddings for.
1177
972
  */
1178
973
  async generateAndSaveEmbeddings(
1179
- ctx: RunActionCtx,
1180
- args: {
1181
- messageIds: string[];
1182
- },
974
+ ctx: ActionCtx,
975
+ args: { messageIds: string[] },
1183
976
  ) {
1184
977
  const messages = (
1185
978
  await ctx.runQuery(this.component.messages.getMessagesByIds, {
@@ -1194,10 +987,6 @@ export class Agent<
1194
987
  .join(", "),
1195
988
  );
1196
989
  }
1197
- await this._generateAndSaveEmbeddings(ctx, messages);
1198
- }
1199
-
1200
- async _generateAndSaveEmbeddings(ctx: RunActionCtx, messages: MessageDoc[]) {
1201
990
  if (messages.some((m) => !m.message)) {
1202
991
  throw new Error(
1203
992
  "Some messages don't have a message: " +
@@ -1207,45 +996,143 @@ export class Agent<
1207
996
  .join(", "),
1208
997
  );
1209
998
  }
1210
- const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
1211
- if (messagesMissingEmbeddings.length === 0) {
1212
- return;
999
+ const embeddingModel = this.getEmbeddingModel();
1000
+ if (!embeddingModel) {
1001
+ throw new Error(
1002
+ "No embeddings were generated for the messages. You must pass an embeddingModel (or textEmbeddingModel) to the agent constructor.",
1003
+ );
1213
1004
  }
1214
- const embeddings = await this.generateEmbeddings(
1005
+ await generateAndSaveEmbeddings(
1215
1006
  ctx,
1007
+ this.component,
1216
1008
  {
1217
- userId: messagesMissingEmbeddings[0]!.userId,
1218
- threadId: messagesMissingEmbeddings[0]!.threadId,
1009
+ ...this.options,
1010
+ agentName: this.options.name,
1011
+ threadId: messages[0].threadId,
1012
+ userId: messages[0].userId,
1013
+ embeddingModel,
1219
1014
  },
1220
- messagesMissingEmbeddings.map((m) => deserializeMessage(m!.message!)),
1015
+ messages,
1221
1016
  );
1222
- if (!embeddings) {
1223
- if (!this.options.textEmbedding) {
1224
- throw new Error(
1225
- "No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.",
1226
- );
1227
- }
1228
- throw new Error(
1229
- "No embeddings were generated for these messages: " +
1230
- messagesMissingEmbeddings.map((m) => m!._id).join(", "),
1231
- );
1232
- }
1233
- await ctx.runMutation(this.component.vector.index.insertBatch, {
1234
- vectorDimension: embeddings.dimension,
1235
- vectors: messagesMissingEmbeddings
1236
- .map((m, i) => ({
1237
- messageId: m!._id,
1238
- model: embeddings.model,
1239
- table: "messages",
1240
- userId: m.userId,
1241
- threadId: m.threadId,
1242
- vector: embeddings.vectors[i],
1243
- }))
1244
- .filter(
1245
- (v): v is Extract<typeof v, { vector: number[] }> =>
1246
- v.vector !== null,
1247
- ),
1017
+ }
1018
+
1019
+ /**
1020
+ * Approve a tool call that requires human approval.
1021
+ * Saves a `tool-approval-response` message to the thread.
1022
+ * After calling this, call `agent.streamText` or `agent.generateText`
1023
+ * with `promptMessageId` set to the returned `messageId` to continue
1024
+ * generation the AI SDK will automatically execute the approved tool.
1025
+ *
1026
+ * The approval response is attached to the same generation order as the
1027
+ * original approval request, preserving tool_call/tool_result adjacency in
1028
+ * the continuation context even if newer thread messages exist.
1029
+ *
1030
+ * @param ctx A ctx object from a mutation.
1031
+ * @param args.threadId The thread containing the tool call.
1032
+ * @param args.approvalId The approval ID from the tool-approval-request part.
1033
+ * @param args.reason Optional reason for approval.
1034
+ * @returns The messageId of the saved approval response message.
1035
+ */
1036
+ async approveToolCall(
1037
+ ctx: MutationCtx,
1038
+ args: { threadId: string; approvalId: string; reason?: string },
1039
+ ): Promise<{ messageId: string }> {
1040
+ return this.respondToToolCallApproval(ctx, { ...args, approved: true });
1041
+ }
1042
+
1043
+ /**
1044
+ * Deny a tool call that requires human approval.
1045
+ * Saves a `tool-approval-response` message to the thread.
1046
+ * After calling this, call `agent.streamText` or `agent.generateText`
1047
+ * with `promptMessageId` set to the returned `messageId` to continue
1048
+ * generation — the AI SDK will automatically create an `execution-denied`
1049
+ * result and let the model respond accordingly.
1050
+ *
1051
+ * @param ctx A ctx object from a mutation.
1052
+ * @param args.threadId The thread containing the tool call.
1053
+ * @param args.approvalId The approval ID from the tool-approval-request part.
1054
+ * @param args.reason Optional reason for denial.
1055
+ * @returns The messageId of the saved denial response message.
1056
+ */
1057
+ async denyToolCall(
1058
+ ctx: MutationCtx,
1059
+ args: { threadId: string; approvalId: string; reason?: string },
1060
+ ): Promise<{ messageId: string }> {
1061
+ return this.respondToToolCallApproval(ctx, { ...args, approved: false });
1062
+ }
1063
+
1064
+ private async respondToToolCallApproval(
1065
+ ctx: MutationCtx,
1066
+ args: {
1067
+ threadId: string;
1068
+ approvalId: string;
1069
+ approved: boolean;
1070
+ reason?: string;
1071
+ },
1072
+ ): Promise<{ messageId: string }> {
1073
+ const promptMessageId = await this.getApprovalRequestMessageId(ctx, {
1074
+ threadId: args.threadId,
1075
+ approvalId: args.approvalId,
1076
+ });
1077
+
1078
+ const { messageId } = await this.saveMessage(ctx, {
1079
+ threadId: args.threadId,
1080
+ promptMessageId,
1081
+ skipEmbeddings: true,
1082
+ message: {
1083
+ role: "tool",
1084
+ content: [
1085
+ {
1086
+ type: "tool-approval-response",
1087
+ approvalId: args.approvalId,
1088
+ approved: args.approved,
1089
+ reason: args.reason,
1090
+ },
1091
+ ],
1092
+ },
1248
1093
  });
1094
+ return { messageId };
1095
+ }
1096
+
1097
+ private async getApprovalRequestMessageId(
1098
+ ctx: MutationCtx,
1099
+ args: { threadId: string; approvalId: string },
1100
+ ): Promise<string> {
1101
+ // NOTE: This pagination returns messages in descending order (newest first).
1102
+ // The "already handled" check (tool-approval-response) relies on seeing
1103
+ // responses before their corresponding requests. If the pagination order
1104
+ // changes, this logic will need to be updated.
1105
+ let cursor: string | null = null;
1106
+ do {
1107
+ const page = await this.listMessages(ctx, {
1108
+ threadId: args.threadId,
1109
+ paginationOpts: { cursor, numItems: 100 },
1110
+ });
1111
+ for (const message of page.page) {
1112
+ const content = message.message?.content;
1113
+ if (!Array.isArray(content)) continue;
1114
+ for (const part of content) {
1115
+ const typedPart = part as { type?: unknown; approvalId?: unknown };
1116
+ if (
1117
+ typedPart.type === "tool-approval-response" &&
1118
+ typedPart.approvalId === args.approvalId
1119
+ ) {
1120
+ throw new Error(`Approval ${args.approvalId} was already handled`);
1121
+ }
1122
+ if (
1123
+ typedPart.type === "tool-approval-request" &&
1124
+ typedPart.approvalId === args.approvalId
1125
+ ) {
1126
+ return message._id;
1127
+ }
1128
+ }
1129
+ }
1130
+ cursor = page.isDone ? null : page.continueCursor;
1131
+ } while (cursor !== null);
1132
+
1133
+ throw new Error(
1134
+ `Approval request ${args.approvalId} was not found in thread ${args.threadId}`,
1135
+ );
1249
1136
  }
1250
1137
 
1251
1138
  /**
@@ -1277,22 +1164,22 @@ export class Agent<
1277
1164
  */
1278
1165
  provider?: string;
1279
1166
  },
1280
- ): Promise<{ messages: MessageDoc[]; pending?: MessageDoc }> {
1281
- const messages = await serializeNewMessagesInStep(
1167
+ ): Promise<{ messages: MessageDoc[] }> {
1168
+ const { messages } = await serializeNewMessagesInStep(
1282
1169
  ctx,
1283
1170
  this.component,
1284
1171
  args.step,
1285
1172
  {
1286
- provider: args.provider ?? this.options.chat.provider,
1287
- model: args.model ?? this.options.chat.modelId,
1173
+ provider: args.provider ?? getProviderName(this.options.languageModel),
1174
+ model: args.model ?? getModelName(this.options.languageModel),
1288
1175
  },
1289
1176
  );
1290
1177
  const embeddings = await this.generateEmbeddings(
1291
1178
  ctx,
1292
1179
  { userId: args.userId, threadId: args.threadId },
1293
- messages.map((m) => deserializeMessage(m.message)),
1180
+ messages.map((m) => m.message),
1294
1181
  );
1295
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
1182
+ return ctx.runMutation(this.component.messages.addMessages, {
1296
1183
  userId: args.userId,
1297
1184
  threadId: args.threadId,
1298
1185
  agentName: this.options.name,
@@ -1301,7 +1188,6 @@ export class Agent<
1301
1188
  embeddings,
1302
1189
  failPendingSteps: false,
1303
1190
  });
1304
- return saved;
1305
1191
  }
1306
1192
 
1307
1193
  /**
@@ -1322,23 +1208,29 @@ export class Agent<
1322
1208
  result: GenerateObjectResult<unknown>;
1323
1209
  metadata?: Omit<MessageWithMetadata, "message">;
1324
1210
  },
1325
- ): Promise<void> {
1211
+ ): Promise<{ messages: MessageDoc[] }> {
1326
1212
  const { messages } = await serializeObjectResult(
1327
1213
  ctx,
1328
1214
  this.component,
1329
1215
  args.result,
1330
1216
  {
1331
- model: args.model ?? this.options.chat.modelId,
1332
- provider: args.provider ?? this.options.chat.provider,
1217
+ model:
1218
+ args.model ??
1219
+ args.metadata?.model ??
1220
+ getModelName(this.options.languageModel),
1221
+ provider:
1222
+ args.provider ??
1223
+ args.metadata?.provider ??
1224
+ getProviderName(this.options.languageModel),
1333
1225
  },
1334
1226
  );
1335
1227
  const embeddings = await this.generateEmbeddings(
1336
1228
  ctx,
1337
1229
  { userId: args.userId, threadId: args.threadId },
1338
- messages.map((m) => deserializeMessage(m.message)),
1230
+ messages.map((m) => m.message),
1339
1231
  );
1340
1232
 
1341
- await ctx.runMutation(this.component.messages.addMessages, {
1233
+ return ctx.runMutation(this.component.messages.addMessages, {
1342
1234
  userId: args.userId,
1343
1235
  threadId: args.threadId,
1344
1236
  promptMessageId: args.promptMessageId,
@@ -1346,7 +1238,6 @@ export class Agent<
1346
1238
  messages,
1347
1239
  embeddings,
1348
1240
  agentName: this.options.name,
1349
- pending: false,
1350
1241
  });
1351
1242
  }
1352
1243
 
@@ -1358,25 +1249,17 @@ export class Agent<
1358
1249
  * @param args What message to save. Generally the parent message sent into
1359
1250
  * the generateText call.
1360
1251
  */
1361
- async completeMessage(
1362
- ctx: RunMutationCtx,
1252
+ async finalizeMessage(
1253
+ ctx: MutationCtx | ActionCtx,
1363
1254
  args: {
1364
- threadId: string;
1365
1255
  messageId: string;
1366
- result: { kind: "error"; error: string } | { kind: "success" };
1256
+ result: { status: "failed"; error: string } | { status: "success" };
1367
1257
  },
1368
1258
  ): Promise<void> {
1369
- const result = args.result;
1370
- if (result.kind === "success") {
1371
- await ctx.runMutation(this.component.messages.commitMessage, {
1372
- messageId: args.messageId,
1373
- });
1374
- } else {
1375
- await ctx.runMutation(this.component.messages.rollbackMessage, {
1376
- messageId: args.messageId,
1377
- error: result.error,
1378
- });
1379
- }
1259
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
1260
+ messageId: args.messageId,
1261
+ result: args.result,
1262
+ });
1380
1263
  }
1381
1264
 
1382
1265
  /**
@@ -1385,13 +1268,13 @@ export class Agent<
1385
1268
  * @param args The message fields to update.
1386
1269
  */
1387
1270
  async updateMessage(
1388
- ctx: RunMutationCtx,
1271
+ ctx: MutationCtx | ActionCtx,
1389
1272
  args: {
1390
1273
  /** The id of the message to update. */
1391
1274
  messageId: string;
1392
1275
  patch: {
1393
1276
  /** The message to replace the existing message. */
1394
- message: (ModelMessage & { id?: string }) | Message;
1277
+ message: ModelMessage | Message;
1395
1278
  /** The status to set on the message. */
1396
1279
  status: "success" | "error";
1397
1280
  /** The error message to set on the message. */
@@ -1435,10 +1318,8 @@ export class Agent<
1435
1318
  * @param args The ids of the messages to delete.
1436
1319
  */
1437
1320
  async deleteMessages(
1438
- ctx: RunMutationCtx,
1439
- args: {
1440
- messageIds: string[];
1441
- },
1321
+ ctx: MutationCtx | ActionCtx,
1322
+ args: { messageIds: string[] },
1442
1323
  ): Promise<void> {
1443
1324
  await ctx.runMutation(this.component.messages.deleteByIds, args);
1444
1325
  }
@@ -1450,10 +1331,8 @@ export class Agent<
1450
1331
  * @param args The id of the message to delete.
1451
1332
  */
1452
1333
  async deleteMessage(
1453
- ctx: RunMutationCtx,
1454
- args: {
1455
- messageId: string;
1456
- },
1334
+ ctx: MutationCtx | ActionCtx,
1335
+ args: { messageId: string },
1457
1336
  ): Promise<void> {
1458
1337
  await ctx.runMutation(this.component.messages.deleteByIds, {
1459
1338
  messageIds: [args.messageId],
@@ -1498,7 +1377,7 @@ export class Agent<
1498
1377
  * @param args The range of messages to delete.
1499
1378
  */
1500
1379
  async deleteMessageRange(
1501
- ctx: RunMutationCtx,
1380
+ ctx: MutationCtx | ActionCtx,
1502
1381
  args: {
1503
1382
  threadId: string;
1504
1383
  startOrder: number;
@@ -1524,11 +1403,8 @@ export class Agent<
1524
1403
  * @param args The id of the thread to delete and optionally the page size to use for the delete.
1525
1404
  */
1526
1405
  async deleteThreadAsync(
1527
- ctx: RunMutationCtx,
1528
- args: {
1529
- threadId: string;
1530
- pageSize?: number;
1531
- },
1406
+ ctx: MutationCtx | ActionCtx,
1407
+ args: { threadId: string; pageSize?: number },
1532
1408
  ): Promise<void> {
1533
1409
  await ctx.runMutation(this.component.threads.deleteAllForThreadIdAsync, {
1534
1410
  threadId: args.threadId,
@@ -1544,11 +1420,8 @@ export class Agent<
1544
1420
  * @param args The id of the thread to delete and optionally the page size to use for the delete.
1545
1421
  */
1546
1422
  async deleteThreadSync(
1547
- ctx: RunActionCtx,
1548
- args: {
1549
- threadId: string;
1550
- pageSize?: number;
1551
- },
1423
+ ctx: ActionCtx,
1424
+ args: { threadId: string; pageSize?: number },
1552
1425
  ): Promise<void> {
1553
1426
  await ctx.runAction(this.component.threads.deleteAllForThreadIdSync, {
1554
1427
  threadId: args.threadId,
@@ -1556,278 +1429,6 @@ export class Agent<
1556
1429
  });
1557
1430
  }
1558
1431
 
1559
- async _saveMessagesAndFetchContext<
1560
- T extends {
1561
- id?: string;
1562
- prompt?: string | (ModelMessage | Message)[];
1563
- messages?: (ModelMessage | Message)[];
1564
- system?: string;
1565
- promptMessageId?: string;
1566
- model?: LanguageModelV2;
1567
- maxRetries?: number;
1568
- },
1569
- >(
1570
- ctx: RunActionCtx,
1571
- args: T,
1572
- {
1573
- userId: argsUserId,
1574
- threadId,
1575
- contextOptions,
1576
- storageOptions,
1577
- }: {
1578
- userId: string | undefined;
1579
- threadId: string | undefined;
1580
- } & Options,
1581
- ): Promise<{
1582
- args: T & { model: LanguageModelV2 };
1583
- userId: string | undefined;
1584
- messageId: string | undefined;
1585
- order: number | undefined;
1586
- stepOrder: number | undefined;
1587
- }> {
1588
- // If only a promptMessageId is provided, this will be empty.
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 }];
1595
- const userId =
1596
- argsUserId ??
1597
- (threadId &&
1598
- (await ctx.runQuery(this.component.threads.getThread, { threadId }))
1599
- ?.userId);
1600
- // If only a messageId is provided, this will add that message to the end.
1601
- const contextMessages = await this.fetchContextMessages(ctx, {
1602
- userId,
1603
- threadId,
1604
- upToAndIncludingMessageId: args.promptMessageId,
1605
- messages,
1606
- contextOptions,
1607
- });
1608
- // If it was a promptMessageId, pop it off context messages
1609
- // and add to the end of messages.
1610
- const promptMessageIndex = args.promptMessageId
1611
- ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
1612
- : -1;
1613
- const promptMessage =
1614
- promptMessageIndex !== -1
1615
- ? contextMessages.splice(promptMessageIndex, 1)[0]
1616
- : undefined;
1617
-
1618
- let messageId = promptMessage?._id;
1619
- let order = promptMessage?.order;
1620
- let stepOrder = promptMessage?.stepOrder;
1621
- if (
1622
- threadId &&
1623
- messages.length + prompt.length &&
1624
- storageOptions?.saveMessages !== "none" &&
1625
- // If it was a promptMessageId, we don't want to save it again.
1626
- (!args.promptMessageId || storageOptions?.saveMessages === "all")
1627
- ) {
1628
- const saveAll = storageOptions?.saveMessages === "all";
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 };
1633
- const saved = await this.saveMessages(ctx, {
1634
- threadId,
1635
- userId,
1636
- messages: toSave,
1637
- metadata,
1638
- failPendingSteps: true,
1639
- });
1640
- messageId = saved.lastMessageId;
1641
- order = saved.messages.at(-1)?.order;
1642
- stepOrder = saved.messages.at(-1)?.stepOrder;
1643
- }
1644
-
1645
- if (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
- }
1650
- // Lazily generate embeddings for the prompt message, if it doesn't have
1651
- // embeddings yet. This can happen if the message was saved in a mutation
1652
- // where the LLM is not available.
1653
- if (!promptMessage.embeddingId && this.options.textEmbedding) {
1654
- await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
1655
- }
1656
- }
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
-
1668
- let processedMessages = [
1669
- ...prePrompt,
1670
- ...messages,
1671
- ...prompt,
1672
- ...existingResponses,
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
- }
1680
-
1681
- // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
1682
- if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
1683
- processedMessages = await this._inlineMessagesFiles(processedMessages);
1684
- }
1685
-
1686
- const { prompt: _, model, ...rest } = args;
1687
- return {
1688
- args: {
1689
- ...rest,
1690
- maxRetries: args.maxRetries ?? this.options.maxRetries,
1691
- model: model ?? this.options.chat,
1692
- system: args.system ?? this.options.instructions,
1693
- messages: processedMessages,
1694
- } as T & { model: LanguageModelV2 },
1695
- userId,
1696
- messageId,
1697
- order,
1698
- stepOrder,
1699
- };
1700
- }
1701
-
1702
- async doEmbed(
1703
- ctx: RunActionCtx,
1704
- options: {
1705
- userId: string | undefined;
1706
- threadId: string | undefined;
1707
- values: string[];
1708
- abortSignal?: AbortSignal;
1709
- headers?: Record<string, string>;
1710
- },
1711
- ): Promise<{ embeddings: number[][] }> {
1712
- const embeddingModel = this.options.textEmbedding;
1713
- assert(
1714
- embeddingModel,
1715
- "a textEmbedding model is required to be set on the Agent that you're doing vector search with",
1716
- );
1717
- const result = await embedMany({
1718
- model: embeddingModel,
1719
- values: options.values,
1720
- abortSignal: options.abortSignal,
1721
- headers: options.headers,
1722
- maxRetries: this.options.maxRetries,
1723
- });
1724
- if (this.options.usageHandler && result.usage) {
1725
- await this.options.usageHandler(ctx, {
1726
- userId: options.userId,
1727
- threadId: options.threadId,
1728
- agentName: this.options.name,
1729
- model: embeddingModel.modelId,
1730
- provider: embeddingModel.provider,
1731
- providerMetadata: undefined,
1732
- usage: {
1733
- inputTokens: result.usage.tokens,
1734
- outputTokens: 0,
1735
- totalTokens: result.usage.tokens,
1736
- },
1737
- });
1738
- }
1739
- return { embeddings: result.embeddings };
1740
- }
1741
-
1742
- /**
1743
- * Process messages to inline file and image URLs that point to localhost
1744
- * by converting them to base64. This solves the problem of LLMs not being
1745
- * able to access localhost URLs.
1746
- */
1747
- private async _inlineMessagesFiles(
1748
- messages: (ModelMessage | Message)[],
1749
- ): Promise<(ModelMessage | Message)[]> {
1750
- // Process each message to convert localhost URLs to base64
1751
- return Promise.all(
1752
- messages.map(async (message): Promise<ModelMessage | Message> => {
1753
- if (
1754
- (message.role !== "user" && message.role !== "assistant") ||
1755
- typeof message.content === "string" ||
1756
- !Array.isArray(message.content)
1757
- ) {
1758
- return message;
1759
- }
1760
-
1761
- const processedContent = await Promise.all(
1762
- message.content.map(async (part) => {
1763
- if (part.type === "image" && part.image instanceof URL) {
1764
- assert(
1765
- message.role === "user",
1766
- "Images can only be in user messages",
1767
- );
1768
- if (this._isLocalhostUrl(part.image)) {
1769
- const imageData = await this._downloadFile(part.image);
1770
- return {
1771
- ...part,
1772
- image: imageData,
1773
- } as ImagePart;
1774
- }
1775
- }
1776
-
1777
- // Handle file parts
1778
- if (part.type === "file" && part.data instanceof URL) {
1779
- if (this._isLocalhostUrl(part.data)) {
1780
- const fileData = await this._downloadFile(part.data);
1781
- return {
1782
- ...part,
1783
- data: fileData,
1784
- } as FilePart;
1785
- }
1786
- }
1787
-
1788
- return part;
1789
- }),
1790
- );
1791
- if (message.role === "user") {
1792
- return {
1793
- ...message,
1794
- content: processedContent as UserContent,
1795
- };
1796
- } else {
1797
- return {
1798
- ...message,
1799
- content: processedContent as AssistantContent,
1800
- };
1801
- }
1802
- }),
1803
- );
1804
- }
1805
-
1806
- /**
1807
- * Check if a URL points to localhost
1808
- */
1809
- private _isLocalhostUrl(url: URL): boolean {
1810
- return (
1811
- url.hostname === "localhost" ||
1812
- url.hostname === "127.0.0.1" ||
1813
- url.hostname === "::1" ||
1814
- url.hostname === "0.0.0.0"
1815
- );
1816
- }
1817
-
1818
- /**
1819
- * Download a file from a URL
1820
- */
1821
- private async _downloadFile(url: URL): Promise<ArrayBuffer> {
1822
- // Fetch the file
1823
- const response = await fetch(url);
1824
- if (!response.ok) {
1825
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
1826
- }
1827
-
1828
- return await response.arrayBuffer();
1829
- }
1830
-
1831
1432
  /**
1832
1433
  * WORKFLOW UTILITIES
1833
1434
  */
@@ -1871,23 +1472,7 @@ export class Agent<
1871
1472
  * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1872
1473
  */
1873
1474
  asTextAction<DataModel extends GenericDataModel>(
1874
- spec?: {
1875
- /**
1876
- * When to stop generating text.
1877
- * Defaults to the {@link Agent["options"].stopWhen} option.
1878
- */
1879
- stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
1880
- /**
1881
- * The {@link ContextOptions} to use for fetching contextual messages and
1882
- * saving input/output messages.
1883
- * Defaults to the {@link Agent.contextOptions} option.
1884
- */
1885
- contextOptions?: ContextOptions;
1886
- /**
1887
- * The {@link StorageOptions} to use for saving input/output messages.
1888
- * Defaults to the {@link Agent.storageOptions} option.
1889
- */
1890
- storageOptions?: StorageOptions;
1475
+ spec: MaybeCustomCtx<CustomCtx, DataModel, AgentTools> & {
1891
1476
  /**
1892
1477
  * Whether to stream the text.
1893
1478
  * If false, it will generate the text in a single call. (default)
@@ -1896,57 +1481,34 @@ export class Agent<
1896
1481
  * defaults if you pass true.
1897
1482
  */
1898
1483
  stream?: boolean | StreamingOptions;
1899
- } & (CustomCtx extends Record<string, unknown>
1900
- ? {
1901
- /**
1902
- * If you have a custom ctx that you use with the Agent
1903
- * (e.g. new Agent<{ orgId: string }>(...))
1904
- * you need to provide this function to add any extra fields.
1905
- * e.g.
1906
- * ```ts
1907
- * const myAgent = new Agent<{ orgId: string }>(...);
1908
- * const myAction = myAgent.asTextAction({
1909
- * customCtx: (ctx: ActionCtx, target, llmArgs) => {
1910
- * const orgId = await lookupOrgId(ctx, target.threadId);
1911
- * return { orgId };
1912
- * },
1913
- * });
1914
- * ```
1915
- * Then, in your tools, you can
1916
- */
1917
- customCtx: (
1918
- ctx: GenericActionCtx<DataModel>,
1919
- target: {
1920
- userId?: string | undefined;
1921
- threadId?: string | undefined;
1922
- },
1923
- llmArgs: TextArgs<AgentTools>,
1924
- ) => CustomCtx;
1925
- }
1926
- : { customCtx?: never }),
1484
+ /**
1485
+ * When to stop generating text.
1486
+ * Defaults to the {@link Agent["options"].stopWhen} option.
1487
+ */
1488
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
1489
+ } & Options,
1490
+ overrides?: CallSettings,
1927
1491
  ) {
1928
- const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1929
1492
  return internalActionGeneric({
1930
1493
  args: vTextArgs,
1931
1494
  handler: async (ctx_, args) => {
1932
1495
  const stream =
1933
- args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1934
- const targetArgs = { userId: args.userId, threadId: args.threadId };
1496
+ args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1497
+ const { userId, threadId, prompt, messages, maxSteps, ...rest } = args;
1498
+ const targetArgs = { userId, threadId };
1935
1499
  const llmArgs = {
1936
- stopWhen,
1937
- ...omit(args, ["storageOptions", "contextOptions"]),
1938
- messages: args.messages?.map(deserializeMessage),
1939
- prompt: Array.isArray(args.prompt)
1940
- ? args.prompt.map(deserializeMessage)
1941
- : args.prompt,
1500
+ stopWhen: spec?.stopWhen,
1501
+ ...overrides,
1502
+ ...omit(rest, ["storageOptions", "contextOptions", "stream"]),
1503
+ messages: messages?.map(toModelMessage),
1504
+ prompt: Array.isArray(prompt) ? prompt.map(toModelMessage) : prompt,
1942
1505
  toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1943
1506
  } satisfies StreamingTextArgs<AgentTools>;
1944
- if (args.maxSteps) {
1945
- llmArgs.stopWhen = stepCountIs(args.maxSteps);
1507
+ if (maxSteps) {
1508
+ llmArgs.stopWhen = stepCountIs(maxSteps);
1946
1509
  }
1947
1510
  const opts = {
1948
- ...this.options,
1949
- ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
1511
+ ...pick(spec, ["contextOptions", "storageOptions"]),
1950
1512
  ...pick(args, ["contextOptions", "storageOptions"]),
1951
1513
  saveStreamDeltas: stream,
1952
1514
  };
@@ -1954,9 +1516,8 @@ export class Agent<
1954
1516
  spec?.customCtx
1955
1517
  ? { ...ctx_, ...spec.customCtx(ctx_, targetArgs, llmArgs) }
1956
1518
  : ctx_
1957
- ) as UserActionCtx & CustomCtx;
1519
+ ) as GenericActionCtx<GenericDataModel> & CustomCtx;
1958
1520
  if (stream) {
1959
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1960
1521
  const result = await this.streamText<any>(
1961
1522
  ctx,
1962
1523
  targetArgs,
@@ -1966,13 +1527,13 @@ export class Agent<
1966
1527
  await result.consumeStream();
1967
1528
  return {
1968
1529
  text: await result.text,
1969
- messageId: result.messageId,
1530
+ promptMessageId: result.promptMessageId,
1970
1531
  order: result.order,
1971
1532
  finishReason: await result.finishReason,
1972
- warnings: result.warnings,
1533
+ warnings: await result.warnings,
1534
+ savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1973
1535
  };
1974
1536
  } else {
1975
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1976
1537
  const res = await this.generateText<any>(
1977
1538
  ctx,
1978
1539
  targetArgs,
@@ -1981,10 +1542,11 @@ export class Agent<
1981
1542
  );
1982
1543
  return {
1983
1544
  text: res.text,
1984
- messageId: res.messageId,
1545
+ promptMessageId: res.promptMessageId,
1985
1546
  order: res.order,
1986
1547
  finishReason: res.finishReason,
1987
1548
  warnings: res.warnings,
1549
+ savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
1988
1550
  };
1989
1551
  }
1990
1552
  },
@@ -1997,57 +1559,49 @@ export class Agent<
1997
1559
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1998
1560
  * and stopWhen.
1999
1561
  */
2000
- asObjectAction<T>(
2001
- spec: OurObjectArgs<T>,
2002
- options?: {
2003
- contextOptions?: ContextOptions;
2004
- storageOptions?: StorageOptions;
2005
- },
1562
+ asObjectAction<T, DataModel extends GenericDataModel>(
1563
+ objectArgs: GenerateObjectArgs<FlexibleSchema<T>> & Partial<AgentPrompt>,
1564
+ options?: Options & MaybeCustomCtx<CustomCtx, DataModel, AgentTools>,
2006
1565
  ) {
2007
1566
  return internalActionGeneric({
2008
1567
  args: vSafeObjectArgs,
2009
- handler: async (ctx, args) => {
2010
- const { userId, threadId, ...rest } = args;
1568
+ handler: async (ctx_, args) => {
1569
+ const { userId, threadId, callSettings, ...rest } = args;
2011
1570
  const overrides = pick(rest, ["contextOptions", "storageOptions"]);
2012
- const value = await this.generateObject(
2013
- ctx,
2014
- { userId, threadId },
2015
- {
2016
- ...spec,
2017
- ...omit(rest, ["contextOptions", "storageOptions"]),
2018
- } as OurObjectArgs<unknown>,
2019
- { ...this.options, ...options, ...overrides },
2020
- );
1571
+ const targetArgs = { userId, threadId };
1572
+ const llmArgs = {
1573
+ ...objectArgs,
1574
+ ...callSettings,
1575
+ ...omit(rest, ["storageOptions", "contextOptions"]),
1576
+ messages: args.messages?.map(toModelMessage),
1577
+ prompt: Array.isArray(args.prompt)
1578
+ ? args.prompt.map(toModelMessage)
1579
+ : args.prompt,
1580
+ } as GenerateObjectArgs<FlexibleSchema<T>>;
1581
+ const ctx = (
1582
+ options?.customCtx
1583
+ ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs as any) }
1584
+ : ctx_
1585
+ ) as GenericActionCtx<GenericDataModel> & CustomCtx;
1586
+ const value = await this.generateObject(ctx, targetArgs, llmArgs, {
1587
+ ...this.options,
1588
+ ...options,
1589
+ ...overrides,
1590
+ });
2021
1591
  return {
2022
- object: value.object as T,
2023
- messageId: value.messageId,
1592
+ object: convexToJson(value.object as Value) as T,
1593
+ promptMessageId: value.promptMessageId,
2024
1594
  order: value.order,
2025
1595
  finishReason: value.finishReason,
2026
1596
  warnings: value.warnings,
1597
+ savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
2027
1598
  };
2028
1599
  },
2029
1600
  });
2030
1601
  }
2031
1602
 
2032
1603
  /**
2033
- * Save messages to the thread.
2034
- * Useful as a step in Workflows, e.g.
2035
- * ```ts
2036
- * const saveMessages = agent.asSaveMessagesMutation();
2037
- *
2038
- * const myWorkflow = workflow.define({
2039
- * args: {...},
2040
- * handler: async (step, args) => {
2041
- * // do things to create (but not save)messages
2042
- * const { messageIds } = await step.runMutation(internal.foo.saveMessages, {
2043
- * threadId: args.threadId,
2044
- * messages: args.messages,
2045
- * });
2046
- * // ...
2047
- * },
2048
- * })
2049
- * ```
2050
- * @returns A mutation that can be used to save messages to the thread.
1604
+ * @deprecated Use {@link saveMessages} directly instead.
2051
1605
  */
2052
1606
  asSaveMessagesMutation() {
2053
1607
  return internalMutationGeneric({
@@ -2056,226 +1610,21 @@ export class Agent<
2056
1610
  userId: v.optional(v.string()),
2057
1611
  promptMessageId: v.optional(v.string()),
2058
1612
  messages: v.array(vMessageWithMetadata),
2059
- pending: v.optional(v.boolean()),
2060
1613
  failPendingSteps: v.optional(v.boolean()),
1614
+ embeddings: v.optional(vMessageEmbeddings),
2061
1615
  },
2062
1616
  handler: async (ctx, args) => {
2063
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
1617
+ const { messages } = await this.saveMessages(ctx, {
2064
1618
  ...args,
2065
- messages: args.messages.map((m) => deserializeMessage(m.message)),
1619
+ messages: args.messages.map((m) => toModelMessage(m.message)),
2066
1620
  metadata: args.messages.map(({ message: _, ...m }) => m),
1621
+ skipEmbeddings: true,
2067
1622
  });
2068
1623
  return {
2069
- lastMessageId,
2070
- messageIds: messages.map((m) => m._id),
1624
+ lastMessageId: messages.at(-1)!._id,
1625
+ messages: messages.map((m) => pick(m, ["_id", "order", "stepOrder"])),
2071
1626
  };
2072
1627
  },
2073
1628
  });
2074
1629
  }
2075
1630
  }
2076
-
2077
- /**
2078
- * Create a thread to store messages with an Agent.
2079
- * @param ctx The context from a mutation or action.
2080
- * @param component The Agent component, usually `components.agent`.
2081
- * @param args The associated thread metadata.
2082
- * @returns The id of the created thread.
2083
- */
2084
- export async function createThread(
2085
- ctx: RunMutationCtx,
2086
- component: AgentComponent,
2087
- args?: {
2088
- userId?: string | null;
2089
- title?: string;
2090
- summary?: string;
2091
- },
2092
- ) {
2093
- const { _id: threadId } = await ctx.runMutation(
2094
- component.threads.createThread,
2095
- {
2096
- userId: args?.userId ?? undefined,
2097
- title: args?.title,
2098
- summary: args?.summary,
2099
- },
2100
- );
2101
- return threadId;
2102
- }
2103
-
2104
- /**
2105
- * Get the metadata for a thread.
2106
- * @param ctx A ctx object from a query, mutation, or action.
2107
- * @param args.threadId The thread to get the metadata for.
2108
- * @returns The metadata for the thread.
2109
- */
2110
- export async function getThreadMetadata(
2111
- ctx: RunQueryCtx,
2112
- component: AgentComponent,
2113
- args: { threadId: string },
2114
- ): Promise<ThreadDoc> {
2115
- const thread = await ctx.runQuery(component.threads.getThread, {
2116
- threadId: args.threadId,
2117
- });
2118
- if (!thread) {
2119
- throw new Error("Thread not found");
2120
- }
2121
- return thread;
2122
- }
2123
-
2124
- type SaveMessagesArgs = {
2125
- threadId: string;
2126
- userId?: string | null;
2127
- /**
2128
- * The message that these messages are in response to. They will be
2129
- * the same "order" as this message, at increasing stepOrder(s).
2130
- */
2131
- promptMessageId?: string;
2132
- /**
2133
- * The messages to save.
2134
- */
2135
- messages: ((ModelMessage & { id?: string | undefined }) | Message)[];
2136
- /**
2137
- * Metadata to save with the messages. Each element corresponds to the
2138
- * message at the same index.
2139
- */
2140
- metadata?: Omit<MessageWithMetadata, "message">[];
2141
- /**
2142
- * If false, it will "commit" the messages immediately.
2143
- * If true, it will mark them as pending until the final step has finished.
2144
- * Defaults to false.
2145
- */
2146
- pending?: boolean;
2147
- /**
2148
- * If true, it will fail any pending steps.
2149
- * Defaults to false.
2150
- */
2151
- failPendingSteps?: boolean;
2152
- /**
2153
- * The embeddings to save with the messages.
2154
- */
2155
- embeddings?: Omit<MessageEmbeddings, "dimension">;
2156
- };
2157
-
2158
- /**
2159
- * Explicitly save messages associated with the thread (& user if provided)
2160
- */
2161
- export async function saveMessages(
2162
- ctx: RunMutationCtx,
2163
- component: AgentComponent,
2164
- args: SaveMessagesArgs & {
2165
- /**
2166
- * The agent name to associate with the messages.
2167
- */
2168
- agentName?: string;
2169
- },
2170
- ) {
2171
- let embeddings: MessageEmbeddings | undefined;
2172
- if (args.embeddings) {
2173
- const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
2174
- if (dimension) {
2175
- validateVectorDimension(dimension);
2176
- embeddings = {
2177
- model: args.embeddings.model,
2178
- dimension,
2179
- vectors: args.embeddings.vectors,
2180
- };
2181
- }
2182
- }
2183
- const result = await ctx.runMutation(component.messages.addMessages, {
2184
- threadId: args.threadId,
2185
- userId: args.userId ?? undefined,
2186
- agentName: args.agentName,
2187
- promptMessageId: args.promptMessageId,
2188
- embeddings,
2189
- messages: await Promise.all(
2190
- args.messages.map(async (m, i) => {
2191
- const { message, fileIds } = await serializeMessage(ctx, component, m);
2192
- return {
2193
- ...args.metadata?.[i],
2194
- message,
2195
- fileIds,
2196
- } as MessageWithMetadata;
2197
- }),
2198
- ),
2199
- failPendingSteps: args.failPendingSteps ?? false,
2200
- pending: args.pending ?? false,
2201
- });
2202
- return {
2203
- lastMessageId: result.messages.at(-1)!._id,
2204
- messages: result.messages,
2205
- };
2206
- }
2207
-
2208
- type SaveMessageArgs = {
2209
- threadId: string;
2210
- userId?: string | null;
2211
- /**
2212
- * Metadata to save with the messages. Each element corresponds to the
2213
- * message at the same index.
2214
- */
2215
- metadata?: Omit<MessageWithMetadata, "message">;
2216
- /**
2217
- * The embedding to save with the message.
2218
- */
2219
- embedding?: {
2220
- vector: number[];
2221
- model: string;
2222
- };
2223
- } & (
2224
- | {
2225
- prompt?: undefined;
2226
- /**
2227
- * The message to save.
2228
- */
2229
- message: ModelMessage | Message;
2230
- }
2231
- | {
2232
- /*
2233
- * The prompt to save with the message.
2234
- */
2235
- prompt: string;
2236
- message?: undefined;
2237
- }
2238
- );
2239
-
2240
- /**
2241
- * Save a message to the thread.
2242
- * @param ctx A ctx object from a mutation or action.
2243
- * @param args The message and what to associate it with (user / thread)
2244
- * You can pass extra metadata alongside the message, e.g. associated fileIds.
2245
- * @returns The messageId of the saved message.
2246
- */
2247
- export async function saveMessage(
2248
- ctx: RunMutationCtx,
2249
- component: AgentComponent,
2250
- args: SaveMessageArgs & {
2251
- /**
2252
- * The agent name to associate with the message.
2253
- */
2254
- agentName?: string;
2255
- },
2256
- ) {
2257
- let embeddings:
2258
- | {
2259
- vectors: number[][];
2260
- model: string;
2261
- }
2262
- | undefined;
2263
- if (args.embedding && args.embedding.vector) {
2264
- embeddings = {
2265
- model: args.embedding.model,
2266
- vectors: [args.embedding.vector],
2267
- };
2268
- }
2269
- const { lastMessageId, messages } = await saveMessages(ctx, component, {
2270
- threadId: args.threadId,
2271
- userId: args.userId ?? undefined,
2272
- agentName: args.agentName,
2273
- messages:
2274
- args.prompt !== undefined
2275
- ? [{ role: "user", content: args.prompt }]
2276
- : [args.message],
2277
- metadata: args.metadata ? [args.metadata] : undefined,
2278
- embeddings,
2279
- });
2280
- return { messageId: lastMessageId, message: messages.at(-1)! };
2281
- }