@samchon/openapi
Version:
OpenAPI definitions and converters for 'typia' and 'nestia'.
180 lines (171 loc) • 6.27 kB
text/typescript
import { OpenApi } from "../../OpenApi";
import { OpenApiV3 } from "../../OpenApiV3";
import { OpenApiV3_1 } from "../../OpenApiV3_1";
import { IGeminiSchema } from "../../structures/IGeminiSchema";
import { ILlmFunction } from "../../structures/ILlmFunction";
import { ILlmSchemaV3 } from "../../structures/ILlmSchemaV3";
import { IOpenApiSchemaError } from "../../structures/IOpenApiSchemaError";
import { IResult } from "../../structures/IResult";
import { LlmTypeCheckerV3 } from "../../utils/LlmTypeCheckerV3";
import { OpenApiTypeChecker } from "../../utils/OpenApiTypeChecker";
import { LlmParametersFinder } from "./LlmParametersComposer";
import { LlmSchemaV3Composer } from "./LlmSchemaV3Composer";
export namespace GeminiSchemaComposer {
/**
* @internal
*/
export const IS_DEFS = false;
export const parameters = (props: {
config: IGeminiSchema.IConfig;
components: OpenApi.IComponents;
schema: OpenApi.IJsonSchema.IObject | OpenApi.IJsonSchema.IReference;
accessor?: string;
refAccessor?: string;
}): IResult<IGeminiSchema.IParameters, IOpenApiSchemaError> => {
const entity: IResult<OpenApi.IJsonSchema.IObject, IOpenApiSchemaError> =
LlmParametersFinder.parameters({
...props,
method: "GeminiSchemaComposer.parameters",
});
if (entity.success === false) return entity;
return schema(props) as IResult<
IGeminiSchema.IParameters,
IOpenApiSchemaError
>;
};
export const schema = (props: {
config: IGeminiSchema.IConfig;
components: OpenApi.IComponents;
schema: OpenApi.IJsonSchema;
accessor?: string;
refAccessor?: string;
}): IResult<IGeminiSchema, IOpenApiSchemaError> => {
// TRANSFORM TO LLM SCHEMA OF V3.0
const result: IResult<ILlmSchemaV3, IOpenApiSchemaError> =
LlmSchemaV3Composer.schema({
...props,
config: {
recursive: props.config.recursive,
constraint: false,
},
validate: (next, accessor): IOpenApiSchemaError.IReason[] => {
if (OpenApiTypeChecker.isObject(next)) {
if (!!next.additionalProperties)
return [
{
schema: next,
accessor: `${accessor}.additionalProperties`,
message: "Gemini does not allow additionalProperties.",
},
];
} else if (
OpenApiTypeChecker.isOneOf(next) &&
isOneOf(props.components)(next)
)
return [
{
schema: next,
accessor: accessor,
message: "Gemini does not allow union type.",
},
];
return [];
},
});
if (result.success === false) return result;
// SPECIALIZATIONS
LlmTypeCheckerV3.visit({
schema: result.value,
closure: (v) => {
if (v.title !== undefined) {
if (v.description === undefined) v.description = v.title;
else {
const title: string = v.title.endsWith(".")
? v.title.substring(0, v.title.length - 1)
: v.title;
v.description = v.description.startsWith(title)
? v.description
: `${title}.\n\n${v.description}`;
}
delete v.title;
}
if (
LlmTypeCheckerV3.isObject(v) &&
v.additionalProperties !== undefined
) {
delete (v as Partial<ILlmSchemaV3.IObject>).additionalProperties;
}
},
});
// DO NOT ALLOW UNION TYPE
return result;
};
export const separateParameters = (props: {
predicate: (schema: IGeminiSchema) => boolean;
parameters: IGeminiSchema.IParameters;
}): ILlmFunction.ISeparated<"gemini"> => {
const separated: ILlmFunction.ISeparated<"3.0"> =
LlmSchemaV3Composer.separateParameters(
props as {
predicate: (schema: ILlmSchemaV3) => boolean;
parameters: ILlmSchemaV3.IParameters;
},
);
return separated as any as ILlmFunction.ISeparated<"gemini">;
};
export const invert = (props: {
schema: IGeminiSchema;
}): OpenApi.IJsonSchema => LlmSchemaV3Composer.invert(props);
}
const isOneOf =
(components: OpenApi.IComponents) =>
(schema: OpenApi.IJsonSchema): boolean => {
const union: OpenApiV3_1.IJsonSchema[] = [];
const already: Set<string> = new Set();
const visit = (schema: OpenApi.IJsonSchema): void => {
if (
OpenApiTypeChecker.isBoolean(schema) ||
OpenApiTypeChecker.isInteger(schema) ||
OpenApiTypeChecker.isNumber(schema) ||
OpenApiTypeChecker.isString(schema)
)
union.push({ ...schema });
else if (
OpenApiTypeChecker.isArray(schema) ||
OpenApiTypeChecker.isTuple(schema) ||
OpenApiTypeChecker.isObject(schema)
)
union.push(schema);
else if (OpenApiTypeChecker.isOneOf(schema)) schema.oneOf.forEach(visit);
else if (OpenApiTypeChecker.isReference(schema)) {
if (already.has(schema.$ref)) union.push(schema);
else {
already.add(schema.$ref);
const target: OpenApi.IJsonSchema | undefined =
components.schemas?.[schema.$ref.split("/").pop()!];
if (target === undefined) union.push(schema);
else visit(target);
}
}
};
const visitConstant = (schema: OpenApi.IJsonSchema): void => {
const insert = (value: any): void => {
const matched: OpenApiV3_1.IJsonSchema.INumber | undefined = union.find(
(u) =>
(u as OpenApiV3.IJsonSchema.__ISignificant<any>).type ===
typeof value,
) as OpenApiV3.IJsonSchema.INumber | undefined;
if (matched !== undefined) {
matched.enum ??= [];
matched.enum.push(value);
} else union.push({ type: typeof value as "number", enum: [value] });
};
if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const);
else if (OpenApiTypeChecker.isOneOf(schema))
for (const u of schema.oneOf)
if (OpenApiTypeChecker.isConstant(u)) insert(u.const);
};
visit(schema);
visitConstant(schema);
return union.length > 1;
};