Rhys Sullivanfix naming and release7b942677 months agoHistory
333 lines · 9.7 KB · TypeScript
RawDownload
import * as Match from "effect/Match";import { typeSignatureFromSchema } from "@executor/codemode-core"; import type {  OpenApiExample,  OpenApiMediaContent,  OpenApiInvocationPayload,  OpenApiRefHintTable,  OpenApiResponseVariant,  OpenApiToolDocumentation,  OpenApiToolProviderData,} from "./openapi-types";import {  openApiProviderDataFromDefinition,  type OpenApiToolDefinition,} from "./openapi-definitions";import {  resolveSchemaWithRefHints,  resolveTypingSchemasWithRefHints,} from "./openapi-schema-refs"; const asRecord = (value: unknown): Record<string, unknown> =>  typeof value === "object" && value !== null && !Array.isArray(value)    ? (value as Record<string, unknown>)    : {}; const isStrictEmptyObjectSchema = (value: unknown): boolean => {  const schema = asRecord(value);  if (schema.type !== "object" && schema.properties === undefined) {    return false;  }   const properties = asRecord(schema.properties);  return Object.keys(properties).length === 0 && schema.additionalProperties === false;}; export const openApiOutputTypeSignatureFromSchema = (  schema: unknown,  maxLength: number = 320, ): string => {  if (schema === undefined || schema === null) {    return "void";  }   if (isStrictEmptyObjectSchema(schema)) {    return "{}";  }   return typeSignatureFromSchema(schema, "unknown", maxLength);}; const firstExample = (  examples: ReadonlyArray<OpenApiExample> | undefined,): OpenApiExample | undefined => examples?.[0]; const schemaProperty = (  schema: unknown,  propertyName: string,): unknown | undefined => {  const record = asRecord(schema);  const properties = asRecord(record.properties);  return properties[propertyName];}; const groupedSchemaForParameter = (  schema: unknown,  location: OpenApiInvocationPayload["parameters"][number]["location"],  name: string,): unknown | undefined => {  const direct = schemaProperty(schema, name);  if (direct !== undefined) {    return direct;  }   const groupKey =    location === "header"      ? "headers"      : location === "cookie"        ? "cookies"        : location;  const groupSchema = schemaProperty(schema, groupKey);  return groupSchema === undefined ? undefined : schemaProperty(groupSchema, name);}; const preferredContentSchema = (  contents: ReadonlyArray<OpenApiMediaContent> | undefined,): unknown | undefined => {  if (!contents || contents.length === 0) {    return undefined;  }   const preferred = [...contents].sort((left, right) => left.mediaType.localeCompare(right.mediaType))    .find((content) => content.mediaType === "application/json")    ?? [...contents].find((content) => content.mediaType.toLowerCase().includes("+json"))    ?? [...contents].find((content) => content.mediaType.toLowerCase().includes("json"))    ?? contents[0];   return preferred?.schema;}; const withDescription = (  schema: unknown,  description: string | undefined,): unknown => {  if (!description) {    return schema;  }   const record = asRecord(schema);  if (Object.keys(record).length === 0) {    return schema;  }   return {    ...record,    description,  };}; const callInputSchemaFromInvocation = (input: {  invocation: OpenApiInvocationPayload;  documentation?: OpenApiToolDocumentation;  parameterSourceSchema?: unknown;  requestBodySchema?: unknown;}): Record<string, unknown> | undefined => {  const invocation = input.invocation;  const properties: Record<string, unknown> = {};  const required: string[] = [];   for (const parameter of invocation.parameters) {    const matchingDocs = input.documentation?.parameters.find((candidate) =>      candidate.name === parameter.name && candidate.location === parameter.location    );    const parameterSchema = groupedSchemaForParameter(      input.parameterSourceSchema,      parameter.location,      parameter.name,    ) ?? preferredContentSchema(parameter.content) ?? {      type: "string",    };     properties[parameter.name] = withDescription(      parameterSchema,      matchingDocs?.description,    );    if (parameter.required) {      required.push(parameter.name);    }  }   if (invocation.requestBody) {    properties.body = withDescription(      input.requestBodySchema ?? preferredContentSchema(invocation.requestBody.contents) ?? {        type: "object",      },      input.documentation?.requestBody?.description,    );    if (invocation.requestBody.required) {      required.push("body");    }  }   return Object.keys(properties).length > 0    ? {      type: "object",      properties,      ...(required.length > 0 ? { required } : {}),      additionalProperties: false,    }    : undefined;}; const schemaRefPlaceholder = (  refHintKey: string | undefined,): Record<string, string> | undefined =>  typeof refHintKey === "string" && refHintKey.length > 0    ? { $ref: refHintKey }    : undefined; const inferRefHintPlaceholders = (  definition: OpenApiToolDefinition,): {  requestBodySchema?: Record<string, string>;  outputSchema?: Record<string, string>;} => {  const refHintKeys = definition.typing?.refHintKeys ?? [];   return Match.value(definition.invocation.requestBody).pipe(    Match.when(null, () => ({      outputSchema: schemaRefPlaceholder(refHintKeys[0]),    })),    Match.orElse(() => ({      requestBodySchema: schemaRefPlaceholder(refHintKeys[0]),      outputSchema: schemaRefPlaceholder(refHintKeys[1]),    })),  );}; const resolvePresentationSchemas = (input: {  definition: OpenApiToolDefinition;  refHintTable?: Readonly<OpenApiRefHintTable>;}): {  inputSchema?: unknown;  outputSchema?: unknown;} => {  const resolvedTyping = resolveTypingSchemasWithRefHints(    input.definition.typing,    input.refHintTable,  );  const inferredRefPlaceholders = inferRefHintPlaceholders(input.definition);  const resolvedRequestBodySchema = resolveSchemaWithRefHints(    inferredRefPlaceholders.requestBodySchema,    input.refHintTable,  );  const requestBodySchema =    schemaProperty(resolvedTyping.inputSchema, "body")    ?? schemaProperty(resolvedTyping.inputSchema, "input")    ?? (resolvedRequestBodySchema !== undefined && resolvedRequestBodySchema !== null      ? resolvedRequestBodySchema      : undefined);   const inputSchema = callInputSchemaFromInvocation({    invocation: input.definition.invocation,    documentation: input.definition.documentation,    parameterSourceSchema: resolvedTyping.inputSchema,    ...(requestBodySchema !== undefined      ? { requestBodySchema }      : {}),  })    ?? resolvedTyping.inputSchema    ?? undefined;  const outputSchema =    resolvedTyping.outputSchema    ?? resolveSchemaWithRefHints(      inferredRefPlaceholders.outputSchema,      input.refHintTable,    );   return {    ...(inputSchema !== undefined && inputSchema !== null ? { inputSchema } : {}),    ...(outputSchema !== undefined && outputSchema !== null ? { outputSchema } : {}),  };}; const resolveResponseVariantsWithRefHints = (input: {  variants: ReadonlyArray<OpenApiResponseVariant> | undefined;  refHintTable?: Readonly<OpenApiRefHintTable>;}): ReadonlyArray<OpenApiResponseVariant> | undefined => {  if (!input.variants || input.variants.length === 0) {    return undefined;  }   return input.variants.map((variant) => {    const resolvedSchema = resolveSchemaWithRefHints(      variant.schema,      input.refHintTable,    );     return {      ...variant,      ...(resolvedSchema !== undefined ? { schema: resolvedSchema } : {}),    };  });}; const buildExampleInput = (  documentation: OpenApiToolDocumentation | undefined,): Record<string, unknown> | undefined => {  if (!documentation) {    return undefined;  }   const input: Record<string, unknown> = {};   for (const parameter of documentation.parameters) {    const example = firstExample(parameter.examples);    if (!example) {      continue;    }     input[parameter.name] = JSON.parse(example.valueJson) as unknown;  }   const requestBodyExample = firstExample(documentation.requestBody?.examples);  if (requestBodyExample) {    input.body = JSON.parse(requestBodyExample.valueJson) as unknown;  }   return Object.keys(input).length > 0 ? input : undefined;}; const buildExampleOutput = (  documentation: OpenApiToolDocumentation | undefined,): unknown | undefined => {  const example = firstExample(documentation?.response?.examples)?.valueJson;  return example ? JSON.parse(example) as unknown : undefined;}; export type OpenApiToolPresentation = {  inputTypePreview: string;  outputTypePreview: string;  inputSchema?: unknown;  outputSchema?: unknown;  exampleInput?: unknown;  exampleOutput?: unknown;  providerData: OpenApiToolProviderData;}; export const buildOpenApiToolPresentation = (input: {  definition: OpenApiToolDefinition;  refHintTable?: Readonly<OpenApiRefHintTable>;}): OpenApiToolPresentation => {  const { inputSchema, outputSchema } = resolvePresentationSchemas(input);  const responses = resolveResponseVariantsWithRefHints({    variants: input.definition.responses,    refHintTable: input.refHintTable,  });  const exampleInput = buildExampleInput(input.definition.documentation);  const exampleOutput = buildExampleOutput(input.definition.documentation);   return {    inputTypePreview: typeSignatureFromSchema(inputSchema, "unknown", Infinity),    outputTypePreview: openApiOutputTypeSignatureFromSchema(outputSchema, Infinity),    ...(inputSchema !== undefined ? { inputSchema } : {}),    ...(outputSchema !== undefined ? { outputSchema } : {}),    ...(exampleInput !== undefined ? { exampleInput } : {}),    ...(exampleOutput !== undefined ? { exampleOutput } : {}),    providerData: {      ...openApiProviderDataFromDefinition(input.definition),      ...(responses ? { responses } : {}),    } satisfies OpenApiToolProviderData,  };};