@convex-dev/agent 0.1.10-alpha.1 → 0.1.11

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 (175) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/client/index.d.ts +10 -11
  3. package/dist/esm/client/index.d.ts.map +1 -1
  4. package/dist/esm/client/index.js +27 -28
  5. package/dist/esm/client/index.js.map +1 -1
  6. package/dist/esm/client/streaming.d.ts.map +1 -1
  7. package/dist/esm/client/streaming.js +1 -7
  8. package/dist/esm/client/streaming.js.map +1 -1
  9. package/dist/esm/component/_generated/api.d.ts +2 -13
  10. package/dist/esm/component/files.d.ts +1 -1
  11. package/dist/esm/component/files.d.ts.map +1 -1
  12. package/dist/esm/component/files.js +6 -4
  13. package/dist/esm/component/files.js.map +1 -1
  14. package/dist/esm/component/messages.d.ts +30 -30
  15. package/dist/esm/component/schema.d.ts +74 -84
  16. package/dist/esm/component/schema.d.ts.map +1 -1
  17. package/dist/esm/component/schema.js +2 -3
  18. package/dist/esm/component/schema.js.map +1 -1
  19. package/dist/esm/component/streams.d.ts +0 -7
  20. package/dist/esm/component/streams.d.ts.map +1 -1
  21. package/dist/esm/component/streams.js +22 -49
  22. package/dist/esm/component/streams.js.map +1 -1
  23. package/dist/esm/component/threads.d.ts +8 -8
  24. package/dist/esm/component/vector/tables.d.ts +3 -5
  25. package/dist/esm/component/vector/tables.d.ts.map +1 -1
  26. package/dist/esm/mapping.d.ts +4 -3
  27. package/dist/esm/mapping.d.ts.map +1 -1
  28. package/dist/esm/mapping.js +7 -5
  29. package/dist/esm/mapping.js.map +1 -1
  30. package/dist/esm/react/deltas.d.ts.map +1 -1
  31. package/dist/esm/react/deltas.js +2 -2
  32. package/dist/esm/react/deltas.js.map +1 -1
  33. package/dist/esm/react/index.d.ts +2 -4
  34. package/dist/esm/react/index.d.ts.map +1 -1
  35. package/dist/esm/react/index.js +9 -22
  36. package/dist/esm/react/index.js.map +1 -1
  37. package/dist/esm/react/optimisticallySendMessage.js +1 -1
  38. package/dist/esm/react/optimisticallySendMessage.js.map +1 -1
  39. package/dist/esm/validators.d.ts +10 -15
  40. package/dist/esm/validators.d.ts.map +1 -1
  41. package/dist/esm/validators.js +0 -2
  42. package/dist/esm/validators.js.map +1 -1
  43. package/dist/esm.tsbuildinfo +1 -1
  44. package/package.json +24 -49
  45. package/src/client/index.ts +42 -39
  46. package/src/client/streaming.ts +1 -7
  47. package/src/component/_generated/api.d.ts +2 -13
  48. package/src/component/files.ts +6 -4
  49. package/src/component/schema.ts +2 -3
  50. package/src/component/streams.ts +39 -85
  51. package/src/component/vector/tables.ts +1 -1
  52. package/src/mapping.ts +17 -6
  53. package/src/react/deltas.ts +2 -4
  54. package/src/react/index.ts +11 -23
  55. package/src/react/optimisticallySendMessage.ts +1 -1
  56. package/src/validators.ts +0 -6
  57. package/dist/commonjs/client/_generated/_ignore.d.ts +0 -1
  58. package/dist/commonjs/client/_generated/_ignore.d.ts.map +0 -1
  59. package/dist/commonjs/client/_generated/_ignore.js +0 -3
  60. package/dist/commonjs/client/_generated/_ignore.js.map +0 -1
  61. package/dist/commonjs/client/createTool.d.ts +0 -47
  62. package/dist/commonjs/client/createTool.d.ts.map +0 -1
  63. package/dist/commonjs/client/createTool.js +0 -46
  64. package/dist/commonjs/client/createTool.js.map +0 -1
  65. package/dist/commonjs/client/files.d.ts +0 -56
  66. package/dist/commonjs/client/files.d.ts.map +0 -1
  67. package/dist/commonjs/client/files.js +0 -124
  68. package/dist/commonjs/client/files.js.map +0 -1
  69. package/dist/commonjs/client/index.d.ts +0 -1356
  70. package/dist/commonjs/client/index.d.ts.map +0 -1
  71. package/dist/commonjs/client/index.js +0 -1148
  72. package/dist/commonjs/client/index.js.map +0 -1
  73. package/dist/commonjs/client/streaming.d.ts +0 -57
  74. package/dist/commonjs/client/streaming.d.ts.map +0 -1
  75. package/dist/commonjs/client/streaming.js +0 -149
  76. package/dist/commonjs/client/streaming.js.map +0 -1
  77. package/dist/commonjs/client/types.d.ts +0 -352
  78. package/dist/commonjs/client/types.d.ts.map +0 -1
  79. package/dist/commonjs/client/types.js +0 -2
  80. package/dist/commonjs/client/types.js.map +0 -1
  81. package/dist/commonjs/component/_generated/api.d.ts +0 -1949
  82. package/dist/commonjs/component/_generated/api.d.ts.map +0 -1
  83. package/dist/commonjs/component/_generated/api.js +0 -22
  84. package/dist/commonjs/component/_generated/api.js.map +0 -1
  85. package/dist/commonjs/component/_generated/dataModel.d.ts +0 -60
  86. package/dist/commonjs/component/_generated/server.d.ts +0 -149
  87. package/dist/commonjs/component/_generated/server.d.ts.map +0 -1
  88. package/dist/commonjs/component/_generated/server.js +0 -74
  89. package/dist/commonjs/component/_generated/server.js.map +0 -1
  90. package/dist/commonjs/component/apiKeys.d.ts +0 -11
  91. package/dist/commonjs/component/apiKeys.d.ts.map +0 -1
  92. package/dist/commonjs/component/apiKeys.js +0 -69
  93. package/dist/commonjs/component/apiKeys.js.map +0 -1
  94. package/dist/commonjs/component/convex.config.d.ts +0 -3
  95. package/dist/commonjs/component/convex.config.d.ts.map +0 -1
  96. package/dist/commonjs/component/convex.config.js +0 -3
  97. package/dist/commonjs/component/convex.config.js.map +0 -1
  98. package/dist/commonjs/component/files.d.ts +0 -90
  99. package/dist/commonjs/component/files.d.ts.map +0 -1
  100. package/dist/commonjs/component/files.js +0 -150
  101. package/dist/commonjs/component/files.js.map +0 -1
  102. package/dist/commonjs/component/messages.d.ts +0 -1479
  103. package/dist/commonjs/component/messages.d.ts.map +0 -1
  104. package/dist/commonjs/component/messages.js +0 -478
  105. package/dist/commonjs/component/messages.js.map +0 -1
  106. package/dist/commonjs/component/schema.d.ts +0 -4592
  107. package/dist/commonjs/component/schema.d.ts.map +0 -1
  108. package/dist/commonjs/component/schema.js +0 -165
  109. package/dist/commonjs/component/schema.js.map +0 -1
  110. package/dist/commonjs/component/streams.d.ts +0 -249
  111. package/dist/commonjs/component/streams.d.ts.map +0 -1
  112. package/dist/commonjs/component/streams.js +0 -346
  113. package/dist/commonjs/component/streams.js.map +0 -1
  114. package/dist/commonjs/component/threads.d.ts +0 -101
  115. package/dist/commonjs/component/threads.d.ts.map +0 -1
  116. package/dist/commonjs/component/threads.js +0 -185
  117. package/dist/commonjs/component/threads.js.map +0 -1
  118. package/dist/commonjs/component/users.d.ts +0 -52
  119. package/dist/commonjs/component/users.d.ts.map +0 -1
  120. package/dist/commonjs/component/users.js +0 -230
  121. package/dist/commonjs/component/users.js.map +0 -1
  122. package/dist/commonjs/component/vector/index.d.ts +0 -61
  123. package/dist/commonjs/component/vector/index.d.ts.map +0 -1
  124. package/dist/commonjs/component/vector/index.js +0 -148
  125. package/dist/commonjs/component/vector/index.js.map +0 -1
  126. package/dist/commonjs/component/vector/tables.d.ts +0 -60
  127. package/dist/commonjs/component/vector/tables.d.ts.map +0 -1
  128. package/dist/commonjs/component/vector/tables.js +0 -56
  129. package/dist/commonjs/component/vector/tables.js.map +0 -1
  130. package/dist/commonjs/mapping.d.ts +0 -50
  131. package/dist/commonjs/mapping.d.ts.map +0 -1
  132. package/dist/commonjs/mapping.js +0 -257
  133. package/dist/commonjs/mapping.js.map +0 -1
  134. package/dist/commonjs/package.json +0 -3
  135. package/dist/commonjs/react/deltas.d.ts +0 -29
  136. package/dist/commonjs/react/deltas.d.ts.map +0 -1
  137. package/dist/commonjs/react/deltas.js +0 -268
  138. package/dist/commonjs/react/deltas.js.map +0 -1
  139. package/dist/commonjs/react/index.d.ts +0 -97
  140. package/dist/commonjs/react/index.d.ts.map +0 -1
  141. package/dist/commonjs/react/index.js +0 -224
  142. package/dist/commonjs/react/index.js.map +0 -1
  143. package/dist/commonjs/react/optimisticallySendMessage.d.ts +0 -8
  144. package/dist/commonjs/react/optimisticallySendMessage.d.ts.map +0 -1
  145. package/dist/commonjs/react/optimisticallySendMessage.js +0 -40
  146. package/dist/commonjs/react/optimisticallySendMessage.js.map +0 -1
  147. package/dist/commonjs/react/toUIMessages.d.ts +0 -13
  148. package/dist/commonjs/react/toUIMessages.d.ts.map +0 -1
  149. package/dist/commonjs/react/toUIMessages.js +0 -146
  150. package/dist/commonjs/react/toUIMessages.js.map +0 -1
  151. package/dist/commonjs/react/types.d.ts +0 -26
  152. package/dist/commonjs/react/types.d.ts.map +0 -1
  153. package/dist/commonjs/react/types.js +0 -2
  154. package/dist/commonjs/react/types.js.map +0 -1
  155. package/dist/commonjs/react/useSmoothText.d.ts +0 -20
  156. package/dist/commonjs/react/useSmoothText.d.ts.map +0 -1
  157. package/dist/commonjs/react/useSmoothText.js +0 -51
  158. package/dist/commonjs/react/useSmoothText.js.map +0 -1
  159. package/dist/commonjs/shared.d.ts +0 -10
  160. package/dist/commonjs/shared.d.ts.map +0 -1
  161. package/dist/commonjs/shared.js +0 -30
  162. package/dist/commonjs/shared.js.map +0 -1
  163. package/dist/commonjs/validators.d.ts +0 -5721
  164. package/dist/commonjs/validators.d.ts.map +0 -1
  165. package/dist/commonjs/validators.js +0 -317
  166. package/dist/commonjs/validators.js.map +0 -1
  167. package/dist/commonjs/vitest.config.d.ts +0 -3
  168. package/dist/commonjs/vitest.config.d.ts.map +0 -1
  169. package/dist/commonjs/vitest.config.js +0 -7
  170. package/dist/commonjs/vitest.config.js.map +0 -1
  171. package/dist/commonjs.tsbuildinfo +0 -1
  172. package/dist/esm/vitest.config.d.ts +0 -3
  173. package/dist/esm/vitest.config.d.ts.map +0 -1
  174. package/dist/esm/vitest.config.js +0 -7
  175. package/dist/esm/vitest.config.js.map +0 -1
