| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.toStrictJsonSchema = toStrictJsonSchema;
- function toStrictJsonSchema(schema) {
- if (schema.type !== 'object') {
- throw new Error(`Root schema must have type: 'object' but got type: ${schema.type ? `'${schema.type}'` : 'undefined'}`);
- }
- const schemaCopy = structuredClone(schema);
- return ensureStrictJsonSchema(schemaCopy, [], schemaCopy);
- }
- function isNullable(schema) {
- if (typeof schema === 'boolean') {
- return false;
- }
- if (schema.type === 'null') {
- return true;
- }
- for (const oneOfVariant of schema.oneOf ?? []) {
- if (isNullable(oneOfVariant)) {
- return true;
- }
- }
- for (const allOfVariant of schema.anyOf ?? []) {
- if (isNullable(allOfVariant)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Mutates the given JSON schema to ensure it conforms to the `strict` standard
- * that the API expects.
- */
- function ensureStrictJsonSchema(jsonSchema, path, root) {
- if (typeof jsonSchema === 'boolean') {
- throw new TypeError(`Expected object schema but got boolean; path=${path.join('/')}`);
- }
- if (!isObject(jsonSchema)) {
- throw new TypeError(`Expected ${JSON.stringify(jsonSchema)} to be an object; path=${path.join('/')}`);
- }
- // Handle $defs (non-standard but sometimes used)
- const defs = jsonSchema.$defs;
- if (isObject(defs)) {
- for (const [defName, defSchema] of Object.entries(defs)) {
- ensureStrictJsonSchema(defSchema, [...path, '$defs', defName], root);
- }
- }
- // Handle definitions (draft-04 style, deprecated in draft-07 but still used)
- const definitions = jsonSchema.definitions;
- if (isObject(definitions)) {
- for (const [definitionName, definitionSchema] of Object.entries(definitions)) {
- ensureStrictJsonSchema(definitionSchema, [...path, 'definitions', definitionName], root);
- }
- }
- // Add additionalProperties: false to object types
- const typ = jsonSchema.type;
- if (typ === 'object' && !('additionalProperties' in jsonSchema)) {
- jsonSchema.additionalProperties = false;
- }
- const required = jsonSchema.required ?? [];
- // Handle object properties
- const properties = jsonSchema.properties;
- if (isObject(properties)) {
- for (const [key, value] of Object.entries(properties)) {
- if (!isNullable(value) && !required.includes(key)) {
- throw new Error(`Zod field at \`${[...path, 'properties', key].join('/')}\` uses \`.optional()\` without \`.nullable()\` which is not supported by the API. See: https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#all-fields-must-be-required`);
- }
- }
- jsonSchema.required = Object.keys(properties);
- jsonSchema.properties = Object.fromEntries(Object.entries(properties).map(([key, propSchema]) => [
- key,
- ensureStrictJsonSchema(propSchema, [...path, 'properties', key], root),
- ]));
- }
- // Handle arrays
- const items = jsonSchema.items;
- if (isObject(items)) {
- jsonSchema.items = ensureStrictJsonSchema(items, [...path, 'items'], root);
- }
- // Handle unions (anyOf)
- const anyOf = jsonSchema.anyOf;
- if (Array.isArray(anyOf)) {
- jsonSchema.anyOf = anyOf.map((variant, i) => ensureStrictJsonSchema(variant, [...path, 'anyOf', String(i)], root));
- }
- // Handle intersections (allOf)
- const allOf = jsonSchema.allOf;
- if (Array.isArray(allOf)) {
- if (allOf.length === 1) {
- const resolved = ensureStrictJsonSchema(allOf[0], [...path, 'allOf', '0'], root);
- Object.assign(jsonSchema, resolved);
- delete jsonSchema.allOf;
- }
- else {
- jsonSchema.allOf = allOf.map((entry, i) => ensureStrictJsonSchema(entry, [...path, 'allOf', String(i)], root));
- }
- }
- // Strip `null` defaults as there's no meaningful distinction
- if (jsonSchema.default === null) {
- delete jsonSchema.default;
- }
- // Handle $ref with additional properties
- const ref = jsonSchema.$ref;
- if (ref && hasMoreThanNKeys(jsonSchema, 1)) {
- if (typeof ref !== 'string') {
- throw new TypeError(`Received non-string $ref - ${ref}; path=${path.join('/')}`);
- }
- const resolved = resolveRef(root, ref);
- if (typeof resolved === 'boolean') {
- throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object schema but got boolean`);
- }
- if (!isObject(resolved)) {
- throw new Error(`Expected \`$ref: ${ref}\` to resolve to an object but got ${JSON.stringify(resolved)}`);
- }
- // Properties from the json schema take priority over the ones on the `$ref`
- Object.assign(jsonSchema, { ...resolved, ...jsonSchema });
- delete jsonSchema.$ref;
- // Since the schema expanded from `$ref` might not have `additionalProperties: false` applied,
- // we call `ensureStrictJsonSchema` again to fix the inlined schema and ensure it's valid.
- return ensureStrictJsonSchema(jsonSchema, path, root);
- }
- return jsonSchema;
- }
- function resolveRef(root, ref) {
- if (!ref.startsWith('#/')) {
- throw new Error(`Unexpected $ref format ${JSON.stringify(ref)}; Does not start with #/`);
- }
- const pathParts = ref.slice(2).split('/');
- let resolved = root;
- for (const key of pathParts) {
- if (!isObject(resolved)) {
- throw new Error(`encountered non-object entry while resolving ${ref} - ${JSON.stringify(resolved)}`);
- }
- const value = resolved[key];
- if (value === undefined) {
- throw new Error(`Key ${key} not found while resolving ${ref}`);
- }
- resolved = value;
- }
- return resolved;
- }
- function isObject(obj) {
- return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
- }
- function hasMoreThanNKeys(obj, n) {
- let i = 0;
- for (const _ in obj) {
- i++;
- if (i > n) {
- return true;
- }
- }
- return false;
- }
- //# sourceMappingURL=transform.js.map
|