@copilotkitnext/agent 1.53.1-next.2 → 1.54.0-next.3

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.
@@ -0,0 +1,158 @@
1
+ import { describe, it, expectTypeOf } from "vitest";
2
+ import { z } from "zod";
3
+ import * as v from "valibot";
4
+ import { type } from "arktype";
5
+ import type { StandardSchemaV1 } from "@copilotkitnext/shared";
6
+ import { defineTool, type ToolDefinition } from "../index";
7
+
8
+ describe("ToolDefinition type inference", () => {
9
+ describe("defineTool with Zod", () => {
10
+ it("infers execute args from Zod schema", () => {
11
+ const tool = defineTool({
12
+ name: "weather",
13
+ description: "Get weather",
14
+ parameters: z.object({
15
+ city: z.string(),
16
+ units: z.enum(["celsius", "fahrenheit"]),
17
+ }),
18
+ execute: async (args) => {
19
+ // args should be fully typed
20
+ expectTypeOf(args).toEqualTypeOf<{
21
+ city: string;
22
+ units: "celsius" | "fahrenheit";
23
+ }>();
24
+ return {};
25
+ },
26
+ });
27
+
28
+ expectTypeOf(tool.name).toBeString();
29
+ expectTypeOf(tool.description).toBeString();
30
+ expectTypeOf(tool.parameters).toMatchTypeOf<StandardSchemaV1>();
31
+ });
32
+
33
+ it("infers execute args with optional Zod fields", () => {
34
+ defineTool({
35
+ name: "search",
36
+ description: "Search",
37
+ parameters: z.object({
38
+ query: z.string(),
39
+ limit: z.number().optional(),
40
+ }),
41
+ execute: async (args) => {
42
+ expectTypeOf(args).toEqualTypeOf<{
43
+ query: string;
44
+ limit?: number | undefined;
45
+ }>();
46
+ return {};
47
+ },
48
+ });
49
+ });
50
+ });
51
+
52
+ describe("defineTool with Valibot", () => {
53
+ it("infers execute args from Valibot schema", () => {
54
+ defineTool({
55
+ name: "search",
56
+ description: "Search",
57
+ parameters: v.object({
58
+ query: v.string(),
59
+ limit: v.number(),
60
+ }),
61
+ execute: async (args) => {
62
+ expectTypeOf(args).toEqualTypeOf<{
63
+ query: string;
64
+ limit: number;
65
+ }>();
66
+ return {};
67
+ },
68
+ });
69
+ });
70
+
71
+ it("infers execute args with optional Valibot fields", () => {
72
+ defineTool({
73
+ name: "search",
74
+ description: "Search",
75
+ parameters: v.object({
76
+ query: v.string(),
77
+ limit: v.optional(v.number()),
78
+ }),
79
+ execute: async (args) => {
80
+ expectTypeOf(args).toEqualTypeOf<{
81
+ query: string;
82
+ limit?: number | undefined;
83
+ }>();
84
+ return {};
85
+ },
86
+ });
87
+ });
88
+ });
89
+
90
+ describe("defineTool with ArkType", () => {
91
+ it("infers execute args from ArkType schema", () => {
92
+ defineTool({
93
+ name: "search",
94
+ description: "Search",
95
+ parameters: type({
96
+ query: "string",
97
+ limit: "number",
98
+ }),
99
+ execute: async (args) => {
100
+ expectTypeOf(args).toEqualTypeOf<{
101
+ query: string;
102
+ limit: number;
103
+ }>();
104
+ return {};
105
+ },
106
+ });
107
+ });
108
+
109
+ it("infers execute args with optional ArkType fields", () => {
110
+ defineTool({
111
+ name: "profile",
112
+ description: "Profile",
113
+ parameters: type({
114
+ name: "string",
115
+ "age?": "number",
116
+ }),
117
+ execute: async (args) => {
118
+ expectTypeOf(args).toEqualTypeOf<{
119
+ name: string;
120
+ age?: number;
121
+ }>();
122
+ return {};
123
+ },
124
+ });
125
+ });
126
+ });
127
+
128
+ describe("ToolDefinition interface", () => {
129
+ it("accepts Zod schema as generic parameter", () => {
130
+ type ZodTool = ToolDefinition<z.ZodObject<{ city: z.ZodString }>>;
131
+
132
+ expectTypeOf<ZodTool["execute"]>().toBeFunction();
133
+ expectTypeOf<Parameters<ZodTool["execute"]>[0]>().toEqualTypeOf<{
134
+ city: string;
135
+ }>();
136
+ });
137
+
138
+ it("default generic parameter is StandardSchemaV1", () => {
139
+ type DefaultTool = ToolDefinition;
140
+
141
+ expectTypeOf<
142
+ DefaultTool["parameters"]
143
+ >().toMatchTypeOf<StandardSchemaV1>();
144
+ });
145
+
146
+ it("preserves schema type through defineTool return", () => {
147
+ const schema = z.object({ x: z.number() });
148
+ const tool = defineTool({
149
+ name: "t",
150
+ description: "d",
151
+ parameters: schema,
152
+ execute: async () => ({}),
153
+ });
154
+
155
+ expectTypeOf(tool.parameters).toEqualTypeOf<typeof schema>();
156
+ });
157
+ });
158
+ });
@@ -421,6 +421,84 @@ describe("convertToolDefinitionsToVercelAITools", () => {
421
421
  });
