@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,32 +1,60 @@
1
- import { embedMany, generateObject, generateText, stepCountIs, streamObject, streamText, } from "ai";
1
+ import { generateObject, generateText, stepCountIs, streamObject } from "ai";
2
+ const MIGRATION_URL = "https://github.com/get-convex/agent/blob/main/MIGRATION.md";
3
+ const warnedDeprecations = new Set();
4
+ function warnDeprecation(key, message) {
5
+ if (!warnedDeprecations.has(key)) {
6
+ warnedDeprecations.add(key);
7
+ console.warn(`[@convex-dev/agent] ${message}\n See: ${MIGRATION_URL}`);
8
+ }
9
+ }
2
10
  import { assert, omit, pick } from "convex-helpers";
3
11
  import { internalActionGeneric, internalMutationGeneric, } from "convex/server";
4
- import { v } from "convex/values";
5
- import { validateVectorDimension, } from "../component/vector/tables.js";
6
- import { deserializeMessage, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
7
- import { extractText, isTool } from "../shared.js";
8
- import { vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
9
- import { createTool, wrapTools } from "./createTool.js";
10
- import { listMessages } from "./listMessages.js";
11
- import { fetchContextMessages } from "./search.js";
12
- import { DeltaStreamer, mergeTransforms, syncStreams, } from "./streaming.js";
12
+ import { convexToJson, v } from "convex/values";
13
+ import {} from "../component/vector/tables.js";
14
+ import { toModelMessage, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
15
+ import { getModelName, getProviderName } from "../shared.js";
16
+ import { vMessageEmbeddings, vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
17
+ import { listMessages, saveMessages, } from "./messages.js";
18
+ import { embedMany, embedMessages, fetchContextMessages, generateAndSaveEmbeddings, } from "./search.js";
19
+ import { startGeneration } from "./start.js";
20
+ import { syncStreams } from "./streaming.js";
21
+ import { createThread, getThreadMetadata } from "./threads.js";
22
+ import { streamText } from "./streamText.js";
23
+ import { errorToString, willContinue } from "./utils.js";
13
24
  export { stepCountIs } from "ai";
14
- export { vMessageDoc, vThreadDoc } from "../component/schema.js";
15
- export { serializeDataOrUrl, deserializeMessage, serializeMessage, } from "../mapping.js";
25
+ export { docsToModelMessages, toModelMessage,
26
+ //** @deprecated use toModelMessage instead */
27
+ toModelMessage as deserializeMessage, guessMimeType, serializeDataOrUrl, serializeMessage, toUIFilePart, } from "../mapping.js";
16
28
  // NOTE: these are also exported via @convex-dev/agent/validators
17
29
  // a future version may put them all here or move these over there
18
- export { vAssistantMessage, vContextOptions, vMessage, vPaginationResult, vProviderMetadata, vStorageOptions, vStreamArgs, vSystemMessage, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
19
- export { getFile, storeFile } from "./files.js";
20
- export { filterOutOrphanedToolMessages, fetchContextMessages, } from "./search.js";
21
- export { abortStream, listStreams, syncStreams } from "./streaming.js";
22
- export { createTool, extractText, isTool, listMessages };
30
+ export { extractText, isTool, sorted } from "../shared.js";
31
+ export { vAssistantMessage, vContent, vContextOptions, vMessage, vMessageDoc, vPaginationResult, vProviderMetadata, vSource, vStorageOptions, vStreamArgs, vSystemMessage, vThreadDoc, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
32
+ export { createTool } from "./createTool.js";
23
33
  export { definePlaygroundAPI, } from "./definePlaygroundAPI.js";
34
+ export { getFile, storeFile } from "./files.js";
35
+ export { listMessages, listUIMessages, saveMessage, saveMessages, } from "./messages.js";
36
+ export { mockModel } from "./mockModel.js";
37
+ export { fetchContextMessages, filterOutOrphanedToolMessages, fetchContextWithPrompt, generateAndSaveEmbeddings, embedMessages, embedMany, } from "./search.js";
38
+ export { startGeneration } from "./start.js";
39
+ export { DEFAULT_STREAMING_OPTIONS, DeltaStreamer, abortStream, compressUIMessageChunks, listStreams, syncStreams, vStreamMessagesReturnValue, } from "./streaming.js";
40
+ export { createThread, getThreadMetadata, searchThreadTitles, updateThreadMetadata, } from "./threads.js";
41
+ export { toUIMessages, fromUIMessages } from "../UIMessages.js";
24
42
  export class Agent {
25
43
  component;
26
44
  options;
27
45
  constructor(component, options) {
28
46
  this.component = component;
29
47
  this.options = options;
48
+ if (this.options.textEmbeddingModel && !this.options.embeddingModel) {
49
+ warnDeprecation("textEmbeddingModel", "textEmbeddingModel is deprecated. Use embeddingModel instead.");
50
+ }
51
+ }
52
+ /**
53
+ * Get the embedding model, prioritizing embeddingModel over textEmbeddingModel.
54
+ * @private
55
+ */
56
+ getEmbeddingModel() {
57
+ return this.options.embeddingModel ?? this.options.textEmbeddingModel;
30
58
  }
31
59
  async createThread(ctx, args) {
32
60
  const threadId = await createThread(ctx, this.component, args);
@@ -36,13 +64,8 @@ export class Agent {
36
64
  const { thread } = await this.continueThread(ctx, {
37
65
  threadId,
38
66
  userId: args?.userId,
39
- usageHandler: args?.usageHandler,
40
- tools: args?.tools,
41
67
  });
42
- return {
43
- threadId,
44
- thread,
45
- };
68
+ return { threadId, thread };
46
69
  }
47
70
  /**
48
71
  * Continues a thread using this agent. Note: threads can be continued
@@ -70,16 +93,25 @@ export class Agent {
70
93
  },
71
94
  };
72
95
  }
96
+ async start(ctx,
73
97
  /**
74
- * Search for threads by title, paginated.
75
- * @param ctx The context passed from the query/mutation/action.
76
- * @returns The threads matching the search, paginated.
98
+ * These are the arguments you'll pass to the LLM call such as
99
+ * `generateText` or `streamText`. This function will look up the context
100
+ * and provide functions to save the steps, abort the generation, and more.
101
+ * The type of the arguments returned infers from the type of the arguments
102
+ * you pass here.
77
103
  */
78
- async searchThreadTitles(ctx, { userId, query, limit, }) {
79
- return ctx.runQuery(this.component.threads.searchThreadTitles, {
80
- userId,
81
- query,
82
- limit: limit ?? 10,
104
+ args, options) {
105
+ return startGeneration(ctx, this.component, {
106
+ ...args,
107
+ tools: (args.tools ?? this.options.tools),
108
+ system: args.system ?? this.options.instructions,
109
+ stopWhen: (args.stopWhen ?? this.options.stopWhen),
110
+ }, {
111
+ ...this.options,
112
+ ...options,
113
+ agentName: this.options.name,
114
+ agentForToolCtx: this,
83
115
  });
84
116
  }
85
117
  /**
@@ -89,80 +121,44 @@ export class Agent {
89
121
  * Use {@link continueThread} to get a version of this function already scoped
90
122
  * to a thread (and optionally userId).
91
123
  * @param ctx The context passed from the action function calling this.
92
- * @param { userId, threadId }: The user and thread to associate the message with
93
- * @param args The arguments to the generateText function, along with extra controls
94
- * for the {@link ContextOptions} and {@link StorageOptions}.
124
+ * @param scope: The user and thread to associate the message with
125
+ * @param generateTextArgs The arguments to the generateText function, along
126
+ * with {@link AgentPrompt} options, such as promptMessageId.
127
+ * @param options Extra controls for the {@link ContextOptions} and {@link StorageOptions}.
95
128
  * @returns The result of the generateText function.
96
129
  */
97
- async generateText(ctx, { userId: argsUserId, threadId, usageHandler, tools: threadTools, }, args, options) {
98
- const opts = { ...this.options, ...options, usageHandler };
99
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
100
- userId: argsUserId ?? undefined,
101
- threadId,
102
- ...opts,
103
- });
104
- const { args: aiArgs, messageId, order, userId } = context;
105
- const toolCtx = {
106
- ...ctx,
107
- userId,
108
- threadId,
109
- messageId,
110
- agent: this,
111
- };
112
- const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
113
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
130
+ async generateText(ctx, threadOpts,
131
+ /**
132
+ * The arguments to the generateText function, similar to the ai sdk's
133
+ * {@link generateText} function, along with Agent prompt options.
134
+ */
135
+ generateTextArgs, options) {
136
+ const { args, promptMessageId, order, ...call } = await this.start(ctx, generateTextArgs, { ...threadOpts, ...options });
137
+ const steps = [];
114
138
  try {
115
139
  const result = (await generateText({
116
- // Can be overridden
117
- stopWhen: this.options.stopWhen,
118
- ...aiArgs,
119
- tools,
140
+ ...args,
141
+ prepareStep: async (options) => {
142
+ const result = await generateTextArgs.prepareStep?.(options);
143
+ call.updateModel(result?.model ?? options.model);
144
+ return result;
145
+ },
120
146
  onStepFinish: async (step) => {
121
- if (threadId && messageId && saveOutput) {
122
- await this.saveStep(ctx, {
123
- userId,
124
- threadId,
125
- promptMessageId: messageId,
126
- model: aiArgs.model.modelId,
127
- provider: aiArgs.model.provider,
128
- step,
129
- });
130
- }
131
- if (this.options.rawRequestResponseHandler) {
132
- await this.options.rawRequestResponseHandler(ctx, {
133
- userId,
134
- threadId,
135
- agentName: this.options.name,
136
- request: step.request,
137
- response: step.response,
138
- });
139
- }
140
- if (opts.usageHandler && step.usage) {
141
- await opts.usageHandler(ctx, {
142
- userId,
143
- threadId,
144
- agentName: this.options.name,
145
- model: aiArgs.model.modelId,
146
- provider: aiArgs.model.provider,
147
- usage: step.usage,
148
- providerMetadata: step.providerMetadata,
149
- });
150
- }
151
- return args.onStepFinish?.(step);
147
+ steps.push(step);
148
+ await call.save({ step }, await willContinue(steps, args.stopWhen));
149
+ return generateTextArgs.onStepFinish?.(step);
152
150
  },
153
151
  }));
154
- result.messageId = messageId;
155
- result.order = order;
156
- return result;
152
+ const metadata = {
153
+ promptMessageId,
154
+ order,
155
+ savedMessages: call.getSavedMessages(),
156
+ messageId: promptMessageId,
157
+ };
158
+ return Object.assign(result, metadata);
157
159
  }
158
160
  catch (error) {
159
- if (threadId && messageId) {
160
- console.error("RollbackMessage", messageId);
161
- await ctx.runMutation(this.component.messages.rollbackMessage, {
162
- messageId,
163
- error: error.message,
164
- });
165
- }
161
+ await call.fail(errorToString(error));
166
162
  throw error;
167
163
  }
168
164
  }
@@ -173,109 +169,30 @@ export class Agent {
173
169
  * Use {@link continueThread} to get a version of this function already scoped
174
170
  * to a thread (and optionally userId).
175
171
  */
176
- async streamText(ctx, { userId: argsUserId, threadId, usageHandler,
177
- /** Note: to get better type inference, pass tools in the next arg */
178
- tools: threadTools, },
172
+ async streamText(ctx, threadOpts,
179
173
  /**
180
- * The arguments to the streamText function, similar to the ai `streamText` function.
174
+ * The arguments to the streamText function, similar to the ai sdk's
175
+ * {@link streamText} function, along with Agent prompt options.
181
176
  */
182
- args,
177
+ streamTextArgs,
183
178
  /**
184
179
  * The {@link ContextOptions} and {@link StorageOptions}
185
180
  * options to use for fetching contextual messages and saving input/output messages.
186
181
  */
187
182
  options) {
188
- const opts = { ...this.options, ...options, usageHandler };
189
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
190
- userId: argsUserId ?? undefined,
191
- threadId,
192
- ...opts,
193
- });
194
- const { args: aiArgs, messageId, order, stepOrder, userId } = context;
195
- const toolCtx = {
196
- ...ctx,
197
- userId,
198
- threadId,
199
- messageId,
200
- agent: this,
201
- };
202
- const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
203
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
204
- const streamer = threadId && opts.saveStreamDeltas
205
- ? new DeltaStreamer(this.component, ctx, opts.saveStreamDeltas, {
206
- threadId,
207
- userId,
208
- agentName: this.options.name,
209
- model: aiArgs.model.modelId,
210
- provider: aiArgs.model.provider,
211
- providerOptions: aiArgs.providerOptions,
212
- order,
213
- stepOrder,
214
- abortSignal: aiArgs.abortSignal,
215
- })
216
- : undefined;
217
- const result = streamText({
218
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
- stopWhen: this.options.stopWhen, // Can be overridden
220
- ...aiArgs,
221
- tools,
222
- abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
223
- experimental_transform: mergeTransforms(options?.saveStreamDeltas, args.experimental_transform),
224
- onChunk: async (event) => {
225
- await streamer?.addParts([event.chunk]);
226
- // console.log("onChunk", chunk);
227
- return args.onChunk?.(event);
228
- },
229
- onError: async (error) => {
230
- console.error("onError", error);
231
- if (threadId && messageId && saveOutput) {
232
- await ctx.runMutation(this.component.messages.rollbackMessage, {
233
- messageId,
234
- error: error.error.message,
235
- });
236
- }
237
- // TODO: update the streamer to error state
238
- return args.onError?.(error);
239
- },
240
- onStepFinish: async (step) => {
241
- // console.log("onStepFinish", step);
242
- if (threadId && messageId && saveOutput) {
243
- const saved = await this.saveStep(ctx, {
244
- userId,
245
- threadId,
246
- model: aiArgs.model.modelId,
247
- provider: aiArgs.model.provider,
248
- promptMessageId: messageId,
249
- step,
250
- });
251
- await streamer?.finish(saved.messages);
252
- }
253
- if (this.options.rawRequestResponseHandler) {
254
- await this.options.rawRequestResponseHandler(ctx, {
255
- userId,
256
- threadId,
257
- agentName: this.options.name,
258
- request: step.request,
259
- response: step.response,
260
- });
261
- }
262
- if (opts.usageHandler && step.usage) {
263
- await opts.usageHandler(ctx, {
264
- userId,
265
- threadId,
266
- agentName: this.options.name,
267
- model: aiArgs.model.modelId,
268
- provider: aiArgs.model.provider,
269
- usage: step.usage,
270
- providerMetadata: step.providerMetadata,
271
- });
272
- }
273
- return args.onStepFinish?.(step);
274
- },
183
+ return streamText(ctx, this.component, {
184
+ ...streamTextArgs,
185
+ model: streamTextArgs.model ?? this.options.languageModel,
186
+ tools: (streamTextArgs.tools ?? this.options.tools),
187
+ system: streamTextArgs.system ?? this.options.instructions,
188
+ stopWhen: (streamTextArgs.stopWhen ?? this.options.stopWhen),
189
+ }, {
190
+ ...threadOpts,
191
+ ...this.options,
192
+ agentName: this.options.name,
193
+ agentForToolCtx: this,
194
+ ...options,
275
195
  });
276
- result.messageId = messageId;
277
- result.order = order;
278
- return result;
279
196
  }
280
197
  /**
281
198
  * This behaves like {@link generateObject} from the "ai" package except that
@@ -284,69 +201,31 @@ export class Agent {
284
201
  * Use {@link continueThread} to get a version of this function already scoped
285
202
  * to a thread (and optionally userId).
286
203
  */
287
- async generateObject(ctx, { userId: argsUserId, threadId, usageHandler, },
204
+ async generateObject(ctx, threadOpts,
288
205
  /**
289
- * The arguments to the generateObject function, similar to the ai.generateObject function.
206
+ * The arguments to the generateObject function, similar to the ai sdk's
207
+ * {@link generateObject} function, along with Agent prompt options.
290
208
  */
291
- args,
209
+ generateObjectArgs,
292
210
  /**
293
211
  * The {@link ContextOptions} and {@link StorageOptions}
294
212
  * options to use for fetching contextual messages and saving input/output messages.
295
213
  */
296
214
  options) {
297
- const opts = { ...this.options, ...options, usageHandler };
298
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
299
- userId: argsUserId ?? undefined,
300
- threadId,
301
- ...opts,
302
- });
303
- const { args: aiArgs, messageId, order, userId } = context;
304
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
215
+ const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, generateObjectArgs, { ...threadOpts, ...options });
305
216
  try {
306
- const result = (await generateObject(
307
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
308
- aiArgs));
309
- if (threadId && messageId && saveOutput) {
310
- await this.saveObject(ctx, {
311
- threadId,
312
- promptMessageId: messageId,
313
- result,
314
- userId,
315
- model: aiArgs.model.modelId,
316
- provider: aiArgs.model.provider,
317
- });
318
- }
319
- result.messageId = messageId;
320
- result.order = order;
321
- if (this.options.rawRequestResponseHandler) {
322
- await this.options.rawRequestResponseHandler(ctx, {
323
- userId,
324
- threadId,
325
- agentName: this.options.name,
326
- request: result.request,
327
- response: result.response,
328
- });
329
- }
330
- if (opts.usageHandler && result.usage) {
331
- await opts.usageHandler(ctx, {
332
- userId,
333
- threadId,
334
- agentName: this.options.name,
335
- model: aiArgs.model.modelId,
336
- provider: aiArgs.model.provider,
337
- usage: result.usage,
338
- providerMetadata: result.providerMetadata,
339
- });
340
- }
341
- return result;
217
+ const result = (await generateObject(args));
218
+ await save({ object: result });
219
+ const metadata = {
220
+ promptMessageId,
221
+ order,
222
+ savedMessages: getSavedMessages(),
223
+ messageId: promptMessageId,
224
+ };
225
+ return Object.assign(result, metadata);
342
226
  }
343
227
  catch (error) {
344
- if (threadId && messageId) {
345
- await ctx.runMutation(this.component.messages.rollbackMessage, {
346
- messageId,
347
- error: error.message,
348
- });
349
- }
228
+ await fail(errorToString(error));
350
229
  throw error;
351
230
  }
352
231
  }
@@ -357,80 +236,51 @@ export class Agent {
357
236
  * Use {@link continueThread} to get a version of this function already scoped
358
237
  * to a thread (and optionally userId).
359
238
  */
360
- async streamObject(ctx, { userId: argsUserId, threadId, usageHandler, },
239
+ async streamObject(ctx, threadOpts,
361
240
  /**
362
- * The arguments to the streamObject function, similar to the ai `streamObject` function.
241
+ * The arguments to the streamObject function, similar to the ai sdk's
242
+ * {@link streamObject} function, along with Agent prompt options.
363
243
  */
364
- args,
244
+ streamObjectArgs,
365
245
  /**
366
246
  * The {@link ContextOptions} and {@link StorageOptions}
367
247
  * options to use for fetching contextual messages and saving input/output messages.
368
248
  */
369
249
  options) {
370
- // TODO: unify all this shared code between all the generate* and stream* functions
371
- const opts = { ...this.options, ...options, usageHandler };
372
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
373
- userId: argsUserId ?? undefined,
374
- threadId,
375
- ...opts,
376
- });
377
- const { args: aiArgs, messageId, order, userId } = context;
378
- const saveOutput = opts.storageOptions?.saveMessages !== "none";
379
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ const { args, promptMessageId, order, fail, save, getSavedMessages } = await this.start(ctx, streamObjectArgs, { ...threadOpts, ...options });
380
251
  const stream = streamObject({
381
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
382
- ...aiArgs,
252
+ ...args,
383
253
  onError: async (error) => {
384
- console.error("onError", error);
254
+ console.error(" streamObject onError", error);
255
+ // TODO: content that we have so far
256
+ // content: stream.fullStream.
257
+ await fail(errorToString(error.error));
385
258
  return args.onError?.(error);
386
259
  },
387
260
  onFinish: async (result) => {
388
- if (threadId && messageId && saveOutput) {
389
- await this.saveObject(ctx, {
390
- userId,
391
- threadId,
392
- promptMessageId: messageId,
393
- result: {
394
- object: result.object,
395
- finishReason: "stop",
396
- usage: result.usage,
397
- warnings: result.warnings,
398
- request: await stream.request,
399
- response: result.response,
400
- providerMetadata: result.providerMetadata,
401
- toJsonResponse: stream.toTextStreamResponse,
402
- },
403
- model: aiArgs.model.modelId,
404
- provider: aiArgs.model.provider,
405
- });
406
- }
407
- if (opts.usageHandler && result.usage) {
408
- await opts.usageHandler(ctx, {
409
- userId,
410
- threadId,
411
- agentName: this.options.name,
412
- model: aiArgs.model.modelId,
413
- provider: aiArgs.model.provider,
261
+ await save({
262
+ object: {
263
+ object: result.object,
264
+ finishReason: result.error ? "error" : "stop",
414
265
  usage: result.usage,
415
- providerMetadata: result.providerMetadata,
416
- });
417
- }
418
- if (this.options.rawRequestResponseHandler) {
419
- await this.options.rawRequestResponseHandler(ctx, {
420
- userId,
421
- threadId,
422
- agentName: this.options.name,
266
+ warnings: result.warnings,
423
267
  request: await stream.request,
424
268
  response: result.response,
425
- });
426
- }
427
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
269
+ providerMetadata: result.providerMetadata,
270
+ toJsonResponse: stream.toTextStreamResponse,
271
+ reasoning: undefined,
272
+ },
273
+ });
428
274
  return args.onFinish?.(result);
429
275
  },
430
276
  });
431
- stream.messageId = messageId;
432
- stream.order = order;
433
- return stream;
277
+ const metadata = {
278
+ promptMessageId,
279
+ order,
280
+ savedMessages: getSavedMessages(),
281
+ messageId: promptMessageId,
282
+ };
283
+ return Object.assign(stream, metadata);
434
284
  }
435
285
  /**
436
286
  * Save a message to the thread.
@@ -440,22 +290,22 @@ export class Agent {
440
290
  * @returns The messageId of the saved message.
441
291
  */
442
292
  async saveMessage(ctx, args) {
443
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
293
+ const { messages } = await this.saveMessages(ctx, {
444
294
  threadId: args.threadId,
445
295
  userId: args.userId,
446
296
  embeddings: args.embedding
447
- ? {
448
- model: args.embedding.model,
449
- vectors: [args.embedding.vector],
450
- }
297
+ ? { model: args.embedding.model, vectors: [args.embedding.vector] }
451
298
  : undefined,
452
299
  messages: args.prompt !== undefined
453
300
  ? [{ role: "user", content: args.prompt }]
454
301
  : [args.message],
455
302
  metadata: args.metadata ? [args.metadata] : undefined,
456
303
  skipEmbeddings: args.skipEmbeddings,
304
+ promptMessageId: args.promptMessageId,
305
+ pendingMessageId: args.pendingMessageId,
457
306
  });
458
- return { messageId: lastMessageId, message: messages.at(-1) };
307
+ const message = messages.at(-1);
308
+ return { messageId: message._id, message };
459
309
  }
460
310
  /**
461
311
  * Explicitly save messages associated with the thread (& user if provided)
@@ -471,7 +321,7 @@ export class Agent {
471
321
  if (args.embeddings) {
472
322
  embeddings = args.embeddings;
473
323
  }
474
- else if (!skipEmbeddings && this.options.textEmbedding) {
324
+ else if (!skipEmbeddings && this.getEmbeddingModel()) {
475
325
  if (!("runAction" in ctx)) {
476
326
  console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
477
327
  "Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
@@ -485,10 +335,7 @@ export class Agent {
485
335
  "You can explicitly generate them asynchronously by using the scheduler to run an action later that calls `agent.generateAndSaveEmbeddings`.");
486
336
  }
487
337
  else {
488
- embeddings = await this.generateEmbeddings(ctx, {
489
- userId: args.userId ?? undefined,
490
- threadId: args.threadId,
491
- }, args.messages);
338
+ embeddings = await this.generateEmbeddings(ctx, { userId: args.userId ?? undefined, threadId: args.threadId }, args.messages);
492
339
  }
493
340
  }
494
341
  return saveMessages(ctx, this.component, {
@@ -540,14 +387,17 @@ export class Agent {
540
387
  contextOptions,
541
388
  getEmbedding: async (text) => {
542
389
  assert("runAction" in ctx);
543
- assert(this.options.textEmbedding, "A textEmbedding model is required to be set on the Agent that you're doing vector search with");
390
+ const embeddingModel = this.getEmbeddingModel();
391
+ assert(embeddingModel, "An embeddingModel (or textEmbeddingModel) is required to be set on the Agent that you're doing vector search with");
544
392
  return {
545
- embedding: (await this.doEmbed(ctx, {
393
+ embedding: (await embedMany(ctx, {
394
+ ...this.options,
395
+ agentName: this.options.name,
546
396
  userId: args.userId,
547
397
  threadId: args.threadId,
548
398
  values: [text],
549
399
  })).embeddings[0],
550
- embeddingModel: this.options.textEmbedding.modelId,
400
+ embeddingModel: embeddingModel,
551
401
  };
552
402
  },
553
403
  });
@@ -577,40 +427,8 @@ export class Agent {
577
427
  * @param messages The messages to get the embeddings for.
578
428
  * @returns The embeddings for the messages.
579
429
  */
580
- async generateEmbeddings(ctx, { userId, threadId, }, messages) {
581
- if (!this.options.textEmbedding) {
582
- return undefined;
583
- }
584
- let embeddings;
585
- const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
586
- // Find the indexes of the messages that have text.
587
- const textIndexes = messageTexts
588
- .map((t, i) => (t ? i : undefined))
589
- .filter((i) => i !== undefined);
590
- if (textIndexes.length === 0) {
591
- return undefined;
592
- }
593
- // Then embed those messages.
594
- const textEmbeddings = await this.doEmbed(ctx, {
595
- userId,
596
- threadId,
597
- values: messageTexts,
598
- });
599
- // Then assemble the embeddings into a single array with nulls for the messages without text.
600
- const embeddingsOrNull = Array(messages.length).fill(null);
601
- textIndexes.forEach((i, j) => {
602
- embeddingsOrNull[i] = textEmbeddings.embeddings[j];
603
- });
604
- if (textEmbeddings.embeddings.length > 0) {
605
- const dimension = textEmbeddings.embeddings[0].length;
606
- validateVectorDimension(dimension);
607
- embeddings = {
608
- vectors: embeddingsOrNull,
609
- dimension,
610
- model: this.options.textEmbedding.modelId,
611
- };
612
- }
613
- return embeddings;
430
+ async generateEmbeddings(ctx, args, messages) {
431
+ return embedMessages(ctx, { ...args, ...this.options, agentName: this.options.name }, messages);
614
432
  }
615
433
  /**
616
434
  * Generate embeddings for a set of messages, and save them to the database.
@@ -629,9 +447,6 @@ export class Agent {
629
447
  .filter((id) => !messages.some((m) => m?._id === id))
630
448
  .join(", "));
631
449
  }
632
- await this._generateAndSaveEmbeddings(ctx, messages);
633
- }
634
- async _generateAndSaveEmbeddings(ctx, messages) {
635
450
  if (messages.some((m) => !m.message)) {
636
451
  throw new Error("Some messages don't have a message: " +
637
452
  messages
@@ -639,34 +454,17 @@ export class Agent {
639
454
  .map((m) => m._id)
640
455
  .join(", "));
641
456
  }
642
- const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
643
- if (messagesMissingEmbeddings.length === 0) {
644
- return;
457
+ const embeddingModel = this.getEmbeddingModel();
458
+ if (!embeddingModel) {
459
+ throw new Error("No embeddings were generated for the messages. You must pass an embeddingModel (or textEmbeddingModel) to the agent constructor.");
645
460
  }
646
- const embeddings = await this.generateEmbeddings(ctx, {
647
- userId: messagesMissingEmbeddings[0].userId,
648
- threadId: messagesMissingEmbeddings[0].threadId,
649
- }, messagesMissingEmbeddings.map((m) => deserializeMessage(m.message)));
650
- if (!embeddings) {
651
- if (!this.options.textEmbedding) {
652
- throw new Error("No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.");
653
- }
654
- throw new Error("No embeddings were generated for these messages: " +
655
- messagesMissingEmbeddings.map((m) => m._id).join(", "));
656
- }
657
- await ctx.runMutation(this.component.vector.index.insertBatch, {
658
- vectorDimension: embeddings.dimension,
659
- vectors: messagesMissingEmbeddings
660
- .map((m, i) => ({
661
- messageId: m._id,
662
- model: embeddings.model,
663
- table: "messages",
664
- userId: m.userId,
665
- threadId: m.threadId,
666
- vector: embeddings.vectors[i],
667
- }))
668
- .filter((v) => v.vector !== null),
669
- });
461
+ await generateAndSaveEmbeddings(ctx, this.component, {
462
+ ...this.options,
463
+ agentName: this.options.name,
464
+ threadId: messages[0].threadId,
465
+ userId: messages[0].userId,
466
+ embeddingModel,
467
+ }, messages);
670
468
  }
671
469
  /**
672
470
  * Explicitly save a "step" created by the AI SDK.
@@ -674,12 +472,12 @@ export class Agent {
674
472
  * @param args The Step generated by the AI SDK.
675
473
  */
676
474
  async saveStep(ctx, args) {
677
- const messages = await serializeNewMessagesInStep(ctx, this.component, args.step, {
678
- provider: args.provider ?? this.options.chat.provider,
679
- model: args.model ?? this.options.chat.modelId,
475
+ const { messages } = await serializeNewMessagesInStep(ctx, this.component, args.step, {
476
+ provider: args.provider ?? getProviderName(this.options.languageModel),
477
+ model: args.model ?? getModelName(this.options.languageModel),
680
478
  });
681
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => deserializeMessage(m.message)));
682
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
479
+ const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
480
+ return ctx.runMutation(this.component.messages.addMessages, {
683
481
  userId: args.userId,
684
482
  threadId: args.threadId,
685
483
  agentName: this.options.name,
@@ -688,7 +486,6 @@ export class Agent {
688
486
  embeddings,
689
487
  failPendingSteps: false,
690
488
  });
691
- return saved;
692
489
  }
693
490
  /**
694
491
  * Manually save the result of a generateObject call to the thread.
@@ -699,11 +496,15 @@ export class Agent {
699
496
  */
700
497
  async saveObject(ctx, args) {
701
498
  const { messages } = await serializeObjectResult(ctx, this.component, args.result, {
702
- model: args.model ?? this.options.chat.modelId,
703
- provider: args.provider ?? this.options.chat.provider,
499
+ model: args.model ??
500
+ args.metadata?.model ??
501
+ getModelName(this.options.languageModel),
502
+ provider: args.provider ??
503
+ args.metadata?.provider ??
504
+ getProviderName(this.options.languageModel),
704
505
  });
705
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => deserializeMessage(m.message)));
706
- await ctx.runMutation(this.component.messages.addMessages, {
506
+ const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
507
+ return ctx.runMutation(this.component.messages.addMessages, {
707
508
  userId: args.userId,
708
509
  threadId: args.threadId,
709
510
  promptMessageId: args.promptMessageId,
@@ -711,7 +512,6 @@ export class Agent {
711
512
  messages,
712
513
  embeddings,
713
514
  agentName: this.options.name,
714
- pending: false,
715
515
  });
716
516
  }
717
517
  /**
@@ -722,19 +522,11 @@ export class Agent {
722
522
  * @param args What message to save. Generally the parent message sent into
723
523
  * the generateText call.
724
524
  */
725
- async completeMessage(ctx, args) {
726
- const result = args.result;
727
- if (result.kind === "success") {
728
- await ctx.runMutation(this.component.messages.commitMessage, {
729
- messageId: args.messageId,
730
- });
731
- }
732
- else {
733
- await ctx.runMutation(this.component.messages.rollbackMessage, {
734
- messageId: args.messageId,
735
- error: result.error,
736
- });
737
- }
525
+ async finalizeMessage(ctx, args) {
526
+ await ctx.runMutation(this.component.messages.finalizeMessage, {
527
+ messageId: args.messageId,
528
+ result: args.result,
529
+ });
738
530
  }
739
531
  /**
740
532
  * Update a message by its id.
@@ -847,206 +639,6 @@ export class Agent {
847
639
  limit: args.pageSize,
848
640
  });
849
641
  }
850
- async _saveMessagesAndFetchContext(ctx, args, { userId: argsUserId, threadId, contextOptions, storageOptions, }) {
851
- // If only a promptMessageId is provided, this will be empty.
852
- const messages = args.messages ?? [];
853
- const prompt = !args.prompt
854
- ? []
855
- : Array.isArray(args.prompt)
856
- ? args.prompt
857
- : [{ role: "user", content: args.prompt }];
858
- const userId = argsUserId ??
859
- (threadId &&
860
- (await ctx.runQuery(this.component.threads.getThread, { threadId }))
861
- ?.userId);
862
- // If only a messageId is provided, this will add that message to the end.
863
- const contextMessages = await this.fetchContextMessages(ctx, {
864
- userId,
865
- threadId,
866
- upToAndIncludingMessageId: args.promptMessageId,
867
- messages,
868
- contextOptions,
869
- });
870
- // If it was a promptMessageId, pop it off context messages
871
- // and add to the end of messages.
872
- const promptMessageIndex = args.promptMessageId
873
- ? contextMessages.findIndex((m) => m._id === args.promptMessageId)
874
- : -1;
875
- const promptMessage = promptMessageIndex !== -1
876
- ? contextMessages.splice(promptMessageIndex, 1)[0]
877
- : undefined;
878
- let messageId = promptMessage?._id;
879
- let order = promptMessage?.order;
880
- let stepOrder = promptMessage?.stepOrder;
881
- if (threadId &&
882
- messages.length + prompt.length &&
883
- storageOptions?.saveMessages !== "none" &&
884
- // If it was a promptMessageId, we don't want to save it again.
885
- (!args.promptMessageId || storageOptions?.saveMessages === "all")) {
886
- const saveAll = storageOptions?.saveMessages === "all";
887
- const coreMessages = [...messages, ...prompt];
888
- const toSave = saveAll ? coreMessages : coreMessages.slice(-1);
889
- const metadata = Array.from({ length: toSave.length }, () => ({}));
890
- metadata[metadata.length - 1] = { id: args.id };
891
- const saved = await this.saveMessages(ctx, {
892
- threadId,
893
- userId,
894
- messages: toSave,
895
- metadata,
896
- failPendingSteps: true,
897
- });
898
- messageId = saved.lastMessageId;
899
- order = saved.messages.at(-1)?.order;
900
- stepOrder = saved.messages.at(-1)?.stepOrder;
901
- }
902
- if (promptMessage?.message) {
903
- if (!args.prompt) {
904
- // If they override the prompt, we skip the existing prompt message.
905
- messages.push(deserializeMessage(promptMessage.message));
906
- }
907
- // Lazily generate embeddings for the prompt message, if it doesn't have
908
- // embeddings yet. This can happen if the message was saved in a mutation
909
- // where the LLM is not available.
910
- if (!promptMessage.embeddingId && this.options.textEmbedding) {
911
- await this._generateAndSaveEmbeddings(ctx, [promptMessage]);
912
- }
913
- }
914
- const prePrompt = contextMessages.map((m) => deserializeMessage(m.message));
915
- let existingResponses = [];
916
- if (promptMessageIndex !== -1) {
917
- // pull any messages that already responded to the prompt off
918
- // and add them after the prompt
919
- existingResponses = prePrompt.splice(promptMessageIndex);
920
- }
921
- let processedMessages = [
922
- ...prePrompt,
923
- ...messages,
924
- ...prompt,
925
- ...existingResponses,
926
- ];
927
- if (promptMessageIndex === -1) {
928
- processedMessages.push(...prompt);
929
- }
930
- else {
931
- // We add the prompt where the prompt message was
932
- processedMessages.splice(promptMessageIndex, 0, ...prompt);
933
- }
934
- // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
935
- if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
936
- processedMessages = await this._inlineMessagesFiles(processedMessages);
937
- }
938
- const { prompt: _, model, ...rest } = args;
939
- return {
940
- args: {
941
- ...rest,
942
- maxRetries: args.maxRetries ?? this.options.maxRetries,
943
- model: model ?? this.options.chat,
944
- system: args.system ?? this.options.instructions,
945
- messages: processedMessages,
946
- },
947
- userId,
948
- messageId,
949
- order,
950
- stepOrder,
951
- };
952
- }
953
- async doEmbed(ctx, options) {
954
- const embeddingModel = this.options.textEmbedding;
955
- assert(embeddingModel, "a textEmbedding model is required to be set on the Agent that you're doing vector search with");
956
- const result = await embedMany({
957
- model: embeddingModel,
958
- values: options.values,
959
- abortSignal: options.abortSignal,
960
- headers: options.headers,
961
- maxRetries: this.options.maxRetries,
962
- });
963
- if (this.options.usageHandler && result.usage) {
964
- await this.options.usageHandler(ctx, {
965
- userId: options.userId,
966
- threadId: options.threadId,
967
- agentName: this.options.name,
968
- model: embeddingModel.modelId,
969
- provider: embeddingModel.provider,
970
- providerMetadata: undefined,
971
- usage: {
972
- inputTokens: result.usage.tokens,
973
- outputTokens: 0,
974
- totalTokens: result.usage.tokens,
975
- },
976
- });
977
- }
978
- return { embeddings: result.embeddings };
979
- }
980
- /**
981
- * Process messages to inline file and image URLs that point to localhost
982
- * by converting them to base64. This solves the problem of LLMs not being
983
- * able to access localhost URLs.
984
- */
985
- async _inlineMessagesFiles(messages) {
986
- // Process each message to convert localhost URLs to base64
987
- return Promise.all(messages.map(async (message) => {
988
- if ((message.role !== "user" && message.role !== "assistant") ||
989
- typeof message.content === "string" ||
990
- !Array.isArray(message.content)) {
991
- return message;
992
- }
993
- const processedContent = await Promise.all(message.content.map(async (part) => {
994
- if (part.type === "image" && part.image instanceof URL) {
995
- assert(message.role === "user", "Images can only be in user messages");
996
- if (this._isLocalhostUrl(part.image)) {
997
- const imageData = await this._downloadFile(part.image);
998
- return {
999
- ...part,
1000
- image: imageData,
1001
- };
1002
- }
1003
- }
1004
- // Handle file parts
1005
- if (part.type === "file" && part.data instanceof URL) {
1006
- if (this._isLocalhostUrl(part.data)) {
1007
- const fileData = await this._downloadFile(part.data);
1008
- return {
1009
- ...part,
1010
- data: fileData,
1011
- };
1012
- }
1013
- }
1014
- return part;
1015
- }));
1016
- if (message.role === "user") {
1017
- return {
1018
- ...message,
1019
- content: processedContent,
1020
- };
1021
- }
1022
- else {
1023
- return {
1024
- ...message,
1025
- content: processedContent,
1026
- };
1027
- }
1028
- }));
1029
- }
1030
- /**
1031
- * Check if a URL points to localhost
1032
- */
1033
- _isLocalhostUrl(url) {
1034
- return (url.hostname === "localhost" ||
1035
- url.hostname === "127.0.0.1" ||
1036
- url.hostname === "::1" ||
1037
- url.hostname === "0.0.0.0");
1038
- }
1039
- /**
1040
- * Download a file from a URL
1041
- */
1042
- async _downloadFile(url) {
1043
- // Fetch the file
1044
- const response = await fetch(url);
1045
- if (!response.ok) {
1046
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
1047
- }
1048
- return await response.arrayBuffer();
1049
- }
1050
642
  /**
1051
643
  * WORKFLOW UTILITIES
1052
644
  */
@@ -1087,28 +679,26 @@ export class Agent {
1087
679
  * @param spec Configuration for the agent acting as an action, including
1088
680
  * {@link ContextOptions}, {@link StorageOptions}, and {@link stopWhen}.
1089
681
  */
1090
- asTextAction(spec) {
1091
- const stopWhen = spec?.stopWhen ?? this.options.stopWhen;
682
+ asTextAction(spec, overrides) {
1092
683
  return internalActionGeneric({
1093
684
  args: vTextArgs,
1094
685
  handler: async (ctx_, args) => {
1095
- const stream = args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1096
- const targetArgs = { userId: args.userId, threadId: args.threadId };
686
+ const stream = args.stream === true ? spec?.stream || true : (spec?.stream ?? false);
687
+ const { userId, threadId, prompt, messages, maxSteps, ...rest } = args;
688
+ const targetArgs = { userId, threadId };
1097
689
  const llmArgs = {
1098
- stopWhen,
1099
- ...omit(args, ["storageOptions", "contextOptions"]),
1100
- messages: args.messages?.map(deserializeMessage),
1101
- prompt: Array.isArray(args.prompt)
1102
- ? args.prompt.map(deserializeMessage)
1103
- : args.prompt,
690
+ stopWhen: spec?.stopWhen,
691
+ ...overrides,
692
+ ...omit(rest, ["storageOptions", "contextOptions", "stream"]),
693
+ messages: messages?.map(toModelMessage),
694
+ prompt: Array.isArray(prompt) ? prompt.map(toModelMessage) : prompt,
1104
695
  toolChoice: args.toolChoice,
1105
696
  };
1106
- if (args.maxSteps) {
1107
- llmArgs.stopWhen = stepCountIs(args.maxSteps);
697
+ if (maxSteps) {
698
+ llmArgs.stopWhen = stepCountIs(maxSteps);
1108
699
  }
1109
700
  const opts = {
1110
- ...this.options,
1111
- ...(spec && pick(spec, ["contextOptions", "storageOptions"])),
701
+ ...pick(spec, ["contextOptions", "storageOptions"]),
1112
702
  ...pick(args, ["contextOptions", "storageOptions"]),
1113
703
  saveStreamDeltas: stream,
1114
704
  };
@@ -1116,26 +706,26 @@ export class Agent {
1116
706
  ? { ...ctx_, ...spec.customCtx(ctx_, targetArgs, llmArgs) }
1117
707
  : ctx_);
1118
708
  if (stream) {
1119
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1120
709
  const result = await this.streamText(ctx, targetArgs, llmArgs, opts);
1121
710
  await result.consumeStream();
1122
711
  return {
1123
712
  text: await result.text,
1124
- messageId: result.messageId,
713
+ promptMessageId: result.promptMessageId,
1125
714
  order: result.order,
1126
715
  finishReason: await result.finishReason,
1127
- warnings: result.warnings,
716
+ warnings: await result.warnings,
717
+ savedMessageIds: result.savedMessages?.map((m) => m._id) ?? [],
1128
718
  };
1129
719
  }
1130
720
  else {
1131
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1132
721
  const res = await this.generateText(ctx, targetArgs, llmArgs, opts);
1133
722
  return {
1134
723
  text: res.text,
1135
- messageId: res.messageId,
724
+ promptMessageId: res.promptMessageId,
1136
725
  order: res.order,
1137
726
  finishReason: res.finishReason,
1138
727
  warnings: res.warnings,
728
+ savedMessageIds: res.savedMessages?.map((m) => m._id) ?? [],
1139
729
  };
1140
730
  }
1141
731
  },
@@ -1148,45 +738,43 @@ export class Agent {
1148
738
  * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1149
739
  * and stopWhen.
1150
740
  */
1151
- asObjectAction(spec, options) {
741
+ asObjectAction(objectArgs, options) {
1152
742
  return internalActionGeneric({
1153
743
  args: vSafeObjectArgs,
1154
- handler: async (ctx, args) => {
1155
- const { userId, threadId, ...rest } = args;
744
+ handler: async (ctx_, args) => {
745
+ const { userId, threadId, callSettings, ...rest } = args;
1156
746
  const overrides = pick(rest, ["contextOptions", "storageOptions"]);
1157
- const value = await this.generateObject(ctx, { userId, threadId }, {
1158
- ...spec,
1159
- ...omit(rest, ["contextOptions", "storageOptions"]),
1160
- }, { ...this.options, ...options, ...overrides });
747
+ const targetArgs = { userId, threadId };
748
+ const llmArgs = {
749
+ ...objectArgs,
750
+ ...callSettings,
751
+ ...omit(rest, ["storageOptions", "contextOptions"]),
752
+ messages: args.messages?.map(toModelMessage),
753
+ prompt: Array.isArray(args.prompt)
754
+ ? args.prompt.map(toModelMessage)
755
+ : args.prompt,
756
+ };
757
+ const ctx = (options?.customCtx
758
+ ? { ...ctx_, ...options.customCtx(ctx_, targetArgs, llmArgs) }
759
+ : ctx_);
760
+ const value = await this.generateObject(ctx, targetArgs, llmArgs, {
761
+ ...this.options,
762
+ ...options,
763
+ ...overrides,
764
+ });
1161
765
  return {
1162
- object: value.object,
1163
- messageId: value.messageId,
766
+ object: convexToJson(value.object),
767
+ promptMessageId: value.promptMessageId,
1164
768
  order: value.order,
1165
769
  finishReason: value.finishReason,
1166
770
  warnings: value.warnings,
771
+ savedMessageIds: value.savedMessages?.map((m) => m._id) ?? [],
1167
772
  };
1168
773
  },
1169
774
  });
1170
775
  }
1171
776
  /**
1172
- * Save messages to the thread.
1173
- * Useful as a step in Workflows, e.g.
1174
- * ```ts
1175
- * const saveMessages = agent.asSaveMessagesMutation();
1176
- *
1177
- * const myWorkflow = workflow.define({
1178
- * args: {...},
1179
- * handler: async (step, args) => {
1180
- * // do things to create (but not save)messages
1181
- * const { messageIds } = await step.runMutation(internal.foo.saveMessages, {
1182
- * threadId: args.threadId,
1183
- * messages: args.messages,
1184
- * });
1185
- * // ...
1186
- * },
1187
- * })
1188
- * ```
1189
- * @returns A mutation that can be used to save messages to the thread.
777
+ * @deprecated Use {@link saveMessages} directly instead.
1190
778
  */
1191
779
  asSaveMessagesMutation() {
1192
780
  return internalMutationGeneric({
@@ -1195,116 +783,22 @@ export class Agent {
1195
783
  userId: v.optional(v.string()),
1196
784
  promptMessageId: v.optional(v.string()),
1197
785
  messages: v.array(vMessageWithMetadata),
1198
- pending: v.optional(v.boolean()),
1199
786
  failPendingSteps: v.optional(v.boolean()),
787
+ embeddings: v.optional(vMessageEmbeddings),
1200
788
  },
1201
789
  handler: async (ctx, args) => {
1202
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
790
+ const { messages } = await this.saveMessages(ctx, {
1203
791
  ...args,
1204
- messages: args.messages.map((m) => deserializeMessage(m.message)),
792
+ messages: args.messages.map((m) => toModelMessage(m.message)),
1205
793
  metadata: args.messages.map(({ message: _, ...m }) => m),
794
+ skipEmbeddings: true,
1206
795
  });
1207
796
  return {
1208
- lastMessageId,
1209
- messageIds: messages.map((m) => m._id),
797
+ lastMessageId: messages.at(-1)._id,
798
+ messages: messages.map((m) => pick(m, ["_id", "order", "stepOrder"])),
1210
799
  };
1211
800
  },
1212
801
  });
1213
802
  }
1214
803
  }
1215
- /**
1216
- * Create a thread to store messages with an Agent.
1217
- * @param ctx The context from a mutation or action.
1218
- * @param component The Agent component, usually `components.agent`.
1219
- * @param args The associated thread metadata.
1220
- * @returns The id of the created thread.
1221
- */
1222
- export async function createThread(ctx, component, args) {
1223
- const { _id: threadId } = await ctx.runMutation(component.threads.createThread, {
1224
- userId: args?.userId ?? undefined,
1225
- title: args?.title,
1226
- summary: args?.summary,
1227
- });
1228
- return threadId;
1229
- }
1230
- /**
1231
- * Get the metadata for a thread.
1232
- * @param ctx A ctx object from a query, mutation, or action.
1233
- * @param args.threadId The thread to get the metadata for.
1234
- * @returns The metadata for the thread.
1235
- */
1236
- export async function getThreadMetadata(ctx, component, args) {
1237
- const thread = await ctx.runQuery(component.threads.getThread, {
1238
- threadId: args.threadId,
1239
- });
1240
- if (!thread) {
1241
- throw new Error("Thread not found");
1242
- }
1243
- return thread;
1244
- }
1245
- /**
1246
- * Explicitly save messages associated with the thread (& user if provided)
1247
- */
1248
- export async function saveMessages(ctx, component, args) {
1249
- let embeddings;
1250
- if (args.embeddings) {
1251
- const dimension = args.embeddings.vectors.find((v) => v !== null)?.length;
1252
- if (dimension) {
1253
- validateVectorDimension(dimension);
1254
- embeddings = {
1255
- model: args.embeddings.model,
1256
- dimension,
1257
- vectors: args.embeddings.vectors,
1258
- };
1259
- }
1260
- }
1261
- const result = await ctx.runMutation(component.messages.addMessages, {
1262
- threadId: args.threadId,
1263
- userId: args.userId ?? undefined,
1264
- agentName: args.agentName,
1265
- promptMessageId: args.promptMessageId,
1266
- embeddings,
1267
- messages: await Promise.all(args.messages.map(async (m, i) => {
1268
- const { message, fileIds } = await serializeMessage(ctx, component, m);
1269
- return {
1270
- ...args.metadata?.[i],
1271
- message,
1272
- fileIds,
1273
- };
1274
- })),
1275
- failPendingSteps: args.failPendingSteps ?? false,
1276
- pending: args.pending ?? false,
1277
- });
1278
- return {
1279
- lastMessageId: result.messages.at(-1)._id,
1280
- messages: result.messages,
1281
- };
1282
- }
1283
- /**
1284
- * Save a message to the thread.
1285
- * @param ctx A ctx object from a mutation or action.
1286
- * @param args The message and what to associate it with (user / thread)
1287
- * You can pass extra metadata alongside the message, e.g. associated fileIds.
1288
- * @returns The messageId of the saved message.
1289
- */
1290
- export async function saveMessage(ctx, component, args) {
1291
- let embeddings;
1292
- if (args.embedding && args.embedding.vector) {
1293
- embeddings = {
1294
- model: args.embedding.model,
1295
- vectors: [args.embedding.vector],
1296
- };
1297
- }
1298
- const { lastMessageId, messages } = await saveMessages(ctx, component, {
1299
- threadId: args.threadId,
1300
- userId: args.userId ?? undefined,
1301
- agentName: args.agentName,
1302
- messages: args.prompt !== undefined
1303
- ? [{ role: "user", content: args.prompt }]
1304
- : [args.message],
1305
- metadata: args.metadata ? [args.metadata] : undefined,
1306
- embeddings,
1307
- });
1308
- return { messageId: lastMessageId, message: messages.at(-1) };
1309
- }
1310
804
  //# sourceMappingURL=index.js.map