UNPKG

@samchon/openapi

Version:

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

371 lines (360 loc) 15.7 kB
import { ChatGptTypeChecker } from "../../utils/ChatGptTypeChecker.mjs"; import { LlmTypeCheckerV3_1 } from "../../utils/LlmTypeCheckerV3_1.mjs"; import { NamingConvention } from "../../utils/NamingConvention.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 { LlmSchemaV3_1Composer } from "./LlmSchemaV3_1Composer.mjs"; var ChatGptSchemaComposer; (function(ChatGptSchemaComposer) { ChatGptSchemaComposer.IS_DEFS = true; ChatGptSchemaComposer.parameters = props => { var _a; (_a = props.config).strict ?? (_a.strict = false); const result = LlmSchemaV3_1Composer.parameters({ ...props, config: { reference: props.config.reference, constraint: false }, validate: props.config.strict === true ? validateStrict : undefined }); if (result.success === false) return result; for (const key of Object.keys(result.value.$defs)) result.value.$defs[key] = transform({ config: props.config, schema: result.value.$defs[key] }); return { success: true, value: transform({ config: props.config, schema: result.value }) }; }; ChatGptSchemaComposer.schema = props => { var _a; (_a = props.config).strict ?? (_a.strict = false); const oldbie = new Set(Object.keys(props.$defs)); const result = LlmSchemaV3_1Composer.schema({ ...props, config: { reference: props.config.reference, constraint: false }, validate: props.config.strict === true ? validateStrict : undefined }); if (result.success === false) return result; for (const key of Object.keys(props.$defs)) if (oldbie.has(key) === false) props.$defs[key] = transform({ config: props.config, schema: props.$defs[key] }); return { success: true, value: transform({ config: props.config, schema: result.value }) }; }; const validateStrict = (schema, accessor) => { const reasons = []; if (OpenApiTypeChecker.isObject(schema)) { if (!!schema.additionalProperties) reasons.push({ schema, accessor: `${accessor}.additionalProperties`, message: "ChatGPT does not allow additionalProperties in strict mode, the dynamic key typed object." }); for (const key of Object.keys(schema.properties ?? {})) if (schema.required?.includes(key) === false) reasons.push({ schema, accessor: `${accessor}.properties.${key}`, message: "ChatGPT does not allow optional properties in strict mode." }); } return reasons; }; const transform = 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(ChatGptSchemaComposer.schema).filter((([key, value]) => key.startsWith("x-") && value !== undefined))) }; const visit = input => { if (LlmTypeCheckerV3_1.isOneOf(input)) input.oneOf.forEach(visit); else if (LlmTypeCheckerV3_1.isArray(input)) union.push({ ...input, items: transform({ config: props.config, schema: input.items }) }); else if (LlmTypeCheckerV3_1.isObject(input)) union.push({ ...input, properties: Object.fromEntries(Object.entries(input.properties).map((([key, value]) => [ key, transform({ config: props.config, schema: value }) ]))), additionalProperties: props.config.strict === true ? false : typeof input.additionalProperties === "object" && input.additionalProperties !== null ? transform({ config: props.config, schema: input.additionalProperties }) : input.additionalProperties, description: JsonDescriptionUtil.take(input) }); else if (LlmTypeCheckerV3_1.isConstant(input) === false) union.push(input); }; const visitConstant = input => { const insert = value => { const matched = union.find((u => u?.type === typeof value)); if (matched !== undefined) { matched.enum ?? (matched.enum = []); matched.enum.push(value); } else union.push({ type: typeof value, enum: [ value ] }); }; if (OpenApiTypeChecker.isConstant(input)) insert(input.const); else if (OpenApiTypeChecker.isOneOf(input)) input.oneOf.forEach((s => visitConstant(s))); }; visit(props.schema); visitConstant(props.schema); if (union.length === 0) return { ...attribute, type: undefined }; else if (union.length === 1) return { ...attribute, ...union[0], description: ChatGptTypeChecker.isReference(union[0]) ? undefined : union[0].description ?? attribute.description }; return { ...attribute, anyOf: union.map((u => ({ ...u, description: ChatGptTypeChecker.isReference(u) ? undefined : u.description }))) }; }; ChatGptSchemaComposer.separateParameters = props => { const convention = props.convention ?? ((key, type) => `${key}.${NamingConvention.capitalize(type)}`); const [llm, human] = separateObject({ predicate: props.predicate, convention, $defs: props.parameters.$defs, schema: props.parameters }); if (llm === null || human === null) return { llm: llm ?? { type: "object", properties: {}, required: [], additionalProperties: false, $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: ChatGptSchemaComposer.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 (ChatGptTypeChecker.isUnknown(props.schema) || ChatGptTypeChecker.isAnyOf(props.schema)) return [ props.schema, null ]; else if (ChatGptTypeChecker.isObject(props.schema)) return separateObject({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema }); else if (ChatGptTypeChecker.isArray(props.schema)) return separateArray({ predicate: props.predicate, convention: props.convention, $defs: props.$defs, schema: props.schema }); else if (ChatGptTypeChecker.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 => { s.required = s.required.filter((key => s.properties?.[key] !== undefined)); return s; }; ChatGptSchemaComposer.invert = props => { const union = []; const attribute = { title: props.schema.title, description: props.schema.description, ...Object.fromEntries(Object.entries(props.schema).filter((([key, value]) => key.startsWith("x-") && value !== undefined))), example: props.schema.example, examples: props.schema.examples }; const next = schema => ChatGptSchemaComposer.invert({ components: props.components, $defs: props.$defs, schema }); const visit = schema => { var _a; if (ChatGptTypeChecker.isArray(schema)) union.push({ ...schema, ...LlmDescriptionInverter.array(schema.description), items: next(schema.items) }); else if (ChatGptTypeChecker.isObject(schema)) union.push({ ...schema, properties: Object.fromEntries(Object.entries(schema.properties).map((([key, value]) => [ key, next(value) ]))), additionalProperties: typeof schema.additionalProperties === "object" && schema.additionalProperties !== null ? next(schema.additionalProperties) : schema.additionalProperties }); else if (ChatGptTypeChecker.isAnyOf(schema)) schema.anyOf.forEach(visit); else if (ChatGptTypeChecker.isReference(schema)) { const key = schema.$ref.split("#/$defs/")[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] ?? {}); } union.push({ ...schema, $ref: `#/components/schemas/${key}` }); } else if (ChatGptTypeChecker.isBoolean(schema)) if (!!schema.enum?.length) schema.enum.forEach((v => union.push({ const: v }))); else union.push(schema); else if (ChatGptTypeChecker.isInteger(schema) || ChatGptTypeChecker.isNumber(schema)) if (!!schema.enum?.length) schema.enum.forEach((v => union.push({ const: v }))); else union.push({ ...schema, ...LlmDescriptionInverter.numeric(schema.description), ...{ enum: undefined } }); else if (ChatGptTypeChecker.isString(schema)) if (!!schema.enum?.length) schema.enum.forEach((v => union.push({ const: v }))); else union.push({ ...schema, ...LlmDescriptionInverter.string(schema.description), ...{ enum: undefined } }); else union.push({ ...schema }); }; visit(props.schema); return { ...attribute, ...union.length === 0 ? { type: undefined } : union.length === 1 ? { ...union[0] } : { oneOf: union.map((u => ({ ...u, nullable: undefined }))) } }; }; })(ChatGptSchemaComposer || (ChatGptSchemaComposer = {})); export { ChatGptSchemaComposer }; //# sourceMappingURL=ChatGptSchemaComposer.mjs.map