@convex-dev/agent 0.6.3 → 0.7.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 (246) hide show
  1. package/MIGRATION.md +60 -116
  2. package/dist/client/messages.d.ts +1 -398
  3. package/dist/client/messages.d.ts.map +1 -1
  4. package/dist/client/messages.js +3 -47
  5. package/dist/client/messages.js.map +1 -1
  6. package/dist/client/types.d.ts +1 -455
  7. package/dist/client/types.d.ts.map +1 -1
  8. package/dist/component/_generated/component.d.ts +805 -45
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/apiKeys.js +5 -5
  11. package/dist/component/apiKeys.js.map +1 -1
  12. package/dist/component/files.d.ts.map +1 -1
  13. package/dist/component/files.js +13 -11
  14. package/dist/component/files.js.map +1 -1
  15. package/dist/component/messages.d.ts +1252 -201
  16. package/dist/component/messages.d.ts.map +1 -1
  17. package/dist/component/messages.js +83 -47
  18. package/dist/component/messages.js.map +1 -1
  19. package/dist/component/schema.d.ts +2593 -332
  20. package/dist/component/schema.d.ts.map +1 -1
  21. package/dist/component/streams.d.ts +7 -1
  22. package/dist/component/streams.d.ts.map +1 -1
  23. package/dist/component/streams.js +46 -47
  24. package/dist/component/streams.js.map +1 -1
  25. package/dist/component/threads.d.ts.map +1 -1
  26. package/dist/component/threads.js +8 -9
  27. package/dist/component/threads.js.map +1 -1
  28. package/dist/component/users.js +2 -2
  29. package/dist/component/users.js.map +1 -1
  30. package/dist/component/vector/index.d.ts.map +1 -1
  31. package/dist/component/vector/index.js +14 -8
  32. package/dist/component/vector/index.js.map +1 -1
  33. package/dist/react/index.d.ts +5 -5
  34. package/dist/react/index.d.ts.map +1 -1
  35. package/dist/react/index.js +5 -5
  36. package/dist/react/index.js.map +1 -1
  37. package/dist/shared.d.ts +19 -7
  38. package/dist/shared.d.ts.map +1 -1
  39. package/dist/shared.js +4 -4
  40. package/dist/shared.js.map +1 -1
  41. package/dist/streaming/materializePersistedUIMessageChunks.d.ts +16 -0
  42. package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -0
  43. package/dist/streaming/materializePersistedUIMessageChunks.js +302 -0
  44. package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -0
  45. package/dist/streaming/persistedUIMessageChunks.d.ts +106 -0
  46. package/dist/streaming/persistedUIMessageChunks.d.ts.map +1 -0
  47. package/dist/streaming/persistedUIMessageChunks.js +475 -0
  48. package/dist/streaming/persistedUIMessageChunks.js.map +1 -0
  49. package/dist/validators.d.ts +10026 -1055
  50. package/dist/validators.d.ts.map +1 -1
  51. package/dist/validators.js +71 -9
  52. package/dist/validators.js.map +1 -1
  53. package/dist/{UIMessages.d.ts → vercel/UIMessages.d.ts} +1 -1
  54. package/dist/vercel/UIMessages.d.ts.map +1 -0
  55. package/dist/{UIMessages.js → vercel/UIMessages.js} +63 -1
  56. package/dist/vercel/UIMessages.js.map +1 -0
  57. package/dist/{client → vercel/client}/createTool.d.ts +23 -16
  58. package/dist/vercel/client/createTool.d.ts.map +1 -0
  59. package/dist/{client → vercel/client}/createTool.js +2 -0
  60. package/dist/vercel/client/createTool.js.map +1 -0
  61. package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts +593 -134
  62. package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts.map +1 -1
  63. package/dist/{client → vercel/client}/definePlaygroundAPI.js +13 -9
  64. package/dist/vercel/client/definePlaygroundAPI.js.map +1 -0
  65. package/dist/{client → vercel/client}/files.d.ts +2 -2
  66. package/dist/vercel/client/files.d.ts.map +1 -0
  67. package/dist/vercel/client/files.js.map +1 -0
  68. package/dist/vercel/client/messages.d.ts +542 -0
  69. package/dist/vercel/client/messages.d.ts.map +1 -0
  70. package/dist/vercel/client/messages.js +66 -0
  71. package/dist/vercel/client/messages.js.map +1 -0
  72. package/dist/{client → vercel/client}/mockModel.d.ts +17 -17
  73. package/dist/vercel/client/mockModel.d.ts.map +1 -0
  74. package/dist/{client → vercel/client}/mockModel.js +12 -13
  75. package/dist/vercel/client/mockModel.js.map +1 -0
  76. package/dist/{client → vercel/client}/saveInputMessages.d.ts +2 -2
  77. package/dist/vercel/client/saveInputMessages.d.ts.map +1 -0
  78. package/dist/vercel/client/saveInputMessages.js.map +1 -0
  79. package/dist/{client → vercel/client}/search.d.ts +119 -22
  80. package/dist/vercel/client/search.d.ts.map +1 -0
  81. package/dist/{client → vercel/client}/search.js +2 -2
  82. package/dist/vercel/client/search.js.map +1 -0
  83. package/dist/{client → vercel/client}/start.d.ts +26 -11
  84. package/dist/vercel/client/start.d.ts.map +1 -0
  85. package/dist/{client → vercel/client}/start.js +64 -22
  86. package/dist/vercel/client/start.js.map +1 -0
  87. package/dist/{client → vercel/client}/streamText.d.ts +13 -12
  88. package/dist/vercel/client/streamText.d.ts.map +1 -0
  89. package/dist/{client → vercel/client}/streamText.js +43 -9
  90. package/dist/vercel/client/streamText.js.map +1 -0
  91. package/dist/{client → vercel/client}/streaming.d.ts +1410 -183
  92. package/dist/vercel/client/streaming.d.ts.map +1 -0
  93. package/dist/{client → vercel/client}/streaming.js +120 -52
  94. package/dist/vercel/client/streaming.js.map +1 -0
  95. package/dist/vercel/client/types.d.ts +456 -0
  96. package/dist/vercel/client/types.d.ts.map +1 -0
  97. package/dist/vercel/client/types.js +3 -0
  98. package/dist/vercel/client/types.js.map +1 -0
  99. package/dist/{client → vercel/client}/utils.d.ts +3 -2
  100. package/dist/vercel/client/utils.d.ts.map +1 -0
  101. package/dist/{client → vercel/client}/utils.js +2 -2
  102. package/dist/vercel/client/utils.js.map +1 -0
  103. package/dist/vercel/deltas.d.ts +31 -0
  104. package/dist/vercel/deltas.d.ts.map +1 -0
  105. package/dist/vercel/deltas.js +525 -0
  106. package/dist/vercel/deltas.js.map +1 -0
  107. package/dist/{client → vercel}/index.d.ts +484 -97
  108. package/dist/vercel/index.d.ts.map +1 -0
  109. package/dist/{client → vercel}/index.js +65 -38
  110. package/dist/vercel/index.js.map +1 -0
  111. package/dist/{mapping.d.ts → vercel/mapping.d.ts} +5 -13
  112. package/dist/vercel/mapping.d.ts.map +1 -0
  113. package/dist/{mapping.js → vercel/mapping.js} +411 -112
  114. package/dist/vercel/mapping.js.map +1 -0
  115. package/dist/{react → vercel/react}/optimisticallySendMessage.d.ts +2 -2
  116. package/dist/vercel/react/optimisticallySendMessage.d.ts.map +1 -0
  117. package/dist/vercel/react/optimisticallySendMessage.js.map +1 -0
  118. package/dist/{react → vercel/react}/types.d.ts +1 -1
  119. package/dist/vercel/react/types.d.ts.map +1 -0
  120. package/dist/{react → vercel/react}/types.js.map +1 -1
  121. package/dist/{react → vercel/react}/useDeltaStreams.d.ts +1 -1
  122. package/dist/vercel/react/useDeltaStreams.d.ts.map +1 -0
  123. package/dist/{react → vercel/react}/useDeltaStreams.js +1 -1
  124. package/dist/vercel/react/useDeltaStreams.js.map +1 -0
  125. package/dist/{react → vercel/react}/useStreamingUIMessages.d.ts +1 -1
  126. package/dist/vercel/react/useStreamingUIMessages.d.ts.map +1 -0
  127. package/dist/vercel/react/useStreamingUIMessages.js +112 -0
  128. package/dist/vercel/react/useStreamingUIMessages.js.map +1 -0
  129. package/dist/{react → vercel/react}/useThreadMessages.d.ts +1 -1
  130. package/dist/vercel/react/useThreadMessages.d.ts.map +1 -0
  131. package/dist/{react → vercel/react}/useThreadMessages.js +1 -1
  132. package/dist/vercel/react/useThreadMessages.js.map +1 -0
  133. package/dist/{react → vercel/react}/useUIMessages.d.ts +2 -1
  134. package/dist/vercel/react/useUIMessages.d.ts.map +1 -0
  135. package/dist/{react → vercel/react}/useUIMessages.js +8 -4
  136. package/dist/vercel/react/useUIMessages.js.map +1 -0
  137. package/package.json +30 -22
  138. package/src/client/messages.ts +5 -107
  139. package/src/client/types.ts +0 -621
  140. package/src/component/_generated/component.ts +1089 -48
  141. package/src/component/apiKeys.ts +5 -5
  142. package/src/component/files.test.ts +1 -1
  143. package/src/component/files.ts +14 -12
  144. package/src/component/messages.test.ts +273 -1
  145. package/src/component/messages.ts +99 -57
  146. package/src/component/streams.ts +80 -64
  147. package/src/component/threads.ts +12 -9
  148. package/src/component/users.ts +2 -2
  149. package/src/component/vector/index.ts +14 -7
  150. package/src/eslintConfig.test.ts +44 -0
  151. package/src/react/index.ts +8 -5
  152. package/src/shared.ts +26 -36
  153. package/src/streaming/materializePersistedUIMessageChunks.test.ts +513 -0
  154. package/src/streaming/materializePersistedUIMessageChunks.ts +398 -0
  155. package/src/streaming/persistedUIMessageChunks.test.ts +205 -0
  156. package/src/streaming/persistedUIMessageChunks.ts +658 -0
  157. package/src/validators.test.ts +4 -1
  158. package/src/validators.ts +89 -8
  159. package/src/{UIMessages.combineUIMessages.test.ts → vercel/UIMessages.combineUIMessages.test.ts} +4 -1
  160. package/src/{UIMessages.ts → vercel/UIMessages.ts} +96 -5
  161. package/src/{client → vercel/client}/approval.test.ts +48 -19
  162. package/src/{client → vercel/client}/createTool.ts +63 -23
  163. package/src/{client → vercel/client}/definePlaygroundAPI.ts +44 -29
  164. package/src/{client → vercel/client}/files.ts +2 -2
  165. package/src/{client → vercel/client}/index.test.ts +162 -3
  166. package/src/vercel/client/messages.ts +182 -0
  167. package/src/{client → vercel/client}/mockModel.ts +38 -39
  168. package/src/{client → vercel/client}/saveInputMessages.test.ts +1 -1
  169. package/src/{client → vercel/client}/saveInputMessages.ts +7 -4
  170. package/src/{client → vercel/client}/search.test.ts +3 -3
  171. package/src/{client → vercel/client}/search.ts +4 -4
  172. package/src/{client → vercel/client}/setup.test.ts +6 -3
  173. package/src/vercel/client/start.test.ts +14 -0
  174. package/src/{client → vercel/client}/start.ts +147 -36
  175. package/src/vercel/client/streamText.test.ts +223 -0
  176. package/src/{client → vercel/client}/streamText.ts +102 -31
  177. package/src/{client → vercel/client}/streaming.integration.test.ts +40 -115
  178. package/src/{client → vercel/client}/streaming.test.ts +178 -4
  179. package/src/{client → vercel/client}/streaming.ts +114 -52
  180. package/src/vercel/client/types.test.ts +13 -0
  181. package/src/vercel/client/types.ts +679 -0
  182. package/src/{client → vercel/client}/utils.ts +13 -7
  183. package/src/vercel/deltas.test.ts +898 -0
  184. package/src/vercel/deltas.ts +633 -0
  185. package/src/{fromUIMessages.test.ts → vercel/fromUIMessages.test.ts} +17 -1
  186. package/src/{client → vercel}/index.ts +169 -62
  187. package/src/vercel/mapping.test.ts +965 -0
  188. package/src/{mapping.ts → vercel/mapping.ts} +501 -131
  189. package/src/{react → vercel/react}/optimisticallySendMessage.ts +2 -2
  190. package/src/{react → vercel/react}/types.ts +1 -1
  191. package/src/{react → vercel/react}/useDeltaStreams.ts +6 -2
  192. package/src/{react → vercel/react}/useStreamingUIMessages.ts +67 -35
  193. package/src/{react → vercel/react}/useThreadMessages.ts +2 -2
  194. package/src/{react → vercel/react}/useUIMessages.test.ts +80 -1
  195. package/src/{react → vercel/react}/useUIMessages.ts +13 -5
  196. package/src/{toUIMessages.test.ts → vercel/toUIMessages.test.ts} +49 -2
  197. package/dist/UIMessages.d.ts.map +0 -1
  198. package/dist/UIMessages.js.map +0 -1
  199. package/dist/client/createTool.d.ts.map +0 -1
  200. package/dist/client/createTool.js.map +0 -1
  201. package/dist/client/definePlaygroundAPI.js.map +0 -1
  202. package/dist/client/files.d.ts.map +0 -1
  203. package/dist/client/files.js.map +0 -1
  204. package/dist/client/index.d.ts.map +0 -1
  205. package/dist/client/index.js.map +0 -1
  206. package/dist/client/mockModel.d.ts.map +0 -1
  207. package/dist/client/mockModel.js.map +0 -1
  208. package/dist/client/saveInputMessages.d.ts.map +0 -1
  209. package/dist/client/saveInputMessages.js.map +0 -1
  210. package/dist/client/search.d.ts.map +0 -1
  211. package/dist/client/search.js.map +0 -1
  212. package/dist/client/start.d.ts.map +0 -1
  213. package/dist/client/start.js.map +0 -1
  214. package/dist/client/streamText.d.ts.map +0 -1
  215. package/dist/client/streamText.js.map +0 -1
  216. package/dist/client/streaming.d.ts.map +0 -1
  217. package/dist/client/streaming.js.map +0 -1
  218. package/dist/client/utils.d.ts.map +0 -1
  219. package/dist/client/utils.js.map +0 -1
  220. package/dist/deltas.d.ts +0 -43
  221. package/dist/deltas.d.ts.map +0 -1
  222. package/dist/deltas.js +0 -446
  223. package/dist/deltas.js.map +0 -1
  224. package/dist/mapping.d.ts.map +0 -1
  225. package/dist/mapping.js.map +0 -1
  226. package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
  227. package/dist/react/optimisticallySendMessage.js.map +0 -1
  228. package/dist/react/types.d.ts.map +0 -1
  229. package/dist/react/useDeltaStreams.d.ts.map +0 -1
  230. package/dist/react/useDeltaStreams.js.map +0 -1
  231. package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
  232. package/dist/react/useStreamingUIMessages.js +0 -92
  233. package/dist/react/useStreamingUIMessages.js.map +0 -1
  234. package/dist/react/useThreadMessages.d.ts.map +0 -1
  235. package/dist/react/useThreadMessages.js.map +0 -1
  236. package/dist/react/useUIMessages.d.ts.map +0 -1
  237. package/dist/react/useUIMessages.js.map +0 -1
  238. package/src/client/streamText.test.ts +0 -102
  239. package/src/deltas.test.ts +0 -626
  240. package/src/deltas.ts +0 -569
  241. package/src/mapping.test.ts +0 -402
  242. /package/dist/{client → vercel/client}/files.js +0 -0
  243. /package/dist/{client → vercel/client}/saveInputMessages.js +0 -0
  244. /package/dist/{react → vercel/react}/optimisticallySendMessage.js +0 -0
  245. /package/dist/{react → vercel/react}/types.js +0 -0
  246. /package/src/{UIMessages.test.ts → vercel/UIMessages.test.ts} +0 -0
