@mastra/schema-compat
Version:
Tool schema compatibility layer for Mastra.ai
167 lines (166 loc) • 8.23 kB
JavaScript
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