@techery/zod-to-vertex-schema
Version:
Convert Zod schemas to Vertex AI/Gemini compatible schemas
268 lines • 9.16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaType = void 0;
exports.zodDynamicEnum = zodDynamicEnum;
exports.zodToVertexSchema = zodToVertexSchema;
const zod_1 = require("zod");
var SchemaType;
(function (SchemaType) {
/** String type. */
SchemaType["STRING"] = "STRING";
/** Number type. */
SchemaType["NUMBER"] = "NUMBER";
/** Integer type. */
SchemaType["INTEGER"] = "INTEGER";
/** Boolean type. */
SchemaType["BOOLEAN"] = "BOOLEAN";
/** Array type. */
SchemaType["ARRAY"] = "ARRAY";
/** Object type. */
SchemaType["OBJECT"] = "OBJECT";
})(SchemaType || (exports.SchemaType = SchemaType = {}));
function zodDynamicEnum(values) {
return zod_1.z.enum(values);
}
/**
* Main function to convert any Zod schema into a Gemini-compatible VertexSchema.
* Defaults are intentionally ignored (not included in final schema).
*/
function zodToVertexSchema(schema) {
const isZodNull = schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodNull;
// 1) If schema is optional or nullable, unwrap it and mark "nullable" if appropriate.
if (!isZodNull && (schema.isOptional() || schema.isNullable())) {
const baseSchema = unwrapOptionalOrNullable(schema);
const geminiInner = zodToVertexSchema(baseSchema);
// If the Zod schema is nullable, reflect that in the Gemini schema
if (schema.isNullable()) {
geminiInner.nullable = true;
}
// Carry over the description if present
if (schema.description) {
geminiInner.description = schema.description;
}
return geminiInner;
}
// 2) Dispatch based on known Zod constructors
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodString) {
return makeStringSchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodNumber) {
return makeNumberSchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodBoolean) {
return {
type: SchemaType.BOOLEAN,
description: schema.description,
};
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodObject) {
return makeObjectSchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodArray) {
return makeArraySchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodEnum) {
return makeEnumSchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodUnion) {
return makeUnionSchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodLiteral) {
return makeLiteralSchema(schema);
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodNull) {
// No native "null" type in Gemini. Use type=STRING + nullable=true as a fallback.
return {
type: SchemaType.STRING,
nullable: true,
description: schema.description,
};
}
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodDiscriminatedUnion) {
return makeDiscriminatedUnionSchema(schema);
}
throw new Error(`Unsupported Zod type: ${schema.constructor.name}`);
}
/**
* If a schema is optional/null/default, unwrap to the "inner" schema.
* This way we can apply the relevant checks to the base type.
*/
function unwrapOptionalOrNullable(schema) {
var _a, _b;
if (schema._def.typeName === zod_1.ZodFirstPartyTypeKind.ZodNull) {
throw new Error('ZodNull is not supported');
}
// Zod chain can store underlying schema in ._def.innerType or ._def.schema.
if ((_a = schema._def) === null || _a === void 0 ? void 0 : _a.innerType) {
return schema._def.innerType;
}
if ((_b = schema._def) === null || _b === void 0 ? void 0 : _b.schema) {
return schema._def.schema;
}
return schema;
}
/**
* Convert a ZodString into a Gemini schema:
* - format
* - (Ignores any default value)
*/
function makeStringSchema(schema) {
const gemini = {
type: SchemaType.STRING,
description: schema.description,
};
// Iterate over each check in the ZodString's definition.
const checks = schema._def.checks || [];
for (const check of checks) {
switch (check.kind) {
case 'datetime':
gemini.format = 'date-time';
break;
case 'date':
gemini.format = 'date';
break;
default:
throw new Error(`Unsupported string check: ${check.kind}`);
}
}
return gemini;
}
/**
* Convert z.number() to a Gemini schema:
* - type=NUMBER or INTEGER
* - min => minimum, max => maximum
* - Ignores default values
* - Includes description
*/
function makeNumberSchema(schema) {
const gemini = {
description: schema.description,
};
const checks = schema._def.checks || [];
const isInt = checks.some((c) => c.kind === 'int');
gemini.type = isInt ? SchemaType.INTEGER : SchemaType.NUMBER;
for (const check of checks) {
if (check.kind === 'min') {
gemini.minimum = check.value;
}
else if (check.kind === 'max') {
gemini.maximum = check.value;
}
}
return gemini;
}
/**
* Convert z.object(...) to a Gemini schema:
* - type=OBJECT
* - properties => recursively derived from each field
* - required => array of non-optional field names
* - propertyOrdering => preserve the field order
* - Ignores default
* - Includes description
*/
function makeObjectSchema(schema) {
const shape = schema._def.shape();
const propertyKeys = Object.keys(shape);
const properties = {};
const required = [];
const propertyOrdering = [];
for (const key of propertyKeys) {
const fieldSchema = shape[key];
propertyOrdering.push(key);
if (!fieldSchema.isOptional()) {
required.push(key);
}
// Recursively convert each field to a Gemini schema
properties[key] = zodToVertexSchema(fieldSchema);
}
const gemini = {
type: SchemaType.OBJECT,
properties,
propertyOrdering,
description: schema.description,
};
if (required.length > 0) {
gemini.required = required;
}
return gemini;
}
/**
* Convert z.array(T) => type=ARRAY with "items" as T's schema,
* and minItems / maxItems / exactLength if specified.
* (Ignores default, includes description.)
*/
function makeArraySchema(schema) {
const itemsGemini = zodToVertexSchema(schema.element);
const gemini = {
type: SchemaType.ARRAY,
items: itemsGemini,
description: schema.description,
};
// For arrays, Zod uses ._def.minLength / ._def.maxLength / ._def.exactLength
const { minLength, maxLength, exactLength } = schema._def;
if ((minLength === null || minLength === void 0 ? void 0 : minLength.value) !== undefined) {
gemini.minItems = minLength.value;
}
if ((maxLength === null || maxLength === void 0 ? void 0 : maxLength.value) !== undefined) {
gemini.maxItems = maxLength.value;
}
if ((exactLength === null || exactLength === void 0 ? void 0 : exactLength.value) !== undefined) {
gemini.minItems = exactLength.value;
gemini.maxItems = exactLength.value;
}
return gemini;
}
/**
* Convert z.enum([...]) => type=STRING + enum=[...].
* Defaults are ignored. Description is included.
*/
function makeEnumSchema(schema) {
return {
type: SchemaType.STRING,
enum: schema._def.values,
description: schema.description,
};
}
/**
* Convert a non-discriminated z.union([...]) into anyOf: [ ... ].
*/
function makeUnionSchema(schema) {
const variants = schema._def.options;
return {
anyOf: variants.map((variant) => zodToVertexSchema(variant)),
};
}
/**
* Convert z.literal(...) => type + enum with a single value.
* (No default to consider, plus description.)
*/
function makeLiteralSchema(schema) {
const literalValue = schema._def.value;
const valueType = typeof literalValue;
const gemini = {
description: schema.description,
};
// We only handle "string" in detail here, fallback for other literal types
// as type=STRING with that single enum value as a string
if (valueType === 'string') {
gemini.type = SchemaType.STRING;
gemini.enum = [literalValue];
}
else {
throw new Error(`Unsupported literal type. Gemini doesn't support ${valueType} literals.`);
}
return gemini;
}
/**
* Convert a discriminated union => anyOf for each branch.
* Ignores default. No special "discriminator" field included.
*/
function makeDiscriminatedUnionSchema(schema) {
const { optionsMap } = schema._def;
// Convert each variant in the union
const anyOfSchemas = Array.from(optionsMap.values()).map((option) => zodToVertexSchema(option));
return {
anyOf: anyOfSchemas,
};
}
//# sourceMappingURL=zod-to-vertex-schema.js.map