@@ -1,4 +1,9 @@
1
- import type { ToolResultOutput } from "@ai-sdk/provider-utils";
1
+ import type { JSONObject } from "@ai-sdk/provider";
2
+ import type {
3
+ Context,
4
+ Experimental_SandboxSession,
5
+ ToolResultOutput,
6
+ } from "@ai-sdk/provider-utils";
2
7
  import type {
3
8
  FlexibleSchema,
4
9
  ModelMessage,
@@ -8,8 +13,8 @@ import type {
8
13
  } from "ai";
9
14
  import { tool } from "ai";
10
15
  import type { GenericActionCtx, GenericDataModel } from "convex/server";
11
- import type { ProviderOptions } from "../validators.js";
12
- import type { Agent } from "./index.js";
16
+ import type { ProviderOptions } from "../../validators.js";
17
+ import type { Agent } from "../index.js";
13
18
 
14
19
  const MIGRATION_URL = "node_modules/@convex-dev/agent/MIGRATION.md";
15
20
  const warnedDeprecations = new Set<string>();
@@ -28,12 +33,16 @@ export type ToolCtx<DataModel extends GenericDataModel = GenericDataModel> =
28
33
  messageId?: string;
29
34
  };
30
35
 
36
+ export type AgentToolExecutionOptions<TOOL_CONTEXT extends Context = Context> =
37
+ ToolExecutionOptions<TOOL_CONTEXT>;
38
+
31
39
  /**
32
40
  * Function that is called to determine if the tool needs approval before it can be executed.
33
41
  */
