@convex-dev/agent 0.5.0-alpha.1 → 0.6.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +32 -27
  2. package/dist/UIMessages.d.ts +46 -0
  3. package/dist/UIMessages.d.ts.map +1 -0
  4. package/dist/UIMessages.js +546 -0
  5. package/dist/UIMessages.js.map +1 -0
  6. package/dist/client/createTool.d.ts +129 -27
  7. package/dist/client/createTool.d.ts.map +1 -1
  8. package/dist/client/createTool.js +66 -12
  9. package/dist/client/createTool.js.map +1 -1
  10. package/dist/client/defaultComponent.d.ts +11 -0
  11. package/dist/client/defaultComponent.d.ts.map +1 -0
  12. package/dist/client/defaultComponent.js +7 -0
  13. package/dist/client/defaultComponent.js.map +1 -0
  14. package/dist/client/definePlaygroundAPI.d.ts +1323 -192
  15. package/dist/client/definePlaygroundAPI.d.ts.map +1 -1
  16. package/dist/client/definePlaygroundAPI.js +52 -28
  17. package/dist/client/definePlaygroundAPI.js.map +1 -1
  18. package/dist/client/files.d.ts +20 -7
  19. package/dist/client/files.d.ts.map +1 -1
  20. package/dist/client/files.js +68 -11
  21. package/dist/client/files.js.map +1 -1
  22. package/dist/client/index.d.ts +1056 -965
  23. package/dist/client/index.d.ts.map +1 -1
  24. package/dist/client/index.js +242 -748
  25. package/dist/client/index.js.map +1 -1
  26. package/dist/client/messages.d.ts +461 -0
  27. package/dist/client/messages.d.ts.map +1 -0
  28. package/dist/client/messages.js +106 -0
  29. package/dist/client/messages.js.map +1 -0
  30. package/dist/client/mockModel.d.ts +42 -0
  31. package/dist/client/mockModel.d.ts.map +1 -0
  32. package/dist/client/mockModel.js +175 -0
  33. package/dist/client/mockModel.js.map +1 -0
  34. package/dist/client/saveInputMessages.d.ts +20 -0
  35. package/dist/client/saveInputMessages.d.ts.map +1 -0
  36. package/dist/client/saveInputMessages.js +58 -0
  37. package/dist/client/saveInputMessages.js.map +1 -0
  38. package/dist/client/search.d.ts +346 -35
  39. package/dist/client/search.d.ts.map +1 -1
  40. package/dist/client/search.js +350 -39
  41. package/dist/client/search.js.map +1 -1
  42. package/dist/client/start.d.ts +84 -0
  43. package/dist/client/start.d.ts.map +1 -0
  44. package/dist/client/start.js +171 -0
  45. package/dist/client/start.js.map +1 -0
  46. package/dist/client/streamText.d.ts +46 -0
  47. package/dist/client/streamText.d.ts.map +1 -0
  48. package/dist/client/streamText.js +93 -0
  49. package/dist/client/streamText.js.map +1 -0
  50. package/dist/client/streaming.d.ts +3705 -32
  51. package/dist/client/streaming.d.ts.map +1 -1
  52. package/dist/client/streaming.js +141 -59
  53. package/dist/client/streaming.js.map +1 -1
  54. package/dist/client/threads.d.ts +46 -0
  55. package/dist/client/threads.d.ts.map +1 -0
  56. package/dist/client/threads.js +49 -0
  57. package/dist/client/threads.js.map +1 -0
  58. package/dist/client/types.d.ts +265 -128
  59. package/dist/client/types.d.ts.map +1 -1
  60. package/dist/client/utils.d.ts +4 -0
  61. package/dist/client/utils.d.ts.map +1 -0
  62. package/dist/client/utils.js +21 -0
  63. package/dist/client/utils.js.map +1 -0
  64. package/dist/component/_generated/api.d.ts +24 -2178
  65. package/dist/component/_generated/api.d.ts.map +1 -1
  66. package/dist/component/_generated/api.js +10 -1
  67. package/dist/component/_generated/api.js.map +1 -1
  68. package/dist/component/_generated/component.d.ts +3119 -0
  69. package/dist/component/_generated/component.d.ts.map +1 -0
  70. package/dist/component/_generated/component.js +11 -0
  71. package/dist/component/_generated/component.js.map +1 -0
  72. package/dist/component/_generated/dataModel.d.ts +4 -18
  73. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  74. package/dist/component/_generated/dataModel.js +11 -0
  75. package/dist/component/_generated/dataModel.js.map +1 -0
  76. package/dist/component/_generated/server.d.ts +10 -38
  77. package/dist/component/_generated/server.d.ts.map +1 -1
  78. package/dist/component/_generated/server.js +9 -5
  79. package/dist/component/_generated/server.js.map +1 -1
  80. package/dist/component/files.d.ts +16 -10
  81. package/dist/component/files.d.ts.map +1 -1
  82. package/dist/component/files.js +10 -2
  83. package/dist/component/files.js.map +1 -1
  84. package/dist/component/messages.d.ts +2553 -342
  85. package/dist/component/messages.d.ts.map +1 -1
  86. package/dist/component/messages.js +387 -154
  87. package/dist/component/messages.js.map +1 -1
  88. package/dist/component/schema.d.ts +5697 -3584
  89. package/dist/component/schema.d.ts.map +1 -1
  90. package/dist/component/schema.js +18 -41
  91. package/dist/component/schema.js.map +1 -1
  92. package/dist/component/streams.d.ts +35 -335
  93. package/dist/component/streams.d.ts.map +1 -1
  94. package/dist/component/streams.js +114 -73
  95. package/dist/component/streams.js.map +1 -1
  96. package/dist/component/threads.d.ts +16 -16
  97. package/dist/component/users.d.ts +4 -4
  98. package/dist/component/vector/index.d.ts +1 -1
  99. package/dist/component/vector/index.d.ts.map +1 -1
  100. package/dist/component/vector/index.js +1 -3
  101. package/dist/component/vector/index.js.map +1 -1
  102. package/dist/deltas.d.ts +43 -0
  103. package/dist/deltas.d.ts.map +1 -0
  104. package/dist/deltas.js +447 -0
  105. package/dist/deltas.js.map +1 -0
  106. package/dist/mapping.d.ts +20 -20
  107. package/dist/mapping.d.ts.map +1 -1
  108. package/dist/mapping.js +313 -96
  109. package/dist/mapping.js.map +1 -1
  110. package/dist/react/SmoothText.d.ts +5 -0
  111. package/dist/react/SmoothText.d.ts.map +1 -0
  112. package/dist/react/SmoothText.js +6 -0
  113. package/dist/react/SmoothText.js.map +1 -0
  114. package/dist/react/index.d.ts +5 -77
  115. package/dist/react/index.d.ts.map +1 -1
  116. package/dist/react/index.js +6 -160
  117. package/dist/react/index.js.map +1 -1
  118. package/dist/react/optimisticallySendMessage.d.ts +36 -3
  119. package/dist/react/optimisticallySendMessage.d.ts.map +1 -1
  120. package/dist/react/optimisticallySendMessage.js +35 -9
  121. package/dist/react/optimisticallySendMessage.js.map +1 -1
  122. package/dist/react/types.d.ts +4 -18
  123. package/dist/react/types.d.ts.map +1 -1
  124. package/dist/react/useDeltaStreams.d.ts +10 -0
  125. package/dist/react/useDeltaStreams.d.ts.map +1 -0
  126. package/dist/react/useDeltaStreams.js +101 -0
  127. package/dist/react/useDeltaStreams.js.map +1 -0
  128. package/dist/react/useSmoothText.d.ts +13 -12
  129. package/dist/react/useSmoothText.d.ts.map +1 -1
  130. package/dist/react/useSmoothText.js +32 -15
  131. package/dist/react/useSmoothText.js.map +1 -1
  132. package/dist/react/useStreamingUIMessages.d.ts +22 -0
  133. package/dist/react/useStreamingUIMessages.d.ts.map +1 -0
  134. package/dist/react/useStreamingUIMessages.js +92 -0
  135. package/dist/react/useStreamingUIMessages.js.map +1 -0
  136. package/dist/react/useThreadMessages.d.ts +104 -0
  137. package/dist/react/useThreadMessages.d.ts.map +1 -0
  138. package/dist/react/useThreadMessages.js +148 -0
  139. package/dist/react/useThreadMessages.js.map +1 -0
  140. package/dist/react/useUIMessages.d.ts +96 -0
  141. package/dist/react/useUIMessages.d.ts.map +1 -0
  142. package/dist/react/useUIMessages.js +108 -0
  143. package/dist/react/useUIMessages.js.map +1 -0
  144. package/dist/shared.d.ts +20 -4
  145. package/dist/shared.d.ts.map +1 -1
  146. package/dist/shared.js +45 -8
  147. package/dist/shared.js.map +1 -1
  148. package/dist/validators.d.ts +22981 -5666
  149. package/dist/validators.d.ts.map +1 -1
  150. package/dist/validators.js +245 -137
  151. package/dist/validators.js.map +1 -1
  152. package/package.json +98 -50
  153. package/src/UIMessages.combineUIMessages.test.ts +239 -0
  154. package/src/UIMessages.test.ts +273 -0
  155. package/src/UIMessages.ts +739 -0
  156. package/src/client/createTool.ts +293 -76
  157. package/src/client/defaultComponent.ts +17 -0
  158. package/src/client/definePlaygroundAPI.ts +67 -31
  159. package/src/client/files.ts +100 -20
  160. package/src/client/index.test.ts +40 -85
  161. package/src/client/index.ts +520 -1290
  162. package/src/client/messages.ts +237 -0
  163. package/src/client/mockModel.ts +245 -0
  164. package/src/client/saveInputMessages.test.ts +583 -0
  165. package/src/client/saveInputMessages.ts +101 -0
  166. package/src/client/search.test.ts +1207 -0
  167. package/src/client/search.ts +577 -70
  168. package/src/client/start.ts +310 -0
  169. package/src/client/streamText.ts +163 -0
  170. package/src/client/streaming.test.ts +186 -0
  171. package/src/client/streaming.ts +219 -97
  172. package/src/client/threads.ts +83 -0
  173. package/src/client/types.ts +368 -219
  174. package/src/client/utils.ts +27 -0
  175. package/src/component/_generated/api.ts +64 -0
  176. package/src/component/_generated/component.ts +4913 -0
  177. package/src/component/_generated/{server.d.ts → server.ts} +33 -21
  178. package/src/component/files.ts +11 -2
  179. package/src/component/messages.test.ts +195 -51
  180. package/src/component/messages.ts +490 -201
  181. package/src/component/schema.ts +20 -46
  182. package/src/component/setup.test.ts +7 -0
  183. package/src/component/streams.ts +184 -83
  184. package/src/component/users.test.ts +0 -1
  185. package/src/component/vector/index.ts +1 -3
  186. package/src/deltas.test.ts +626 -0
  187. package/src/deltas.ts +570 -0
  188. package/src/fromUIMessages.test.ts +497 -0
  189. package/src/mapping.test.ts +103 -6
  190. package/src/mapping.ts +422 -161
  191. package/src/react/SmoothText.tsx +9 -0
  192. package/src/react/index.ts +10 -230
  193. package/src/react/optimisticallySendMessage.ts +55 -12
  194. package/src/react/types.ts +6 -39
  195. package/src/react/useDeltaStreams.ts +154 -0
  196. package/src/react/useSmoothText.ts +56 -36
  197. package/src/react/useStreamingUIMessages.ts +143 -0
  198. package/src/react/useThreadMessages.ts +262 -0
  199. package/src/react/useUIMessages.test.ts +255 -0
  200. package/src/react/useUIMessages.ts +195 -0
  201. package/src/shared.ts +88 -12
  202. package/src/test.ts +18 -0
  203. package/src/toUIMessages.test.ts +1269 -0
  204. package/src/validators.test.ts +18 -19
  205. package/src/validators.ts +325 -185
  206. package/dist/client/_generated/_ignore.d.ts +0 -1
  207. package/dist/client/_generated/_ignore.d.ts.map +0 -1
  208. package/dist/client/_generated/_ignore.js +0 -3
  209. package/dist/client/_generated/_ignore.js.map +0 -1
  210. package/dist/client/listMessages.d.ts +0 -22
  211. package/dist/client/listMessages.d.ts.map +0 -1
  212. package/dist/client/listMessages.js +0 -25
  213. package/dist/client/listMessages.js.map +0 -1
  214. package/dist/package.json +0 -3
  215. package/dist/react/deltas.d.ts +0 -26
  216. package/dist/react/deltas.d.ts.map +0 -1
  217. package/dist/react/deltas.js +0 -384
  218. package/dist/react/deltas.js.map +0 -1
  219. package/dist/react/toUIMessages.d.ts +0 -15
  220. package/dist/react/toUIMessages.d.ts.map +0 -1
  221. package/dist/react/toUIMessages.js +0 -211
  222. package/dist/react/toUIMessages.js.map +0 -1
  223. package/src/client/listMessages.ts +0 -38
  224. package/src/component/_generated/api.d.ts +0 -2202
  225. package/src/component/_generated/api.js +0 -23
  226. package/src/component/_generated/server.js +0 -90
  227. package/src/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  228. package/src/react/deltas.test.ts +0 -315
  229. package/src/react/deltas.ts +0 -478
  230. package/src/react/toUIMessages.test.ts +0 -420
  231. package/src/react/toUIMessages.ts +0 -253
  232. package/src/vitest.config.ts +0 -7
  233. /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 = "https://github.com/get-convex/agent/blob/main/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"),
