@samchon/openapi
Version:
OpenAPI definitions and converters for 'typia' and 'nestia'.
419 lines (408 loc) • 18.6 kB
JavaScript
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