agent_patch
runs/2026-02-27__21-30-28__gpt-5-3-codex/flux-pr-4811/flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/agent-logs/agent.patch
11657 bytes
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/docs/content/json-schema.mdx b/app/packages/docs/content/json-schema.mdx
index 958aa6d..c6a2f09 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/docs/content/json-schema.mdx
+++ b/app/packages/docs/content/json-schema.mdx
@@ -214,8 +214,9 @@ Below is a quick reference for each supported parameter. Each one is explained i
interface ToJSONSchemaParams {
/** The JSON Schema version to target.
* - `"draft-2020-12"` — Default. JSON Schema Draft 2020-12
- * - `"draft-7"` — JSON Schema Draft 7 */
- target?: "draft-7" | "draft-2020-12";
+ * - `"draft-7"` — JSON Schema Draft 7
+ * - `"draft-4"` — JSON Schema Draft 4 */
+ target?: "draft-4" | "draft-7" | "draft-2020-12";
/** A registry used to look up metadata for each schema.
* Any schema with an `id` property will be extracted as a $def. */
@@ -250,6 +251,7 @@ To set the target JSON Schema version, use the `target` parameter. By default, Z
```ts
z.toJSONSchema(schema, { target: "draft-7" });
z.toJSONSchema(schema, { target: "draft-2020-12" });
+z.toJSONSchema(schema, { target: "draft-4" });
```
### `metadata`
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts b/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
index f6e18bb..33bb061 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
+++ b/app/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
@@ -539,6 +539,65 @@ describe("toJSONSchema", () => {
`);
});
+ test("draft-4 target", () => {
+ expect(z.toJSONSchema(z.string(), { target: "draft-4" })).toMatchInlineSnapshot(`
+ {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "string",
+ }
+ `);
+
+ expect(z.toJSONSchema(z.number().gt(5).lt(10), { target: "draft-4" })).toMatchInlineSnapshot(`
+ {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "exclusiveMaximum": true,
+ "exclusiveMinimum": true,
+ "maximum": 10,
+ "minimum": 5,
+ "type": "number",
+ }
+ `);
+
+ expect(z.toJSONSchema(z.record(z.string(), z.boolean()), { target: "draft-4" })).toMatchInlineSnapshot(`
+ {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "additionalProperties": {
+ "type": "boolean",
+ },
+ "type": "object",
+ }
+ `);
+
+ expect(z.toJSONSchema(z.literal("hello"), { target: "draft-4" })).toMatchInlineSnapshot(`
+ {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "enum": [
+ "hello",
+ ],
+ "type": "string",
+ }
+ `);
+
+ expect(
+ z.toJSONSchema(z.string().startsWith("a").endsWith("z"), { target: "draft-4" })
+ ).toMatchInlineSnapshot(`
+ {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "allOf": [
+ {
+ "pattern": "^a.*",
+ "type": "string",
+ },
+ {
+ "pattern": ".*z$",
+ "type": "string",
+ },
+ ],
+ "type": "string",
+ }
+ `);
+ });
+
test("arrays", () => {
expect(z.toJSONSchema(z.array(z.string()))).toMatchInlineSnapshot(`
{
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/json-schema.ts b/app/packages/zod/src/v4/core/json-schema.ts
index 4b3abfb..0fa7af5 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/json-schema.ts
+++ b/app/packages/zod/src/v4/core/json-schema.ts
@@ -45,7 +45,10 @@ export type Schema =
export type _JSONSchema = boolean | JSONSchema;
export type JSONSchema = {
[k: string]: unknown;
- $schema?: "https://json-schema.org/draft/2020-12/schema" | "http://json-schema.org/draft-07/schema#";
+ $schema?:
+ | "https://json-schema.org/draft/2020-12/schema"
+ | "http://json-schema.org/draft-07/schema#"
+ | "http://json-schema.org/draft-04/schema#";
$id?: string;
$anchor?: string;
$ref?: string;
@@ -75,9 +78,9 @@ export type JSONSchema = {
not?: _JSONSchema;
multipleOf?: number;
maximum?: number;
- exclusiveMaximum?: number;
+ exclusiveMaximum?: number | boolean;
minimum?: number;
- exclusiveMinimum?: number;
+ exclusiveMinimum?: number | boolean;
maxLength?: number;
minLength?: number;
pattern?: string;
diff --git a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/to-json-schema.ts b/app/packages/zod/src/v4/core/to-json-schema.ts
index 001f195..4c5153d 100644
--- a/tmp/agent-patch-flux-pr-4811.1-of-1.2026-02-27__21-30-28__gpt-5-3-codex/app/packages/zod/src/v4/core/to-json-schema.ts
+++ b/app/packages/zod/src/v4/core/to-json-schema.ts
@@ -10,8 +10,9 @@ interface JSONSchemaGeneratorParams {
metadata?: $ZodRegistry<Record<string, any>>;
/** The JSON Schema version to target.
* - `"draft-2020-12"` — Default. JSON Schema Draft 2020-12
- * - `"draft-7"` — JSON Schema Draft 7 */
- target?: "draft-7" | "draft-2020-12";
+ * - `"draft-7"` — JSON Schema Draft 7
+ * - `"draft-4"` — JSON Schema Draft 4 */
+ target?: "draft-4" | "draft-7" | "draft-2020-12";
/** How to handle unrepresentable types.
* - `"throw"` — Default. Unrepresentable types throw an error
* - `"any"` — Unrepresentable types become `{}` */
@@ -71,7 +72,7 @@ interface Seen {
export class JSONSchemaGenerator {
metadataRegistry: $ZodRegistry<Record<string, any>>;
- target: "draft-7" | "draft-2020-12";
+ target: "draft-4" | "draft-7" | "draft-2020-12";
unrepresentable: "throw" | "any";
override: (ctx: {
zodSchema: schemas.$ZodTypes;
@@ -163,7 +164,7 @@ export class JSONSchemaGenerator {
else if (regexes.length > 1) {
result.schema.allOf = [
...regexes.map((regex) => ({
- ...(this.target === "draft-7" ? ({ type: "string" } as const) : {}),
+ ...(this.target !== "draft-2020-12" ? ({ type: "string" } as const) : {}),
pattern: regex.source,
})),
];
@@ -178,21 +179,47 @@ export class JSONSchemaGenerator {
if (typeof format === "string" && format.includes("int")) json.type = "integer";
else json.type = "number";
- if (typeof exclusiveMinimum === "number") json.exclusiveMinimum = exclusiveMinimum;
- if (typeof minimum === "number") {
- json.minimum = minimum;
- if (typeof exclusiveMinimum === "number") {
- if (exclusiveMinimum >= minimum) delete json.minimum;
- else delete json.exclusiveMinimum;
+ const hasExclusiveMinimum = typeof exclusiveMinimum === "number";
+ const hasMinimum = typeof minimum === "number";
+ const effectiveMinimum =
+ hasExclusiveMinimum && (!hasMinimum || exclusiveMinimum >= minimum)
+ ? exclusiveMinimum
+ : hasMinimum
+ ? minimum
+ : undefined;
+ const minimumIsExclusive =
+ hasExclusiveMinimum && (!hasMinimum || exclusiveMinimum >= minimum);
+
+ if (typeof effectiveMinimum === "number") {
+ if (this.target === "draft-4") {
+ json.minimum = effectiveMinimum;
+ if (minimumIsExclusive) json.exclusiveMinimum = true;
+ } else if (minimumIsExclusive) {
+ json.exclusiveMinimum = effectiveMinimum;
+ } else {
+ json.minimum = effectiveMinimum;
}
}
- if (typeof exclusiveMaximum === "number") json.exclusiveMaximum = exclusiveMaximum;
- if (typeof maximum === "number") {
- json.maximum = maximum;
- if (typeof exclusiveMaximum === "number") {
- if (exclusiveMaximum <= maximum) delete json.maximum;
- else delete json.exclusiveMaximum;
+ const hasExclusiveMaximum = typeof exclusiveMaximum === "number";
+ const hasMaximum = typeof maximum === "number";
+ const effectiveMaximum =
+ hasExclusiveMaximum && (!hasMaximum || exclusiveMaximum <= maximum)
+ ? exclusiveMaximum
+ : hasMaximum
+ ? maximum
+ : undefined;
+ const maximumIsExclusive =
+ hasExclusiveMaximum && (!hasMaximum || exclusiveMaximum <= maximum);
+
+ if (typeof effectiveMaximum === "number") {
+ if (this.target === "draft-4") {
+ json.maximum = effectiveMaximum;
+ if (maximumIsExclusive) json.exclusiveMaximum = true;
+ } else if (maximumIsExclusive) {
+ json.exclusiveMaximum = effectiveMaximum;
+ } else {
+ json.maximum = effectiveMaximum;
}
}
@@ -379,11 +406,18 @@ export class JSONSchemaGenerator {
case "record": {
const json: JSONSchema.ObjectSchema = _json as any;
json.type = "object";
- json.propertyNames = this.process(def.keyType, { ...params, path: [...params.path, "propertyNames"] });
- json.additionalProperties = this.process(def.valueType, {
+ const keySchema = this.process(def.keyType, {
+ ...params,
+ path: [...params.path, "propertyNames"],
+ });
+ const valueSchema = this.process(def.valueType, {
...params,
path: [...params.path, "additionalProperties"],
});
+ if (this.target !== "draft-4") {
+ json.propertyNames = keySchema;
+ }
+ json.additionalProperties = valueSchema;
break;
}
case "map": {
@@ -432,7 +466,11 @@ export class JSONSchemaGenerator {
} else if (vals.length === 1) {
const val = vals[0]!;
json.type = val === null ? ("null" as const) : (typeof val as any);
- json.const = val;
+ if (this.target === "draft-4") {
+ json.enum = [val];
+ } else {
+ json.const = val;
+ }
} else {
if (vals.every((v) => typeof v === "number")) json.type = "number";
if (vals.every((v) => typeof v === "string")) json.type = "string";
@@ -749,7 +787,7 @@ export class JSONSchemaGenerator {
// merge referenced schema into current
const refSchema = this.seen.get(ref)!.schema;
- if (refSchema.$ref && params.target === "draft-7") {
+ if (refSchema.$ref && params.target !== "draft-2020-12") {
schema.allOf = schema.allOf ?? [];
schema.allOf.push(refSchema);
} else {
@@ -776,6 +814,8 @@ export class JSONSchemaGenerator {
result.$schema = "https://json-schema.org/draft/2020-12/schema";
} else if (this.target === "draft-7") {
result.$schema = "http://json-schema.org/draft-07/schema#";
+ } else if (this.target === "draft-4") {
+ result.$schema = "http://json-schema.org/draft-04/schema#";
} else {
// @ts-ignore
console.warn(`Invalid target: ${this.target}`);