232
+ * languageModel: openai.chat("gpt-4o-mini"),
182
233
  */
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")
193
- */
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.
226
- */
227
- maxRetries?: number;
228
- /**
229
- * The usage handler to use for this agent.
249
+ * determines when to stop. Defaults to the AI SDK default.
230
250
  */
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,24 @@ 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
- ),
1248
- });
1249
1017
  }
1250
1018
 
1251
1019
  /**
@@ -1277,22 +1045,22 @@ export class Agent<
1277
1045
  */
1278
1046
  provider?: string;
1279
1047
  },
1280
- ): Promise<{ messages: MessageDoc[]; pending?: MessageDoc }> {
1281
- const messages = await serializeNewMessagesInStep(
1048
+ ): Promise<{ messages: MessageDoc[] }> {
1049
+ const { messages } = await serializeNewMessagesInStep(
1282
1050
  ctx,
1283
1051
  this.component,
1284
1052
  args.step,
1285
1053
  {
1286
- provider: args.provider ?? this.options.chat.provider,
1287
- model: args.model ?? this.options.chat.modelId,
1054
+ provider: args.provider ?? getProviderName(this.options.languageModel),
1055
+ model: args.model ?? getModelName(this.options.languageModel),
1288
1056
  },
1289
1057
  );
1290
1058
  const embeddings = await this.generateEmbeddings(
1291
1059
  ctx,
1292
1060
  { userId: args.userId, threadId: args.threadId },
1293
- messages.map((m) => deserializeMessage(m.message)),
1061
+ messages.map((m) => m.message),
1294
1062
  );
1295
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
1063
+ return ctx.runMutation(this.component.messages.addMessages, {
1296
1064
  userId: args.userId,
1297
1065
  threadId: args.threadId,
1298
1066
  agentName: this.options.name,
@@ -1301,7 +1069,6 @@ export class Agent<
1301
1069
  embeddings,
1302
1070
  failPendingSteps: false,
1303
1071
  });
1304
- return saved;
1305
1072
  }
1306
1073
 
1307
1074
  /**
@@ -1322,23 +1089,29 @@ export class Agent<
1322
1089
  result: GenerateObjectResult<unknown>;
1323
1090
  metadata?: Omit<MessageWithMetadata, "message">;
1324
1091
  },
1325
- ): Promise<void> {
1092
+ ): Promise<{ messages: MessageDoc[] }> {
1326
1093
  const { messages } = await serializeObjectResult(
1327
1094
  ctx,
1328
1095
  this.component,
1329
1096
  args.result,
1330
1097
  {
1331
- model: args.model ?? this.options.chat.modelId,
1332
- provider: args.provider ?? this.options.chat.provider,
1098
+ model:
1099
+ args.model ??
1100
+ args.metadata?.model ??
1101
+ getModelName(this.options.languageModel),
1102
+ provider:
1103
+ args.provider ??
1104
+ args.metadata?.provider ??
1105
+ getProviderName(this.options.languageModel),
1333
1106
  },
1334
1107
  );
1335
1108
  const embeddings = await this.generateEmbeddings(
1336
1109
  ctx,
1337
1110
  { userId: args.userId, threadId: args.threadId },
1338
- messages.map((m) => deserializeMessage(m.message)),
1111
+ messages.map((m) => m.message),
1339
1112
  );
1340
1113
 
1341
- await ctx.runMutation(this.component.messages.addMessages, {
1114
+ return ctx.runMutation(this.component.messages.addMessages, {
1342
1115
  userId: args.userId,
1343
1116
  threadId: args.threadId,
1344
1117
  promptMessageId: args.promptMessageId,
@@ -1346,7 +1119,6 @@ export class Agent<
1346
1119
  messages,
1347
1120
  embeddings,
1348
1121
  agentName: this.options.name,
1349
- pending: false,
1350
1122
  });
1351
1123
  }
1352
1124
 
@@ -1358,25 +1130,17 @@ export class Agent<
1358
1130
  * @param args What message to save. Generally the parent message sent into
1359
1131
  * the generateText call.
1360
1132
  */
1361
- async completeMessage(
1362
- ctx: RunMutationCtx,
1133
+ async finalizeMessage(
1134
+ ctx: MutationCtx | ActionCtx,
1363
1135
  args: {
1364
- threadId: string;
1365
1136
  messageId: string;
1366
- result: { kind: "error"; error: string } | { kind: "success" };
1137
+ result: { status: "failed"; error: string } | { status: "success" };
1367
1138
  },
1368
1139
  ): 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
- }
1140
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
1141
+ messageId: args.messageId,
1142
+ result: args.result,
1143
+ });
1380
1144
  }
