UNPKG

@genkit-ai/core

Version:

Genkit AI framework core libraries.

220 lines (215 loc) 6.65 kB
import { Validator } from "@cfworker/json-schema"; import Ajv from "ajv"; import addFormats from "ajv-formats"; import { z } from "zod"; import zodToJsonSchema from "zod-to-json-schema"; import { getGenkitRuntimeConfig } from "./config.mjs"; import { GenkitError } from "./error.mjs"; const ajv = new Ajv(); addFormats(ajv); const jsonSchemas = /* @__PURE__ */ new WeakMap(); const ajvValidators = /* @__PURE__ */ new WeakMap(); const cfWorkerValidators = /* @__PURE__ */ new WeakMap(); const schemaAnnotations = /* @__PURE__ */ new WeakMap(); function annotateSchema(schema, annotations) { const current = schemaAnnotations.get(schema) || {}; schemaAnnotations.set(schema, { ...current, ...annotations }); return schema; } class ValidationError extends GenkitError { constructor({ data, errors, schema }) { super({ status: "INVALID_ARGUMENT", message: `Schema validation failed. Parse Errors: ${errors.map((e) => `- ${e.path}: ${e.message}`).join("\n")} Provided data: ${JSON.stringify(data, null, 2)} Required JSON schema: ${JSON.stringify(schema, null, 2)}`, detail: { errors, schema } }); } } function toJsonSchema({ jsonSchema, schema }) { if (!jsonSchema && !schema) return null; if (jsonSchema) return jsonSchema; if (jsonSchemas.has(schema)) return jsonSchemas.get(schema); const outSchema = zodToJsonSchema(schema, { removeAdditionalStrategy: "strict" }); const annotatedSchema = applyAnnotations(schema, outSchema); jsonSchemas.set(schema, annotatedSchema); return annotatedSchema; } function applyAnnotations(schema, json) { if (!json || typeof json !== "object") return json; const annotationsToApply = []; let current = schema; while (current) { const ann = schemaAnnotations.get(current); if (ann) annotationsToApply.push(ann); if (current instanceof z.ZodOptional || current instanceof z.ZodNullable || current instanceof z.ZodDefault || current instanceof z.ZodEffects) { current = current._def.innerType || current._def.schema; } else { break; } } const resolvedAnnotations = {}; for (let i = annotationsToApply.length - 1; i >= 0; i--) { Object.assign(resolvedAnnotations, annotationsToApply[i]); } for (const key in resolvedAnnotations) { if (Object.prototype.hasOwnProperty.call(json, key)) { console.warn( `Annotation key "${key}" conflicts with existing JSON schema property and will be ignored.` ); continue; } json[key] = resolvedAnnotations[key]; } const inner = current; if (inner instanceof z.ZodObject && json.properties) { for (const key in inner.shape) { if (json.properties[key]) { applyAnnotations(inner.shape[key], json.properties[key]); } } } else if (inner instanceof z.ZodArray && json.items) { applyAnnotations(inner.element, json.items); } else if (inner instanceof z.ZodUnion && json.anyOf) { for (let i = 0; i < inner.options.length; i++) { applyAnnotations(inner.options[i], json.anyOf[i]); } } else if (inner instanceof z.ZodIntersection && json.allOf) { const schemas = []; const collect = (s) => { if (s instanceof z.ZodIntersection) { collect(s._def.left); collect(s._def.right); } else { schemas.push(s); } }; collect(inner); if (schemas.length === json.allOf.length) { for (let i = 0; i < schemas.length; i++) { applyAnnotations(schemas[i], json.allOf[i]); } } } else if (inner instanceof z.ZodRecord && json.additionalProperties) { applyAnnotations(inner.valueSchema, json.additionalProperties); } else if (inner instanceof z.ZodTuple && Array.isArray(json.items)) { for (let i = 0; i < inner.items.length; i++) { applyAnnotations(inner.items[i], json.items[i]); } } else if (inner instanceof z.ZodDiscriminatedUnion && json.anyOf) { for (let i = 0; i < inner.options.length; i++) { applyAnnotations(inner.options[i], json.anyOf[i]); } } return json; } function ajvErrorToValidationErrorDetail(error) { return { path: error.instancePath.substring(1).replace(/\//g, ".") || "(root)", message: error.message }; } function cfWorkerErrorToValidationErrorDetail(error) { const path = error.instanceLocation.startsWith("#/") ? error.instanceLocation.substring(2) : ""; return { path: path.replace(/\//g, ".") || "(root)", message: error.error }; } function validateSchema(data, options) { const toValidate = toJsonSchema(options); if (!toValidate) { return { valid: true, schema: toValidate }; } const validationMode = getGenkitRuntimeConfig().jsonSchemaMode; if (validationMode === "interpret") { let validator2 = cfWorkerValidators.get(toValidate); if (!validator2) { validator2 = new Validator(toValidate); cfWorkerValidators.set(toValidate, validator2); } const result = validator2.validate(sanitizeForJsonSchema(data)); if (!result.valid) { return { valid: false, errors: result.errors.map(cfWorkerErrorToValidationErrorDetail), schema: toValidate }; } return { valid: result.valid, schema: toValidate }; } let validator = ajvValidators.get(toValidate); if (!validator) { validator = ajv.compile(toValidate); ajvValidators.set(toValidate, validator); } const valid = validator(data); if (!valid) { return { valid: false, errors: (validator.errors ?? []).map(ajvErrorToValidationErrorDetail), schema: toValidate }; } return { valid, schema: toValidate }; } function parseSchema(data, options) { const result = validateSchema(data, options); if (!result.valid) { throw new ValidationError({ data, errors: result.errors, schema: result.schema }); } return data; } function defineSchema(registry, name, schema) { registry.registerSchema(name, { schema }); return schema; } function defineJsonSchema(registry, name, jsonSchema) { registry.registerSchema(name, { jsonSchema }); return jsonSchema; } function sanitizeForJsonSchema(data) { if (Array.isArray(data)) { return data.map(sanitizeForJsonSchema); } else if (data !== null && typeof data === "object") { const out = {}; for (const key in data) { if (data[key] !== void 0) { out[key] = sanitizeForJsonSchema(data[key]); } } return out; } return data; } export { ValidationError, annotateSchema, defineJsonSchema, defineSchema, parseSchema, toJsonSchema, validateSchema, z }; //# sourceMappingURL=schema.mjs.map