422
422
  });
423
423
 
424
+ describe("ensureObjectArgs via convertMessagesToVercelAISDKMessages", () => {
425
+ function makeAssistantWithToolArgs(argsJson: string): Message[] {
426
+ return [
427
+ {
428
+ id: "1",
429
+ role: "assistant",
430
+ content: "calling tool",
431
+ toolCalls: [
432
+ {
433
+ id: "call1",
434
+ type: "function",
435
+ function: {
436
+ name: "myTool",
437
+ arguments: argsJson,
438
+ },
439
+ },
440
+ ],
441
+ },
442
+ ];
443
+ }
444
+
445
+ it("should pass through valid object arguments", () => {
446
+ const result = convertMessagesToVercelAISDKMessages(
447
+ makeAssistantWithToolArgs('{"key":"value"}'),
448
+ );
449
+ const toolCall = (result[0] as any).content[1];
450
+ expect(toolCall.input).toEqual({ key: "value" });
451
+ });
452
+
453
+ it("should replace a string argument with an empty object", () => {
454
+ const result = convertMessagesToVercelAISDKMessages(
455
+ makeAssistantWithToolArgs('""'),
456
+ );
457
+ const toolCall = (result[0] as any).content[1];
458
+ expect(toolCall.input).toEqual({});
459
+ });
460
+
461
+ it("should replace an array argument with an empty object", () => {
462
+ const result = convertMessagesToVercelAISDKMessages(
463
+ makeAssistantWithToolArgs("[1,2,3]"),
464
+ );
465
+ const toolCall = (result[0] as any).content[1];
466
+ expect(toolCall.input).toEqual({});
467
+ });
468
+
469
+ it("should replace a null argument with an empty object", () => {
470
+ const result = convertMessagesToVercelAISDKMessages(
471
+ makeAssistantWithToolArgs("null"),
472
+ );
473
+ const toolCall = (result[0] as any).content[1];
474
+ expect(toolCall.input).toEqual({});
475
+ });
476
+
477
+ it("should replace a numeric argument with an empty object", () => {
478
+ const result = convertMessagesToVercelAISDKMessages(
479
+ makeAssistantWithToolArgs("42"),
480
+ );
481
+ const toolCall = (result[0] as any).content[1];
482
+ expect(toolCall.input).toEqual({});
483
+ });
484
+
485
+ it("should replace a boolean argument with an empty object", () => {
486
+ const result = convertMessagesToVercelAISDKMessages(
487
+ makeAssistantWithToolArgs("true"),
488
+ );
489
+ const toolCall = (result[0] as any).content[1];
490
+ expect(toolCall.input).toEqual({});
491
+ });
492
+
493
+ it("should replace unparseable JSON with an empty object", () => {
494
+ const result = convertMessagesToVercelAISDKMessages(
495
+ makeAssistantWithToolArgs("{broken"),
496
+ );
497
+ const toolCall = (result[0] as any).content[1];
498
+ expect(toolCall.input).toEqual({});
499
+ });
500
+ });
501
+
424
502
  describe("defineTool", () => {
425
503
  it("should create a ToolDefinition", () => {
426
504
  const tool = defineTool({
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Regression tests proving that the agent package's defineTool and
3
+ * convertToolDefinitionsToVercelAITools still work identically with
4
+ * Zod schemas after the Standard Schema migration.
5
+ *
6
+ * Covers:
7
+ * 1. defineTool with complex Zod schemas
8
+ * 2. convertToolDefinitionsToVercelAITools passes Zod schemas directly
9
+ * (not through JSON Schema conversion — critical behavioral regression)
10
+ * 3. BuiltInAgent with Zod tools still works end-to-end
11
+ * 4. execute callback receives correctly shaped args
12
+ */
13
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
14
+ import { z } from "zod";
15
+ import {
16
+ defineTool,
17
+ convertToolDefinitionsToVercelAITools,
18
+ BuiltInAgent,
19
+ } from "../index";
20
+ import type { RunAgentInput } from "@ag-ui/client";
21
+ import { streamText } from "ai";
22
+ import { mockStreamTextResponse, finish, collectEvents } from "./test-helpers";
23
+
24
+ vi.mock("ai", async () => {
25
+ const actual = await vi.importActual("ai");
26
+ return {
27
+ ...actual,
28
+ streamText: vi.fn(),
29
+ tool: vi.fn((config) => config),
30
+ stepCountIs: vi.fn((count: number) => ({ type: "stepCount", count })),
31
+ };
32
+ });
33
+
34
+ vi.mock("@ai-sdk/openai", () => ({
35
+ createOpenAI: vi.fn(() => (modelId: string) => ({
36
+ modelId,
37
+ provider: "openai",
38
+ })),
39
+ }));
40
+
41
+ describe("defineTool Zod regression", () => {
42
+ it("complex Zod schema with nested objects, arrays, unions", () => {
43
+ const tool = defineTool({
44
+ name: "complexTool",
45
+ description: "A complex tool",
46
+ parameters: z.object({
47
+ query: z.string().describe("Search query"),
48
+ filters: z
49
+ .object({
50
+ category: z.enum(["books", "movies", "music"]).optional(),
51
+ minRating: z.number().min(0).max(5).optional(),
52
+ tags: z.array(z.string()).optional(),
53
+ })
54
+ .optional(),
55
+ pagination: z
56
+ .object({
57
+ page: z.number().int().positive().default(1),
58
+ perPage: z.number().int().positive().default(20),
59
+ })
60
+ .optional(),
61
+ }),
62
+ execute: async (args) => {
63
+ return { query: args.query };
64
+ },
65
+ });
66
+
67
+ expect(tool.name).toBe("complexTool");
68
+ expect(tool.parameters["~standard"].vendor).toBe("zod");
69
+ expect(tool.parameters["~standard"].version).toBe(1);
70
+ });
71
+
72
+ it("Zod schema with discriminated union", () => {
73
+ const tool = defineTool({
74
+ name: "actionTool",
75
+ description: "An action tool",
76
+ parameters: z.object({
77
+ action: z.discriminatedUnion("type", [
78
+ z.object({ type: z.literal("search"), query: z.string() }),
79
+ z.object({ type: z.literal("navigate"), url: z.string().url() }),
80
+ z.object({
81
+ type: z.literal("execute"),
82
+ code: z.string(),
83
+ language: z.enum(["javascript", "python"]),
84
+ }),
85
+ ]),
86
+ }),
87
+ execute: async (args) => {
88
+ return { action: args.action.type };
89
+ },
90
+ });
91
+
92
+ expect(tool.name).toBe("actionTool");
93
+ expect(tool.parameters["~standard"].vendor).toBe("zod");
94
+ });
95
+
96
+ it("Zod schema with nullable and record fields", () => {
97
+ const tool = defineTool({
98
+ name: "flexTool",
99
+ description: "Flexible tool",
100
+ parameters: z.object({
101
+ title: z.string(),
102
+ description: z.string().nullable(),
103
+ metadata: z.record(z.string(), z.unknown()).optional(),
104
+ }),
105
+ execute: async (args) => {
106
+ return { title: args.title };
107
+ },
108
+ });
109
+
110
+ expect(tool.name).toBe("flexTool");
111
+ expect(tool.parameters["~standard"].vendor).toBe("zod");
112
+ });
113
+
114
+ it("execute callback receives correctly typed args", async () => {
115
+ const receivedArgs: unknown[] = [];
116
+
117
+ const tool = defineTool({
118
+ name: "argCapture",
119
+ description: "Captures args",
120
+ parameters: z.object({
121
+ city: z.string(),
122
+ units: z.enum(["celsius", "fahrenheit"]),
123
+ }),
124
+ execute: async (args) => {
125
+ receivedArgs.push(args);
126
+ return { temp: 22 };
127
+ },
128
+ });
129
+
130
+ await tool.execute({ city: "Berlin", units: "celsius" });
131
+
132
+ expect(receivedArgs).toHaveLength(1);
133
+ expect(receivedArgs[0]).toEqual({ city: "Berlin", units: "celsius" });
134
+ });
135
+ });
136
+
137
+ describe("convertToolDefinitionsToVercelAITools Zod regression", () => {
138
+ it("Zod schemas are passed directly to AI SDK (not converted via JSON Schema)", () => {
139
+ const zodSchema = z.object({
140
+ city: z.string(),
141
+ units: z.enum(["celsius", "fahrenheit"]).optional(),
142
+ });
143
+
144
+ const tools = [
145
+ defineTool({
146
+ name: "weather",
147
+ description: "Get weather",
148
+ parameters: zodSchema,
149
+ execute: async () => ({ temp: 22 }),
150
+ }),
151
+ ];
152
+
153
+ const aiTools = convertToolDefinitionsToVercelAITools(tools);
154
+
155
+ expect(aiTools).toHaveProperty("weather");
156
+ // The inputSchema should be the original Zod schema (passed directly),
157
+ // NOT a jsonSchema() wrapper. We verify by checking the Zod-specific
158
+ // ~standard.vendor property is preserved on the schema.
159
+ const inputSchema = aiTools.weather.inputSchema;
160
+ expect(inputSchema).toBeDefined();
161
+ // For Zod schemas, the AI SDK receives the raw Zod object (which has ~standard.vendor === "zod")
162
+ // If it went through jsonSchema(), it would lose the ~standard property
163
+ expect(inputSchema["~standard"]?.vendor).toBe("zod");
164
+ });
165
+
166
+ it("multiple Zod tools all get direct pass-through", () => {
167
+ const tools = [
168
+ defineTool({
169
+ name: "tool1",
170
+ description: "Tool 1",
171
+ parameters: z.object({ a: z.string() }),
172
+ execute: async () => ({}),
173
+ }),
174
+ defineTool({
175
+ name: "tool2",
176
+ description: "Tool 2",
177
+ parameters: z.object({ b: z.number(), c: z.boolean().optional() }),
178
+ execute: async () => ({}),
179
+ }),
180
+ defineTool({
181
+ name: "tool3",
182
+ description: "Tool 3",
183
+ parameters: z.object({
184
+ nested: z.object({ x: z.number(), y: z.number() }),
185
+ }),
186
+ execute: async () => ({}),
187
+ }),
188
+ ];
189
+
190
+ const aiTools = convertToolDefinitionsToVercelAITools(tools);
191
+
192
+ for (const name of ["tool1", "tool2", "tool3"]) {
193
+ expect(aiTools).toHaveProperty(name);
194
+ expect(aiTools[name].inputSchema["~standard"]?.vendor).toBe("zod");
195
+ expect(typeof aiTools[name].execute).toBe("function");
196
+ }
197
+ });
198
+
199
+ it("preserves tool description and execute function", () => {
200
+ const executeFn = vi.fn().mockResolvedValue({ result: "ok" });
201
+
202
+ const tools = [
203
+ defineTool({
204
+ name: "myTool",
205
+ description: "My custom tool description",
206
+ parameters: z.object({ input: z.string() }),
207
+ execute: executeFn,
208
+ }),
209
+ ];
210
+
211
+ const aiTools = convertToolDefinitionsToVercelAITools(tools);
212
+
213
+ expect(aiTools.myTool.description).toBe("My custom tool description");
214
+ expect(aiTools.myTool.execute).toBeDefined();
215
+ });
216
+ });
217
+
218
+ describe("BuiltInAgent Zod regression", () => {
219
+ const originalEnv = process.env;
220
+
221
+ beforeEach(() => {
222
+ vi.clearAllMocks();
223
+ process.env = { ...originalEnv };
224
+ process.env.OPENAI_API_KEY = "test-key";
225
+ });
226
+
227
+ afterEach(() => {
228
+ process.env = originalEnv;
229
+ });
230
+
231
+ it("Zod tools are included in streamText call", async () => {
232
+ const agent = new BuiltInAgent({
233
+ model: "openai/gpt-4o",
234
+ tools: [
235
+ defineTool({
236
+ name: "weather",
237
+ description: "Get weather",
238
+ parameters: z.object({
239
+ city: z.string(),
240
+ units: z.enum(["celsius", "fahrenheit"]).optional(),
241
+ }),
242
+ execute: async () => ({ temp: 22 }),
243
+ }),
244
+ ],
245
+ });
246
+
247
+ vi.mocked(streamText).mockReturnValue(
248
+ mockStreamTextResponse([finish()]) as any,
249
+ );
250
+
251
+ const input: RunAgentInput = {
252
+ threadId: "thread1",
253
+ runId: "run1",
254
+ messages: [],
255
+ tools: [],
256
+ context: [],
257
+ state: {},
258
+ };
259
+
260
+ await collectEvents(agent["run"](input));
261
+
262
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
263
+ expect(callArgs.tools).toHaveProperty("weather");
264
+ expect(typeof callArgs.tools?.weather.execute).toBe("function");
265
+ });
266
+
267
+ it("multiple Zod tools coexist with built-in state tools", async () => {
268
+ const agent = new BuiltInAgent({
269
+ model: "openai/gpt-4o",
270
+ tools: [
271
+ defineTool({
272
+ name: "search",
273
+ description: "Search",
274
+ parameters: z.object({ query: z.string() }),
275
+ execute: async () => ({}),
276
+ }),
277
+ defineTool({
278
+ name: "calculate",
279
+ description: "Calculate",
280
+ parameters: z.object({
281
+ expression: z.string(),
282
+ precision: z.number().int().optional(),
283
+ }),
284
+ execute: async () => ({}),
285
+ }),
286
+ ],
287
+ });
288
+
289
+ vi.mocked(streamText).mockReturnValue(
290
+ mockStreamTextResponse([finish()]) as any,
291
+ );
292
+
293
+ const input: RunAgentInput = {
294
+ threadId: "thread1",
295
+ runId: "run1",
296
+ messages: [],
297
+ tools: [],
298
+ context: [],
299
+ state: {},
300
+ };
301
+
302
+ await collectEvents(agent["run"](input));
303
+
304
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
305
+ // User-defined Zod tools
306
+ expect(callArgs.tools).toHaveProperty("search");
307
+ expect(callArgs.tools).toHaveProperty("calculate");
308
+ // Built-in state tools
309
+ expect(callArgs.tools).toHaveProperty("AGUISendStateSnapshot");
310
+ expect(callArgs.tools).toHaveProperty("AGUISendStateDelta");
311
+ });
312
+
313
+ it("Zod tool execute is invocable after conversion", async () => {
314
+ const executeFn = vi.fn().mockResolvedValue({ temp: 22 });
315
+
316
+ const agent = new BuiltInAgent({
317
+ model: "openai/gpt-4o",
318
+ tools: [
319
+ defineTool({
320
+ name: "weather",
321
+ description: "Get weather",
322
+ parameters: z.object({ city: z.string() }),
323
+ execute: executeFn,
324
+ }),
325
+ ],
326
+ });
327
+
328
+ vi.mocked(streamText).mockReturnValue(
329
+ mockStreamTextResponse([finish()]) as any,
330
+ );
331
+
332
+ const input: RunAgentInput = {
333
+ threadId: "thread1",
334
+ runId: "run1",
335
+ messages: [],
336
+ tools: [],
337
+ context: [],
338
+ state: {},
339
+ };
340
+
341
+ await collectEvents(agent["run"](input));
342
+
343
+ const callArgs = vi.mocked(streamText).mock.calls[0][0];
344
+ const weatherTool = callArgs.tools?.weather;
345
+
346
+ // Invoke the execute function to verify it's wired correctly
347
+ await weatherTool.execute({ city: "Berlin" });
348
+ expect(executeFn).toHaveBeenCalledWith({ city: "Berlin" });
349
+ });
350
+ });
package/src/index.ts CHANGED
@@ -42,7 +42,14 @@ import { createOpenAI } from "@ai-sdk/openai";
42
42
  import { createAnthropic } from "@ai-sdk/anthropic";
43
43
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
44
44
  import { randomUUID } from "crypto";
45
+ import { safeParseToolArgs } from "@copilotkitnext/shared";
45
46
  import { z } from "zod";
47
+ import type {
48
+ StandardSchemaV1,
49
+ InferSchemaOutput,
50
+ } from "@copilotkitnext/shared";
51
+ import { schemaToJsonSchema } from "@copilotkitnext/shared";
52
+ import { jsonSchema as aiJsonSchema } from "ai";
46
53
  import {
47
54
  StreamableHTTPClientTransport,
48
55
  StreamableHTTPClientTransportOptions,
@@ -224,27 +231,27 @@ export function resolveModel(
224
231
  * Tool definition for BuiltInAgent
225
232
  */
226
233
  export interface ToolDefinition<
227
- TParameters extends z.ZodTypeAny = z.ZodTypeAny,
234
+ TParameters extends StandardSchemaV1 = StandardSchemaV1,
228
235
  > {
229
236
  name: string;
230
237
  description: string;
231
238
  parameters: TParameters;
232
- execute: (args: z.infer<TParameters>) => Promise<unknown>;
239
+ execute: (args: InferSchemaOutput<TParameters>) => Promise<unknown>;
233
240
  }
234
241
 
235
242
  /**
236
243
  * Define a tool for use with BuiltInAgent
237
244
  * @param name - The name of the tool
238
245
  * @param description - Description of what the tool does
239
- * @param parameters - Zod schema for the tool's input parameters
246
+ * @param parameters - Schema for the tool's input parameters (any Standard Schema V1 compatible library: Zod, Valibot, ArkType, etc.)
240
247
  * @param execute - Function to execute the tool server-side
241
248
  * @returns Tool definition
242
249
  */
243
- export function defineTool<TParameters extends z.ZodTypeAny>(config: {
250
+ export function defineTool<TParameters extends StandardSchemaV1>(config: {
244
251
  name: string;
245
252
  description: string;
246
253
  parameters: TParameters;
247
- execute: (args: z.infer<TParameters>) => Promise<unknown>;
254
+ execute: (args: InferSchemaOutput<TParameters>) => Promise<unknown>;
248
255
  }): ToolDefinition<TParameters> {
249
256
  return {
250
257
  name: config.name,
@@ -327,7 +334,7 @@ export function convertMessagesToVercelAISDKMessages(
327
334
  type: "tool-call",
328
335
  toolCallId: toolCall.id,
329
336
  toolName: toolCall.function.name,
330
- input: JSON.parse(toolCall.function.arguments),
337
+ input: safeParseToolArgs(toolCall.function.arguments),
331
338
  };
332
339
  parts.push(toolCallPart);
333
340
  }
@@ -480,7 +487,18 @@ export function convertToolsToVercelAITools(
480
487
  }
481
488
 
482
489
  /**
483
- * Converts ToolDefinition array to Vercel AI SDK ToolSet
490
+ * Check whether a schema is a Zod schema by inspecting its Standard Schema vendor.
491
+ */
492
+ function isZodSchema(schema: StandardSchemaV1): boolean {
493
+ return schema["~standard"]?.vendor === "zod";
494
+ }
495
+
496
+ /**
497
+ * Converts ToolDefinition array to Vercel AI SDK ToolSet.
498
+ *
499
+ * For Zod schemas, passes them directly to the AI SDK (Zod satisfies FlexibleSchema).
500
+ * For non-Zod schemas, converts to JSON Schema via schemaToJsonSchema() and wraps
501
+ * with the AI SDK's jsonSchema() helper.
484
502
  */
485
503
  export function convertToolDefinitionsToVercelAITools(
486
504
  tools: ToolDefinition[],
@@ -489,11 +507,22 @@ export function convertToolDefinitionsToVercelAITools(
489
507
  const result: Record<string, any> = {};
490
508
 
491
509
  for (const tool of tools) {
492
- result[tool.name] = createVercelAISDKTool({
493
- description: tool.description,
494
- inputSchema: tool.parameters,
495
- execute: tool.execute,
496
- });
510
+ if (isZodSchema(tool.parameters)) {
511
+ // Zod schemas can be passed directly to AI SDK (satisfies FlexibleSchema)
512
+ result[tool.name] = createVercelAISDKTool({
513
+ description: tool.description,
514
+ inputSchema: tool.parameters as any,
515
+ execute: tool.execute,
516
+ });
517
+ } else {
518
+ // Non-Zod: convert to JSON Schema and wrap with AI SDK's jsonSchema()
519
+ const jsonSchemaObj = schemaToJsonSchema(tool.parameters);
520
+ result[tool.name] = createVercelAISDKTool({
521
+ description: tool.description,
522
+ inputSchema: aiJsonSchema(jsonSchemaObj),
523
+ execute: tool.execute,
524
+ });
525
+ }
497
526
  }
498
527
 
499
528
  return result;