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