From 8790728a05fe82536d5a3e6b8941b204e27dea25 Mon Sep 17 00:00:00 2001 From: viniciusventura29 Date: Mon, 2 Mar 2026 14:23:32 -0300 Subject: [PATCH 1/4] feat(tool-adapter): enhance numeric field handling in request schemas - Updated `coerceNumericField` to accept numeric strings for both integer and float fields, normalizing them to numbers. - Added tests to ensure that numeric strings are correctly parsed and coerced in the `flattenRequestSchema` function. - Improved type handling for path parameters, allowing for more flexible input in request validation. --- vtex/server/lib/tool-adapter.test.ts | 18 +++++++++++++++++- vtex/server/lib/tool-adapter.ts | 21 ++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/vtex/server/lib/tool-adapter.test.ts b/vtex/server/lib/tool-adapter.test.ts index df2d25a8..83dc7db5 100644 --- a/vtex/server/lib/tool-adapter.test.ts +++ b/vtex/server/lib/tool-adapter.test.ts @@ -16,7 +16,9 @@ describe("flattenRequestSchema", () => { const flat = flattenRequestSchema(schema as any); expect(Object.keys(flat.shape)).toEqual(["productId", "slug"]); // path params should remain as-is (not wrapped in optional) - expect(flat.shape.productId).toBeInstanceOf(z.ZodNumber); + const parsed = flat.parse({ productId: "42", slug: "shirt" }); + expect(parsed.productId).toBe(42); + expect(typeof parsed.productId).toBe("number"); expect(flat.shape.slug).toBeInstanceOf(z.ZodString); }); @@ -105,6 +107,20 @@ describe("flattenRequestSchema", () => { const flat = flattenRequestSchema(schema as any); expect(Object.keys(flat.shape)).toEqual([]); }); + + test("accepts numeric strings for int path params", () => { + const schema = z.object({ + path: z.object({ collectionId: z.int() }), + query: z.optional(z.never()), + body: z.optional(z.never()), + headers: z.object({ Accept: z.string() }), + }); + + const flat = flattenRequestSchema(schema as any); + const parsed = flat.parse({ collectionId: "2247" }); + expect(parsed.collectionId).toBe(2247); + expect(typeof parsed.collectionId).toBe("number"); + }); }); // ── unflattenToStructured ───────────────────────────────────────────────────── diff --git a/vtex/server/lib/tool-adapter.ts b/vtex/server/lib/tool-adapter.ts index 7cdba65c..33a6c9ce 100644 --- a/vtex/server/lib/tool-adapter.ts +++ b/vtex/server/lib/tool-adapter.ts @@ -19,12 +19,23 @@ function coerceNumericField(field: any): any { const inner = coerceNumericField(field.unwrap()); return z.optional(inner); } - // ZodInt is a subclass/variant of ZodNumber in Zod v4 - if (field instanceof (z as any).ZodInt) { - return z.coerce.number().int(); - } if (field instanceof z.ZodNumber) { - return z.coerce.number(); + const isIntLike = typeof field.isInt === "boolean" ? field.isInt : false; + if (isIntLike) { + // Accepts JSON numbers and numeric strings from tool UIs, then normalizes to int. + return z + .union([z.number().int(), z.string().regex(/^-?\d+$/)]) + .transform((value) => + typeof value === "string" ? Number.parseInt(value, 10) : value, + ); + } + + // Accepts numbers and numeric strings (e.g. "12.5"), normalizing to number. + return z + .union([z.number(), z.string().regex(/^-?\d+(\.\d+)?$/)]) + .transform((value) => + typeof value === "string" ? Number(value) : value, + ); } return field; } From 9e53d7c3e298517ef75166caacfd2dbb37b3f993 Mon Sep 17 00:00:00 2001 From: viniciusventura29 Date: Mon, 2 Mar 2026 14:30:52 -0300 Subject: [PATCH 2/4] fix(tsconfig): update types for bun compatibility - Changed the TypeScript types configuration from "bun-types" to "bun" for improved compatibility and clarity in the project setup. --- vtex/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vtex/tsconfig.json b/vtex/tsconfig.json index 73f2066f..5c22cb32 100644 --- a/vtex/tsconfig.json +++ b/vtex/tsconfig.json @@ -23,7 +23,7 @@ "shared/*": ["./shared/*"], "server/*": ["./server/*"] }, - "types": ["@cloudflare/workers-types", "bun-types"] + "types": ["@cloudflare/workers-types", "bun"] }, "include": ["server", "shared", "scripts"], "exclude": ["server/generated"] From 83c03d31a9afa7902a6c1b63bf228c179a0c446c Mon Sep 17 00:00:00 2001 From: viniciusventura29 Date: Mon, 2 Mar 2026 16:27:11 -0300 Subject: [PATCH 3/4] fix(tool-adapter): handle null and undefined data in tool creation - Updated the `createToolFromOperation` function to return a structured response when the result data is null, undefined, or not an object, ensuring consistent handling of various data types. --- vtex/server/lib/tool-adapter.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vtex/server/lib/tool-adapter.ts b/vtex/server/lib/tool-adapter.ts index 33a6c9ce..498862fb 100644 --- a/vtex/server/lib/tool-adapter.ts +++ b/vtex/server/lib/tool-adapter.ts @@ -307,9 +307,14 @@ export function createToolFromOperation(config: ToolFromOperationConfig) { : JSON.stringify(result.error), ); } - return Array.isArray(result.data) + const data = Array.isArray(result.data) ? { items: result.data } : result.data; + + if (data === null || data === undefined || typeof data !== "object") { + return { result: data }; + } + return data; }, }); } From 28fc4ec31ab54d02a024c6d9f78db655c181ef5b Mon Sep 17 00:00:00 2001 From: viniciusventura29 Date: Mon, 2 Mar 2026 16:46:05 -0300 Subject: [PATCH 4/4] fix(tool-adapter): improve error handling in createToolFromOperation - Enhanced the error handling in the `createToolFromOperation` function to provide clearer error messages. The error message is now derived from the error object directly, ensuring that both string and object errors are handled appropriately. --- vtex/server/lib/tool-adapter.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vtex/server/lib/tool-adapter.ts b/vtex/server/lib/tool-adapter.ts index 498862fb..305b88a5 100644 --- a/vtex/server/lib/tool-adapter.ts +++ b/vtex/server/lib/tool-adapter.ts @@ -301,11 +301,14 @@ export function createToolFromOperation(config: ToolFromOperationConfig) { config.sdkFn({ client, ...structured } as any), ); if (result.error) { - throw new Error( - typeof result.error === "string" - ? result.error - : JSON.stringify(result.error), - ); + const err = result.error; + const message = + err instanceof Error + ? err.message + : typeof err === "string" + ? err + : JSON.stringify(err); + throw new Error(message); } const data = Array.isArray(result.data) ? { items: result.data }