Showing the changes to packages/codemode-openapi/src (8 of 78 files). Show all
Unified · Split
| @@ -8,13 +8,13 @@ export { · 7 unmodified lines | ||
| 8 | 8 | } from "./openapi-extraction"; |
| 9 | 9 | export { |
| 10 | 10 | compileOpenApiToolDefinitions, |
| 11 | − openApiProviderDataJsonFromDefinition, | |
| 11 | + openApiProviderDataFromDefinition, | |
| 12 | 12 | OpenApiToolDefinitionSchema, |
| 13 | 13 | type OpenApiToolDefinition, |
| 14 | 14 | } from "./openapi-definitions"; |
| 15 | 15 | export { |
| 16 | 16 | buildOpenApiToolPresentation, |
| 17 | − openApiOutputTypeSignatureFromSchemaJson, | |
| 17 | + openApiOutputTypeSignatureFromSchema, | |
| 18 | 18 | type OpenApiToolPresentation, |
| 19 | 19 | } from "./openapi-tool-presentation"; |
| 20 | 20 | export { |
| @@ -23,7 +23,7 @@ export { · 2 unmodified lines | ||
| 23 | 23 | createOpenApiToolsFromManifest, |
| 24 | 24 | createOpenApiToolsFromSpec, |
| 25 | 25 | } from "./openapi-tools"; |
| 26 | −export { resolveSchemaJsonWithRefHints, resolveTypingSchemasWithRefHints } from "./openapi-schema-refs"; | |
| 26 | +export { resolveSchemaWithRefHints, resolveTypingSchemasWithRefHints } from "./openapi-schema-refs"; | |
| 27 | 27 | export { |
| 28 | 28 | OPEN_API_HTTP_METHODS, |
| 29 | 29 | OPEN_API_PARAMETER_LOCATIONS, |
| @@ -236,10 +236,9 @@ export const compileOpenApiToolDefinitions = ( · 235 unmodified lines | ||
| 236 | 236 | ); |
| 237 | 237 | }; |
| 238 | 238 | |
| 239 | −export const openApiProviderDataJsonFromDefinition = ( | |
| 239 | +export const openApiProviderDataFromDefinition = ( | |
| 240 | 240 | definition: OpenApiToolDefinition, |
| 241 | −): string => | |
| 242 | − JSON.stringify({ | |
| 241 | +): typeof OpenApiToolProviderDataSchema.Type => ({ | |
| 243 | 242 | kind: "openapi", |
| 244 | 243 | toolId: definition.toolId, |
| 245 | 244 | rawToolId: definition.rawToolId, |
| @@ -26,8 +26,8 @@ type ParsedInputSchema = { · 25 unmodified lines | ||
| 26 | 26 | required?: Array<string>; |
| 27 | 27 | }; |
| 28 | 28 | |
| 29 | −const parseInputSchema = (inputSchemaJson: string | undefined): ParsedInputSchema | null => | |
| 30 | − inputSchemaJson ? (JSON.parse(inputSchemaJson) as ParsedInputSchema) : null; | |
| 29 | +const parseInputSchema = (inputSchema: unknown): ParsedInputSchema | null => | |
| 30 | + inputSchema ? (inputSchema as ParsedInputSchema) : null; | |
| 31 | 31 | |
| 32 | 32 | const expectedTools = [ |
| 33 | 33 | { |
| @@ -89,7 +89,7 @@ describe("openapi-extraction real specs", () => { · 55 unmodified lines | ||
| 89 | 89 | expect(tool?.path).toBe(expectedTool.path); |
| 90 | 90 | expect(tool?.tags).toEqual(expectedTool.tags); |
| 91 | 91 | |
| 92 | − const inputSchema = parseInputSchema(tool?.typing?.inputSchemaJson); | |
| 92 | + const inputSchema = parseInputSchema(tool?.typing?.inputSchema); | |
| 93 | 93 | expect(inputSchema?.type).toBe("object"); |
| 94 | 94 | expect(Object.keys(inputSchema?.properties ?? {})).toEqual( |
| 95 | 95 | expect.arrayContaining([...expectedTool.requiredInputs]), |
| @@ -2,8 +2,8 @@ type JsonObject = Record<string, unknown>; · 1 unmodified line | ||
| 2 | 2 | type RefHintValue = string | JsonObject; |
| 3 | 3 | |
| 4 | 4 | type DiscoveryTypingLike = { |
| 5 | − inputSchemaJson?: string; | |
| 6 | − outputSchemaJson?: string; | |
| 5 | + inputSchema?: unknown; | |
| 6 | + outputSchema?: unknown; | |
| 7 | 7 | }; |
| 8 | 8 | |
| 9 | 9 | const isJsonObject = (value: unknown): value is JsonObject => |
| @@ -92,50 +92,40 @@ const resolveSchemaNode = ( · 82 unmodified lines | ||
| 92 | 92 | return next; |
| 93 | 93 | }; |
| 94 | 94 | |
| 95 | −export const resolveSchemaJsonWithRefHints = ( | |
| 96 | − schemaJson: string | undefined, | |
| 95 | +export const resolveSchemaWithRefHints = ( | |
| 96 | + schema: unknown, | |
| 97 | 97 | refHintTable: Readonly<Record<string, RefHintValue>> | undefined, |
| 98 | −): string | null => { | |
| 99 | − if (!schemaJson) { | |
| 98 | +): unknown | null => { | |
| 99 | + if (schema === undefined || schema === null) { | |
| 100 | 100 | return null; |
| 101 | 101 | } |
| 102 | 102 | |
| 103 | − const parsedSchema = parseJson(schemaJson); | |
| 104 | − if (parsedSchema === null) { | |
| 105 | − return schemaJson; | |
| 106 | − } | |
| 107 | − | |
| 108 | 103 | if (!refHintTable || Object.keys(refHintTable).length === 0) { |
| 109 | − return schemaJson; | |
| 104 | + return schema; | |
| 110 | 105 | } |
| 111 | 106 | |
| 112 | 107 | const resolved = resolveSchemaNode( |
| 113 | − parsedSchema, | |
| 108 | + schema, | |
| 114 | 109 | refHintTable, |
| 115 | 110 | new Map<string, unknown>(), |
| 116 | 111 | new Set<string>(), |
| 117 | 112 | ); |
| 118 | − | |
| 119 | − try { | |
| 120 | − return JSON.stringify(resolved); | |
| 121 | − } catch { | |
| 122 | − return schemaJson; | |
| 123 | − } | |
| 113 | + return resolved; | |
| 124 | 114 | }; |
| 125 | 115 | |
| 126 | 116 | export const resolveTypingSchemasWithRefHints = ( |
| 127 | 117 | typing: DiscoveryTypingLike | undefined, |
| 128 | 118 | refHintTable: Readonly<Record<string, RefHintValue>> | undefined, |
| 129 | 119 | ): { |
| 130 | − inputSchemaJson: string | null; | |
| 131 | − outputSchemaJson: string | null; | |
| 120 | + inputSchema: unknown | null; | |
| 121 | + outputSchema: unknown | null; | |
| 132 | 122 | } => ({ |
| 133 | − inputSchemaJson: resolveSchemaJsonWithRefHints( | |
| 134 | − typing?.inputSchemaJson, | |
| 123 | + inputSchema: resolveSchemaWithRefHints( | |
| 124 | + typing?.inputSchema, | |
| 135 | 125 | refHintTable, |
| 136 | 126 | ), |
| 137 | − outputSchemaJson: resolveSchemaJsonWithRefHints( | |
| 138 | − typing?.outputSchemaJson, | |
| 127 | + outputSchema: resolveSchemaWithRefHints( | |
| 128 | + typing?.outputSchema, | |
| 139 | 129 | refHintTable, |
| 140 | 130 | ), |
| 141 | 131 | }); |
| @@ -1,22 +1,16 @@ | ||
| 1 | −import { typeSignatureFromSchemaJson } from "@executor/codemode-core"; | |
| 1 | +import { typeSignatureFromSchema } from "@executor/codemode-core"; | |
| 2 | 2 | |
| 3 | 3 | import type { |
| 4 | 4 | OpenApiExample, |
| 5 | + OpenApiInvocationPayload, | |
| 5 | 6 | OpenApiToolDocumentation, |
| 7 | + OpenApiToolProviderData, | |
| 6 | 8 | } from "./openapi-types"; |
| 7 | 9 | import { |
| 8 | − openApiProviderDataJsonFromDefinition, | |
| 10 | + openApiProviderDataFromDefinition, | |
| 9 | 11 | type OpenApiToolDefinition, |
| 10 | 12 | } from "./openapi-definitions"; |
| 11 | 13 | |
| 12 | −const parseJson = (value: string): unknown | undefined => { | |
| 13 | − try { | |
| 14 | − return JSON.parse(value) as unknown; | |
| 15 | − } catch { | |
| 16 | − return undefined; | |
| 17 | − } | |
| 18 | −}; | |
| 19 | − | |
| 20 | 14 | const asRecord = (value: unknown): Record<string, unknown> => |
| 21 | 15 | typeof value === "object" && value !== null && !Array.isArray(value) |
| 22 | 16 | ? (value as Record<string, unknown>) |
| @@ -32,33 +26,62 @@ const isStrictEmptyObjectSchema = (value: unknown): boolean => { · 9 unmodified lines | ||
| 32 | 26 | return Object.keys(properties).length === 0 && schema.additionalProperties === false; |
| 33 | 27 | }; |
| 34 | 28 | |
| 35 | −export const openApiOutputTypeSignatureFromSchemaJson = ( | |
| 36 | − schemaJson: string | undefined, | |
| 29 | +export const openApiOutputTypeSignatureFromSchema = ( | |
| 30 | + schema: unknown, | |
| 37 | 31 | maxLength: number = 320, |
| 38 | 32 | ): string => { |
| 39 | − if (!schemaJson) { | |
| 33 | + if (schema === undefined || schema === null) { | |
| 40 | 34 | return "void"; |
| 41 | 35 | } |
| 42 | 36 | |
| 43 | − const parsed = parseJson(schemaJson); | |
| 44 | − if (parsed === undefined) { | |
| 45 | − return "unknown"; | |
| 46 | − } | |
| 47 | − | |
| 48 | − if (isStrictEmptyObjectSchema(parsed)) { | |
| 37 | + if (isStrictEmptyObjectSchema(schema)) { | |
| 49 | 38 | return "{}"; |
| 50 | 39 | } |
| 51 | 40 | |
| 52 | − return typeSignatureFromSchemaJson(schemaJson, "unknown", maxLength); | |
| 41 | + return typeSignatureFromSchema(schema, "unknown", maxLength); | |
| 53 | 42 | }; |
| 54 | 43 | |
| 55 | 44 | const firstExample = ( |
| 56 | 45 | examples: ReadonlyArray<OpenApiExample> | undefined, |
| 57 | 46 | ): OpenApiExample | undefined => examples?.[0]; |
| 58 | 47 | |
| 59 | −const buildExampleInputJson = ( | |
| 48 | +const fallbackInputSchemaFromInvocation = ( | |
| 49 | + invocation: OpenApiInvocationPayload, | |
| 50 | +): Record<string, unknown> | undefined => { | |
| 51 | + const properties: Record<string, unknown> = {}; | |
| 52 | + const required: string[] = []; | |
| 53 | + | |
| 54 | + for (const parameter of invocation.parameters) { | |
| 55 | + properties[parameter.name] = { | |
| 56 | + type: "string", | |
| 57 | + }; | |
| 58 | + if (parameter.required) { | |
| 59 | + required.push(parameter.name); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + if (invocation.requestBody) { | |
| 64 | + properties.body = { | |
| 65 | + type: "object", | |
| 66 | + }; | |
| 67 | + if (invocation.requestBody.required) { | |
| 68 | + required.push("body"); | |
| 69 | + } | |
| 70 | + } | |
| 71 | + | |
| 72 | + return Object.keys(properties).length > 0 | |
| 73 | + ? { | |
| 74 | + type: "object", | |
| 75 | + properties, | |
| 76 | + ...(required.length > 0 ? { required } : {}), | |
| 77 | + additionalProperties: false, | |
| 78 | + } | |
| 79 | + : undefined; | |
| 80 | +}; | |
| 81 | + | |
| 82 | +const buildExampleInput = ( | |
| 60 | 83 | documentation: OpenApiToolDocumentation | undefined, |
| 61 | −): string | undefined => { | |
| 84 | +): Record<string, unknown> | undefined => { | |
| 62 | 85 | if (!documentation) { |
| 63 | 86 | return undefined; |
| 64 | 87 | } |
| @@ -71,52 +94,51 @@ const buildExampleInputJson = ( · 6 unmodified lines | ||
| 71 | 94 | continue; |
| 72 | 95 | } |
| 73 | 96 | |
| 74 | − const parsedValue = parseJson(example.valueJson); | |
| 75 | − if (parsedValue !== undefined) { | |
| 76 | − input[parameter.name] = parsedValue; | |
| 77 | − } | |
| 97 | + input[parameter.name] = JSON.parse(example.valueJson) as unknown; | |
| 78 | 98 | } |
| 79 | 99 | |
| 80 | 100 | const requestBodyExample = firstExample(documentation.requestBody?.examples); |
| 81 | 101 | if (requestBodyExample) { |
| 82 | − const parsedValue = parseJson(requestBodyExample.valueJson); | |
| 83 | − if (parsedValue !== undefined) { | |
| 84 | − input.body = parsedValue; | |
| 85 | − } | |
| 102 | + input.body = JSON.parse(requestBodyExample.valueJson) as unknown; | |
| 86 | 103 | } |
| 87 | 104 | |
| 88 | − return Object.keys(input).length > 0 ? JSON.stringify(input) : undefined; | |
| 105 | + return Object.keys(input).length > 0 ? input : undefined; | |
| 89 | 106 | }; |
| 90 | 107 | |
| 91 | −const buildExampleOutputJson = ( | |
| 108 | +const buildExampleOutput = ( | |
| 92 | 109 | documentation: OpenApiToolDocumentation | undefined, |
| 93 | −): string | undefined => firstExample(documentation?.response?.examples)?.valueJson; | |
| 110 | +): unknown | undefined => { | |
| 111 | + const example = firstExample(documentation?.response?.examples)?.valueJson; | |
| 112 | + return example ? JSON.parse(example) as unknown : undefined; | |
| 113 | +}; | |
| 94 | 114 | |
| 95 | 115 | export type OpenApiToolPresentation = { |
| 96 | 116 | inputType: string; |
| 97 | 117 | outputType: string; |
| 98 | − inputSchemaJson?: string; | |
| 99 | − outputSchemaJson?: string; | |
| 100 | − exampleInputJson?: string; | |
| 101 | − exampleOutputJson?: string; | |
| 102 | − providerDataJson: string; | |
| 118 | + inputSchema?: unknown; | |
| 119 | + outputSchema?: unknown; | |
| 120 | + exampleInput?: unknown; | |
| 121 | + exampleOutput?: unknown; | |
| 122 | + providerData: OpenApiToolProviderData; | |
| 103 | 123 | }; |
| 104 | 124 | |
| 105 | 125 | export const buildOpenApiToolPresentation = (input: { |
| 106 | 126 | definition: OpenApiToolDefinition; |
| 107 | 127 | }): OpenApiToolPresentation => { |
| 108 | − const inputSchemaJson = input.definition.typing?.inputSchemaJson; | |
| 109 | − const outputSchemaJson = input.definition.typing?.outputSchemaJson; | |
| 110 | − const exampleInputJson = buildExampleInputJson(input.definition.documentation); | |
| 111 | − const exampleOutputJson = buildExampleOutputJson(input.definition.documentation); | |
| 128 | + const inputSchema = | |
| 129 | + input.definition.typing?.inputSchema | |
| 130 | + ?? fallbackInputSchemaFromInvocation(input.definition.invocation); | |
| 131 | + const outputSchema = input.definition.typing?.outputSchema; | |
| 132 | + const exampleInput = buildExampleInput(input.definition.documentation); | |
| 133 | + const exampleOutput = buildExampleOutput(input.definition.documentation); | |
| 112 | 134 | |
| 113 | 135 | return { |
| 114 | − inputType: typeSignatureFromSchemaJson(inputSchemaJson, "unknown", Infinity), | |
| 115 | − outputType: openApiOutputTypeSignatureFromSchemaJson(outputSchemaJson, Infinity), | |
| 116 | − ...(inputSchemaJson ? { inputSchemaJson } : {}), | |
| 117 | − ...(outputSchemaJson ? { outputSchemaJson } : {}), | |
| 118 | − ...(exampleInputJson ? { exampleInputJson } : {}), | |
| 119 | − ...(exampleOutputJson ? { exampleOutputJson } : {}), | |
| 120 | − providerDataJson: openApiProviderDataJsonFromDefinition(input.definition), | |
| 136 | + inputType: typeSignatureFromSchema(inputSchema, "unknown", Infinity), | |
| 137 | + outputType: openApiOutputTypeSignatureFromSchema(outputSchema, Infinity), | |
| 138 | + ...(inputSchema !== undefined ? { inputSchema } : {}), | |
| 139 | + ...(outputSchema !== undefined ? { outputSchema } : {}), | |
| 140 | + ...(exampleInput !== undefined ? { exampleInput } : {}), | |
| 141 | + ...(exampleOutput !== undefined ? { exampleOutput } : {}), | |
| 142 | + providerData: openApiProviderDataFromDefinition(input.definition), | |
| 121 | 143 | }; |
| 122 | 144 | }; |
| @@ -291,8 +291,8 @@ describe("openapi-tools", () => { · 290 unmodified lines | ||
| 291 | 291 | const resolvedGet = resolveToolDefinition(tools["source.demo.repos.getRepo"]!); |
| 292 | 292 | expect(resolvedGet.metadata?.sourceKey).toBe("api.demo"); |
| 293 | 293 | expect(resolvedGet.metadata?.providerKind).toBe("openapi"); |
| 294 | − expect(resolvedGet.metadata?.providerDataJson).toContain('"group":"repos"'); | |
| 295 | − expect(resolvedGet.metadata?.inputSchemaJson).toBeDefined(); | |
| 294 | + expect(resolvedGet.metadata?.providerData).toMatchObject({ group: "repos" }); | |
| 295 | + expect(resolvedGet.metadata?.inputSchema).toBeDefined(); | |
| 296 | 296 | }), |
| 297 | 297 | ); |
| 298 | 298 | |
| @@ -23,7 +23,7 @@ import { · 22 unmodified lines | ||
| 23 | 23 | type OpenApiToolDefinition, |
| 24 | 24 | } from "./openapi-definitions"; |
| 25 | 25 | import { buildOpenApiToolPresentation } from "./openapi-tool-presentation"; |
| 26 | −import { resolveSchemaJsonWithRefHints } from "./openapi-schema-refs"; | |
| 26 | +import { resolveSchemaWithRefHints } from "./openapi-schema-refs"; | |
| 27 | 27 | import { |
| 28 | 28 | extractOpenApiManifest, |
| 29 | 29 | type OpenApiExtractionError, |
| @@ -262,21 +262,21 @@ const summarizeHttpResponseBody = (body: unknown): string | null => { · 232 unmodified lines | ||
| 262 | 262 | } |
| 263 | 263 | }; |
| 264 | 264 | |
| 265 | −const inputSchemaFromTypingJson = (input: { | |
| 266 | − inputSchemaJson: string | undefined; | |
| 265 | +const inputSchemaFromTyping = (input: { | |
| 266 | + inputSchema: unknown; | |
| 267 | 267 | refHintTable?: Readonly<OpenApiRefHintTable>; |
| 268 | 268 | }) => { |
| 269 | − const resolvedSchemaJson = resolveSchemaJsonWithRefHints( | |
| 270 | − input.inputSchemaJson, | |
| 269 | + const resolvedSchema = resolveSchemaWithRefHints( | |
| 270 | + input.inputSchema, | |
| 271 | 271 | input.refHintTable, |
| 272 | − ) ?? input.inputSchemaJson; | |
| 272 | + ) ?? input.inputSchema; | |
| 273 | 273 | |
| 274 | − if (!resolvedSchemaJson) { | |
| 274 | + if (resolvedSchema === undefined || resolvedSchema === null) { | |
| 275 | 275 | return unknownInputSchema; |
| 276 | 276 | } |
| 277 | 277 | |
| 278 | 278 | try { |
| 279 | − return standardSchemaFromJsonSchema(JSON.parse(resolvedSchemaJson), { | |
| 279 | + return standardSchemaFromJsonSchema(resolvedSchema, { | |
| 280 | 280 | vendor: "openapi", |
| 281 | 281 | fallback: unknownInputSchema, |
| 282 | 282 | }); |
| @@ -469,8 +469,8 @@ export const createOpenApiToolFromDefinition = ( · 186 unmodified lines | ||
| 469 | 469 | return toTool({ |
| 470 | 470 | tool: { |
| 471 | 471 | description: input.definition.description, |
| 472 | − inputSchema: inputSchemaFromTypingJson({ | |
| 473 | − inputSchemaJson: presentation.inputSchemaJson ?? input.definition.typing?.inputSchemaJson, | |
| 472 | + inputSchema: inputSchemaFromTyping({ | |
| 473 | + inputSchema: presentation.inputSchema ?? input.definition.typing?.inputSchema, | |
| 474 | 474 | refHintTable: input.refHintTable, |
| 475 | 475 | }), |
| 476 | 476 | execute: async (args: unknown) => { |
| @@ -526,12 +526,20 @@ export const createOpenApiToolFromDefinition = ( · 49 unmodified lines | ||
| 526 | 526 | sourceKey: input.sourceKey, |
| 527 | 527 | inputType: presentation.inputType, |
| 528 | 528 | outputType: presentation.outputType, |
| 529 | − inputSchemaJson: presentation.inputSchemaJson, | |
| 530 | − outputSchemaJson: presentation.outputSchemaJson, | |
| 531 | − exampleInputJson: presentation.exampleInputJson, | |
| 532 | − exampleOutputJson: presentation.exampleOutputJson, | |
| 529 | + ...((presentation.inputSchema ?? input.definition.typing?.inputSchema) !== undefined | |
| 530 | + ? { inputSchema: presentation.inputSchema ?? input.definition.typing?.inputSchema } | |
| 531 | + : {}), | |
| 532 | + ...((presentation.outputSchema ?? input.definition.typing?.outputSchema) !== undefined | |
| 533 | + ? { outputSchema: presentation.outputSchema ?? input.definition.typing?.outputSchema } | |
| 534 | + : {}), | |
| 535 | + ...(presentation.exampleInput !== undefined | |
| 536 | + ? { exampleInput: presentation.exampleInput } | |
| 537 | + : {}), | |
| 538 | + ...(presentation.exampleOutput !== undefined | |
| 539 | + ? { exampleOutput: presentation.exampleOutput } | |
| 540 | + : {}), | |
| 533 | 541 | providerKind: "openapi", |
| 534 | − providerDataJson: presentation.providerDataJson, | |
| 542 | + providerData: presentation.providerData, | |
| 535 | 543 | }, |
| 536 | 544 | }); |
| 537 | 545 | }; |
| @@ -58,8 +58,8 @@ export const OpenApiInvocationPayloadSchema = Schema.Struct({ · 57 unmodified lines | ||
| 58 | 58 | }); |
| 59 | 59 | |
| 60 | 60 | export const DiscoveryTypingPayloadSchema = Schema.Struct({ |
| 61 | − inputSchemaJson: Schema.optional(Schema.String), | |
| 62 | − outputSchemaJson: Schema.optional(Schema.String), | |
| 61 | + inputSchema: Schema.optional(Schema.Unknown), | |
| 62 | + outputSchema: Schema.optional(Schema.Unknown), | |
| 63 | 63 | refHintKeys: Schema.optional(Schema.Array(Schema.String)), |
| 64 | 64 | }); |
| 65 | 65 | |