@mastra/schema-compat
Version:
Tool schema compatibility layer for Mastra.ai
1 lines • 17.3 kB
Source Map (JSON)
{"version":3,"file":"zod-to-json.cjs","names":["zV4"],"sources":["../src/zod-to-json.ts"],"sourcesContent":["import type { JSONSchema7 } from 'json-schema';\nimport type { ZodSchema as ZodSchemaV3 } from 'zod/v3';\nimport { z as zV4 } from 'zod/v4';\nimport type { Targets } from 'zod-to-json-schema';\nimport zodToJsonSchemaOriginal from 'zod-to-json-schema';\n\n// Symbol to mark schemas as already patched (for idempotency)\nconst PATCHED = Symbol('__mastra_patched__');\n\n/**\n * Recursively patch Zod v4 record schemas that are missing valueType.\n * This fixes a bug in Zod v4 where z.record(valueSchema) doesn't set def.valueType.\n * The single-arg form should set valueType but instead only sets keyType.\n *\n * Idempotent — marks patched schemas with a Symbol so repeat calls no-op.\n *\n * @internal Exported so the Zod v4 standard-schema adapter can apply the\n * same patch before its native `toJSONSchema` call (the legacy `zodToJsonSchema`\n * entry already calls it; the `applyCompatLayer` path otherwise wouldn't).\n * Not part of the public API.\n */\nexport function patchRecordSchemas(schema: any): any {\n if (!schema || typeof schema !== 'object') return schema;\n\n // Skip if already patched (idempotency check)\n if ((schema as any)[PATCHED]) return schema;\n (schema as any)[PATCHED] = true;\n\n // Check the _zod.def location (v4 structure)\n const def = schema._zod?.def;\n\n // Fix record schemas with missing valueType\n if (def?.type === 'record' && def.keyType && !def.valueType) {\n // The bug: z.record(valueSchema) puts the value in keyType instead of valueType\n // Fix: move it to valueType and set keyType to string (the default)\n def.valueType = def.keyType;\n def.keyType = zV4.string();\n }\n\n // Recursively patch nested schemas\n if (!def) return schema;\n\n if (def.type === 'object' && def.shape) {\n const shape = typeof def.shape === 'function' ? def.shape() : def.shape;\n for (const key of Object.keys(shape)) {\n patchRecordSchemas(shape[key]);\n }\n }\n\n if (def.type === 'array' && def.element) {\n patchRecordSchemas(def.element);\n }\n\n if (def.type === 'union' && def.options) {\n def.options.forEach(patchRecordSchemas);\n }\n\n if (def.type === 'record') {\n if (def.keyType) patchRecordSchemas(def.keyType);\n if (def.valueType) patchRecordSchemas(def.valueType);\n }\n\n // Handle intersection types\n if (def.type === 'intersection') {\n if (def.left) patchRecordSchemas(def.left);\n if (def.right) patchRecordSchemas(def.right);\n }\n\n // Handle lazy types - patch the schema returned by the getter\n if (def.type === 'lazy') {\n // For lazy schemas, we need to patch the schema when it's accessed\n // Store the original getter and wrap it\n if (def.getter && typeof def.getter === 'function') {\n const originalGetter = def.getter;\n def.getter = function () {\n const innerSchema = originalGetter();\n if (innerSchema) {\n patchRecordSchemas(innerSchema);\n }\n return innerSchema;\n };\n }\n }\n\n // Handle wrapper types that have innerType\n // This covers: optional, nullable, default, catch, nullish, and any other wrappers\n if (def.innerType) {\n patchRecordSchemas(def.innerType);\n }\n\n return schema;\n}\n\n/**\n * Recursively fixes anyOf patterns that some providers (like OpenAI) don't accept.\n * Converts anyOf: [{type: X}, {type: \"null\"}] to type: [X, \"null\"]\n * Also fixes empty {} property schemas by converting to a union of primitive types.\n */\nfunction fixAnyOfNullable(schema: JSONSchema7): JSONSchema7 {\n if (typeof schema !== 'object' || schema === null) {\n return schema;\n }\n\n const result = { ...schema };\n\n // Fix anyOf pattern: [{type: X}, {type: \"null\"}] or [{type: \"null\"}, {type: X}]\n if (result.anyOf && Array.isArray(result.anyOf) && result.anyOf.length === 2) {\n const nullSchema = result.anyOf.find((s: any) => typeof s === 'object' && s !== null && s.type === 'null');\n const otherSchema = result.anyOf.find((s: any) => typeof s === 'object' && s !== null && s.type !== 'null');\n\n if (nullSchema && otherSchema && typeof otherSchema === 'object' && otherSchema.type) {\n // Convert anyOf to type array format\n // Normalize sibling fields (like properties/items) before returning\n const { anyOf, ...rest } = result;\n const fixedRest = fixAnyOfNullable(rest as JSONSchema7);\n const fixedOther = fixAnyOfNullable(otherSchema as JSONSchema7);\n return {\n ...fixedRest,\n ...fixedOther,\n type: (Array.isArray(fixedOther.type)\n ? [...fixedOther.type, 'null']\n : [fixedOther.type, 'null']) as JSONSchema7['type'],\n };\n }\n }\n\n // Fix empty property schemas {} - OpenAI requires a type key\n if (result.properties && typeof result.properties === 'object' && !Array.isArray(result.properties)) {\n result.properties = Object.fromEntries(\n Object.entries(result.properties).map(([key, value]) => {\n const propSchema = value as JSONSchema7;\n\n // If property is an empty object {}, convert to allow primitive types\n // Note: We exclude 'object' (requires additionalProperties) and 'array' (requires items) for OpenAI\n if (\n typeof propSchema === 'object' &&\n propSchema !== null &&\n !Array.isArray(propSchema) &&\n Object.keys(propSchema).length === 0\n ) {\n return [key, { type: ['string', 'number', 'boolean', 'null'] as JSONSchema7['type'] }];\n }\n\n // Recursively fix nested schemas\n return [key, fixAnyOfNullable(propSchema)];\n }),\n );\n }\n\n // Recursively fix items in arrays\n if (result.items) {\n if (Array.isArray(result.items)) {\n result.items = result.items.map(item => fixAnyOfNullable(item as JSONSchema7));\n } else {\n result.items = fixAnyOfNullable(result.items as JSONSchema7);\n }\n }\n\n // Recursively fix anyOf/oneOf/allOf schemas\n if (result.anyOf && Array.isArray(result.anyOf)) {\n result.anyOf = result.anyOf.map(s => fixAnyOfNullable(s as JSONSchema7));\n }\n if (result.oneOf && Array.isArray(result.oneOf)) {\n result.oneOf = result.oneOf.map(s => fixAnyOfNullable(s as JSONSchema7));\n }\n if (result.allOf && Array.isArray(result.allOf)) {\n result.allOf = result.allOf.map(s => fixAnyOfNullable(s as JSONSchema7));\n }\n\n return result;\n}\n\n/**\n * Recursively ensures all properties in an object schema are included in the `required` array.\n * OpenAI's strict structured output mode requires every key in `properties` to also appear in `required`.\n *\n * @param schema - The JSON Schema to process\n * @returns A new schema with all properties marked as required\n */\nexport function ensureAllPropertiesRequired(schema: JSONSchema7): JSONSchema7 {\n if (typeof schema !== 'object' || schema === null) {\n return schema;\n }\n\n const result = { ...schema };\n\n if (result.type === 'object' && result.properties) {\n result.required = Object.keys(result.properties);\n result.properties = Object.fromEntries(\n Object.entries(result.properties).map(([key, value]) => [key, ensureAllPropertiesRequired(value as JSONSchema7)]),\n );\n }\n\n if (result.items) {\n if (Array.isArray(result.items)) {\n result.items = result.items.map(item => ensureAllPropertiesRequired(item as JSONSchema7));\n } else if (typeof result.items === 'object') {\n result.items = ensureAllPropertiesRequired(result.items as JSONSchema7);\n }\n }\n\n if (result.additionalProperties && typeof result.additionalProperties === 'object') {\n result.additionalProperties = ensureAllPropertiesRequired(result.additionalProperties as JSONSchema7);\n }\n\n if (result.anyOf && Array.isArray(result.anyOf)) {\n result.anyOf = result.anyOf.map(s => ensureAllPropertiesRequired(s as JSONSchema7));\n }\n if (result.oneOf && Array.isArray(result.oneOf)) {\n result.oneOf = result.oneOf.map(s => ensureAllPropertiesRequired(s as JSONSchema7));\n }\n if (result.allOf && Array.isArray(result.allOf)) {\n result.allOf = result.allOf.map(s => ensureAllPropertiesRequired(s as JSONSchema7));\n }\n\n return result;\n}\n\n/**\n * Prepare a JSON Schema for OpenAI strict mode by ensuring all object properties\n * are required and all objects have additionalProperties: false.\n */\nexport function prepareJsonSchemaForOpenAIStrictMode(schema: JSONSchema7): JSONSchema7 {\n const withRequired = ensureAllPropertiesRequired(schema);\n return ensureAdditionalPropertiesFalse(withRequired);\n}\n\nfunction ensureAdditionalPropertiesFalse(schema: JSONSchema7): JSONSchema7 {\n if (typeof schema !== 'object' || schema === null) {\n return schema;\n }\n\n const result = { ...schema };\n\n if (result.type === 'object' || result.properties) {\n result.additionalProperties = false;\n }\n\n if (result.properties) {\n result.properties = Object.fromEntries(\n Object.entries(result.properties).map(([key, value]) => [\n key,\n ensureAdditionalPropertiesFalse(value as JSONSchema7),\n ]),\n );\n }\n\n if (result.items) {\n if (Array.isArray(result.items)) {\n result.items = result.items.map(item => ensureAdditionalPropertiesFalse(item as JSONSchema7));\n } else if (typeof result.items === 'object') {\n result.items = ensureAdditionalPropertiesFalse(result.items as JSONSchema7);\n }\n }\n\n if (result.anyOf && Array.isArray(result.anyOf)) {\n result.anyOf = result.anyOf.map(s => ensureAdditionalPropertiesFalse(s as JSONSchema7));\n }\n if (result.oneOf && Array.isArray(result.oneOf)) {\n result.oneOf = result.oneOf.map(s => ensureAdditionalPropertiesFalse(s as JSONSchema7));\n }\n if (result.allOf && Array.isArray(result.allOf)) {\n result.allOf = result.allOf.map(s => ensureAdditionalPropertiesFalse(s as JSONSchema7));\n }\n\n return result;\n}\n\n// export function zotToJsonSchema(zodSchema: ZodSchemaV3 | ZodSchemaV4, target: Targets = 'jsonSchema7', strategy: 'none' | 'seen' | 'root' | 'relative' = 'relative'): JSONSchema7 {\n// const target = 'draft-07' as StandardJSONSchemaV1.Target;\n// const standardSchema = toStandardSchema(zodSchema);\n// const jsonSchema = standardSchemaToJSONSchema(standardSchema, {\n// target,\n// });\n\n// traverse(jsonSchema, {\n// cb: {\n// pre: (schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema) => {\n// this.preProcessJSONNode(schema, parentSchema);\n// },\n// post: (schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema) => {\n// this.postProcessJSONNode(schema, parentSchema);\n// },\n// },\n// });\n\n// }\n\nexport function zodToJsonSchema(\n zodSchema: any,\n target: Targets = 'jsonSchema7',\n strategy: 'none' | 'seen' | 'root' | 'relative' = 'relative',\n): JSONSchema7 {\n // Route based on whether the schema is v4 (has _zod) or v3 (only has _def).\n // We use zV4.toJSONSchema (imported from 'zod/v4') for v4 schemas, since the\n // default 'zod' import may resolve to v3 depending on the environment.\n // Without this check, v3 schemas passed to v4's toJSONSchema would throw\n // \"Cannot read properties of undefined (reading 'def')\".\n if (zodSchema?._zod) {\n // Zod v4 path - patch record schemas before converting\n patchRecordSchemas(zodSchema);\n\n const jsonSchema = zV4.toJSONSchema(zodSchema, {\n unrepresentable: 'any',\n io: 'input',\n override: (ctx: any) => {\n // Handle both Zod v4 structures: _def directly or nested in _zod\n const def = ctx.zodSchema?._def || ctx.zodSchema?._zod?.def;\n // Check for date type using both possible property names\n if (def && (def.typeName === 'ZodDate' || def.type === 'date')) {\n ctx.jsonSchema.type = 'string';\n ctx.jsonSchema.format = 'date-time';\n }\n // Add additionalProperties: false for object types to match Zod v3 behavior\n // This is required for OpenAI strict mode function calling\n if (def && (def.typeName === 'ZodObject' || def.type === 'object')) {\n ctx.jsonSchema.additionalProperties = false;\n }\n },\n }) as JSONSchema7;\n\n // Fix anyOf patterns for nullable fields - required for OpenAI compatibility\n return fixAnyOfNullable(jsonSchema);\n } else {\n // Zod v3 path - use the original converter\n return zodToJsonSchemaOriginal(zodSchema as ZodSchemaV3, {\n $refStrategy: strategy,\n target,\n }) as JSONSchema7;\n }\n}\n"],"mappings":"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"}