34
42
  export type ToolNeedsApprovalFunctionCtx<
35
43
  INPUT,
36
44
  Ctx extends ToolCtx = ToolCtx,
45
+ TOOL_CONTEXT extends Context = Context,
37
46
  > = (
38
47
  ctx: Ctx,
39
48
  input: INPUT,
@@ -52,7 +61,7 @@ export type ToolNeedsApprovalFunctionCtx<
52
61
  *
53
62
  * Experimental (can break in patch releases).
54
63
  */
55
- experimental_context?: unknown;
64
+ context: TOOL_CONTEXT;
56
65
  },
57
66
  ) => boolean | PromiseLike<boolean>;
58
67
 
@@ -60,10 +69,11 @@ export type ToolExecuteFunctionCtx<
60
69
  INPUT,
61
70
  OUTPUT,
62
71
  Ctx extends ToolCtx = ToolCtx,
72
+ TOOL_CONTEXT extends Context = Context,
63
73
  > = (
64
74
  ctx: Ctx,
65
75
  input: INPUT,
66
- options: ToolExecutionOptions,
76
+ options: AgentToolExecutionOptions<TOOL_CONTEXT>,
67
77
  ) => AsyncIterable<OUTPUT> | PromiseLike<OUTPUT>;
68
78
 
69
79
  type NeverOptional<N, T> = 0 extends 1 & N
