Skip to content

Commit f1dc8f1

Browse files
fix(deps): update dependency zod to v4 (#67)
* fix(deps): update dependency zod to v4 * fix zod error message and type change * lint * fix zod issues * lint * fix unit test * lint * test fix --------- Co-authored-by: Twisha Bansal <twishabansal@google.com>
1 parent fb3d4aa commit f1dc8f1

File tree

8 files changed

+78
-62
lines changed

8 files changed

+78
-62
lines changed

packages/toolbox-core/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/toolbox-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@
6666
"dependencies": {
6767
"axios": "^1.9.0",
6868
"google-auth-library": "^10.0.0",
69-
"zod": "^3.24.4"
69+
"zod": "^4.0.0"
7070
}
7171
}

packages/toolbox-core/src/toolbox_core/protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function buildZodShapeFromParam(param: ParameterSchema): ZodTypeAny {
146146
export function createZodSchemaFromParams(
147147
params: ParameterSchema[]
148148
): ZodObject<ZodRawShape> {
149-
const shape: ZodRawShape = {};
149+
const shape: {[k: string]: ZodTypeAny} = {};
150150
for (const param of params) {
151151
shape[param.name] = buildZodShapeFromParam(param);
152152
}

packages/toolbox-core/src/toolbox_core/tool.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,15 @@ function ToolboxTool(
8787
}
8888

8989
const toolUrl = `${baseUrl}/api/tool/${name}/invoke`;
90+
91+
// Only omit keys that actually exist in the provided schema.
92+
// This handles cases where `paramSchema` is already partial.
93+
// Could be partial due to bound params being used while loading tools.
9094
const boundKeys = Object.keys(boundParams);
95+
const existingSchemaKeys = Object.keys(paramSchema.shape);
96+
const keysToOmit = boundKeys.filter(key => existingSchemaKeys.includes(key));
9197
const userParamSchema = paramSchema.omit(
92-
Object.fromEntries(boundKeys.map(k => [k, true]))
98+
Object.fromEntries(keysToOmit.map(k => [k, true]))
9399
);
94100

95101
const callable = async function (
@@ -116,7 +122,7 @@ function ToolboxTool(
116122
validatedUserArgs = userParamSchema.parse(callArguments);
117123
} catch (error) {
118124
if (error instanceof ZodError) {
119-
const errorMessages = error.errors.map(
125+
const errorMessages = error.issues.map(
120126
e => `${e.path.join('.') || 'payload'}: ${e.message}`
121127
);
122128
throw new Error(

packages/toolbox-core/test/e2e/test.e2e.ts

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {ToolboxTool} from '../../src/toolbox_core/tool';
1818
import {AxiosError} from 'axios';
1919
import {CustomGlobal} from './types';
2020
import {authTokenGetter} from './utils';
21-
import {ZodOptional, ZodNullable, ZodTypeAny} from 'zod';
2221

2322
describe('ToolboxClient E2E Tests', () => {
2423
let commonToolboxClient: ToolboxClient;
@@ -46,13 +45,13 @@ describe('ToolboxClient E2E Tests', () => {
4645

4746
it('should invoke the getNRowsTool with missing params', async () => {
4847
await expect(getNRowsTool()).rejects.toThrow(
49-
/Argument validation failed for tool "get-n-rows":\s*- num_rows: Required/
48+
/num_rows: Invalid input: expected string, received undefined/
5049
);
5150
});
5251

5352
it('should invoke the getNRowsTool with wrong param type', async () => {
5453
await expect(getNRowsTool({num_rows: 2})).rejects.toThrow(
55-
/Argument validation failed for tool "get-n-rows":\s*- num_rows: Expected string, received number/
54+
/num_rows: Invalid input: expected string, received number/
5655
);
5756
});
5857
});
@@ -295,7 +294,7 @@ describe('ToolboxClient E2E Tests', () => {
295294
});
296295

297296
it('should fail when a tool with a param requiring auth is run with insufficient auth claims', async () => {
298-
expect.assertions(3); // Adjusted to account for logApiError's console.error
297+
expect.assertions(3); // An AxiosError is expected.
299298

300299
const tool = await commonToolboxClient.loadTool(
301300
'get-row-by-content-auth',
@@ -327,28 +326,36 @@ describe('ToolboxClient E2E Tests', () => {
327326

328327
it('should correctly identify required and optional parameters in the schema', () => {
329328
const paramSchema = searchRowsTool.getParamSchema();
330-
const {shape} = paramSchema;
331329

332-
// Required param 'email'
333-
expect(shape.email.isOptional()).toBe(false);
334-
expect(shape.email.isNullable()).toBe(false);
335-
expect(shape.email._def.typeName).toBe('ZodString');
330+
// Test the behavior of the required 'email' parameter
331+
expect(paramSchema.safeParse({email: 'test@example.com'}).success).toBe(
332+
true
333+
);
334+
expect(paramSchema.safeParse({}).success).toBe(false); // Fails when missing
335+
expect(paramSchema.safeParse({email: null}).success).toBe(false); // Fails when null
336336

337-
// Optional param 'data'
338-
expect(shape.data.isOptional()).toBe(true);
339-
expect(shape.data.isNullable()).toBe(true);
337+
// Test the behavior of the optional 'data' parameter
338+
expect(
339+
paramSchema.safeParse({email: 'test@example.com', data: 'some data'})
340+
.success
341+
).toBe(true);
340342
expect(
341-
(shape.data as ZodOptional<ZodNullable<ZodTypeAny>>).unwrap().unwrap()
342-
._def.typeName
343-
).toBe('ZodString');
343+
paramSchema.safeParse({email: 'test@example.com', data: null}).success
344+
).toBe(true); // Should succeed with null
345+
expect(paramSchema.safeParse({email: 'test@example.com'}).success).toBe(
346+
true
347+
); // Should succeed when omitted
344348

345-
// Optional param 'id'
346-
expect(shape.id.isOptional()).toBe(true);
347-
expect(shape.id.isNullable()).toBe(true);
349+
// Test the behavior of the optional 'id' parameter
350+
expect(
351+
paramSchema.safeParse({email: 'test@example.com', id: 123}).success
352+
).toBe(true);
348353
expect(
349-
(shape.id as ZodOptional<ZodNullable<ZodTypeAny>>).unwrap().unwrap()
350-
._def.typeName
351-
).toBe('ZodNumber');
354+
paramSchema.safeParse({email: 'test@example.com', id: null}).success
355+
).toBe(true); // Should succeed with null
356+
expect(paramSchema.safeParse({email: 'test@example.com'}).success).toBe(
357+
true
358+
); // Should succeed when omitted
352359
});
353360

354361
it('should run tool with optional params omitted', async () => {
@@ -419,16 +426,14 @@ describe('ToolboxClient E2E Tests', () => {
419426

420427
it('should fail when a required param is missing', async () => {
421428
await expect(searchRowsTool({id: 5, data: 'row5'})).rejects.toThrow(
422-
/Argument validation failed for tool "search-rows":\s*- email: Required/
429+
/email: Invalid input: expected string, received undefined/
423430
);
424431
});
425432

426433
it('should fail when a required param is null', async () => {
427434
await expect(
428435
searchRowsTool({email: null, id: 5, data: 'row5'})
429-
).rejects.toThrow(
430-
/Argument validation failed for tool "search-rows":\s*- email: Expected string, received null/
431-
);
436+
).rejects.toThrow(/email: Invalid input: expected string, received null/);
432437
});
433438

434439
it('should run tool with all default params', async () => {

packages/toolbox-core/test/test.client.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ type CallableToolReturnedByFactory = ReturnType<OriginalToolboxToolType>;
3232

3333
type InferredZodTool = z.infer<typeof ZodToolSchema>;
3434

35-
const createMockZodObject = (
36-
shape: ZodRawShape = {}
37-
): ZodObject<ZodRawShape, 'strip', ZodTypeAny> =>
35+
const createMockZodObject = (shape: ZodRawShape = {}): ZodObject<ZodRawShape> =>
3836
({
3937
parse: jest.fn(args => args),
4038
_def: {
@@ -45,7 +43,7 @@ const createMockZodObject = (
4543
pick: jest.fn().mockReturnThis(),
4644
omit: jest.fn().mockReturnThis(),
4745
extend: jest.fn().mockReturnThis(),
48-
}) as unknown as ZodObject<ZodRawShape, 'strip', ZodTypeAny>;
46+
}) as unknown as ZodObject<ZodRawShape>;
4947

5048
// --- Mocking External Dependencies ---
5149
jest.mock('axios');
@@ -146,29 +144,29 @@ describe('ToolboxClient', () => {
146144
},
147145
overrides: {
148146
manifestData?: Partial<ZodManifest>;
149-
zodParamsSchema?: ZodObject<ZodRawShape, 'strip', ZodTypeAny>;
147+
zodParamsSchema?: ZodObject<ZodRawShape>;
150148
toolInstance?: Partial<CallableToolReturnedByFactory>;
151149
} = {}
152150
) => {
153151
const manifestData: ZodManifest = {
154152
serverVersion: '1.0.0',
155-
tools: {[toolName]: toolDefinition as unknown as InferredZodTool}, // Cast here if ZodManifest expects InferredZodTool
153+
tools: {[toolName]: toolDefinition as unknown as InferredZodTool},
156154
...overrides.manifestData,
157155
} as ZodManifest; // Outer cast to ZodManifest
158156

159157
const zodParamsSchema =
160158
overrides.zodParamsSchema ||
161159
createMockZodObject(
162160
(toolDefinition.parameters as unknown as ParameterSchema[]).reduce(
163-
(shapeAccumulator: ZodRawShape, param) => {
161+
(shapeAccumulator: {[k: string]: ZodTypeAny}, param) => {
164162
if (!param.authSources) {
165163
shapeAccumulator[param.name] = {
166164
_def: {typeName: 'ZodString'},
167165
} as unknown as ZodTypeAny;
168166
}
169167
return shapeAccumulator;
170168
},
171-
{} as ZodRawShape
169+
{}
172170
)
173171
);
174172

@@ -296,11 +294,11 @@ describe('ToolboxClient', () => {
296294
const mockApiResponseData = {invalid: 'manifest structure'};
297295
const mockZodError = new ZodError([
298296
{
297+
input: undefined,
299298
path: ['tools'],
300299
message: 'Required',
301300
code: 'invalid_type',
302301
expected: 'object',
303-
received: 'undefined',
304302
},
305303
]);
306304

@@ -480,18 +478,15 @@ describe('ToolboxClient', () => {
480478
tools: toolDefinitions,
481479
};
482480

483-
const zodParamsSchemas: Record<
484-
string,
485-
ZodObject<ZodRawShape, 'strip', ZodTypeAny>
486-
> = {};
481+
const zodParamsSchemas: Record<string, ZodObject<ZodRawShape>> = {};
487482
const toolInstances: Record<string, CallableToolReturnedByFactory> = {};
488483
const orderedToolNames = Object.keys(toolDefinitions);
489484

490485
orderedToolNames.forEach(tName => {
491486
const tDef = toolDefinitions[tName];
492487
zodParamsSchemas[tName] = createMockZodObject(
493488
(tDef.parameters as ParameterSchema[]).reduce(
494-
(acc: ZodRawShape, p) => {
489+
(acc: {[k: string]: ZodTypeAny}, p) => {
495490
acc[p.name] = {
496491
_def: {typeName: 'ZodString'},
497492
} as unknown as ZodTypeAny;
@@ -750,6 +745,7 @@ describe('ToolboxClient', () => {
750745
const mockApiResponseData = {invalid: 'toolset structure'};
751746
const mockZodError = new ZodError([
752747
{
748+
input: undefined,
753749
path: ['serverVersion'],
754750
message: 'Zod validation failed on toolset',
755751
code: 'custom',

packages/toolbox-core/test/test.protocol.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
// HELPER FUNCTIONS
2525

2626
const getErrorMessages = (error: ZodError): string[] => {
27-
return error.errors.map(e => {
27+
return error.issues.map(e => {
2828
if (e.path.length > 0) {
2929
return `${e.path.join('.')}: ${e.message}`;
3030
}
@@ -175,7 +175,11 @@ describe('ZodParameterSchema', () => {
175175
const data = {name: 'testArray', description: 'An array', type: 'array'};
176176
expectParseFailure(ZodParameterSchema, data, errors => {
177177
expect(errors).toEqual(
178-
expect.arrayContaining([expect.stringMatching(/items: Required/i)])
178+
expect.arrayContaining([
179+
expect.stringMatching(
180+
'items: Invalid input: expected object, received undefined'
181+
),
182+
])
179183
);
180184
});
181185
});
@@ -196,9 +200,7 @@ describe('ZodParameterSchema', () => {
196200
const data = {name: 'testParam', description: 'A param'}; // type is missing
197201
expectParseFailure(ZodParameterSchema, data, errors => {
198202
expect(errors).toEqual(
199-
expect.arrayContaining([
200-
expect.stringMatching(/Invalid discriminator value/i),
201-
])
203+
expect.arrayContaining([expect.stringMatching('type: Invalid input')])
202204
);
203205
});
204206
});
@@ -289,7 +291,7 @@ describe('ZodManifestSchema', () => {
289291
expectParseFailure(ZodManifestSchema, data, errors => {
290292
expect(errors).toEqual(
291293
expect.arrayContaining([
292-
expect.stringMatching(/Tool name cannot be empty/i),
294+
expect.stringMatching('tools.: Invalid key in record'),
293295
])
294296
);
295297
});
@@ -315,9 +317,7 @@ describe('createZodObjectSchemaFromParameters', () => {
315317

316318
expectParseSuccess(schema, {});
317319
expectParseFailure(schema, {anyKey: 'anyValue'}, errors => {
318-
expect(
319-
errors.some(e => /Unrecognized key\(s\) in object: 'anyKey'/.test(e))
320-
).toBe(true);
320+
expect(errors.some(e => /unrecognized key/i.test(e))).toBe(true);
321321
});
322322
});
323323

@@ -339,10 +339,14 @@ describe('createZodObjectSchemaFromParameters', () => {
339339
schema,
340340
{username: 'john_doe', age: '30', isActive: true},
341341
errors =>
342-
expect(errors).toContain('age: Expected number, received string')
342+
expect(errors).toContain(
343+
'age: Invalid input: expected number, received string'
344+
)
343345
);
344346
expectParseFailure(schema, {username: 'john_doe', isActive: true}, errors =>
345-
expect(errors).toContain('age: Required')
347+
expect(errors).toContain(
348+
'age: Invalid input: expected number, received undefined'
349+
)
346350
);
347351
});
348352

@@ -365,7 +369,9 @@ describe('createZodObjectSchemaFromParameters', () => {
365369
expectParseSuccess(schema, {tags: ['news', 'tech'], id: 1});
366370

367371
expectParseFailure(schema, {tags: ['news', 123], id: 1}, errors => {
368-
expect(errors).toContain('tags.1: Expected string, received number');
372+
expect(errors).toContain(
373+
'tags.1: Invalid input: expected string, received number'
374+
);
369375
});
370376
});
371377

@@ -406,7 +412,7 @@ describe('createZodObjectSchemaFromParameters', () => {
406412
},
407413
errors => {
408414
expect(errors).toContain(
409-
'matrix.0.1: Expected number, received string'
415+
'matrix.0.1: Invalid input: expected number, received string'
410416
);
411417
}
412418
);
@@ -439,7 +445,9 @@ describe('createZodObjectSchemaFromParameters', () => {
439445

440446
it('should fail if a required parameter is missing', () => {
441447
expectParseFailure(schema, {optionalParam: 'value'}, errors => {
442-
expect(errors).toContain('requiredParam: Required');
448+
expect(errors).toContain(
449+
'requiredParam: Invalid input: expected string, received undefined'
450+
);
443451
});
444452
});
445453

packages/toolbox-core/test/test.tool.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ describe('ToolboxTool', () => {
222222
it('should throw a generic error if paramSchema.parse throws a non-ZodError', async () => {
223223
const customError = new Error('A non-Zod parsing error occurred!');
224224
const failingSchema = {
225+
shape: {},
225226
parse: jest.fn().mockImplementation(() => {
226227
throw customError;
227228
}),
@@ -275,7 +276,7 @@ describe('ToolboxTool', () => {
275276
basicParamSchema
276277
);
277278
await expect(currentTool()).rejects.toThrow(
278-
'Argument validation failed for tool "myTestTool":\n - query: Required'
279+
'Argument validation failed for tool "myTestTool":\n - query: Invalid input: expected string, received undefined'
279280
);
280281
expect(mockAxiosPost).not.toHaveBeenCalled();
281282
});

0 commit comments

Comments
 (0)