UNPKG

@samchon/openapi

Version:

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

188 lines (180 loc) 8.73 kB
import { OpenApiValidator } from "../utils/OpenApiValidator.mjs"; import { LlmSchemaComposer } from "./LlmSchemaComposer.mjs"; var HttpLlmComposer; (function(HttpLlmComposer) { HttpLlmComposer.application = props => { const errors = props.migrate.errors.filter((e => e.operation()["x-samchon-human"] !== true)).map((e => ({ method: e.method, path: e.path, messages: e.messages, operation: () => e.operation(), route: () => undefined }))); const functions = props.migrate.routes.filter((e => e.operation()["x-samchon-human"] !== true)).map(((route, i) => { if (route.method === "head") { errors.push({ method: route.method, path: route.path, messages: [ "HEAD method is not supported in the LLM application." ], operation: () => route.operation(), route: () => route }); return null; } else if (route.body?.type === "multipart/form-data" || route.success?.type === "multipart/form-data") { errors.push({ method: route.method, path: route.path, messages: [ `The "multipart/form-data" content type is not supported in the LLM application.` ], operation: () => route.operation(), route: () => route }); return null; } const localErrors = []; const func = composeFunction({ model: props.model, config: props.options, components: props.migrate.document().components, route, errors: localErrors }); if (func === null) errors.push({ method: route.method, path: route.path, messages: localErrors, operation: () => route.operation(), route: () => route }); return func; })).filter((v => v !== null)); const app = { model: props.model, options: props.options, functions, errors }; HttpLlmComposer.shorten(app, props.options?.maxLength ?? 64); return app; }; const composeFunction = props => { const endpoint = `$input.paths[${JSON.stringify(props.route.path)}][${JSON.stringify(props.route.method)}]`; const operation = props.route.operation(); const description = (() => { if (!operation.summary?.length || !operation.description?.length) return [ operation.summary || operation.description, operation.summary?.length ?? operation.description?.length ?? 0 ]; const summary = operation.summary.endsWith(".") ? operation.summary.slice(0, -1) : operation.summary; const final = operation.description.startsWith(summary) ? operation.description : summary + ".\n\n" + operation.description; return [ final, final.length ]; })(); if (description[1] > 1024) { props.errors.push(`The description of the function is too long (must be equal or less than 1,024 characters, but ${description[1].toLocaleString()} length).`); } const name = emend(props.route.accessor.join("_")); const isNameVariable = /^[a-zA-Z0-9_-]+$/.test(name); const isNameStartsWithNumber = /^[0-9]/.test(name[0] ?? ""); if (isNameVariable === false) props.errors.push(`Elements of path (separated by '/') must be composed with alphabets, numbers, underscores, and hyphens`); const parameters = { type: "object", properties: Object.fromEntries([ ...props.route.parameters.map((s => [ s.key, { ...s.schema, title: s.parameter().title ?? s.schema.title, description: s.parameter().description ?? s.schema.description } ])), ...props.route.query ? [ [ props.route.query.key, { ...props.route.query.schema, title: props.route.query.title() ?? props.route.query.schema.title, description: props.route.query.description() ?? props.route.query.schema.description } ] ] : [], ...props.route.body ? [ [ props.route.body.key, { ...props.route.body.schema, description: props.route.body.description() ?? props.route.body.schema.description } ] ] : [] ]) }; parameters.required = Object.keys(parameters.properties ?? {}); const llmParameters = LlmSchemaComposer.parameters(props.model)({ config: props.config, components: props.components, schema: parameters, accessor: `${endpoint}.parameters` }); const output = props.route.success ? LlmSchemaComposer.schema(props.model)({ config: props.config, components: props.components, schema: props.route.success.schema, accessor: `${endpoint}.responses[${JSON.stringify(props.route.success.status)}][${JSON.stringify(props.route.success.type)}].schema`, $defs: llmParameters.success ? llmParameters.value.$defs : {} }) : undefined; if (output?.success === false || llmParameters.success === false || isNameVariable === false || isNameStartsWithNumber === true || description[1] > 1024) { if (output?.success === false) props.errors.push(...output.error.reasons.map((r => `${r.accessor}: ${r.message}`))); if (llmParameters.success === false) props.errors.push(...llmParameters.error.reasons.map((r => { const accessor = r.accessor.replace(`parameters.properties["body"]`, `requestBody.content[${JSON.stringify(props.route.body?.type ?? "application/json")}].schema`); return `${accessor}: ${r.message}`; }))); return null; } return { method: props.route.method, path: props.route.path, name, parameters: llmParameters.value, separated: props.config.separate ? LlmSchemaComposer.separateParameters(props.model)({ predicate: props.config.separate, parameters: llmParameters.value, equals: props.config.equals ?? false }) : undefined, output: output?.value, description: description[0], deprecated: operation.deprecated, tags: operation.tags, validate: OpenApiValidator.create({ components: props.components, schema: parameters, required: true, equals: props.config.equals ?? false }), route: () => props.route, operation: () => props.route.operation() }; }; HttpLlmComposer.shorten = (app, limit = 64) => { const dictionary = new Set; const longFunctions = []; for (const func of app.functions) { dictionary.add(func.name); if (func.name.length > limit) { longFunctions.push(func); } } if (longFunctions.length === 0) return; let index = 0; for (const func of longFunctions) { let success = false; let rename = str => { dictionary.delete(func.name); dictionary.add(str); func.name = str; success = true; }; for (let i = 1; i < func.route().accessor.length; ++i) { const shortName = func.route().accessor.slice(i).join("_"); if (shortName.length > limit - 8) continue; else if (dictionary.has(shortName) === false) rename(shortName); else { const newName = `_${index}_${shortName}`; if (dictionary.has(newName) === true) continue; rename(newName); ++index; } break; } if (success === false) rename(randomFormatUuid()); } }; })(HttpLlmComposer || (HttpLlmComposer = {})); const randomFormatUuid = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c => { const r = Math.random() * 16 | 0; const v = c === "x" ? r : r & 3 | 8; return v.toString(16); })); const emend = str => { for (const ch of FORBIDDEN) str = str.split(ch).join("_"); return str; }; const FORBIDDEN = [ "$", "%", "." ]; export { HttpLlmComposer }; //# sourceMappingURL=HttpLlmApplicationComposer.mjs.map