UNPKG

@mastra/schema-compat

Version:

Tool schema compatibility layer for Mastra.ai

167 lines (166 loc) 8.23 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs"); let zod_v4 = require("zod/v4"); let zod_to_json_schema = require("zod-to-json-schema"); zod_to_json_schema = require_rolldown_runtime.__toESM(zod_to_json_schema, 1); //#region src/zod-to-json.ts const PATCHED = Symbol("__mastra_patched__"); /** * Recursively patch Zod v4 record schemas that are missing valueType. * This fixes a bug in Zod v4 where z.record(valueSchema) doesn't set def.valueType. * The single-arg form should set valueType but instead only sets keyType. * * Idempotent — marks patched schemas with a Symbol so repeat calls no-op. * * @internal Exported so the Zod v4 standard-schema adapter can apply the * same patch before its native `toJSONSchema` call (the legacy `zodToJsonSchema` * entry already calls it; the `applyCompatLayer` path otherwise wouldn't). * Not part of the public API. */ function patchRecordSchemas(schema) { if (!schema || typeof schema !== "object") return schema; if (schema[PATCHED]) return schema; schema[PATCHED] = true; const def = schema._zod?.def; if (def?.type === "record" && def.keyType && !def.valueType) { def.valueType = def.keyType; def.keyType = zod_v4.z.string(); } if (!def) return schema; if (def.type === "object" && def.shape) { const shape = typeof def.shape === "function" ? def.shape() : def.shape; for (const key of Object.keys(shape)) patchRecordSchemas(shape[key]); } if (def.type === "array" && def.element) patchRecordSchemas(def.element); if (def.type === "union" && def.options) def.options.forEach(patchRecordSchemas); if (def.type === "record") { if (def.keyType) patchRecordSchemas(def.keyType); if (def.valueType) patchRecordSchemas(def.valueType); } if (def.type === "intersection") { if (def.left) patchRecordSchemas(def.left); if (def.right) patchRecordSchemas(def.right); } if (def.type === "lazy") { if (def.getter && typeof def.getter === "function") { const originalGetter = def.getter; def.getter = function() { const innerSchema = originalGetter(); if (innerSchema) patchRecordSchemas(innerSchema); return innerSchema; }; } } if (def.innerType) patchRecordSchemas(def.innerType); return schema; } /** * Recursively fixes anyOf patterns that some providers (like OpenAI) don't accept. * Converts anyOf: [{type: X}, {type: "null"}] to type: [X, "null"] * Also fixes empty {} property schemas by converting to a union of primitive types. */ function fixAnyOfNullable(schema) { if (typeof schema !== "object" || schema === null) return schema; const result = { ...schema }; if (result.anyOf && Array.isArray(result.anyOf) && result.anyOf.length === 2) { const nullSchema = result.anyOf.find((s) => typeof s === "object" && s !== null && s.type === "null"); const otherSchema = result.anyOf.find((s) => typeof s === "object" && s !== null && s.type !== "null"); if (nullSchema && otherSchema && typeof otherSchema === "object" && otherSchema.type) { const { anyOf, ...rest } = result; const fixedRest = fixAnyOfNullable(rest); const fixedOther = fixAnyOfNullable(otherSchema); return { ...fixedRest, ...fixedOther, type: Array.isArray(fixedOther.type) ? [...fixedOther.type, "null"] : [fixedOther.type, "null"] }; } } if (result.properties && typeof result.properties === "object" && !Array.isArray(result.properties)) result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => { const propSchema = value; if (typeof propSchema === "object" && propSchema !== null && !Array.isArray(propSchema) && Object.keys(propSchema).length === 0) return [key, { type: [ "string", "number", "boolean", "null" ] }]; return [key, fixAnyOfNullable(propSchema)]; })); if (result.items) if (Array.isArray(result.items)) result.items = result.items.map((item) => fixAnyOfNullable(item)); else result.items = fixAnyOfNullable(result.items); if (result.anyOf && Array.isArray(result.anyOf)) result.anyOf = result.anyOf.map((s) => fixAnyOfNullable(s)); if (result.oneOf && Array.isArray(result.oneOf)) result.oneOf = result.oneOf.map((s) => fixAnyOfNullable(s)); if (result.allOf && Array.isArray(result.allOf)) result.allOf = result.allOf.map((s) => fixAnyOfNullable(s)); return result; } /** * Recursively ensures all properties in an object schema are included in the `required` array. * OpenAI's strict structured output mode requires every key in `properties` to also appear in `required`. * * @param schema - The JSON Schema to process * @returns A new schema with all properties marked as required */ function ensureAllPropertiesRequired(schema) { if (typeof schema !== "object" || schema === null) return schema; const result = { ...schema }; if (result.type === "object" && result.properties) { result.required = Object.keys(result.properties); result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => [key, ensureAllPropertiesRequired(value)])); } if (result.items) { if (Array.isArray(result.items)) result.items = result.items.map((item) => ensureAllPropertiesRequired(item)); else if (typeof result.items === "object") result.items = ensureAllPropertiesRequired(result.items); } if (result.additionalProperties && typeof result.additionalProperties === "object") result.additionalProperties = ensureAllPropertiesRequired(result.additionalProperties); if (result.anyOf && Array.isArray(result.anyOf)) result.anyOf = result.anyOf.map((s) => ensureAllPropertiesRequired(s)); if (result.oneOf && Array.isArray(result.oneOf)) result.oneOf = result.oneOf.map((s) => ensureAllPropertiesRequired(s)); if (result.allOf && Array.isArray(result.allOf)) result.allOf = result.allOf.map((s) => ensureAllPropertiesRequired(s)); return result; } /** * Prepare a JSON Schema for OpenAI strict mode by ensuring all object properties * are required and all objects have additionalProperties: false. */ function prepareJsonSchemaForOpenAIStrictMode(schema) { return ensureAdditionalPropertiesFalse(ensureAllPropertiesRequired(schema)); } function ensureAdditionalPropertiesFalse(schema) { if (typeof schema !== "object" || schema === null) return schema; const result = { ...schema }; if (result.type === "object" || result.properties) result.additionalProperties = false; if (result.properties) result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => [key, ensureAdditionalPropertiesFalse(value)])); if (result.items) { if (Array.isArray(result.items)) result.items = result.items.map((item) => ensureAdditionalPropertiesFalse(item)); else if (typeof result.items === "object") result.items = ensureAdditionalPropertiesFalse(result.items); } if (result.anyOf && Array.isArray(result.anyOf)) result.anyOf = result.anyOf.map((s) => ensureAdditionalPropertiesFalse(s)); if (result.oneOf && Array.isArray(result.oneOf)) result.oneOf = result.oneOf.map((s) => ensureAdditionalPropertiesFalse(s)); if (result.allOf && Array.isArray(result.allOf)) result.allOf = result.allOf.map((s) => ensureAdditionalPropertiesFalse(s)); return result; } function zodToJsonSchema(zodSchema, target = "jsonSchema7", strategy = "relative") { if (zodSchema?._zod) { patchRecordSchemas(zodSchema); return fixAnyOfNullable(zod_v4.z.toJSONSchema(zodSchema, { unrepresentable: "any", io: "input", override: (ctx) => { const def = ctx.zodSchema?._def || ctx.zodSchema?._zod?.def; if (def && (def.typeName === "ZodDate" || def.type === "date")) { ctx.jsonSchema.type = "string"; ctx.jsonSchema.format = "date-time"; } if (def && (def.typeName === "ZodObject" || def.type === "object")) ctx.jsonSchema.additionalProperties = false; } })); } else return (0, zod_to_json_schema.default)(zodSchema, { $refStrategy: strategy, target }); } //#endregion exports.ensureAllPropertiesRequired = ensureAllPropertiesRequired; exports.patchRecordSchemas = patchRecordSchemas; exports.prepareJsonSchemaForOpenAIStrictMode = prepareJsonSchemaForOpenAIStrictMode; exports.zodToJsonSchema = zodToJsonSchema; //# sourceMappingURL=zod-to-json.cjs.map