1381
1145
 
1382
1146
  /**
@@ -1385,13 +1149,13 @@ export class Agent<
1385
1149
  * @param args The message fields to update.
1386
1150
  */
1387
1151
  async updateMessage(
1388
- ctx: RunMutationCtx,
1152
+ ctx: MutationCtx | ActionCtx,
1389
1153
  args: {
1390
1154
  /** The id of the message to update. */
1391
1155
  messageId: string;
1392
1156
  patch: {
1393
1157
  /** The message to replace the existing message. */
1394
- message: (ModelMessage & { id?: string }) | Message;
1158
+ message: ModelMessage | Message;
1395
1159
  /** The status to set on the message. */
1396
1160
  status: "success" | "error";
1397
1161
  /** The error message to set on the message. */
@@ -1435,10 +1199,8 @@ export class Agent<
1435
1199
  * @param args The ids of the messages to delete.
1436
1200
  */
1437
1201
  async deleteMessages(
1438
- ctx: RunMutationCtx,
1439
- args: {
1440
- messageIds: string[];
1441
- },
1202
+ ctx: MutationCtx | ActionCtx,
1203
+ args: { messageIds: string[] },
1442
1204
  ): Promise<void> {
1443
1205
  await ctx.runMutation(this.component.messages.deleteByIds, args);
1444
1206
  }
@@ -1450,10 +1212,8 @@ export class Agent<
1450
1212
  * @param args The id of the message to delete.
1451
1213
  */
1452
1214
  async deleteMessage(
1453
- ctx: RunMutationCtx,
1454
- args: {
1455
- messageId: string;
1456
- },
1215
+ ctx: MutationCtx | ActionCtx,
1216
+ args: { messageId: string },
1457
1217
  ): Promise<void> {
1458
1218
  await ctx.runMutation(this.component.messages.deleteByIds, {
1459
1219
  messageIds: [args.messageId],
@@ -1498,7 +1258,7 @@ export class Agent<
1498
1258
  * @param args The range of messages to delete.
1499
1259
  */
1500
1260
  async deleteMessageRange(
1501
- ctx: RunMutationCtx,
1261
+ ctx: MutationCtx | ActionCtx,
1502
1262
  args: {
1503
1263
  threadId: string;
1504
1264
  startOrder: number;
@@ -1524,11 +1284,8 @@ export class Agent<
1524
1284
  * @param args The id of the thread to delete and optionally the page size to use for the delete.
1525
1285
  */
1526
1286
  async deleteThreadAsync(
1527
- ctx: RunMutationCtx,
1528
- args: {
1529
- threadId: string;
1530
- pageSize?: number;
1531
- },
1287
+ ctx: MutationCtx | ActionCtx,
1288
+ args: { threadId: string; pageSize?: number },
1532
1289
  ): Promise<void> {
1533
1290
  await ctx.runMutation(this.component.threads.deleteAllForThreadIdAsync, {
1534
1291
  threadId: args.threadId,
@@ -1544,11 +1301,8 @@ export class Agent<
1544
1301
  * @param args The id of the thread to delete and optionally the page size to use for the delete.
1545
1302
  */
1546
1303
  async deleteThreadSync(
1547
- ctx: RunActionCtx,
1548
- args: {
1549
- threadId: string;
1550
- pageSize?: number;
1551
- },
1304
+ ctx: ActionCtx,
1305
+ args: { threadId: string; pageSize?: number },
1552
1306
  ): Promise<void> {
1553
1307
  await ctx.runAction(this.component.threads.deleteAllForThreadIdSync, {
1554
1308
  threadId: args.threadId,
@@ -1556,278 +1310,6 @@ export class Agent<
1556
1310
  });
1557
1311
  }
1558
1312
 
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
1313
  /**
1832
1314
  * WORKFLOW UTILITIES
1833
1315
  */
@@ -1871,23 +1353,7 @@ export class Agent<
1871
1353
  * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1872
1354
  */
1873
1355
  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;
1356
+ spec: MaybeCustomCtx<CustomCtx, DataModel, AgentTools> & {
1891
1357
  /**
1892
1358
  * Whether to stream the text.
1893
1359
  * If false, it will generate the text in a single call. (default)
@@ -1896,57 +1362,34 @@ export class Agent<
1896
1362
  * defaults if you pass true.
1897
1363
  */
1898
1364
  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 }),
1365
+ /**
1366
+ * When to stop generating text.
1367
+ * Defaults to the {@link Agent["options"].stopWhen} option.
1368
+ */
1369
+ stopWhen?: StopCondition<AgentTools> | Array<StopCondition<AgentTools>>;
1370
+ } & Options,
1371
+ overrides?: CallSettings,
1927
1372
  ) {
1928
- const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
1929
1373
  return internalActionGeneric({
1930
1374
  args: vTextArgs,
1931
1375
  handler: async (ctx_, args) => {
1932
1376
  const stream =
1933
- args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1934
- const targetArgs = { userId: args.userId, threadId: args.threadId };
1377
+ args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
1378
+ const { userId, threadId, prompt, messages, maxSteps, ...rest } = args;
1379
+ const targetArgs = { userId, threadId };
1935
1380
  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,
1381
+ stopWhen: spec?.stopWhen,
1382
+ ...overrides,
1383
+ ...omit(rest, ["storageOptions", "contextOptions", "stream"]),
1384
+ messages: messages?.map(toModelMessage),
1385
+ prompt: Array.isArray(prompt) ? prompt.map(toModelMessage) : prompt,
1942
1386
  toolChoice: args.toolChoice as ToolChoice<AgentTools>,
1943
1387
  } satisfies StreamingTextArgs<AgentTools>;
1944
- if (args.maxSteps) {
1945
- llmArgs.stopWhen = stepCountIs(args.maxSteps);
1388
+ if (maxSteps) {
1389
+ llmArgs.stopWhen = stepCountIs(maxSteps);
1946
1390
  }
1947
1391
  const opts = {
1948
- ...this.options,
1949
- ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
1392
+ ...pick(spec, ["contextOptions", "storageOptions"]),
1950
1393
  ...pick(args, ["contextOptions", "storageOptions"]),
1951
1394
  saveStreamDeltas: stream,
1952
1395
  };
@@ -1954,9 +1397,8 @@ export class Agent<
1954
1397
  spec?.customCtx
1955
1398
  ? { ...ctx_, ...spec.customCtx(ctx_, targetArgs, llmArgs) }
1956
1399
  : ctx_
1957
- ) as UserActionCtx & CustomCtx;
1400
+ ) as GenericActionCtx<GenericDataModel> & CustomCtx;
1958
1401
  if (stream) {
1959
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1960
1402
  const result = await this.streamText<any>(
1961
1403
  ctx,
1962
1404
  targetArgs,
@@ -1966,13 +1408,13 @@ export class Agent<
1966
1408
  await result.consumeStream();
1967
1409
  return {
1968
1410
  text: await result.text,
1969
- messageId: result.messageId,
1411
+ promptMessageId: result.promptMessageId,
1970
1412
  order: result.order,
1971
1413
  finishReason: await result.finishReason,
1972
- warnings: result.warnings,
1414
+ warnings: await result.warnings,
1415
+ savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1973
1416
  };
1974
1417
  } else {
1975
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1976
1418
  const res = await this.generateText<any>(
1977
1419
  ctx,
1978
1420
  targetArgs,
@@ -1981,10 +1423,11 @@ export class Agent<
1981
1423
  );
1982
1424
  return {
1983
1425
  text: res.text,
1984
- messageId: res.messageId,
1426
+ promptMessageId: res.promptMessageId,
1985
1427
  order: res.order,
1986
1428
  finishReason: res.finishReason,
1987
1429
  warnings: res.warnings,
1430
+ savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
1988
1431
  };
1989
1432
  }
1990
1433
  },
@@ -1997,57 +1440,49 @@ export class Agent<
1997
1440
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1998
1441
  * and stopWhen.
1999
1442
  */
2000
- asObjectAction<T>(
2001
- spec: OurObjectArgs<T>,
2002
- options?: {
2003
- contextOptions?: ContextOptions;
2004
- storageOptions?: StorageOptions;
2005
- },
1443
+ asObjectAction<T, DataModel extends GenericDataModel>(
1444
+ objectArgs: GenerateObjectArgs<FlexibleSchema<T>> & Partial<AgentPrompt>,
1445
+ options?: Options & MaybeCustomCtx<CustomCtx, DataModel, AgentTools>,
2006
1446
  ) {
2007
1447
  return internalActionGeneric({
2008
1448
  args: vSafeObjectArgs,
2009
- handler: async (ctx, args) => {
2010
- const { userId, threadId, ...rest } = args;
1449
+ handler: async (ctx_, args) => {
1450
+ const { userId, threadId, callSettings, ...rest } = args;
2011
1451
  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
- );
1452
+ const targetArgs = { userId, threadId };
1453
+ const llmArgs = {
1454
+ ...objectArgs,
1455
+ ...callSettings,
1456
+ ...omit(rest, ["storageOptions", "contextOptions"]),
1457
+ messages: args.messages?.map(toModelMessage),
1458
+ prompt: Array.isArray(args.prompt)
1459
+ ? args.prompt.map(toModelMessage)
1460
+ : args.prompt,
1461
+ } as GenerateObjectArgs<FlexibleSchema<T>>;
1462
+ const ctx = (
1463
+ options?.customCtx
1464
+ ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs as any) }
1465
+ : ctx_
1466
+ ) as GenericActionCtx<GenericDataModel> & CustomCtx;
1467
+ const value = await this.generateObject(ctx, targetArgs, llmArgs, {
1468
+ ...this.options,
1469
+ ...options,
1470
+ ...overrides,
1471
+ });
2021
1472
  return {
2022
- object: value.object as T,
2023
- messageId: value.messageId,
1473
+ object: convexToJson(value.object as Value) as T,
1474
+ promptMessageId: value.promptMessageId,
2024
1475
  order: value.order,
2025
1476
  finishReason: value.finishReason,
2026
1477
  warnings: value.warnings,
1478
+ savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
2027
1479
  };
2028
1480
  },
2029
1481
  });
2030
1482
  }
2031
1483
 
2032
1484
  /**
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.
1485
+ * @deprecated Use {@link saveMessages} directly instead.
2051
1486
  */
2052
1487
  asSaveMessagesMutation() {
2053
1488
  return internalMutationGeneric({
@@ -2056,226 +1491,21 @@ export class Agent<
2056
1491
  userId: v.optional(v.string()),
2057
1492
  promptMessageId: v.optional(v.string()),
2058
1493
  messages: v.array(vMessageWithMetadata),
2059
- pending: v.optional(v.boolean()),
2060
1494
  failPendingSteps: v.optional(v.boolean()),
1495
+ embeddings: v.optional(vMessageEmbeddings),
2061
1496
  },
2062
1497
  handler: async (ctx, args) => {
2063
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
1498
+ const { messages } = await this.saveMessages(ctx, {
2064
1499
  ...args,
2065
- messages: args.messages.map((m) => deserializeMessage(m.message)),
1500
+ messages: args.messages.map((m) => toModelMessage(m.message)),
2066
1501
  metadata: args.messages.map(({ message: _, ...m }) => m),
1502
+ skipEmbeddings: true,
2067
1503
  });
2068
1504
  return {
2069
- lastMessageId,
2070
- messageIds: messages.map((m) => m._id),
1505
+ lastMessageId: messages.at(-1)!._id,
1506
+ messages: messages.map((m) => pick(m, ["_id", "order", "stepOrder"])),
2071
1507
  };
2072
1508
  },
2073
1509
  });
2074
1510
  }
2075
1511
  }
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
- }