@@ -83,6 +93,7 @@ export type ToolOutputPropertiesCtx<
83
93
  INPUT,
84
94
  OUTPUT,
85
95
  Ctx extends ToolCtx = ToolCtx,
96
+ TOOL_CONTEXT extends Context = Context,
86
97
  > = NeverOptional<
87
98
  OUTPUT,
88
99
  {
@@ -93,7 +104,7 @@ export type ToolOutputPropertiesCtx<
93
104
  * @param input - The input of the tool call.
94
105
  * @param options.abortSignal - A signal that can be used to abort the tool call.
95
106
  */
96
- execute?: ToolExecuteFunctionCtx<INPUT, OUTPUT, Ctx>;
107
+ execute?: ToolExecuteFunctionCtx<INPUT, OUTPUT, Ctx, TOOL_CONTEXT>;
97
108
  outputSchema?: FlexibleSchema<OUTPUT>;
98
109
  /**
99
110
  * @deprecated Removed in v0.6.0. Use `execute` instead.
@@ -130,17 +141,27 @@ export type ToolInputProperties<INPUT> = {
130
141
  * @param tool The tool. See https://sdk.vercel.ai/docs/ai-sdk-core/tools-and-tool-calling
131
142
  * Currently contains deprecated parameters `args` and `handler` to maintain backwards compatibility
132
143
  * but these will be removed in the future. Use `inputSchema` and `execute` instead, respectively.
133
- *
144
+ *
134
145
  * @returns A tool to be used with the AI SDK.
135
146
  */
136
- export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
147
+ export function createTool<
148
+ INPUT,
149
+ OUTPUT,
150
+ Ctx extends ToolCtx = ToolCtx,
151
+ TOOL_CONTEXT extends Context = Context,
152
+ >(
137
153
  def: {
138
154
  /**
139
155
  * An optional description of what the tool does.
140
156
  * Will be used by the language model to decide whether to use the tool.
141
157
  * Not used for provider-defined tools.
142
158
  */
143
- description?: string;
159
+ description?:
160
+ | string
161
+ | ((options: {
162
+ context: NoInfer<TOOL_CONTEXT>;
163
+ experimental_sandbox?: Experimental_SandboxSession;
164
+ }) => string);
144
165
  /**
145
166
  * An optional title of the tool.
146
167
  */
@@ -151,6 +172,8 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
151
172
  * functionality that can be fully encapsulated in the provider.
152
173
  */
153
174
  providerOptions?: ProviderOptions;
175
+ metadata?: JSONObject;
176
+ contextSchema?: FlexibleSchema<TOOL_CONTEXT>;
154
177
  } & ToolInputProperties<INPUT> & {
155
178
  /**
156
179
  * An optional list of input examples that show the language
@@ -166,7 +189,8 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
166
189
  | boolean
167
190
  | ToolNeedsApprovalFunctionCtx<
168
191
  [INPUT] extends [never] ? unknown : INPUT,
169
- Ctx
192
+ Ctx,
193
+ TOOL_CONTEXT
170
194
  >;
171
195
  /**
172
196
  * Strict mode setting for the tool.
@@ -186,7 +210,7 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
186
210
  */
187
211
  onInputStart?: (
188
212
  ctx: Ctx,
189
- options: ToolExecutionOptions,
213
+ options: AgentToolExecutionOptions<TOOL_CONTEXT>,
190
214
  ) => void | PromiseLike<void>;
191
215
  /**
192
216
  * Optional function that is called when an argument streaming delta is available.
@@ -194,7 +218,9 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
194
218
  */
195
219
  onInputDelta?: (
196
220
  ctx: Ctx,
197
- options: { inputTextDelta: string } & ToolExecutionOptions,
221
+ options: {
222
+ inputTextDelta: string;
223
+ } & AgentToolExecutionOptions<TOOL_CONTEXT>,
198
224
  ) => void | PromiseLike<void>;
199
225
  /**
200
226
  * Optional function that is called when a tool call can be started,
@@ -204,9 +230,9 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
204
230
  ctx: Ctx,
205
231
  options: {
206
232
  input: [INPUT] extends [never] ? unknown : INPUT;
207
- } & ToolExecutionOptions,
233
+ } & AgentToolExecutionOptions<TOOL_CONTEXT>,
208
234
  ) => void | PromiseLike<void>;
209
- } & ToolOutputPropertiesCtx<INPUT, OUTPUT, Ctx> & {
235
+ } & ToolOutputPropertiesCtx<INPUT, OUTPUT, Ctx, TOOL_CONTEXT> & {
210
236
  /**
211
237
  * Optional conversion function that maps the tool result to an output that can be used by the language model.
212
238
  *
@@ -234,7 +260,7 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
234
260
  },
235
261
  ) => ToolResultOutput | PromiseLike<ToolResultOutput>;
236
262
  },
237
- ): Tool<INPUT, OUTPUT> {
263
+ ): Tool<INPUT, OUTPUT, TOOL_CONTEXT> {
238
264
  // Runtime backwards compat - types will show errors but runtime still works
239
265
  const inputSchema = def.inputSchema ?? (def as any).args;
240
266
  if (!inputSchema)
@@ -260,16 +286,18 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
260
286
  " handler function, define an outputSchema, or both",
261
287
  );
262
288
 
263
- const t = tool<INPUT, OUTPUT>({
289
+ const t = tool<INPUT, OUTPUT, TOOL_CONTEXT>({
264
290
  type: "function",
265
291
  __acceptsCtx: true,
266
292
  ctx: def.ctx,
267
293
  description: def.description,
268
294
  title: def.title,
269
295
  providerOptions: def.providerOptions,
296
+ metadata: def.metadata,
270
297
  inputSchema,
298
+ contextSchema: def.contextSchema,
271
299
  inputExamples: def.inputExamples,
272
- needsApproval(this: Tool<INPUT, OUTPUT>, input, options) {
300
+ needsApproval(this: Tool<INPUT, OUTPUT, TOOL_CONTEXT>, input, options) {
273
301
  const needsApproval = def.needsApproval;
274
302
  if (!needsApproval || typeof needsApproval === "boolean")
275
303
  return Boolean(needsApproval);
@@ -287,9 +315,9 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
287
315
  ...(executeHandler
288
316
  ? {
289
317
  execute(
290
- this: Tool<INPUT, OUTPUT>,
318
+ this: Tool<INPUT, OUTPUT, TOOL_CONTEXT>,
291
319
  input: INPUT,
292
- options: ToolExecutionOptions,
320
+ options: ToolExecutionOptions<TOOL_CONTEXT>,
293
321
  ) {
294
322
  if (!getCtx(this)) {
295
323
  throw new Error(
@@ -306,25 +334,37 @@ export function createTool<INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx>(
306
334
  });
307
335
  if (def.onInputStart) {
308
336
  const origOnInputStart = def.onInputStart;
309
- t.onInputStart = function (this: Tool<INPUT, OUTPUT>, options) {
337
+ t.onInputStart = function (
338
+ this: Tool<INPUT, OUTPUT, TOOL_CONTEXT>,
339
+ options,
340
+ ) {
310
341
  return origOnInputStart.call(this, getCtx(this), options);
311
342
  };
312
343
  }
313
344
  if (def.onInputDelta) {
314
345
  const origOnInputDelta = def.onInputDelta;
315
- t.onInputDelta = function (this: Tool<INPUT, OUTPUT>, options) {
346
+ t.onInputDelta = function (
347
+ this: Tool<INPUT, OUTPUT, TOOL_CONTEXT>,
348
+ options,
349
+ ) {
316
350
  return origOnInputDelta.call(this, getCtx(this), options);
317
351
  };
318
352
  }
319
353
  if (def.onInputAvailable) {
320
354
  const origOnInputAvailable = def.onInputAvailable;
321
- t.onInputAvailable = function (this: Tool<INPUT, OUTPUT>, options) {
355
+ t.onInputAvailable = function (
356
+ this: Tool<INPUT, OUTPUT, TOOL_CONTEXT>,
357
+ options,
358
+ ) {
322
359
  return origOnInputAvailable.call(this, getCtx(this), options);
323
360
  };
324
361
  }
325
362
  if (def.toModelOutput) {
326
363
  const origToModelOutput = def.toModelOutput;
327
- t.toModelOutput = function (this: Tool<INPUT, OUTPUT>, options) {
364
+ t.toModelOutput = function (
365
+ this: Tool<INPUT, OUTPUT, TOOL_CONTEXT>,
366
+ options,
367
+ ) {
328
368
  return origToModelOutput.call(this, getCtx(this), options);
329
369
  };
330
370
  }
@@ -6,30 +6,32 @@ import {
6
6
  type ApiFromModules,
7
7
  type GenericActionCtx,
8
8
  type GenericDataModel,
9
+ type GenericMutationCtx,
9
10
  type GenericQueryCtx,
10
11
  } from "convex/server";
11
12
  import { v } from "convex/values";
12
13
  import {
13
- createThread as createThread_,
14
- listMessages as listMessages_,
15
- toModelMessage,
16
14
  vContextOptions,
17
15
  vMessage,
18
16
  vMessageDoc,
19
17
  vPaginationResult,
20
18
  vStorageOptions,
21
19
  vThreadDoc,
22
- type Agent,
23
- type AgentComponent,
24
20
  vStreamArgs,
25
- syncStreams,
26
- vStreamMessagesReturnValue,
27
- isTool,
28
- extractText,
29
21
  type MessageDoc,
30
- } from "./index.js";
31
- import { serializeNewMessagesInStep } from "../mapping.js";
32
- import { getModelName, getProviderName } from "../shared.js";
22
+ } from "../../validators.js";
23
+ import { createThread as createThread_ } from "../../client/threads.js";
24
+ import {
25
+ extractText,
26
+ getModelName,
27
+ getProviderName,
28
+ isTool,
29
+ } from "../../shared.js";
30
+ import { serializeResponseMessages, toModelMessage } from "../mapping.js";
31
+ import type { Agent } from "../index.js";
32
+ import { listMessages as listMessages_ } from "./messages.js";
33
+ import { syncStreams, vStreamMessagesReturnValue } from "./streaming.js";
34
+ import type { AgentComponent } from "./types.js";
33
35
 
34
36
  export type PlaygroundAPI = ApiFromModules<{
35
37
  playground: ReturnType<typeof definePlaygroundAPI>;
@@ -64,7 +66,10 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
64
66
  }
65
67
  }
66
68
 
67
- async function validateApiKey(ctx: RunQueryCtx, apiKey: string) {
69
+ async function validateApiKey(
70
+ ctx: QueryCtx | MutationCtx | ActionCtx,
71
+ apiKey: string,
72
+ ) {
68
73
  await ctx.runQuery(component.apiKeys.validate, { apiKey });
69
74
  }
70
75
 
@@ -276,18 +281,20 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
276
281
  },
277
282
  { contextOptions, storageOptions, saveStreamDeltas: true },
278
283
  );
279
- const outputMessages = await Promise.all(
280
- (await steps).map(async (step) => {
281
- const { messages } = await serializeNewMessagesInStep(
282
- ctx,
283
- component,
284
- step,
285
- {
286
- model: getModelName(agent.options.languageModel),
287
- provider: getProviderName(agent.options.languageModel),
288
- },
289
- );
290
- return messages.map((messageWithMetadata, i) => {
284
+ const outputMessages: MessageDoc[][] = [];
285
+ for (const step of await steps) {
286
+ const { messages } = await serializeResponseMessages(
287
+ ctx,
288
+ component,
289
+ step,
290
+ {
291
+ model: getModelName(agent.options.languageModel),
292
+ provider: getProviderName(agent.options.languageModel),
293
+ },
294
+ step.response.messages,
295
+ );
296
+ outputMessages.push(
297
+ messages.map((messageWithMetadata, i) => {
291
298
  return {
292
299
  ...messageWithMetadata,
293
300
  tool: isTool(messageWithMetadata.message),
@@ -300,9 +307,9 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
300
307
  order: 0,
301
308
  stepOrder: i + 1,
302
309
  } satisfies MessageDoc;
303
- });
304
- }),
305
- );
310
+ }),
311
+ );
312
+ }
306
313
  return { text: await text, messages: outputMessages.flat() };
307
314
  },
308
315
  returns: v.object({ text: v.string(), messages: v.array(vMessageDoc) }),
@@ -367,4 +374,12 @@ export function definePlaygroundAPI<DataModel extends GenericDataModel>(
367
374
  };
368
375
  }
369
376
 
370
- type RunQueryCtx = { runQuery: GenericQueryCtx<GenericDataModel>["runQuery"] };
377
+ type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
378
+ type MutationCtx = Pick<
379
+ GenericMutationCtx<GenericDataModel>,
380
+ "runQuery" | "runMutation"
381
+ >;
382
+ type ActionCtx = Pick<
383
+ GenericActionCtx<GenericDataModel>,
384
+ "runQuery" | "runMutation" | "runAction"
385
+ >;
@@ -5,14 +5,14 @@ import type {
5
5
  ModelMessage,
6
6
  UserContent,
7
7
  } from "ai";
8
- import type { Id } from "../component/_generated/dataModel.js";
8
+ import type { Id } from "../../component/_generated/dataModel.js";
9
9
  import type {
10
10
  ActionCtx,
11
11
  AgentComponent,
12
12
  MutationCtx,
13
13
  QueryCtx,
14
14
  } from "./types.js";
15
- import type { Message } from "../validators.js";
15
+ import type { Message } from "../../validators.js";
16
16
  import { assert } from "convex-helpers";
17
17
  import type { StorageReader } from "convex/server";
18
18
 
@@ -2,9 +2,10 @@ import { describe, expect, test } from "vitest";
2
2
  import {
3
3
  Agent,
4
4
  createThread,
5
+ createTool,
5
6
  filterOutOrphanedToolMessages,
6
7
  type MessageDoc,
7
- } from "./index.js";
8
+ } from "../index.js";
8
9
  import type { DataModelFromSchemaDefinition } from "convex/server";
9
10
  import {
10
11
  anyApi,
@@ -23,7 +24,7 @@ import { defineSchema } from "convex/server";
23
24
  import { stepCountIs } from "ai";
24
25
  import { components, initConvexTest } from "./setup.test.js";
25
26
  import { z } from "zod/v4";
26
- import { mockModel } from "./mockModel.js";
27
+ import { MockLanguageModel, mockModel } from "./mockModel.js";
27
28
 
28
29
  const schema = defineSchema({});
29
30
  type DataModel = DataModelFromSchemaDefinition<typeof schema>;
@@ -34,12 +35,50 @@ const action = actionGeneric as ActionBuilder<DataModel, "public">;
34
35
 
35
36
  const TEST_TEXT = JSON.stringify({ hello: "world" });
36
37
 
38
+ const agentModel = new MockLanguageModel({
39
+ content: [{ type: "text", text: TEST_TEXT }],
40
+ });
37
41
  const agent = new Agent(components.agent, {
38
42
  name: "test",
39
43
  instructions: "You are a test agent",
44
+ languageModel: agentModel,
45
+ });
46
+
47
+ let capturedRawBodies: { request?: unknown; response?: unknown } | undefined;
48
+ const rawBodyAgent = new Agent(components.agent, {
49
+ name: "raw-body-test",
40
50
  languageModel: mockModel({
41
- content: [{ type: "text", text: TEST_TEXT }],
51
+ doGenerate: async () => ({
52
+ content: [{ type: "text", text: "raw" }],
53
+ finishReason: { unified: "stop", raw: undefined },
54
+ usage: {
55
+ inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
56
+ outputTokens: { total: 1, text: 1, reasoning: 0 },
57
+ },
58
+ request: { body: { prompt: "request-body" } },
59
+ response: { body: { text: "response-body" }, headers: {} },
60
+ warnings: [],
61
+ }),
42
62
  }),
63
+ rawRequestResponseHandler: async (_ctx, event) => {
64
+ capturedRawBodies = {
65
+ request: event.request.body,
66
+ response: event.response.body,
67
+ };
68
+ },
69
+ });
70
+
71
+ export const captureRawBodies = action({
72
+ args: {},
73
+ handler: async (ctx) => {
74
+ capturedRawBodies = undefined;
75
+ await rawBodyAgent.generateText(
76
+ ctx,
77
+ { userId: "raw-body-user" },
78
+ { prompt: "raw" },
79
+ );
80
+ return capturedRawBodies;
81
+ },
43
82
  });
44
83
 
45
84
  export const testQuery = query({
@@ -62,6 +101,77 @@ export const createThreadManually = mutation({
62
101
  },
63
102
  });
64
103
 
104
+ const saveStepAgent = new Agent(components.agent, {
105
+ name: "save-step-test",
106
+ instructions: "test",
107
+ tools: {
108
+ echo: createTool({
109
+ description: "Echo a value",
110
+ inputSchema: z.object({ value: z.string() }),
111
+ execute: async (_ctx, input) => `echo:${input.value}`,
112
+ }),
113
+ },
114
+ languageModel: mockModel({
115
+ contentSteps: [
116
+ [
117
+ {
118
+ type: "tool-call",
119
+ toolCallId: "ss-1",
120
+ toolName: "echo",
121
+ input: JSON.stringify({ value: "hi" }),
122
+ },
123
+ ],
124
+ [{ type: "text", text: "done" }],
125
+ ],
126
+ }),
127
+ stopWhen: stepCountIs(5),
128
+ });
129
+
130
+ export const replayStepsViaSaveStep = action({
131
+ args: { withPreviousStep: v.boolean() },
132
+ handler: async (ctx, args) => {
133
+ const { thread } = await saveStepAgent.createThread(ctx, {
134
+ userId: "ss-gen",
135
+ });
136
+ const genResult = await thread.generateText({ prompt: "echo hi" });
137
+ const steps = genResult.steps;
138
+
139
+ const { threadId } = await saveStepAgent.createThread(ctx, {
140
+ userId: "ss-replay",
141
+ });
142
+ const { messageId: promptMessageId } = await saveStepAgent.saveMessage(
143
+ ctx,
144
+ {
145
+ threadId,
146
+ message: { role: "user", content: "echo hi" },
147
+ skipEmbeddings: true,
148
+ },
149
+ );
150
+ let previousStep: (typeof steps)[number] | undefined;
151
+ for (const step of steps) {
152
+ await saveStepAgent.saveStep(ctx, {
153
+ threadId,
154
+ promptMessageId,
155
+ step,
156
+ previousStep: args.withPreviousStep ? previousStep : undefined,
157
+ });
158
+ previousStep = step;
159
+ }
160
+
161
+ const replayed = await saveStepAgent.listMessages(ctx, {
162
+ threadId,
163
+ paginationOpts: { cursor: null, numItems: 50 },
164
+ statuses: ["success", "pending", "failed"],
165
+ });
166
+ const contentTypes = replayed.page.flatMap((m) =>
167
+ Array.isArray(m.message?.content)
168
+ ? m.message!.content.map((c: { type?: string }) => c.type ?? "text")
169
+ : ["text"],
170
+ );
171
+ return { stepCount: steps.length, contentTypes };
172
+ },
173
+ });
174
+
65
175
  export const createThreadMutation = agent.createThreadMutation();
66
176
  export const generateObjectAction = agent.asObjectAction({
67
177
  schema: z.object({ hello: z.string().describe("A string for testing") }),
@@ -162,6 +272,8 @@ const testApi: ApiFromModules<{
162
272
  generateTextAction: typeof generateTextAction;
163
273
  generateObjectAction: typeof generateObjectAction;
164
274
  saveMessageMutation: typeof saveMessageMutation;
275
+ captureRawBodies: typeof captureRawBodies;
276
+ replayStepsViaSaveStep: typeof replayStepsViaSaveStep;
165
277
  };
166
278
  }>["fns"] = anyApi["index.test"] as any;
167
279
 
@@ -177,6 +289,26 @@ describe("Agent thick client", () => {
177
289
  expect(result).toBeDefined();
178
290
  expect(result).toMatch(TEST_TEXT);
179
291
  });
292
+ test.each([true, false])(
293
+ "saveStep persists each SDK 7 step once (previousStep: %s)",
294
+ async (withPreviousStep) => {
295
+ const t = initConvexTest(schema);
296
+ const res = await t.action(testApi.replayStepsViaSaveStep, {
297
+ withPreviousStep,
298
+ });
299
+ expect(res.stepCount).toBe(2);
300
+ const toolCalls = res.contentTypes.filter(
301
+ (t) => t === "tool-call",
302
+ ).length;
303
+ const toolResults = res.contentTypes.filter(
304
+ (t) => t === "tool-result",
305
+ ).length;
306
+ expect({ toolCalls, toolResults }).toEqual({
307
+ toolCalls: 1,
308
+ toolResults: 1,
309
+ });
310
+ },
311
+ );
180
312
  });
181
313
 
182
314
  describe("filterOutOrphanedToolMessages", () => {
@@ -244,6 +376,14 @@ describe("filterOutOrphanedToolMessages", () => {
244
376
  });
245
377
 
246
378
  describe("Agent option variations and normal behavior", () => {
379
+ test("raw handler opts into retained SDK 7 request and response bodies", async () => {
380
+ const t = initConvexTest(schema);
381
+ await expect(t.action(testApi.captureRawBodies, {})).resolves.toEqual({
382
+ request: { prompt: "request-body" },
383
+ response: { text: "response-body" },
384
+ });
385
+ });
386
+
247
387
  test("Agent can be constructed with minimal options", () => {
248
388
  const a = new Agent(components.agent, {
249
389
  name: "minimal",
@@ -378,6 +518,25 @@ describe("Agent-generated mutations/actions/queries", () => {
378
518
  expect(objResult.object).toBeDefined();
379
519
  });
380
520
 
521
+ test("asTextAction maps its system wire field to AI SDK instructions", async () => {
522
+ const t = initConvexTest(schema);
523
+ const before = agentModel.doGenerateCalls.length;
524
+
525
+ await t.action(testApi.generateTextAction, {
526
+ userId: "8",
527
+ system: "Action-scoped instructions",
528
+ prompt: "Say hi",
529
+ });
530
+
531
+ const call = agentModel.doGenerateCalls.at(-1);
532
+ expect(agentModel.doGenerateCalls).toHaveLength(before + 1);
533
+ expect(call?.prompt[0]).toEqual({
534
+ role: "system",
535
+ content: "Action-scoped instructions",
536
+ });
537
+ expect(call).not.toHaveProperty("system");
538
+ });
539
+
381
540
  test("asSaveMessagesMutation works via t.mutation", async () => {
382
541
  const t = initConvexTest(schema);
383
542
  const threadId = await t.run(async (ctx) =>