Showing the changes to packages/codemode-openapi/src (6 of 91 files). Show all
Unified · Split
| @@ -19,6 +19,7 @@ export { · 18 unmodified lines | ||
| 19 | 19 | } from "./openapi-tool-presentation"; |
| 20 | 20 | export { |
| 21 | 21 | OpenApiToolInvocationError, |
| 22 | + createOpenApiToolFromDefinition, | |
| 22 | 23 | createOpenApiToolsFromManifest, |
| 23 | 24 | createOpenApiToolsFromSpec, |
| 24 | 25 | } from "./openapi-tools"; |
| @@ -39,6 +40,9 @@ export { · 14 unmodified lines | ||
| 39 | 40 | OpenApiRequestBodyDocumentationSchema, |
| 40 | 41 | OpenApiResponseDocumentationSchema, |
| 41 | 42 | OpenApiToolDocumentationSchema, |
| 43 | + OpenApiToolProviderDataSchema, | |
| 44 | + OpenApiRefHintTableSchema, | |
| 45 | + OpenApiRefHintValueSchema, | |
| 42 | 46 | type DiscoveryTypingPayload, |
| 43 | 47 | type OpenApiExtractedTool, |
| 44 | 48 | type OpenApiHttpMethod, |
| @@ -49,6 +53,9 @@ export { · 4 unmodified lines | ||
| 49 | 53 | type OpenApiParameterLocation, |
| 50 | 54 | type OpenApiSpecInput, |
| 51 | 55 | type OpenApiToolManifest, |
| 56 | + type OpenApiToolProviderData, | |
| 57 | + type OpenApiRefHintTable, | |
| 58 | + type OpenApiRefHintValue, | |
| 52 | 59 | type OpenApiToolParameter, |
| 53 | 60 | type OpenApiToolRequestBody, |
| 54 | 61 | } from "./openapi-types"; |
| @@ -5,6 +5,7 @@ import { · 4 unmodified lines | ||
| 5 | 5 | OpenApiHttpMethodSchema, |
| 6 | 6 | OpenApiInvocationPayloadSchema, |
| 7 | 7 | OpenApiToolDocumentationSchema, |
| 8 | + OpenApiToolProviderDataSchema, | |
| 8 | 9 | type OpenApiExtractedTool, |
| 9 | 10 | type OpenApiToolManifest, |
| 10 | 11 | } from "./openapi-types"; |
| @@ -250,5 +251,6 @@ export const openApiProviderDataJsonFromDefinition = ( · 239 unmodified lines | ||
| 250 | 251 | method: definition.method, |
| 251 | 252 | path: definition.path, |
| 252 | 253 | operationHash: definition.operationHash, |
| 254 | + invocation: definition.invocation, | |
| 253 | 255 | documentation: definition.documentation, |
| 254 | − }); | |
| 256 | + } satisfies typeof OpenApiToolProviderDataSchema.Type); | |
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | type JsonObject = Record<string, unknown>; |
| 2 | +type RefHintValue = string | JsonObject; | |
| 2 | 3 | |
| 3 | 4 | type DiscoveryTypingLike = { |
| 4 | 5 | inputSchemaJson?: string; |
| @@ -18,7 +19,7 @@ const parseJson = (value: string): unknown | null => { · 13 unmodified lines | ||
| 18 | 19 | |
| 19 | 20 | const resolveSchemaNode = ( |
| 20 | 21 | value: unknown, |
| 21 | − refHintTable: Readonly<Record<string, string>>, | |
| 22 | + refHintTable: Readonly<Record<string, RefHintValue>>, | |
| 22 | 23 | parsedHintCache: Map<string, unknown>, |
| 23 | 24 | activeRefs: ReadonlySet<string>, |
| 24 | 25 | ): unknown => { |
| @@ -38,7 +39,12 @@ const resolveSchemaNode = ( · 13 unmodified lines | ||
| 38 | 39 | |
| 39 | 40 | if (resolvedRefValue === undefined) { |
| 40 | 41 | const rawHint = refHintTable[ref]; |
| 41 | − resolvedRefValue = typeof rawHint === "string" ? parseJson(rawHint) : null; | |
| 42 | + resolvedRefValue = | |
| 43 | + typeof rawHint === "string" | |
| 44 | + ? parseJson(rawHint) | |
| 45 | + : isJsonObject(rawHint) | |
| 46 | + ? rawHint | |
| 47 | + : null; | |
| 42 | 48 | parsedHintCache.set(ref, resolvedRefValue); |
| 43 | 49 | } |
| 44 | 50 | |
| @@ -88,7 +94,7 @@ const resolveSchemaNode = ( · 43 unmodified lines | ||
| 88 | 94 | |
| 89 | 95 | export const resolveSchemaJsonWithRefHints = ( |
| 90 | 96 | schemaJson: string | undefined, |
| 91 | − refHintTable: Readonly<Record<string, string>> | undefined, | |
| 97 | + refHintTable: Readonly<Record<string, RefHintValue>> | undefined, | |
| 92 | 98 | ): string | null => { |
| 93 | 99 | if (!schemaJson) { |
| 94 | 100 | return null; |
| @@ -119,7 +125,7 @@ export const resolveSchemaJsonWithRefHints = ( · 24 unmodified lines | ||
| 119 | 125 | |
| 120 | 126 | export const resolveTypingSchemasWithRefHints = ( |
| 121 | 127 | typing: DiscoveryTypingLike | undefined, |
| 122 | − refHintTable: Readonly<Record<string, string>> | undefined, | |
| 128 | + refHintTable: Readonly<Record<string, RefHintValue>> | undefined, | |
| 123 | 129 | ): { |
| 124 | 130 | inputSchemaJson: string | null; |
| 125 | 131 | outputSchemaJson: string | null; |
| @@ -1,14 +1,13 @@ | ||
| 1 | 1 | import { typeSignatureFromSchemaJson } from "@executor/codemode-core"; |
| 2 | 2 | |
| 3 | −import { | |
| 4 | − resolveTypingSchemasWithRefHints, | |
| 5 | −} from "./openapi-schema-refs"; | |
| 6 | 3 | import type { |
| 7 | 4 | OpenApiExample, |
| 8 | 5 | OpenApiToolDocumentation, |
| 9 | − OpenApiToolManifest, | |
| 10 | 6 | } from "./openapi-types"; |
| 11 | −import type { OpenApiToolDefinition } from "./openapi-definitions"; | |
| 7 | +import { | |
| 8 | + openApiProviderDataJsonFromDefinition, | |
| 9 | + type OpenApiToolDefinition, | |
| 10 | +} from "./openapi-definitions"; | |
| 12 | 11 | |
| 13 | 12 | const parseJson = (value: string): unknown | undefined => { |
| 14 | 13 | try { |
| @@ -104,15 +103,10 @@ export type OpenApiToolPresentation = { · 89 unmodified lines | ||
| 104 | 103 | }; |
| 105 | 104 | |
| 106 | 105 | export const buildOpenApiToolPresentation = (input: { |
| 107 | − manifest: OpenApiToolManifest; | |
| 108 | 106 | definition: OpenApiToolDefinition; |
| 109 | 107 | }): OpenApiToolPresentation => { |
| 110 | − const resolvedSchemas = resolveTypingSchemasWithRefHints( | |
| 111 | − input.definition.typing, | |
| 112 | − input.manifest.refHintTable, | |
| 113 | − ); | |
| 114 | − const inputSchemaJson = resolvedSchemas.inputSchemaJson ?? undefined; | |
| 115 | − const outputSchemaJson = resolvedSchemas.outputSchemaJson ?? undefined; | |
| 108 | + const inputSchemaJson = input.definition.typing?.inputSchemaJson; | |
| 109 | + const outputSchemaJson = input.definition.typing?.outputSchemaJson; | |
| 116 | 110 | const exampleInputJson = buildExampleInputJson(input.definition.documentation); |
| 117 | 111 | const exampleOutputJson = buildExampleOutputJson(input.definition.documentation); |
| 118 | 112 | |
| @@ -123,19 +117,6 @@ export const buildOpenApiToolPresentation = (input: { · 4 unmodified lines | ||
| 123 | 117 | ...(outputSchemaJson ? { outputSchemaJson } : {}), |
| 124 | 118 | ...(exampleInputJson ? { exampleInputJson } : {}), |
| 125 | 119 | ...(exampleOutputJson ? { exampleOutputJson } : {}), |
| 126 | − providerDataJson: JSON.stringify({ | |
| 127 | − kind: "openapi", | |
| 128 | − toolId: input.definition.toolId, | |
| 129 | − rawToolId: input.definition.rawToolId, | |
| 130 | − operationId: input.definition.operationId, | |
| 131 | − group: input.definition.group, | |
| 132 | − leaf: input.definition.leaf, | |
| 133 | − tags: input.definition.tags, | |
| 134 | − versionSegment: input.definition.versionSegment, | |
| 135 | − method: input.definition.method, | |
| 136 | − path: input.definition.path, | |
| 137 | − operationHash: input.definition.operationHash, | |
| 138 | − documentation: input.definition.documentation, | |
| 139 | − }), | |
| 120 | + providerDataJson: openApiProviderDataJsonFromDefinition(input.definition), | |
| 140 | 121 | }; |
| 141 | 122 | }; |
| @@ -19,11 +19,13 @@ import { · 18 unmodified lines | ||
| 19 | 19 | type OpenApiToolDefinition, |
| 20 | 20 | } from "./openapi-definitions"; |
| 21 | 21 | import { buildOpenApiToolPresentation } from "./openapi-tool-presentation"; |
| 22 | +import { resolveSchemaJsonWithRefHints } from "./openapi-schema-refs"; | |
| 22 | 23 | import { |
| 23 | 24 | extractOpenApiManifest, |
| 24 | 25 | type OpenApiExtractionError, |
| 25 | 26 | } from "./openapi-extraction"; |
| 26 | 27 | import { |
| 28 | + type OpenApiRefHintTable, | |
| 27 | 29 | type OpenApiInvocationPayload, |
| 28 | 30 | type OpenApiSpecInput, |
| 29 | 31 | type OpenApiToolManifest, |
| @@ -256,13 +258,21 @@ const summarizeHttpResponseBody = (body: unknown): string | null => { · 226 unmodified lines | ||
| 256 | 258 | } |
| 257 | 259 | }; |
| 258 | 260 | |
| 259 | −const inputSchemaFromTypingJson = (inputSchemaJson: string | undefined) => { | |
| 260 | − if (!inputSchemaJson) { | |
| 261 | +const inputSchemaFromTypingJson = (input: { | |
| 262 | + inputSchemaJson: string | undefined; | |
| 263 | + refHintTable?: Readonly<OpenApiRefHintTable>; | |
| 264 | +}) => { | |
| 265 | + const resolvedSchemaJson = resolveSchemaJsonWithRefHints( | |
| 266 | + input.inputSchemaJson, | |
| 267 | + input.refHintTable, | |
| 268 | + ) ?? input.inputSchemaJson; | |
| 269 | + | |
| 270 | + if (!resolvedSchemaJson) { | |
| 261 | 271 | return unknownInputSchema; |
| 262 | 272 | } |
| 263 | 273 | |
| 264 | 274 | try { |
| 265 | − return standardSchemaFromJsonSchema(JSON.parse(inputSchemaJson), { | |
| 275 | + return standardSchemaFromJsonSchema(JSON.parse(resolvedSchemaJson), { | |
| 266 | 276 | vendor: "openapi", |
| 267 | 277 | fallback: unknownInputSchema, |
| 268 | 278 | }); |
| @@ -396,6 +406,97 @@ const buildFetchRequest = (input: { · 127 unmodified lines | ||
| 396 | 406 | const createToolPath = (namespace: string | undefined, definition: OpenApiToolDefinition): string => |
| 397 | 407 | namespace ? `${namespace}.${definition.toolId}` : definition.toolId; |
| 398 | 408 | |
| 409 | +export type CreateOpenApiToolFromDefinitionInput = { | |
| 410 | + definition: OpenApiToolDefinition; | |
| 411 | + path: string; | |
| 412 | + sourceKey: string; | |
| 413 | + baseUrl: string; | |
| 414 | + defaultHeaders?: Readonly<Record<string, string>>; | |
| 415 | + credentialHeaders?: Readonly<Record<string, string>>; | |
| 416 | + refHintTable?: Readonly<OpenApiRefHintTable>; | |
| 417 | + httpClientLayer?: Layer.Layer<HttpClient.HttpClient, never, never>; | |
| 418 | +}; | |
| 419 | + | |
| 420 | +export const createOpenApiToolFromDefinition = ( | |
| 421 | + input: CreateOpenApiToolFromDefinitionInput, | |
| 422 | +) => { | |
| 423 | + const defaultHeaders = input.defaultHeaders ?? {}; | |
| 424 | + const credentialHeaders = input.credentialHeaders ?? {}; | |
| 425 | + const httpClientLayer = input.httpClientLayer ?? FetchHttpClient.layer; | |
| 426 | + const presentation = buildOpenApiToolPresentation({ | |
| 427 | + definition: input.definition, | |
| 428 | + }); | |
| 429 | + | |
| 430 | + return toTool({ | |
| 431 | + tool: { | |
| 432 | + description: input.definition.description, | |
| 433 | + inputSchema: inputSchemaFromTypingJson({ | |
| 434 | + inputSchemaJson: presentation.inputSchemaJson ?? input.definition.typing?.inputSchemaJson, | |
| 435 | + refHintTable: input.refHintTable, | |
| 436 | + }), | |
| 437 | + execute: async (args: unknown) => { | |
| 438 | + const decodedArgs = asToolArgs(args); | |
| 439 | + const request = buildFetchRequest({ | |
| 440 | + payload: input.definition.invocation, | |
| 441 | + args: decodedArgs, | |
| 442 | + baseUrl: input.baseUrl, | |
| 443 | + defaultHeaders, | |
| 444 | + credentialHeaders, | |
| 445 | + }); | |
| 446 | + | |
| 447 | + return Effect.runPromise( | |
| 448 | + Effect.gen(function* () { | |
| 449 | + const client = yield* HttpClient.HttpClient; | |
| 450 | + let clientRequest = HttpClientRequest.make( | |
| 451 | + request.method as Parameters<typeof HttpClientRequest.make>[0], | |
| 452 | + )(request.url, { | |
| 453 | + headers: request.headers, | |
| 454 | + }); | |
| 455 | + | |
| 456 | + if (request.body !== undefined) { | |
| 457 | + clientRequest = HttpClientRequest.bodyText( | |
| 458 | + clientRequest, | |
| 459 | + request.body, | |
| 460 | + request.headers["content-type"], | |
| 461 | + ); | |
| 462 | + } | |
| 463 | + | |
| 464 | + const response = yield* client.execute(clientRequest).pipe( | |
| 465 | + Effect.mapError((cause) => | |
| 466 | + cause instanceof Error ? cause : new Error(String(cause)), | |
| 467 | + ), | |
| 468 | + ); | |
| 469 | + const body = yield* decodeHttpClientResponseBody(response); | |
| 470 | + | |
| 471 | + if (response.status < 200 || response.status >= 300) { | |
| 472 | + throw new OpenApiToolInvocationError({ | |
| 473 | + operation: "http_response", | |
| 474 | + message: `OpenAPI request failed with HTTP ${response.status}`, | |
| 475 | + details: summarizeHttpResponseBody(body), | |
| 476 | + }); | |
| 477 | + } | |
| 478 | + | |
| 479 | + return body; | |
| 480 | + }).pipe( | |
| 481 | + Effect.provide(httpClientLayer), | |
| 482 | + ), | |
| 483 | + ); | |
| 484 | + }, | |
| 485 | + }, | |
| 486 | + metadata: { | |
| 487 | + sourceKey: input.sourceKey, | |
| 488 | + inputType: presentation.inputType, | |
| 489 | + outputType: presentation.outputType, | |
| 490 | + inputSchemaJson: presentation.inputSchemaJson, | |
| 491 | + outputSchemaJson: presentation.outputSchemaJson, | |
| 492 | + exampleInputJson: presentation.exampleInputJson, | |
| 493 | + exampleOutputJson: presentation.exampleOutputJson, | |
| 494 | + providerKind: "openapi", | |
| 495 | + providerDataJson: presentation.providerDataJson, | |
| 496 | + }, | |
| 497 | + }); | |
| 498 | +}; | |
| 499 | + | |
| 399 | 500 | export const createOpenApiToolsFromManifest = ( |
| 400 | 501 | input: CreateOpenApiToolsFromManifestInput, |
| 401 | 502 | ): ToolMap => { |
| @@ -410,77 +511,15 @@ export const createOpenApiToolsFromManifest = ( · 8 unmodified lines | ||
| 410 | 511 | |
| 411 | 512 | for (const definition of definitions) { |
| 412 | 513 | const toolPath = createToolPath(input.namespace, definition); |
| 413 | − const presentation = buildOpenApiToolPresentation({ | |
| 414 | − manifest: input.manifest, | |
| 514 | + result[toolPath] = createOpenApiToolFromDefinition({ | |
| 415 | 515 | definition, |
| 416 | − }); | |
| 417 | − | |
| 418 | − result[toolPath] = toTool({ | |
| 419 | − tool: { | |
| 420 | − description: definition.description, | |
| 421 | − inputSchema: inputSchemaFromTypingJson( | |
| 422 | − presentation.inputSchemaJson ?? definition.typing?.inputSchemaJson, | |
| 423 | − ), | |
| 424 | − execute: async (args: unknown) => { | |
| 425 | − const decodedArgs = asToolArgs(args); | |
| 426 | − const request = buildFetchRequest({ | |
| 427 | − payload: definition.invocation, | |
| 428 | − args: decodedArgs, | |
| 429 | − baseUrl, | |
| 430 | − defaultHeaders, | |
| 431 | − credentialHeaders, | |
| 432 | − }); | |
| 433 | − | |
| 434 | − return Effect.runPromise( | |
| 435 | − Effect.gen(function* () { | |
| 436 | − const client = yield* HttpClient.HttpClient; | |
| 437 | − let clientRequest = HttpClientRequest.make( | |
| 438 | − request.method as Parameters<typeof HttpClientRequest.make>[0], | |
| 439 | − )(request.url, { | |
| 440 | − headers: request.headers, | |
| 441 | − }); | |
| 442 | − | |
| 443 | − if (request.body !== undefined) { | |
| 444 | − clientRequest = HttpClientRequest.bodyText( | |
| 445 | − clientRequest, | |
| 446 | − request.body, | |
| 447 | − request.headers["content-type"], | |
| 448 | − ); | |
| 449 | − } | |
| 450 | − | |
| 451 | − const response = yield* client.execute(clientRequest).pipe( | |
| 452 | − Effect.mapError((cause) => | |
| 453 | − cause instanceof Error ? cause : new Error(String(cause)), | |
| 454 | − ), | |
| 455 | − ); | |
| 456 | − const body = yield* decodeHttpClientResponseBody(response); | |
| 457 | − | |
| 458 | − if (response.status < 200 || response.status >= 300) { | |
| 459 | − throw new OpenApiToolInvocationError({ | |
| 460 | − operation: "http_response", | |
| 461 | − message: `OpenAPI request failed with HTTP ${response.status}`, | |
| 462 | − details: summarizeHttpResponseBody(body), | |
| 463 | − }); | |
| 464 | − } | |
| 465 | − | |
| 466 | − return body; | |
| 467 | − }).pipe( | |
| 468 | − Effect.provide(httpClientLayer), | |
| 469 | − ), | |
| 470 | − ); | |
| 471 | − }, | |
| 472 | − }, | |
| 473 | − metadata: { | |
| 474 | − sourceKey, | |
| 475 | − inputType: presentation.inputType, | |
| 476 | − outputType: presentation.outputType, | |
| 477 | − inputSchemaJson: presentation.inputSchemaJson, | |
| 478 | − outputSchemaJson: presentation.outputSchemaJson, | |
| 479 | − exampleInputJson: presentation.exampleInputJson, | |
| 480 | − exampleOutputJson: presentation.exampleOutputJson, | |
| 481 | − providerKind: "openapi", | |
| 482 | − providerDataJson: presentation.providerDataJson, | |
| 483 | − }, | |
| 516 | + path: toolPath, | |
| 517 | + sourceKey, | |
| 518 | + baseUrl, | |
| 519 | + defaultHeaders, | |
| 520 | + credentialHeaders, | |
| 521 | + refHintTable: input.manifest.refHintTable, | |
| 522 | + httpClientLayer, | |
| 484 | 523 | }); |
| 485 | 524 | } |
| 486 | 525 | |
| @@ -63,6 +63,19 @@ export const DiscoveryTypingPayloadSchema = Schema.Struct({ · 62 unmodified lines | ||
| 63 | 63 | refHintKeys: Schema.optional(Schema.Array(Schema.String)), |
| 64 | 64 | }); |
| 65 | 65 | |
| 66 | +export const OpenApiRefHintValueSchema = Schema.Union( | |
| 67 | + Schema.String, | |
| 68 | + Schema.Record({ | |
| 69 | + key: Schema.String, | |
| 70 | + value: Schema.Unknown, | |
| 71 | + }), | |
| 72 | +); | |
| 73 | + | |
| 74 | +export const OpenApiRefHintTableSchema = Schema.Record({ | |
| 75 | + key: Schema.String, | |
| 76 | + value: OpenApiRefHintValueSchema, | |
| 77 | +}); | |
| 78 | + | |
| 66 | 79 | export const OpenApiExampleSchema = Schema.Struct({ |
| 67 | 80 | valueJson: Schema.String, |
| 68 | 81 | mediaType: Schema.optional(Schema.String), |
| @@ -97,6 +110,22 @@ export const OpenApiToolDocumentationSchema = Schema.Struct({ · 28 unmodified lines | ||
| 97 | 110 | response: Schema.optional(OpenApiResponseDocumentationSchema), |
| 98 | 111 | }); |
| 99 | 112 | |
| 113 | +export const OpenApiToolProviderDataSchema = Schema.Struct({ | |
| 114 | + kind: Schema.Literal("openapi"), | |
| 115 | + toolId: Schema.String, | |
| 116 | + rawToolId: Schema.String, | |
| 117 | + operationId: Schema.optional(Schema.String), | |
| 118 | + group: Schema.String, | |
| 119 | + leaf: Schema.String, | |
| 120 | + tags: Schema.Array(Schema.String), | |
| 121 | + versionSegment: Schema.optional(Schema.String), | |
| 122 | + method: OpenApiHttpMethodSchema, | |
| 123 | + path: Schema.String, | |
| 124 | + operationHash: Schema.String, | |
| 125 | + invocation: OpenApiInvocationPayloadSchema, | |
| 126 | + documentation: Schema.optional(OpenApiToolDocumentationSchema), | |
| 127 | +}); | |
| 128 | + | |
| 100 | 129 | export const OpenApiExtractedToolSchema = Schema.Struct({ |
| 101 | 130 | toolId: Schema.String, |
| 102 | 131 | operationId: Schema.optional(Schema.String), |
| @@ -129,10 +158,13 @@ export type OpenApiToolParameter = typeof OpenApiToolParameterSchema.Type; · 26 unmodified lines | ||
| 129 | 158 | export type OpenApiToolRequestBody = typeof OpenApiToolRequestBodySchema.Type; |
| 130 | 159 | export type OpenApiInvocationPayload = typeof OpenApiInvocationPayloadSchema.Type; |
| 131 | 160 | export type DiscoveryTypingPayload = typeof DiscoveryTypingPayloadSchema.Type; |
| 161 | +export type OpenApiRefHintValue = typeof OpenApiRefHintValueSchema.Type; | |
| 162 | +export type OpenApiRefHintTable = typeof OpenApiRefHintTableSchema.Type; | |
| 132 | 163 | export type OpenApiExample = typeof OpenApiExampleSchema.Type; |
| 133 | 164 | export type OpenApiParameterDocumentation = typeof OpenApiParameterDocumentationSchema.Type; |
| 134 | 165 | export type OpenApiRequestBodyDocumentation = typeof OpenApiRequestBodyDocumentationSchema.Type; |
| 135 | 166 | export type OpenApiResponseDocumentation = typeof OpenApiResponseDocumentationSchema.Type; |
| 136 | 167 | export type OpenApiToolDocumentation = typeof OpenApiToolDocumentationSchema.Type; |
| 168 | +export type OpenApiToolProviderData = typeof OpenApiToolProviderDataSchema.Type; | |
| 137 | 169 | export type OpenApiExtractedTool = typeof OpenApiExtractedToolSchema.Type; |
| 138 | 170 | export type OpenApiToolManifest = typeof OpenApiToolManifestSchema.Type; |