@@ -1,1148 +0,0 @@
1
- import { generateObject, generateText, streamObject, streamText } from "ai";
2
- import { assert } from "convex-helpers";
3
- import { internalActionGeneric, internalMutationGeneric, } from "convex/server";
4
- import { v } from "convex/values";
5
- import { validateVectorDimension, } from "../component/vector/tables.js";
6
- import { deserializeMessage, promptOrMessagesToCoreMessages, serializeMessage, serializeNewMessagesInStep, serializeObjectResult, } from "../mapping.js";
7
- import { DEFAULT_MESSAGE_RANGE, DEFAULT_RECENT_MESSAGES, extractText, isTool, } from "../shared.js";
8
- import { vMessageWithMetadata, vSafeObjectArgs, vTextArgs, } from "../validators.js";
9
- import { createTool, wrapTools } from "./createTool.js";
10
- import { DeltaStreamer, mergeTransforms, } from "./streaming.js";
11
- export { storeFile, getFile } from "./files.js";
12
- export { serializeDataOrUrl } from "../mapping.js";
13
- export { vMessageDoc, vThreadDoc } from "../component/schema.js";
14
- export { vAssistantMessage, vContextOptions, vMessage, vPaginationResult, vProviderMetadata, vStorageOptions, vStreamArgs, vSystemMessage, vToolMessage, vUsage, vUserMessage, } from "../validators.js";
15
- export { createTool, extractText, isTool };
16
- export class Agent {
17
- component;
18
- options;
19
- constructor(component, options) {
20
- this.component = component;
21
- this.options = options;
22
- }
23
- async createThread(ctx, args) {
24
- const threadDoc = await ctx.runMutation(this.component.threads.createThread, {
25
- userId: args?.userId,
26
- title: args?.title,
27
- summary: args?.summary,
28
- });
29
- if (!("runAction" in ctx)) {
30
- return { threadId: threadDoc._id };
31
- }
32
- const { thread } = await this.continueThread(ctx, {
33
- threadId: threadDoc._id,
34
- userId: args?.userId,
35
- usageHandler: args?.usageHandler,
36
- tools: args?.tools,
37
- });
38
- return {
39
- threadId: threadDoc._id,
40
- thread,
41
- };
42
- }
43
- /**
44
- * Continues a thread using this agent. Note: threads can be continued
45
- * by different agents. This is a convenience around calling the various
46
- * generate and stream functions with explicit userId and threadId parameters.
47
- * @param ctx The ctx object passed to the action handler
48
- * @param { threadId, userId }: the thread and user to associate the messages with.
49
- * @returns Functions bound to the userId and threadId on a `{thread}` object.
50
- */
51
- async continueThread(ctx, args) {
52
- return {
53
- thread: {
54
- threadId: args.threadId,
55
- getMetadata: this.getThreadMetadata.bind(this, ctx, {
56
- threadId: args.threadId,
57
- }),
58
- updateMetadata: (patch) => ctx.runMutation(this.component.threads.updateThread, {
59
- threadId: args.threadId,
60
- patch,
61
- }),
62
- generateText: this.generateText.bind(this, ctx, args),
63
- streamText: this.streamText.bind(this, ctx, args),
64
- generateObject: this.generateObject.bind(this, ctx, args),
65
- streamObject: this.streamObject.bind(this, ctx, args),
66
- },
67
- };
68
- }
69
- /**
70
- * This behaves like {@link generateText} from the "ai" package except that
71
- * it add context based on the userId and threadId and saves the input and
72
- * resulting messages to the thread, if specified.
73
- * Use {@link continueThread} to get a version of this function already scoped
74
- * to a thread (and optionally userId).
75
- * @param ctx The context passed from the action function calling this.
76
- * @param { userId, threadId }: The user and thread to associate the message with
77
- * @param args The arguments to the generateText function, along with extra controls
78
- * for the {@link ContextOptions} and {@link StorageOptions}.
79
- * @returns The result of the generateText function.
80
- */
81
- async generateText(ctx, { userId: argsUserId, threadId, usageHandler, tools: threadTools, }, args, options) {
82
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
83
- userId: argsUserId,
84
- threadId,
85
- ...options,
86
- });
87
- const { args: aiArgs, messageId, userId } = context;
88
- const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
89
- const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
90
- const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
91
- const trackUsage = usageHandler ?? this.options.usageHandler;
92
- try {
93
- const result = (await generateText({
94
- // Can be overridden
95
- maxSteps: this.options.maxSteps,
96
- ...aiArgs,
97
- tools,
98
- onStepFinish: async (step) => {
99
- if (threadId && messageId && saveOutputMessages) {
100
- await this.saveStep(ctx, {
101
- userId,
102
- threadId,
103
- promptMessageId: messageId,
104
- step,
105
- });
106
- }
107
- if (this.options.rawRequestResponseHandler) {
108
- await this.options.rawRequestResponseHandler(ctx, {
109
- userId,
110
- threadId,
111
- agentName: this.options.name,
112
- request: step.request,
113
- response: step.response,
114
- });
115
- }
116
- if (trackUsage && step.usage) {
117
- await trackUsage(ctx, {
118
- userId,
119
- threadId,
120
- agentName: this.options.name,
121
- model: aiArgs.model.modelId,
122
- provider: aiArgs.model.provider,
123
- usage: step.usage,
124
- providerMetadata: step.providerMetadata,
125
- });
126
- }
127
- return args.onStepFinish?.(step);
128
- },
129
- }));
130
- result.messageId = messageId;
131
- return result;
132
- }
133
- catch (error) {
134
- if (threadId && messageId) {
135
- console.error("RollbackMessage", messageId);
136
- await ctx.runMutation(this.component.messages.rollbackMessage, {
137
- messageId,
138
- error: error.message,
139
- });
140
- }
141
- throw error;
142
- }
143
- }
144
- /**
145
- * This behaves like {@link streamText} from the "ai" package except that
146
- * it add context based on the userId and threadId and saves the input and
147
- * resulting messages to the thread, if specified.
148
- * Use {@link continueThread} to get a version of this function already scoped
149
- * to a thread (and optionally userId).
150
- */
151
- async streamText(ctx, { userId: argsUserId, threadId, usageHandler,
152
- /**
153
- * @deprecated Pass `tools` in the next parameter instead.
154
- * This is only intended to pass through thread-default tools.
155
- */
156
- tools: threadTools, },
157
- /**
158
- * The arguments to the streamText function, similar to the ai `streamText` function.
159
- */
160
- args,
161
- /**
162
- * The {@link ContextOptions} and {@link StorageOptions}
163
- * options to use for fetching contextual messages and saving input/output messages.
164
- */
165
- options) {
166
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
167
- userId: argsUserId,
168
- threadId,
169
- ...options,
170
- });
171
- const { args: aiArgs, messageId, order, stepOrder, userId } = context;
172
- const toolCtx = { ...ctx, userId, threadId, messageId, agent: this };
173
- const tools = wrapTools(toolCtx, args.tools ?? threadTools ?? this.options.tools);
174
- const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
175
- const trackUsage = usageHandler ?? this.options.usageHandler;
176
- const streamer = threadId && options?.saveStreamDeltas
177
- ? new DeltaStreamer(this.component, ctx, options.saveStreamDeltas, {
178
- threadId,
179
- userId,
180
- agentName: this.options.name,
181
- model: aiArgs.model.modelId,
182
- provider: aiArgs.model.provider,
183
- providerOptions: aiArgs.providerOptions,
184
- order,
185
- stepOrder,
186
- abortSignal: aiArgs.abortSignal,
187
- })
188
- : undefined;
189
- const result = streamText({
190
- // Can be overridden
191
- maxSteps: this.options.maxSteps,
192
- ...aiArgs,
193
- tools,
194
- abortSignal: streamer?.abortController.signal ?? aiArgs.abortSignal,
195
- experimental_transform: mergeTransforms(options?.saveStreamDeltas, args.experimental_transform),
196
- onChunk: async (event) => {
197
- await streamer?.addParts([event.chunk]);
198
- // console.log("onChunk", chunk);
199
- return args.onChunk?.(event);
200
- },
201
- onError: async (error) => {
202
- console.error("onError", error);
203
- if (threadId && messageId && saveOutputMessages) {
204
- await ctx.runMutation(this.component.messages.rollbackMessage, {
205
- messageId,
206
- error: error.error.message,
207
- });
208
- }
209
- return args.onError?.(error);
210
- },
211
- onStepFinish: async (step) => {
212
- // console.log("onStepFinish", step);
213
- // TODO: compare delta to the output. internally drop the deltas when committing
214
- if (threadId && messageId) {
215
- const saved = await this.saveStep(ctx, {
216
- userId,
217
- threadId,
218
- promptMessageId: messageId,
219
- step,
220
- });
221
- // TODO: figure out pending/not
222
- await streamer?.finish(saved.messages);
223
- }
224
- if (this.options.rawRequestResponseHandler) {
225
- await this.options.rawRequestResponseHandler(ctx, {
226
- userId,
227
- threadId,
228
- agentName: this.options.name,
229
- request: step.request,
230
- response: step.response,
231
- });
232
- }
233
- if (trackUsage && step.usage) {
234
- await trackUsage(ctx, {
235
- userId,
236
- threadId,
237
- agentName: this.options.name,
238
- model: aiArgs.model.modelId,
239
- provider: aiArgs.model.provider,
240
- usage: step.usage,
241
- providerMetadata: step.providerMetadata,
242
- });
243
- }
244
- return args.onStepFinish?.(step);
245
- },
246
- });
247
- result.messageId = messageId;
248
- return result;
249
- }
250
- /**
251
- * This behaves like {@link generateObject} from the "ai" package except that
252
- * it add context based on the userId and threadId and saves the input and
253
- * resulting messages to the thread, if specified.
254
- * Use {@link continueThread} to get a version of this function already scoped
255
- * to a thread (and optionally userId).
256
- */
257
- async generateObject(ctx, { userId: argsUserId, threadId, usageHandler, },
258
- /**
259
- * The arguments to the generateObject function, similar to the ai.generateObject function.
260
- */
261
- args,
262
- /**
263
- * The {@link ContextOptions} and {@link StorageOptions}
264
- * options to use for fetching contextual messages and saving input/output messages.
265
- */
266
- options) {
267
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
268
- userId: argsUserId,
269
- threadId,
270
- ...options,
271
- });
272
- const { args: aiArgs, messageId, userId } = context;
273
- const trackUsage = usageHandler ?? this.options.usageHandler;
274
- const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
275
- try {
276
- const result = (await generateObject(
277
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
- aiArgs));
279
- if (threadId && messageId && saveOutputMessages) {
280
- await this.saveObject(ctx, {
281
- threadId,
282
- promptMessageId: messageId,
283
- result,
284
- userId,
285
- });
286
- }
287
- result.messageId = messageId;
288
- if (this.options.rawRequestResponseHandler) {
289
- await this.options.rawRequestResponseHandler(ctx, {
290
- userId,
291
- threadId,
292
- agentName: this.options.name,
293
- request: result.request,
294
- response: result.response,
295
- });
296
- }
297
- if (trackUsage && result.usage) {
298
- await trackUsage(ctx, {
299
- userId,
300
- threadId,
301
- agentName: this.options.name,
302
- model: aiArgs.model.modelId,
303
- provider: aiArgs.model.provider,
304
- usage: result.usage,
305
- providerMetadata: result.providerMetadata,
306
- });
307
- }
308
- return result;
309
- }
310
- catch (error) {
311
- if (threadId && messageId) {
312
- await ctx.runMutation(this.component.messages.rollbackMessage, {
313
- messageId,
314
- error: error.message,
315
- });
316
- }
317
- throw error;
318
- }
319
- }
320
- /**
321
- * This behaves like `streamObject` from the "ai" package except that
322
- * it add context based on the userId and threadId and saves the input and
323
- * resulting messages to the thread, if specified.
324
- * Use {@link continueThread} to get a version of this function already scoped
325
- * to a thread (and optionally userId).
326
- */
327
- async streamObject(ctx, { userId: argsUserId, threadId, usageHandler, },
328
- /**
329
- * The arguments to the streamObject function, similar to the ai `streamObject` function.
330
- */
331
- args,
332
- /**
333
- * The {@link ContextOptions} and {@link StorageOptions}
334
- * options to use for fetching contextual messages and saving input/output messages.
335
- */
336
- options) {
337
- // TODO: unify all this shared code between all the generate* and stream* functions
338
- const context = await this._saveMessagesAndFetchContext(ctx, args, {
339
- userId: argsUserId,
340
- threadId,
341
- ...options,
342
- });
343
- const { args: aiArgs, messageId, userId } = context;
344
- const trackUsage = usageHandler ?? this.options.usageHandler;
345
- const saveOutputMessages = this._shouldSaveOutputMessages(options?.storageOptions);
346
- const stream = streamObject({
347
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
- ...aiArgs,
349
- onError: async (error) => {
350
- console.error("onError", error);
351
- return args.onError?.(error);
352
- },
353
- onFinish: async (result) => {
354
- if (threadId && messageId && saveOutputMessages) {
355
- await this.saveObject(ctx, {
356
- userId,
357
- threadId,
358
- promptMessageId: messageId,
359
- result: {
360
- object: result.object,
361
- finishReason: "stop",
362
- usage: result.usage,
363
- warnings: result.warnings,
364
- request: await stream.request,
365
- response: result.response,
366
- providerMetadata: result.providerMetadata,
367
- experimental_providerMetadata: result.experimental_providerMetadata,
368
- logprobs: undefined,
369
- toJsonResponse: stream.toTextStreamResponse,
370
- },
371
- });
372
- }
373
- if (trackUsage && result.usage) {
374
- await trackUsage(ctx, {
375
- userId,
376
- threadId,
377
- agentName: this.options.name,
378
- model: aiArgs.model.modelId,
379
- provider: aiArgs.model.provider,
380
- usage: result.usage,
381
- providerMetadata: result.providerMetadata,
382
- });
383
- }
384
- if (this.options.rawRequestResponseHandler) {
385
- await this.options.rawRequestResponseHandler(ctx, {
386
- userId,
387
- threadId,
388
- agentName: this.options.name,
389
- request: await stream.request,
390
- response: result.response,
391
- });
392
- }
393
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
394
- return args.onFinish?.(result);
395
- },
396
- });
397
- stream.messageId = messageId;
398
- return stream;
399
- }
400
- /**
401
- * Save a message to the thread.
402
- * @param ctx A ctx object from a mutation or action.
403
- * @param args The message and what to associate it with (user / thread)
404
- * You can pass extra metadata alongside the message, e.g. associated fileIds.
405
- * @returns The messageId of the saved message.
406
- */
407
- async saveMessage(ctx, args) {
408
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
409
- threadId: args.threadId,
410
- userId: args.userId,
411
- messages: args.prompt !== undefined
412
- ? [{ role: "user", content: args.prompt }]
413
- : [args.message],
414
- metadata: args.metadata ? [args.metadata] : undefined,
415
- skipEmbeddings: args.skipEmbeddings,
416
- });
417
- return { messageId: lastMessageId, message: messages.at(-1) };
418
- }
419
- /**
420
- * Explicitly save messages associated with the thread (& user if provided)
421
- * @param ctx The ctx parameter to a mutation or action.
422
- * @param args The messages and context to save
423
- * @returns
424
- */
425
- async saveMessages(ctx, args) {
426
- let embeddings;
427
- if (args.skipEmbeddings || !("runAction" in ctx)) {
428
- embeddings = undefined;
429
- if (!args.skipEmbeddings && this.options.textEmbedding) {
430
- console.warn("You're trying to save messages and generate embeddings, but you're in a mutation. " +
431
- "Pass `skipEmbeddings: true` to skip generating embeddings in the mutation and skip this warning. " +
432
- "They will be generated lazily when you generate or stream text / objects. " +
433
- "You can explicitly generate them asynchronously by using the scheduler to run an action later that calls `agent.generateAndSaveEmbeddings`.");
434
- }
435
- }
436
- else {
437
- embeddings = await this.generateEmbeddings(ctx, {
438
- userId: args.userId,
439
- threadId: args.threadId,
440
- }, args.messages);
441
- }
442
- const result = await ctx.runMutation(this.component.messages.addMessages, {
443
- threadId: args.threadId,
444
- userId: args.userId,
445
- agentName: this.options.name,
446
- promptMessageId: args.promptMessageId,
447
- embeddings,
448
- messages: await Promise.all(args.messages.map(async (m, i) => {
449
- const { message, fileIds } = await serializeMessage(ctx, this.component, m);
450
- return {
451
- ...args.metadata?.[i],
452
- message,
453
- fileIds,
454
- };
455
- })),
456
- failPendingSteps: args.failPendingSteps ?? false,
457
- pending: args.pending ?? false,
458
- });
459
- return {
460
- lastMessageId: result.messages.at(-1)._id,
461
- messages: result.messages,
462
- };
463
- }
464
- /**
465
- * List messages from a thread.
466
- * @param ctx A ctx object from a query, mutation, or action.
467
- * @param args.threadId The thread to list messages from.
468
- * @param args.paginationOpts Pagination options (e.g. via usePaginatedQuery).
469
- * @param args.excludeToolMessages Whether to exclude tool messages.
470
- * False by default.
471
- * @param args.statuses What statuses to include. All by default.
472
- * @returns The MessageDoc's in a format compatible with usePaginatedQuery.
473
- */
474
- async listMessages(ctx, args) {
475
- if (args.paginationOpts.numItems === 0) {
476
- return {
477
- page: [],
478
- isDone: true,
479
- continueCursor: args.paginationOpts.cursor ?? "",
480
- };
481
- }
482
- return ctx.runQuery(this.component.messages.listMessagesByThreadId, {
483
- order: "desc",
484
- ...args,
485
- });
486
- }
487
- /**
488
- * A function that handles fetching stream deltas, used with the React hooks
489
- * `useThreadMessages` or `useStreamingThreadMessages`.
490
- * @param ctx A ctx object from a query, mutation, or action.
491
- * @param args.threadId The thread to sync streams for.
492
- * @param args.streamArgs The stream arguments with per-stream cursors.
493
- * @returns The deltas for each stream from their existing cursor.
494
- */
495
- async syncStreams(ctx, args) {
496
- if (!args.streamArgs)
497
- return undefined;
498
- if (args.streamArgs.kind === "list") {
499
- return {
500
- kind: "list",
501
- messages: await ctx.runQuery(this.component.streams.list, {
502
- threadId: args.threadId,
503
- startOrder: args.streamArgs.startOrder,
504
- statuses: args.includeStatuses,
505
- }),
506
- };
507
- }
508
- else {
509
- return {
510
- kind: "deltas",
511
- deltas: await ctx.runQuery(this.component.streams.listDeltas, {
512
- threadId: args.threadId,
513
- cursors: args.streamArgs.cursors,
514
- }),
515
- };
516
- }
517
- }
518
- /**
519
- * Fetch the context messages for a thread.
520
- * @param ctx Either a query, mutation, or action ctx.
521
- * If it is not an action context, you can't do text or
522
- * vector search.
523
- * @param args The associated thread, user, message
524
- * @returns
525
- */
526
- async fetchContextMessages(ctx, args) {
527
- assert(args.userId || args.threadId, "Specify userId or threadId");
528
- // Fetch the latest messages from the thread
529
- let included;
530
- const opts = this._mergedContextOptions(args.contextOptions);
531
- const contextMessages = [];
532
- if (args.threadId &&
533
- (opts.recentMessages !== 0 || args.upToAndIncludingMessageId)) {
534
- const { page } = await ctx.runQuery(this.component.messages.listMessagesByThreadId, {
535
- threadId: args.threadId,
536
- excludeToolMessages: opts.excludeToolMessages,
537
- paginationOpts: {
538
- numItems: opts.recentMessages ?? DEFAULT_RECENT_MESSAGES,
539
- cursor: null,
540
- },
541
- upToAndIncludingMessageId: args.upToAndIncludingMessageId,
542
- order: "desc",
543
- statuses: ["success"],
544
- });
545
- included = new Set(page.map((m) => m._id));
546
- contextMessages.push(
547
- // Reverse since we fetched in descending order
548
- ...page.reverse());
549
- }
550
- if (opts.searchOptions?.textSearch || opts.searchOptions?.vectorSearch) {
551
- const targetMessage = contextMessages.find((m) => m._id === args.upToAndIncludingMessageId)?.message;
552
- const messagesToSearch = targetMessage
553
- ? [targetMessage, ...args.messages]
554
- : args.messages;
555
- if (!("runAction" in ctx)) {
556
- throw new Error("searchUserMessages only works in an action");
557
- }
558
- const searchMessages = await ctx.runAction(this.component.messages.searchMessages, {
559
- searchAllMessagesForUserId: opts?.searchOtherThreads
560
- ? args.userId ??
561
- (args.threadId &&
562
- (await ctx.runQuery(this.component.threads.getThread, {
563
- threadId: args.threadId,
564
- }))?.userId)
565
- : undefined,
566
- threadId: args.threadId,
567
- beforeMessageId: args.upToAndIncludingMessageId,
568
- ...(await this._searchOptionsWithEmbeddingAndDefaults(ctx, { userId: args.userId, threadId: args.threadId }, opts, messagesToSearch)),
569
- });
570
- // TODO: track what messages we used for context
571
- contextMessages.unshift(...searchMessages.filter((m) => !included?.has(m._id)));
572
- }
573
- // Ensure we don't include tool messages without a corresponding tool call
574
- return filterOutOrphanedToolMessages(contextMessages.sort((a, b) =>
575
- // Sort the raw MessageDocs by order and stepOrder
576
- a.order === b.order ? a.stepOrder - b.stepOrder : a.order - b.order));
577
- }
578
- /**
579
- * Get the metadata for a thread.
580
- * @param ctx A ctx object from a query, mutation, or action.
581
- * @param args.threadId The thread to get the metadata for.
582
- * @returns The metadata for the thread.
583
- */
584
- async getThreadMetadata(ctx, args) {
585
- const thread = await ctx.runQuery(this.component.threads.getThread, {
586
- threadId: args.threadId,
587
- });
588
- if (!thread) {
589
- throw new Error("Thread not found");
590
- }
591
- return thread;
592
- }
593
- /**
594
- * Update the metadata for a thread.
595
- * @param ctx A ctx object from a mutation or action.
596
- * @param args.threadId The thread to update the metadata for.
597
- * @param args.patch The patch to apply to the thread.
598
- * @returns The updated thread metadata.
599
- */
600
- async updateThreadMetadata(ctx, args) {
601
- const thread = await ctx.runMutation(this.component.threads.updateThread, args);
602
- return thread;
603
- }
604
- /**
605
- * Get the embeddings for a set of messages.
606
- * @param messages The messages to get the embeddings for.
607
- * @returns The embeddings for the messages.
608
- */
609
- async generateEmbeddings(ctx, { userId, threadId, }, messages) {
610
- if (!this.options.textEmbedding) {
611
- return undefined;
612
- }
613
- let embeddings;
614
- const messageTexts = messages.map((m) => !isTool(m) && extractText(m));
615
- // Find the indexes of the messages that have text.
616
- const textIndexes = messageTexts
617
- .map((t, i) => (t ? i : undefined))
618
- .filter((i) => i !== undefined);
619
- if (textIndexes.length === 0) {
620
- return undefined;
621
- }
622
- // Then embed those messages.
623
- const textEmbeddings = await this.doEmbed(ctx, {
624
- userId,
625
- threadId,
626
- values: messageTexts.filter((t) => !!t),
627
- });
628
- // TODO: record usage of embeddings
629
- // Then assemble the embeddings into a single array with nulls for the messages without text.
630
- const embeddingsOrNull = Array(messages.length).fill(null);
631
- textIndexes.forEach((i, j) => {
632
- embeddingsOrNull[i] = textEmbeddings.embeddings[j];
633
- });
634
- if (textEmbeddings.embeddings.length > 0) {
635
- const dimension = textEmbeddings.embeddings[0].length;
636
- validateVectorDimension(dimension);
637
- embeddings = {
638
- vectors: embeddingsOrNull,
639
- dimension,
640
- model: this.options.textEmbedding.modelId,
641
- };
642
- }
643
- return embeddings;
644
- }
645
- /**
646
- * Generate embeddings for a set of messages, and save them to the database.
647
- * It will not generate or save embeddings for messages that already have an
648
- * embedding.
649
- * @param ctx The ctx parameter to an action.
650
- * @param args The messageIds to generate embeddings for.
651
- */
652
- async generateAndSaveEmbeddings(ctx, args) {
653
- const messages = (await ctx.runQuery(this.component.messages.getMessagesByIds, {
654
- messageIds: args.messageIds,
655
- })).filter((m) => m !== null);
656
- if (messages.length !== args.messageIds.length) {
657
- throw new Error("Some messages were not found: " +
658
- args.messageIds
659
- .filter((id) => !messages.some((m) => m?._id === id))
660
- .join(", "));
661
- }
662
- if (messages.some((m) => !m.message)) {
663
- throw new Error("Some messages don't have a message: " +
664
- args.messageIds
665
- .map((id, i) => (!messages[i].message ? id : undefined))
666
- .filter((id) => id !== undefined)
667
- .join(", "));
668
- }
669
- const messagesMissingEmbeddings = messages.filter((m) => !m.embeddingId);
670
- if (messagesMissingEmbeddings.length === 0) {
671
- return;
672
- }
673
- const embeddings = await this.generateEmbeddings(ctx, {
674
- userId: messagesMissingEmbeddings[0].userId,
675
- threadId: messagesMissingEmbeddings[0].threadId,
676
- }, messagesMissingEmbeddings.map((m) => m.message));
677
- if (!embeddings) {
678
- if (!this.options.textEmbedding) {
679
- throw new Error("No embeddings were generated for the messages. You must pass a textEmbedding model to the agent constructor.");
680
- }
681
- throw new Error("No embeddings were generated for these messages: " +
682
- messagesMissingEmbeddings.map((m) => m._id).join(", "));
683
- }
684
- await ctx.runMutation(this.component.vector.index.insertBatch, {
685
- vectorDimension: embeddings.dimension,
686
- vectors: messagesMissingEmbeddings
687
- .map((m, i) => ({
688
- messageId: m._id,
689
- model: embeddings.model,
690
- table: "messages",
691
- userId: m.userId,
692
- threadId: m.threadId,
693
- vector: embeddings.vectors[i],
694
- }))
695
- .filter((v) => v.vector !== null),
696
- });
697
- }
698
- /**
699
- * Explicitly save a "step" created by the AI SDK.
700
- * @param ctx The ctx argument to a mutation or action.
701
- * @param args The Step generated by the AI SDK.
702
- */
703
- async saveStep(ctx, args) {
704
- const messages = await serializeNewMessagesInStep(ctx, this.component, args.step, {
705
- provider: args.provider ?? this.options.chat.provider,
706
- model: args.model ?? this.options.chat.modelId,
707
- });
708
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
709
- const saved = await ctx.runMutation(this.component.messages.addMessages, {
710
- userId: args.userId,
711
- threadId: args.threadId,
712
- agentName: this.options.name,
713
- promptMessageId: args.promptMessageId,
714
- messages,
715
- embeddings,
716
- failPendingSteps: false,
717
- });
718
- return saved;
719
- }
720
- /**
721
- * Manually save the result of a generateObject call to the thread.
722
- * This happens automatically when using {@link generateObject} or {@link streamObject}
723
- * from the `thread` object created by {@link continueThread} or {@link createThread}.
724
- * @param ctx The context passed from the mutation or action function calling this.
725
- * @param args The arguments to the saveObject function.
726
- */
727
- async saveObject(ctx, args) {
728
- const { messages } = serializeObjectResult(args.result, {
729
- model: this.options.chat.modelId,
730
- provider: this.options.chat.provider,
731
- });
732
- const embeddings = await this.generateEmbeddings(ctx, { userId: args.userId, threadId: args.threadId }, messages.map((m) => m.message));
733
- await ctx.runMutation(this.component.messages.addMessages, {
734
- userId: args.userId,
735
- threadId: args.threadId,
736
- promptMessageId: args.promptMessageId,
737
- failPendingSteps: false,
738
- messages,
739
- embeddings,
740
- agentName: this.options.name,
741
- pending: false,
742
- });
743
- }
744
- /**
745
- * Commit or rollback a message that was pending.
746
- * This is done automatically when saving messages by default.
747
- * If creating pending messages, you can call this when the full "transaction" is done.
748
- * @param ctx The ctx argument to your mutation or action.
749
- * @param args What message to save. Generally the parent message sent into
750
- * the generateText call.
751
- */
752
- async completeMessage(ctx, args) {
753
- const result = args.result;
754
- if (result.kind === "success") {
755
- await ctx.runMutation(this.component.messages.commitMessage, {
756
- messageId: args.messageId,
757
- });
758
- }
759
- else {
760
- await ctx.runMutation(this.component.messages.rollbackMessage, {
761
- messageId: args.messageId,
762
- error: result.error,
763
- });
764
- }
765
- }
766
- async _saveMessagesAndFetchContext(ctx, args, { userId: argsUserId, threadId, contextOptions, storageOptions, }) {
767
- contextOptions ||= this.options.contextOptions;
768
- storageOptions ||= this.options.storageOptions;
769
- // If only a messageId is provided, this will be empty.
770
- const messages = args.promptMessageId
771
- ? []
772
- : promptOrMessagesToCoreMessages(args);
773
- const userId = argsUserId ??
774
- (threadId &&
775
- (await ctx.runQuery(this.component.threads.getThread, { threadId }))
776
- ?.userId);
777
- assert(!args.promptMessageId || !(args.prompt || args.messages), "you can't specify a prompt or message if you specify a promptMessageId");
778
- // If only a messageId is provided, this will add that message to the end.
779
- const contextMessages = await this.fetchContextMessages(ctx, {
780
- userId,
781
- threadId,
782
- upToAndIncludingMessageId: args.promptMessageId,
783
- messages,
784
- contextOptions,
785
- });
786
- // Lazily generate embeddings for the prompt message, if it doesn't have
787
- // embeddings yet. This can happen if the message was saved in a mutation
788
- // where the LLM is not available.
789
- if (args.promptMessageId &&
790
- !contextMessages.at(-1)?.embeddingId &&
791
- this.options.textEmbedding) {
792
- await this.generateAndSaveEmbeddings(ctx, {
793
- messageIds: [args.promptMessageId],
794
- });
795
- }
796
- let messageId = args.promptMessageId;
797
- let order = args.promptMessageId
798
- ? contextMessages.at(-1)?.order
799
- : undefined;
800
- let stepOrder = args.promptMessageId
801
- ? contextMessages.at(-1)?.stepOrder
802
- : undefined;
803
- if (threadId &&
804
- messages.length &&
805
- storageOptions?.saveMessages !== "none" &&
806
- storageOptions?.saveAnyInputMessages !== false) {
807
- const saveAll = storageOptions?.saveMessages === "all";
808
- const coreMessages = saveAll ? messages : messages.slice(-1);
809
- const saved = await this.saveMessages(ctx, {
810
- threadId,
811
- userId,
812
- messages: coreMessages,
813
- metadata: coreMessages.length === 1 ? [{ id: args.id }] : undefined,
814
- pending: true,
815
- failPendingSteps: true,
816
- });
817
- messageId = saved.lastMessageId;
818
- order = saved.messages.at(-1)?.order;
819
- stepOrder = saved.messages.at(-1)?.stepOrder;
820
- }
821
- let processedMessages = [
822
- ...contextMessages.map((m) => deserializeMessage(m.message)),
823
- ...messages,
824
- ];
825
- // Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
826
- if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
827
- processedMessages = await this._inlineMessagesFiles(processedMessages);
828
- }
829
- const { prompt: _, model, ...rest } = args;
830
- return {
831
- args: {
832
- ...rest,
833
- maxRetries: args.maxRetries ?? this.options.maxRetries,
834
- model: model ?? this.options.chat,
835
- system: args.system ?? this.options.instructions,
836
- messages: processedMessages,
837
- },
838
- userId,
839
- messageId,
840
- order,
841
- stepOrder,
842
- };
843
- }
844
- _shouldSaveOutputMessages(storageOpts) {
845
- const opts = storageOpts ?? this.options.storageOptions;
846
- return opts?.saveOutputMessages !== false && opts?.saveMessages !== "none";
847
- }
848
- _mergedContextOptions(opts) {
849
- const searchOptions = {
850
- ...this.options.contextOptions?.searchOptions,
851
- ...opts?.searchOptions,
852
- };
853
- return {
854
- ...this.options.contextOptions,
855
- ...opts,
856
- searchOptions: searchOptions.limit
857
- ? searchOptions
858
- : undefined,
859
- };
860
- }
861
- async _searchOptionsWithEmbeddingAndDefaults(ctx, { userId, threadId }, contextOptions, messages) {
862
- assert(contextOptions.searchOptions?.textSearch ||
863
- contextOptions.searchOptions?.vectorSearch, "searchOptions is required");
864
- assert(messages.length > 0, "Core messages cannot be empty");
865
- const text = extractText(messages.at(-1));
866
- const search = {
867
- limit: contextOptions.searchOptions?.limit ?? 10,
868
- messageRange: {
869
- ...DEFAULT_MESSAGE_RANGE,
870
- ...contextOptions.searchOptions?.messageRange,
871
- },
872
- text: extractText(messages.at(-1)),
873
- };
874
- if (contextOptions.searchOptions?.vectorSearch &&
875
- text &&
876
- this.options.textEmbedding) {
877
- search.vector = (await this.doEmbed(ctx, {
878
- threadId,
879
- userId,
880
- values: [text],
881
- })).embeddings[0];
882
- search.vectorModel = this.options.textEmbedding.modelId;
883
- }
884
- return search;
885
- }
886
- async doEmbed(ctx, options) {
887
- const embedding = this.options.textEmbedding;
888
- assert(embedding, "textEmbedding is required");
889
- const result = await embedding.doEmbed({
890
- values: options.values,
891
- abortSignal: options.abortSignal,
892
- headers: options.headers,
893
- });
894
- if (this.options.usageHandler && result.usage) {
895
- await this.options.usageHandler(ctx, {
896
- userId: options.userId,
897
- threadId: options.threadId,
898
- agentName: this.options.name,
899
- model: embedding.modelId,
900
- provider: embedding.provider,
901
- providerMetadata: result.rawResponse
902
- ? { [embedding.provider]: result.rawResponse }
903
- : undefined,
904
- usage: {
905
- promptTokens: result.usage.tokens,
906
- completionTokens: 0,
907
- totalTokens: result.usage.tokens,
908
- },
909
- });
910
- }
911
- return { embeddings: result.embeddings };
912
- }
913
- /**
914
- * Process messages to inline file and image URLs that point to localhost
915
- * by converting them to base64. This solves the problem of LLMs not being
916
- * able to access localhost URLs.
917
- */
918
- async _inlineMessagesFiles(messages) {
919
- // Process each message to convert localhost URLs to base64
920
- return Promise.all(messages.map(async (message) => {
921
- if (message.role !== "user" ||
922
- typeof message.content === "string" ||
923
- !Array.isArray(message.content)) {
924
- return message;
925
- }
926
- const processedContent = await Promise.all(message.content.map(async (part) => {
927
- if (part.type === "image" && part.image instanceof URL) {
928
- if (this._isLocalhostUrl(part.image)) {
929
- const imageData = await this._downloadFile(part.image);
930
- return {
931
- ...part,
932
- image: imageData,
933
- };
934
- }
935
- }
936
- // Handle file parts
937
- if (part.type === "file" && part.data instanceof URL) {
938
- if (this._isLocalhostUrl(part.data)) {
939
- const fileData = await this._downloadFile(part.data);
940
- return {
941
- ...part,
942
- data: fileData,
943
- };
944
- }
945
- }
946
- return part;
947
- }));
948
- return {
949
- ...message,
950
- content: processedContent,
951
- };
952
- }));
953
- }
954
- /**
955
- * Check if a URL points to localhost
956
- */
957
- _isLocalhostUrl(url) {
958
- return (url.hostname === "localhost" ||
959
- url.hostname === "127.0.0.1" ||
960
- url.hostname === "::1" ||
961
- url.hostname === "0.0.0.0");
962
- }
963
- /**
964
- * Download a file from a URL
965
- */
966
- async _downloadFile(url) {
967
- // Fetch the file
968
- const response = await fetch(url);
969
- if (!response.ok) {
970
- throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
971
- }
972
- return await response.arrayBuffer();
973
- }
974
- /**
975
- * WORKFLOW UTILITIES
976
- */
977
- /**
978
- * Create a mutation that creates a thread so you can call it from a Workflow.
979
- * e.g.
980
- * ```ts
981
- * // in convex/foo.ts
982
- * export const createThread = weatherAgent.createThreadMutation();
983
- *
984
- * const workflow = new WorkflowManager(components.workflow);
985
- * export const myWorkflow = workflow.define({
986
- * args: {},
987
- * handler: async (step) => {
988
- * const { threadId } = await step.runMutation(internal.foo.createThread);
989
- * // use the threadId to generate text, object, etc.
990
- * },
991
- * });
992
- * ```
993
- * @returns A mutation that creates a thread.
994
- */
995
- createThreadMutation() {
996
- return internalMutationGeneric({
997
- args: {
998
- userId: v.optional(v.string()),
999
- title: v.optional(v.string()),
1000
- summary: v.optional(v.string()),
1001
- },
1002
- handler: async (ctx, args) => {
1003
- const { threadId } = await this.createThread(ctx, args);
1004
- return { threadId };
1005
- },
1006
- });
1007
- }
1008
- /**
1009
- * Create an action out of this agent so you can call it from workflows or other actions
1010
- * without a wrapping function.
1011
- * @param spec Configuration for the agent acting as an action, including
1012
- * {@link ContextOptions}, {@link StorageOptions}, and maxSteps.
1013
- */
1014
- asTextAction(spec) {
1015
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1016
- return internalActionGeneric({
1017
- args: vTextArgs,
1018
- handler: async (ctx, args) => {
1019
- const { contextOptions, storageOptions, ...rest } = args;
1020
- const stream = args.stream === true ? spec?.stream || true : spec?.stream ?? false;
1021
- const targetArgs = { userId: args.userId, threadId: args.threadId };
1022
- const llmArgs = { maxSteps, ...rest };
1023
- const opts = {
1024
- contextOptions: contextOptions ??
1025
- spec?.contextOptions ??
1026
- this.options.contextOptions,
1027
- storageOptions: storageOptions ??
1028
- spec?.storageOptions ??
1029
- this.options.storageOptions,
1030
- saveStreamDeltas: stream,
1031
- };
1032
- if (stream) {
1033
- const result = await this.streamText(ctx, targetArgs, llmArgs, opts);
1034
- await result.consumeStream();
1035
- return {
1036
- text: await result.text,
1037
- finishReason: await result.finishReason,
1038
- messageId: result.messageId,
1039
- };
1040
- }
1041
- else {
1042
- const { text, messageId, finishReason } = await this.generateText(ctx, targetArgs, llmArgs, opts);
1043
- return { text, messageId, finishReason };
1044
- }
1045
- },
1046
- });
1047
- }
1048
- /**
1049
- * Create an action that generates an object out of this agent so you can call
1050
- * it from workflows or other actions without a wrapping function.
1051
- * @param spec Configuration for the agent acting as an action, including
1052
- * the normal parameters to {@link generateObject}, plus {@link ContextOptions}
1053
- * and maxSteps.
1054
- */
1055
- asObjectAction(spec, options) {
1056
- const maxSteps = spec?.maxSteps ?? this.options.maxSteps;
1057
- return internalActionGeneric({
1058
- args: vSafeObjectArgs,
1059
- handler: async (ctx, args) => {
1060
- const { contextOptions, storageOptions, ...rest } = args;
1061
- const value = await this.generateObject(ctx, { userId: args.userId, threadId: args.threadId }, {
1062
- ...spec,
1063
- maxSteps,
1064
- ...rest,
1065
- }, {
1066
- contextOptions: contextOptions ??
1067
- options?.contextOptions ??
1068
- this.options.contextOptions,
1069
- storageOptions: storageOptions ??
1070
- options?.storageOptions ??
1071
- this.options.storageOptions,
1072
- });
1073
- return { object: value.object };
1074
- },
1075
- });
1076
- }
1077
- /**
1078
- * Save messages to the thread.
1079
- * Useful as a step in Workflows, e.g.
1080
- * ```ts
1081
- * const saveMessages = agent.asSaveMessagesMutation();
1082
- *
1083
- * const myWorkflow = workflow.define({
1084
- * args: {...},
1085
- * handler: async (step, args) => {
1086
- * // do things to create (but not save)messages
1087
- * const { messageIds } = await step.runMutation(internal.foo.saveMessages, {
1088
- * threadId: args.threadId,
1089
- * messages: args.messages,
1090
- * });
1091
- * // ...
1092
- * },
1093
- * })
1094
- * ```
1095
- * @returns A mutation that can be used to save messages to the thread.
1096
- */
1097
- asSaveMessagesMutation() {
1098
- return internalMutationGeneric({
1099
- args: {
1100
- threadId: v.string(),
1101
- userId: v.optional(v.string()),
1102
- promptMessageId: v.optional(v.string()),
1103
- messages: v.array(vMessageWithMetadata),
1104
- pending: v.optional(v.boolean()),
1105
- failPendingSteps: v.optional(v.boolean()),
1106
- },
1107
- handler: async (ctx, args) => {
1108
- const { lastMessageId, messages } = await this.saveMessages(ctx, {
1109
- ...args,
1110
- messages: args.messages.map((m) => m.message),
1111
- metadata: args.messages.map(({ message: _, ...m }) => m),
1112
- });
1113
- return {
1114
- lastMessageId,
1115
- messageIds: messages.map((m) => m._id),
1116
- };
1117
- },
1118
- });
1119
- }
1120
- }
1121
- export function filterOutOrphanedToolMessages(docs) {
1122
- const toolCallIds = new Set();
1123
- const result = [];
1124
- for (const doc of docs) {
1125
- if (doc.message?.role === "assistant" &&
1126
- Array.isArray(doc.message.content)) {
1127
- for (const content of doc.message.content) {
1128
- if (content.type === "tool-call") {
1129
- toolCallIds.add(content.toolCallId);
1130
- }
1131
- }
1132
- result.push(doc);
1133
- }
1134
- else if (doc.message?.role === "tool") {
1135
- if (doc.message.content.every((c) => toolCallIds.has(c.toolCallId))) {
1136
- result.push(doc);
1137
- }
1138
- else {
1139
- console.debug("Filtering out orphaned tool message", doc);
1140
- }
1141
- }
1142
- else {
1143
- result.push(doc);
1144
- }
1145
- }
1146
- return result;
1147
- }
1148
- //# sourceMappingURL=index.js.map