diff --git a/.changeset/zod-v4-json-schema-fallback.md b/.changeset/zod-v4-json-schema-fallback.md new file mode 100644 index 0000000000..2ac93a93d4 --- /dev/null +++ b/.changeset/zod-v4-json-schema-fallback.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/stagehand": patch +--- + +Use the Zod v4 subpath JSON schema converter when root zod does not expose `toJSONSchema`. diff --git a/packages/core/lib/v3/zodCompat.ts b/packages/core/lib/v3/zodCompat.ts index 2ff7ea0575..76aa688aa1 100644 --- a/packages/core/lib/v3/zodCompat.ts +++ b/packages/core/lib/v3/zodCompat.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { z as zodV4 } from "zod/v4"; import type { ZodObject as Zod4Object, ZodRawShape as Zod4RawShape, @@ -35,15 +36,18 @@ export function toJsonSchema(schema: StagehandZodSchema): JsonSchemaDocument { return zodToJsonSchema(schema); } - // For v4 schemas, use built-in z.toJSONSchema() method - const zodWithJsonSchema = z as typeof z & { - toJSONSchema?: (schema: Zod4TypeAny) => JsonSchemaDocument; - }; + const converters = [zodV4, z] as Array< + typeof z & { + toJSONSchema?: (schema: Zod4TypeAny) => JsonSchemaDocument; + } + >; - if (zodWithJsonSchema.toJSONSchema) { - return zodWithJsonSchema.toJSONSchema(schema as Zod4TypeAny); + for (const zodWithJsonSchema of converters) { + if (zodWithJsonSchema.toJSONSchema) { + return zodWithJsonSchema.toJSONSchema(schema as Zod4TypeAny); + } } - // This should never happen with Zod v4.1+ + // This should never happen with Zod v4.1+ or transitional Zod v3.25+ packages. throw new Error("Zod v4 toJSONSchema method not found"); } diff --git a/packages/core/tests/unit/zod-compat.test.ts b/packages/core/tests/unit/zod-compat.test.ts new file mode 100644 index 0000000000..0d00e6ebac --- /dev/null +++ b/packages/core/tests/unit/zod-compat.test.ts @@ -0,0 +1,28 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +describe("zodCompat", () => { + afterEach(() => { + vi.resetModules(); + vi.doUnmock("zod"); + vi.doUnmock("zod/v4"); + vi.doUnmock("zod-to-json-schema"); + }); + + it("uses zod/v4 JSON schema conversion when root zod lacks toJSONSchema", async () => { + const schema = { _zod: { def: { type: "object" } } }; + const jsonSchema = { + type: "object", + properties: { ok: { type: "boolean" } }, + }; + const toJSONSchema = vi.fn(() => jsonSchema); + + vi.doMock("zod", () => ({ z: {} })); + vi.doMock("zod/v4", () => ({ z: { toJSONSchema } })); + vi.doMock("zod-to-json-schema", () => ({ default: vi.fn() })); + + const { toJsonSchema } = await import("../../lib/v3/zodCompat.js"); + + expect(toJsonSchema(schema as never)).toBe(jsonSchema); + expect(toJSONSchema).toHaveBeenCalledWith(schema); + }); +});