UNPKG

@samchon/openapi

Version:

OpenAPI definitions and converters for 'typia' and 'nestia'.

419 lines (408 loc) 18.6 kB
import { LlmTypeCheckerV3_1 } from "../../utils/LlmTypeCheckerV3_1.mjs"; import { NamingConvention } from "../../utils/NamingConvention.mjs"; import { OpenApiConstraintShifter } from "../../utils/OpenApiConstraintShifter.mjs"; import { OpenApiTypeChecker } from "../../utils/OpenApiTypeChecker.mjs"; import { OpenApiValidator } from "../../utils/OpenApiValidator.mjs"; import { JsonDescriptionUtil } from "../../utils/internal/JsonDescriptionUtil.mjs"; import { LlmDescriptionInverter } from "./LlmDescriptionInverter.mjs"; import { LlmParametersFinder } from "./LlmParametersComposer.mjs"; var LlmSchemaV3_1Composer; (function(LlmSchemaV3_1Composer) { LlmSchemaV3_1Composer.IS_DEFS = true; LlmSchemaV3_1Composer.parameters = props => { const entity = LlmParametersFinder.parameters({ ...props, method: "LlmSchemaV3_1Composer.parameters" }); if (entity.success === false) return entity; const $defs = {}; const result = LlmSchemaV3_1Composer.schema({ ...props, $defs, schema: entity.value }); if (result.success === false) return result; return { success: true, value: { ...result.value, additionalProperties: false, $defs, description: OpenApiTypeChecker.isReference(props.schema) ? JsonDescriptionUtil.cascade({ prefix: "#/components/schemas/", components: props.components, schema: props.schema, escape: true }) : result.value.description } }; }; LlmSchemaV3_1Composer.schema = props => { const union = []; const attribute = { title: props.schema.title, description: props.schema.description, example: props.schema.example, examples: props.schema.examples, ...Object.fromEntries(Object.entries(props.schema).filter((([key, value]) => key.startsWith("x-") && value !== undefined))) }; const reasons = []; OpenApiTypeChecker.visit({ closure: (next, accessor) => { if (props.validate) { reasons.push(...props.validate(next, accessor)); } if (OpenApiTypeChecker.isTuple(next)) reasons.push({ schema: next, accessor, message: `LLM does not allow tuple type.` }); else if (OpenApiTypeChecker.isReference(next)) { const key = next.$ref.split("#/components/schemas/")[1]; if (props.components.schemas?.[key] === undefined) reasons.push({ schema: next, accessor, message: `unable to find reference type ${JSON.stringify(key)}.` }); } }, components: props.components, schema: props.schema, accessor: props.accessor, refAccessor: props.refAccessor }); if (reasons.length > 0) return { success: false, error: { method: "LlmSchemaV3_1Composer.schema", message: "Failed to compose LLM schema of v3.1", reasons } }; const visit = (input, accessor) => { if (OpenApiTypeChecker.isOneOf(input)) { input.oneOf.forEach(((s, i) => visit(s, `${accessor}.oneOf[${i}]`))); return 0; } else if (OpenApiTypeChecker.isReference(input)) { const key = input.$ref.split("#/components/schemas/")[1]; const target = props.components.schemas?.[key]; if (target === undefined) return union.push(null); else if (props.config.reference === true || OpenApiTypeChecker.isRecursiveReference({ components: props.components, schema: input })) { const out = () => union.push({ ...input, $ref: `#/$defs/${key}` }); if (props.$defs[key] !== undefined) return out(); props.$defs[key] = {}; const converted = LlmSchemaV3_1Composer.schema({ config: props.config, components: props.components, $defs: props.$defs, schema: target, refAccessor: props.refAccessor, accessor: `${props.refAccessor ?? "$def"}[${JSON.stringify(key)}]` }); if (converted.success === false) return union.push(null); props.$defs[key] = converted.value; return out(); } else { const length = union.length; visit(target, accessor); if (length === union.length - 1 && union[union.length - 1] !== null) union[union.length - 1] = { ...union[union.length - 1], description: JsonDescriptionUtil.cascade({ prefix: "#/components/schemas/", components: props.components, schema: input, escape: true }) }; else attribute.description = JsonDescriptionUtil.cascade({ prefix: "#/components/schemas/", components: props.components, schema: input, escape: true }); return union.length; } } else if (OpenApiTypeChecker.isObject(input)) { const properties = Object.entries(input.properties ?? {}).reduce(((acc, [key, value]) => { const converted = LlmSchemaV3_1Composer.schema({ config: props.config, components: props.components, $defs: props.$defs, schema: value, refAccessor: props.refAccessor, accessor: `${accessor}.properties[${JSON.stringify(key)}]` }); acc[key] = converted.success ? converted.value : null; if (converted.success === false) reasons.push(...converted.error.reasons); return acc; }), {}); if (Object.values(properties).some((v => v === null))) return union.push(null); const additionalProperties = (() => { if (typeof input.additionalProperties === "object" && input.additionalProperties !== null) { const converted = LlmSchemaV3_1Composer.schema({ config: props.config, components: props.components, $defs: props.$defs, schema: input.additionalProperties, refAccessor: props.refAccessor, accessor: `${accessor}.additionalProperties` }); if (converted.success === false) { reasons.push(...converted.error.reasons); return null; } return converted.value; } return input.additionalProperties; })(); if (additionalProperties === null) return union.push(null); return union.push({ ...input, properties, additionalProperties, required: input.required ?? [] }); } else if (OpenApiTypeChecker.isArray(input)) { const items = LlmSchemaV3_1Composer.schema({ config: props.config, components: props.components, $defs: props.$defs, schema: input.items, refAccessor: props.refAccessor, accessor: `${accessor}.items` }); if (items.success === false) { reasons.push(...items.error.reasons); return union.push(null); } return union.push((props.config.constraint ? x => x : x => OpenApiConstraintShifter.shiftArray(x))({ ...input, items: items.value })); } else if (OpenApiTypeChecker.isString(input)) return union.push((props.config.constraint ? x => x : x => OpenApiConstraintShifter.shiftString(x))({ ...input })); else if (OpenApiTypeChecker.isNumber(input) || OpenApiTypeChecker.isInteger(input)) return union.push((props.config.constraint ? x => x : x => OpenApiConstraintShifter.shiftNumeric(x))({ ...input })); else if (OpenApiTypeChecker.isTuple(input)) return union.push(null); else return union.push({ ...input }); }; visit(props.schema, props.accessor ?? "$input.schema"); if (union.some((u => u === null))) return { success: false, error: { method: "LlmSchemaV3_1Composer.schema", message: "Failed to compose LLM schema of v3.1", reasons } }; else if (union.length === 0) return { success: true, value: { ...attribute, type: undefined } }; else if (union.length === 1) return { success: true, value: { ...attribute, ...union[0] } }; return { success: true, value: { ...attribute, oneOf: union.filter((u => u !== null)) } }; }; LlmSchemaV3_1Composer.separateParameters = props => { const convention = props.convention ?? ((key, type) => `${key}.${NamingConvention.capitalize(type)}`); const [llm, human] = separateObject({ $defs: props.parameters.$defs, schema: props.parameters, predicate: props.predicate, convention }); if (llm === null || human === null) return { llm: llm ?? { type: "object", properties: {}, additionalProperties: false, required: [], $defs: {} }, human }; const output = { llm: { ...llm, $defs: Object.fromEntries(Object.entries(props.parameters.$defs).filter((([key]) => key.endsWith(".Llm")))), additionalProperties: false }, human: { ...human, $defs: Object.fromEntries(Object.entries(props.parameters.$defs).filter((([key]) => key.endsWith(".Human")))), additionalProperties: false } }; for (const key of Object.keys(props.parameters.$defs)) if (key.endsWith(".Llm") === false && key.endsWith(".Human") === false) delete props.parameters.$defs[key]; if (Object.keys(output.llm.properties).length !== 0) { const components = {}; output.validate = OpenApiValidator.create({ components, schema: LlmSchemaV3_1Composer.invert({ components, schema: output.llm, $defs: output.llm.$defs }), required: true }); } return output; }; const separateStation = props => { if (props.predicate(props.schema) === true) return [ null, props.schema ]; else if (LlmTypeCheckerV3_1.isUnknown(props.schema) || LlmTypeCheckerV3_1.isOneOf(props.schema)) return [ props.schema, null ]; else if (LlmTypeCheckerV3_1.isObject(props.schema)) return separateObject({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema }); else if (LlmTypeCheckerV3_1.isArray(props.schema)) return separateArray({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema }); else if (LlmTypeCheckerV3_1.isReference(props.schema)) return separateReference({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema }); return [ props.schema, null ]; }; const separateArray = props => { const [x, y] = separateStation({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema.items }); return [ x !== null ? { ...props.schema, items: x } : null, y !== null ? { ...props.schema, items: y } : null ]; }; const separateObject = props => { if (Object.keys(props.schema.properties ?? {}).length === 0 && !!props.schema.additionalProperties === false) return [ props.schema, null ]; const llm = { ...props.schema, properties: {}, additionalProperties: props.schema.additionalProperties }; const human = { ...props.schema, properties: {} }; for (const [key, value] of Object.entries(props.schema.properties ?? {})) { const [x, y] = separateStation({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: value }); if (x !== null) llm.properties[key] = x; if (y !== null) human.properties[key] = y; } if (typeof props.schema.additionalProperties === "object" && props.schema.additionalProperties !== null) { const [dx, dy] = separateStation({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema.additionalProperties }); llm.additionalProperties = dx ?? false; human.additionalProperties = dy ?? false; } return [ !!Object.keys(llm.properties).length || !!llm.additionalProperties ? shrinkRequired(llm) : null, !!Object.keys(human.properties).length || human.additionalProperties ? shrinkRequired(human) : null ]; }; const separateReference = props => { const key = props.schema.$ref.split("#/$defs/")[1]; const humanKey = props.convention(key, "human"); const llmKey = props.convention(key, "llm"); if (props.$defs?.[humanKey] || props.$defs?.[llmKey]) return [ props.$defs?.[llmKey] ? { ...props.schema, $ref: `#/$defs/${llmKey}` } : null, props.$defs?.[humanKey] ? { ...props.schema, $ref: `#/$defs/${humanKey}` } : null ]; props.$defs[llmKey] = {}; props.$defs[humanKey] = {}; const schema = props.$defs?.[key]; const [llm, human] = separateStation({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema }); if (llm === null || human === null) { delete props.$defs[llmKey]; delete props.$defs[humanKey]; return llm === null ? [ null, props.schema ] : [ props.schema, null ]; } return [ llm !== null ? { ...props.schema, $ref: `#/$defs/${llmKey}` } : null, human !== null ? { ...props.schema, $ref: `#/$defs/${humanKey}` } : null ]; }; const shrinkRequired = s => { if (s.required !== undefined) s.required = s.required.filter((key => s.properties?.[key] !== undefined)); return s; }; LlmSchemaV3_1Composer.invert = props => { var _a; const next = schema => LlmSchemaV3_1Composer.invert({ components: props.components, $defs: props.$defs, schema }); if (LlmTypeCheckerV3_1.isArray(props.schema)) return { ...props.schema, ...LlmDescriptionInverter.array(props.schema.description), items: next(props.schema.items) }; else if (LlmTypeCheckerV3_1.isObject(props.schema)) return { ...props.schema, properties: props.schema.properties ? Object.fromEntries(Object.entries(props.schema.properties).map((([key, value]) => [ key, next(value) ]))) : undefined, additionalProperties: typeof props.schema.additionalProperties === "object" && props.schema.additionalProperties !== null ? next(props.schema.additionalProperties) : props.schema.additionalProperties }; else if (LlmTypeCheckerV3_1.isReference(props.schema)) { const key = props.schema.$ref.split("#/$defs/").at(-1) ?? ""; if (props.components.schemas?.[key] === undefined) { (_a = props.components).schemas ?? (_a.schemas = {}); props.components.schemas[key] = {}; props.components.schemas[key] = next(props.$defs[key] ?? {}); } return { ...props.schema, $ref: `#/components/schemas/${key}` }; } else if (LlmTypeCheckerV3_1.isInteger(props.schema) || LlmTypeCheckerV3_1.isNumber(props.schema)) return { ...props.schema, ...LlmDescriptionInverter.numeric(props.schema.description) }; else if (LlmTypeCheckerV3_1.isString(props.schema)) return { ...props.schema, ...LlmDescriptionInverter.string(props.schema.description) }; return props.schema; }; })(LlmSchemaV3_1Composer || (LlmSchemaV3_1Composer = {})); export { LlmSchemaV3_1Composer }; //# sourceMappingURL=LlmSchemaV3_1Composer.mjs.map