import { Effect, Option, Schema } from "effect";import type { Layer } from "effect";import { HttpClient } from "effect/unstable/http"; import { IntegrationAlreadyExistsError, IntegrationDetectionResult, IntegrationNotFoundError, IntegrationSlug, ToolResult, definePlugin, mergeAuthTemplates, sha256Hex, tool, type AuthMethodDescriptor, type Integration, type IntegrationConfig, type IntegrationRecord, type PluginCtx, type StorageFailure,} from "@executor-js/sdk/core"; import { decodeOpenApiIntegrationConfig, type OpenApiIntegrationConfig } from "./config";import { OpenApiExtractionError, OpenApiOAuthError, OpenApiParseError } from "./errors";import { parse, resolveSpecText } from "./parse";import { extract } from "./extract";import { OAuth2AuthorizationCodeFlow, OAuth2Flows, OAuth2Preset, SecurityScheme, previewSpecText, type SpecPreview,} from "./preview";import { deriveAuthenticationTemplateFromPreview, firstBaseUrlForPreview } from "./derive-auth";import { openApiPresets } from "./presets";import { makeDefaultOpenapiStore, type OpenapiStore } from "./store";import type { Authentication } from "./types";import { normalizeOpenApiAuthInputs, type AuthenticationInput } from "./types";import { ApiKeyAuthTemplate, describeApiKeyAuthMethod } from "@executor-js/sdk/http-auth";import { checkHealthOpenApi, compileOpenApiSpec, invokeOpenApiBackedTool, listHealthCheckCandidatesOpenApi, openApiStoredOperationsFromCompiled, resolveOpenApiBackedAnnotations, resolveOpenApiBackedTools, validateOpenApiBackedToolArgs,} from "./backing";import { resolveServerUrl } from "./openapi-utils"; // ---------------------------------------------------------------------------// Extension input shapes// --------------------------------------------------------------------------- export type OpenApiSpecInput = typeof OpenApiSpecInputSchema.Type; export interface OpenApiPreviewInput { readonly spec: string;} /** Add an OpenAPI integration to the catalog. The integration is the API * surface; connections (the credentials) are attached separately and resolve * their value through the declared `authenticationTemplate`. */export interface OpenApiSpecConfig { readonly spec: OpenApiSpecInput; /** The catalog slug for the new integration (the `<integration>` segment). */ readonly slug: string; /** Display name (defaults to the spec title). */ readonly name?: string; /** Agent-visible description (defaults to the spec's `info.description`, * then the title). */ readonly description?: string; readonly baseUrl?: string; /** Static headers applied to every request (no secret material). */ readonly headers?: Record<string, string>; /** Static query params applied to every request. */ readonly queryParams?: Record<string, string>; /** Auth methods a connection's value renders through - canonical * placements or the request-shaped authoring dialect. */ readonly authenticationTemplate?: readonly AuthenticationInput[];} export interface OpenApiExtensionFailure { readonly _tag: string;} /** Add / merge custom auth methods onto an existing OpenAPI integration's * `authenticationTemplate`. Mirrors the GraphQL plugin's `configure`. */export interface OpenApiConfigureInput { /** The auth methods to add. Each entry is appended to (or, when its `slug` * already exists, replaces) the integration's existing template array. A * custom apiKey method with no `slug` is assigned a generated `custom_<id>` * slug that is collision-checked against the existing template. */ readonly authenticationTemplate: readonly AuthenticationInput[]; readonly mode?: "merge" | "replace";} /** What changed in the tool catalog when a spec was updated in place. Tool * names, not addresses - the same diff applies to every connection. */export interface UpdateSpecResult { readonly slug: IntegrationSlug; readonly toolCount: number; readonly addedTools: readonly string[]; readonly removedTools: readonly string[];} export interface OpenApiUpdateSpecInput { /** New spec source. Omit to re-fetch from the integration's stored * `sourceUrl`. */ readonly spec?: OpenApiSpecInput;} export interface OpenApiPluginExtension { readonly previewSpec: ( input: string | OpenApiPreviewInput, ) => Effect.Effect< SpecPreview, OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure >; readonly addSpec: ( config: OpenApiSpecConfig, ) => Effect.Effect< { readonly slug: IntegrationSlug; readonly toolCount: number }, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | IntegrationAlreadyExistsError | StorageFailure >; /** Re-resolve the integration's spec (from its stored source URL, or the * provided input) and rebuild its tools IN PLACE - connections, * credentials, policies, and the curated description are untouched. */ readonly updateSpec: ( slug: string, input?: OpenApiUpdateSpecInput, ) => Effect.Effect< UpdateSpecResult, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | IntegrationNotFoundError | StorageFailure >; readonly removeSpec: (slug: string) => Effect.Effect<void, StorageFailure>; readonly getIntegration: (slug: string) => Effect.Effect<Integration | null, StorageFailure>; /** Read the integration's full opaque config, including its * `authenticationTemplate`. Returns null when the integration is absent. */ readonly getConfig: ( slug: string, ) => Effect.Effect<OpenApiIntegrationConfig | null, StorageFailure>; /** Add / merge custom auth methods onto the integration's * `authenticationTemplate`. Returns the resulting template array. */ readonly configure: ( slug: string, input: OpenApiConfigureInput, ) => Effect.Effect<readonly Authentication[], StorageFailure>;} // ---------------------------------------------------------------------------// Control-tool input/output schemas// --------------------------------------------------------------------------- const PreviewSpecInputSchema = Schema.Struct({ spec: Schema.String,}); const StaticPreviewServerVariableSchema = Schema.Struct({ default: Schema.String, enum: Schema.NullOr(Schema.Array(Schema.String)), description: Schema.NullOr(Schema.String),});const StaticPreviewServerSchema = Schema.Struct({ url: Schema.String, description: Schema.NullOr(Schema.String), variables: Schema.NullOr(Schema.Record(Schema.String, StaticPreviewServerVariableSchema)),});const StaticPreviewOAuthAuthorizationCodeFlowSchema = Schema.Struct({ authorizationUrl: Schema.String, tokenUrl: Schema.String, refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String),});const StaticPreviewOAuthClientCredentialsFlowSchema = Schema.Struct({ tokenUrl: Schema.String, refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String),});const StaticPreviewOAuthFlowsSchema = Schema.Struct({ authorizationCode: Schema.NullOr(StaticPreviewOAuthAuthorizationCodeFlowSchema), clientCredentials: Schema.NullOr(StaticPreviewOAuthClientCredentialsFlowSchema),});const StaticPreviewSecuritySchemeSchema = Schema.Struct({ name: Schema.String, type: Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]), scheme: Schema.NullOr(Schema.String), bearerFormat: Schema.NullOr(Schema.String), in: Schema.NullOr(Schema.Literals(["header", "query", "cookie"])), headerName: Schema.NullOr(Schema.String), description: Schema.NullOr(Schema.String), flows: Schema.NullOr(StaticPreviewOAuthFlowsSchema), openIdConnectUrl: Schema.NullOr(Schema.String),});const StaticPreviewOAuth2PresetSchema = Schema.Struct({ label: Schema.String, securitySchemeName: Schema.String, flow: Schema.Literals(["authorizationCode", "clientCredentials"]), authorizationUrl: Schema.NullOr(Schema.String), tokenUrl: Schema.String, resource: Schema.NullOr(Schema.String), refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String), identityScopes: Schema.Union([ Schema.Literal("auto"), Schema.Literal(false), Schema.Array(Schema.String), ]), supportsClientIdMetadataDocument: Schema.optional(Schema.Boolean),});const StaticPreviewSpecOutputSchema = Schema.Struct({ title: Schema.NullOr(Schema.String), version: Schema.NullOr(Schema.String), servers: Schema.Array(StaticPreviewServerSchema), operationCount: Schema.Number, tags: Schema.Array(Schema.String), securitySchemes: Schema.Array(StaticPreviewSecuritySchemeSchema), authStrategies: Schema.Array(Schema.Struct({ schemes: Schema.Array(Schema.String) })), headerPresets: Schema.Array( Schema.Struct({ label: Schema.String, headers: Schema.Record(Schema.String, Schema.NullOr(Schema.String)), secretHeaders: Schema.Array(Schema.String), }), ), oauth2Presets: Schema.Array(StaticPreviewOAuth2PresetSchema),});type StaticPreviewSpecOutput = typeof StaticPreviewSpecOutputSchema.Type; const OpenApiSpecInputSchema = Schema.Union([ Schema.Struct({ kind: Schema.Literal("url"), url: Schema.String }), Schema.Struct({ kind: Schema.Literal("blob"), value: Schema.String }),]); const AuthenticationSchema = Schema.Union([ Schema.Struct({ slug: Schema.String, kind: Schema.Literal("oauth2"), authorizationUrl: Schema.String, tokenUrl: Schema.String, resource: Schema.optional(Schema.NullOr(Schema.String)), scopes: Schema.Array(Schema.String), supportsClientIdMetadataDocument: Schema.optional(Schema.Boolean), }), // Credential methods are authored request-shaped - the ONE apikey input // dialect: `{ type: "apiKey", headers: { Authorization: ["Bearer ", // variable("token")] }, queryParams: { … } }`. ApiKeyAuthTemplate,]); const AddSourceInputSchema = Schema.Struct({ spec: OpenApiSpecInputSchema, slug: Schema.String, description: Schema.optional(Schema.String), baseUrl: Schema.optional(Schema.String), headers: Schema.optional(Schema.Record(Schema.String, Schema.String)), queryParams: Schema.optional(Schema.Record(Schema.String, Schema.String)), authenticationTemplate: Schema.optional(Schema.Array(AuthenticationSchema)),}); const AddSourceOutputSchema = Schema.Struct({ slug: Schema.String, toolCount: Schema.Number,}); const PreviewSpecInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(PreviewSpecInputSchema),);const PreviewSpecOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(StaticPreviewSpecOutputSchema),);const AddSourceInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddSourceInputSchema),);const AddSourceOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddSourceOutputSchema),); const openApiToolFailure = (code: string, message: string, details?: unknown) => ToolResult.fail({ code, message, ...(details === undefined ? {} : { details }), }); const staticPreviewOutput = (preview: SpecPreview): StaticPreviewSpecOutput => ({ title: Option.getOrNull(preview.title), version: Option.getOrNull(preview.version), servers: preview.servers.map((server) => ({ url: server.url, description: Option.getOrNull(server.description), variables: Option.getOrNull(server.variables) ? Object.fromEntries( Object.entries(Option.getOrNull(server.variables) ?? {}).map(([name, variable]) => [ name, { default: variable.default, enum: Option.getOrNull(variable.enum), description: Option.getOrNull(variable.description), }, ]), ) : null, })), operationCount: preview.operationCount, tags: preview.tags, securitySchemes: preview.securitySchemes.map((scheme) => ({ name: scheme.name, type: scheme.type, scheme: Option.getOrNull(scheme.scheme), bearerFormat: Option.getOrNull(scheme.bearerFormat), in: Option.getOrNull(scheme.in), headerName: Option.getOrNull(scheme.headerName), description: Option.getOrNull(scheme.description), flows: Option.isSome(scheme.flows) ? { authorizationCode: Option.isSome(scheme.flows.value.authorizationCode) ? { authorizationUrl: scheme.flows.value.authorizationCode.value.authorizationUrl, tokenUrl: scheme.flows.value.authorizationCode.value.tokenUrl, refreshUrl: Option.getOrNull(scheme.flows.value.authorizationCode.value.refreshUrl), scopes: scheme.flows.value.authorizationCode.value.scopes, } : null, clientCredentials: Option.isSome(scheme.flows.value.clientCredentials) ? { tokenUrl: scheme.flows.value.clientCredentials.value.tokenUrl, refreshUrl: Option.getOrNull(scheme.flows.value.clientCredentials.value.refreshUrl), scopes: scheme.flows.value.clientCredentials.value.scopes, } : null, } : null, openIdConnectUrl: Option.getOrNull(scheme.openIdConnectUrl), })), authStrategies: preview.authStrategies, headerPresets: preview.headerPresets, oauth2Presets: preview.oauth2Presets.map((preset) => ({ label: preset.label, securitySchemeName: preset.securitySchemeName, flow: preset.flow, authorizationUrl: Option.getOrNull(preset.authorizationUrl), tokenUrl: preset.tokenUrl, resource: Option.getOrNull(preset.resource), refreshUrl: Option.getOrNull(preset.refreshUrl), scopes: preset.scopes, identityScopes: preset.identityScopes, supportsClientIdMetadataDocument: preset.supportsClientIdMetadataDocument, })),}); const specInputToSourceUrl = (spec: OpenApiSpecInput): string | undefined => spec.kind === "url" ? spec.url : undefined; const OAUTH_DISCOVERED_SCHEME_NAME = "DiscoveredOAuth2";const OPENAPI_HTTP_METHODS = new Set([ "get", "put", "post", "delete", "patch", "head", "options", "trace",]); const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value); const maybeUrl = (value: string): URL | null => { // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: URL parsing accepts user-pasted spec/base URLs try { return new URL(value); } catch { return null; }}; const addProbeCandidate = (candidates: string[], value: string | undefined): void => { const trimmed = value?.trim(); if (!trimmed) return; const parsed = maybeUrl(trimmed); if (!parsed || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) return; const normalized = parsed.toString(); const origin = parsed.origin; if (!candidates.includes(normalized)) candidates.push(normalized); if (!candidates.includes(origin)) candidates.push(origin);}; const oauthProbeCandidates = ( preview: SpecPreview, sourceUrl: string | undefined, baseUrl: string | undefined,): readonly string[] => { const candidates: string[] = []; addProbeCandidate(candidates, baseUrl); for (const server of preview.servers) { addProbeCandidate( candidates, resolveServerUrl(server.url, Option.getOrUndefined(server.variables), {}), ); } addProbeCandidate(candidates, sourceUrl); return candidates;}; const securityRequirementScopes = ( security: unknown, targetSchemes: ReadonlySet<string>,): readonly string[] => { if (!Array.isArray(security)) return []; const scopes = new Set<string>(); for (const requirement of security) { if (!isRecord(requirement)) continue; for (const [scheme, rawScopes] of Object.entries(requirement)) { if (targetSchemes.size > 0 && !targetSchemes.has(scheme)) continue; if (!Array.isArray(rawScopes)) continue; for (const scope of rawScopes) { if (typeof scope === "string" && scope.trim().length > 0) scopes.add(scope.trim()); } } } return [...scopes];}; const collectDeclaredSecurityScopes = (doc: unknown, targetSchemes: ReadonlySet<string>) => { const scopes = new Set<string>(); if (!isRecord(doc)) return [] as readonly string[]; for (const scope of securityRequirementScopes(doc.security, targetSchemes)) scopes.add(scope); const paths = doc.paths; if (!isRecord(paths)) return [...scopes].sort(); for (const pathItem of Object.values(paths)) { if (!isRecord(pathItem)) continue; for (const [method, operation] of Object.entries(pathItem)) { if (!OPENAPI_HTTP_METHODS.has(method.toLowerCase()) || !isRecord(operation)) continue; for (const scope of securityRequirementScopes(operation.security, targetSchemes)) { scopes.add(scope); } } } return [...scopes].sort();}; const nonOAuthSecuritySchemeNames = (preview: SpecPreview): ReadonlySet<string> => new Set( preview.securitySchemes .filter((scheme) => scheme.type === "http" || scheme.type === "apiKey") .map((scheme) => scheme.name), ); const discoveredOAuthPreview = (input: { readonly preview: SpecPreview; readonly authorizationUrl: string; readonly tokenUrl: string; readonly resource?: string | null; readonly scopes: readonly string[]; readonly supportsClientIdMetadataDocument?: boolean;}): SpecPreview => { const scopes = Object.fromEntries(input.scopes.map((scope) => [scope, ""])); const flow = OAuth2AuthorizationCodeFlow.make({ authorizationUrl: input.authorizationUrl, tokenUrl: input.tokenUrl, refreshUrl: Option.none(), scopes, }); const flows = OAuth2Flows.make({ authorizationCode: Option.some(flow), clientCredentials: Option.none(), }); return { ...input.preview, securitySchemes: [ ...input.preview.securitySchemes, SecurityScheme.make({ name: OAUTH_DISCOVERED_SCHEME_NAME, type: "oauth2", scheme: Option.none(), bearerFormat: Option.none(), in: Option.none(), headerName: Option.none(), description: Option.some("Discovered from OAuth authorization-server metadata"), flows: Option.some(flows), openIdConnectUrl: Option.none(), }), ], oauth2Presets: [ ...input.preview.oauth2Presets, OAuth2Preset.make({Lines 1–500 of 1104. Load more
import { Effect, Option, Schema } from "effect"; import type { Layer } from "effect"; import { HttpClient } from "effect/unstable/http"; import { IntegrationAlreadyExistsError, IntegrationDetectionResult, IntegrationNotFoundError, IntegrationSlug, ToolResult, definePlugin, mergeAuthTemplates, sha256Hex, tool, type AuthMethodDescriptor, type Integration, type IntegrationConfig, type IntegrationRecord, type PluginCtx, type StorageFailure, } from "@executor-js/sdk/core"; import { decodeOpenApiIntegrationConfig, type OpenApiIntegrationConfig } from "./config"; import { OpenApiExtractionError, OpenApiOAuthError, OpenApiParseError } from "./errors"; import { parse, resolveSpecText } from "./parse"; import { extract } from "./extract"; import { OAuth2AuthorizationCodeFlow, OAuth2Flows, OAuth2Preset, SecurityScheme, previewSpecText, type SpecPreview, } from "./preview"; import { deriveAuthenticationTemplateFromPreview, firstBaseUrlForPreview } from "./derive-auth"; import { openApiPresets } from "./presets"; import { makeDefaultOpenapiStore, type OpenapiStore } from "./store"; import type { Authentication } from "./types"; import { normalizeOpenApiAuthInputs, type AuthenticationInput } from "./types"; import { ApiKeyAuthTemplate, describeApiKeyAuthMethod } from "@executor-js/sdk/http-auth"; import { checkHealthOpenApi, compileOpenApiSpec, invokeOpenApiBackedTool, listHealthCheckCandidatesOpenApi, openApiStoredOperationsFromCompiled, resolveOpenApiBackedAnnotations, resolveOpenApiBackedTools, validateOpenApiBackedToolArgs, } from "./backing"; import { resolveServerUrl } from "./openapi-utils"; // --------------------------------------------------------------------------- // Extension input shapes // --------------------------------------------------------------------------- export type OpenApiSpecInput = typeof OpenApiSpecInputSchema.Type; export interface OpenApiPreviewInput { readonly spec: string; } /** Add an OpenAPI integration to the catalog. The integration is the API * surface; connections (the credentials) are attached separately and resolve * their value through the declared `authenticationTemplate`. */ export interface OpenApiSpecConfig { readonly spec: OpenApiSpecInput; /** The catalog slug for the new integration (the `<integration>` segment). */ readonly slug: string; /** Display name (defaults to the spec title). */ readonly name?: string; /** Agent-visible description (defaults to the spec's `info.description`, * then the title). */ readonly description?: string; readonly baseUrl?: string; /** Static headers applied to every request (no secret material). */ readonly headers?: Record<string, string>; /** Static query params applied to every request. */ readonly queryParams?: Record<string, string>; /** Auth methods a connection's value renders through - canonical * placements or the request-shaped authoring dialect. */ readonly authenticationTemplate?: readonly AuthenticationInput[]; } export interface OpenApiExtensionFailure { readonly _tag: string; } /** Add / merge custom auth methods onto an existing OpenAPI integration's * `authenticationTemplate`. Mirrors the GraphQL plugin's `configure`. */ export interface OpenApiConfigureInput { /** The auth methods to add. Each entry is appended to (or, when its `slug` * already exists, replaces) the integration's existing template array. A * custom apiKey method with no `slug` is assigned a generated `custom_<id>` * slug that is collision-checked against the existing template. */ readonly authenticationTemplate: readonly AuthenticationInput[]; readonly mode?: "merge" | "replace"; } /** What changed in the tool catalog when a spec was updated in place. Tool * names, not addresses - the same diff applies to every connection. */ export interface UpdateSpecResult { readonly slug: IntegrationSlug; readonly toolCount: number; readonly addedTools: readonly string[]; readonly removedTools: readonly string[]; } export interface OpenApiUpdateSpecInput { /** New spec source. Omit to re-fetch from the integration's stored * `sourceUrl`. */ readonly spec?: OpenApiSpecInput; } export interface OpenApiPluginExtension { readonly previewSpec: ( input: string | OpenApiPreviewInput, ) => Effect.Effect< SpecPreview, OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure >; readonly addSpec: ( config: OpenApiSpecConfig, ) => Effect.Effect< { readonly slug: IntegrationSlug; readonly toolCount: number }, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | IntegrationAlreadyExistsError | StorageFailure >; /** Re-resolve the integration's spec (from its stored source URL, or the * provided input) and rebuild its tools IN PLACE - connections, * credentials, policies, and the curated description are untouched. */ readonly updateSpec: ( slug: string, input?: OpenApiUpdateSpecInput, ) => Effect.Effect< UpdateSpecResult, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | IntegrationNotFoundError | StorageFailure >; readonly removeSpec: (slug: string) => Effect.Effect<void, StorageFailure>; readonly getIntegration: (slug: string) => Effect.Effect<Integration | null, StorageFailure>; /** Read the integration's full opaque config, including its * `authenticationTemplate`. Returns null when the integration is absent. */ readonly getConfig: ( slug: string, ) => Effect.Effect<OpenApiIntegrationConfig | null, StorageFailure>; /** Add / merge custom auth methods onto the integration's * `authenticationTemplate`. Returns the resulting template array. */ readonly configure: ( slug: string, input: OpenApiConfigureInput, ) => Effect.Effect<readonly Authentication[], StorageFailure>; } // --------------------------------------------------------------------------- // Control-tool input/output schemas // --------------------------------------------------------------------------- const PreviewSpecInputSchema = Schema.Struct({ spec: Schema.String, }); const StaticPreviewServerVariableSchema = Schema.Struct({ default: Schema.String, enum: Schema.NullOr(Schema.Array(Schema.String)), description: Schema.NullOr(Schema.String), }); const StaticPreviewServerSchema = Schema.Struct({ url: Schema.String, description: Schema.NullOr(Schema.String), variables: Schema.NullOr(Schema.Record(Schema.String, StaticPreviewServerVariableSchema)), }); const StaticPreviewOAuthAuthorizationCodeFlowSchema = Schema.Struct({ authorizationUrl: Schema.String, tokenUrl: Schema.String, refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String), }); const StaticPreviewOAuthClientCredentialsFlowSchema = Schema.Struct({ tokenUrl: Schema.String, refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String), }); const StaticPreviewOAuthFlowsSchema = Schema.Struct({ authorizationCode: Schema.NullOr(StaticPreviewOAuthAuthorizationCodeFlowSchema), clientCredentials: Schema.NullOr(StaticPreviewOAuthClientCredentialsFlowSchema), }); const StaticPreviewSecuritySchemeSchema = Schema.Struct({ name: Schema.String, type: Schema.Literals(["http", "apiKey", "oauth2", "openIdConnect"]), scheme: Schema.NullOr(Schema.String), bearerFormat: Schema.NullOr(Schema.String), in: Schema.NullOr(Schema.Literals(["header", "query", "cookie"])), headerName: Schema.NullOr(Schema.String), description: Schema.NullOr(Schema.String), flows: Schema.NullOr(StaticPreviewOAuthFlowsSchema), openIdConnectUrl: Schema.NullOr(Schema.String), }); const StaticPreviewOAuth2PresetSchema = Schema.Struct({ label: Schema.String, securitySchemeName: Schema.String, flow: Schema.Literals(["authorizationCode", "clientCredentials"]), authorizationUrl: Schema.NullOr(Schema.String), tokenUrl: Schema.String, resource: Schema.NullOr(Schema.String), refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String), identityScopes: Schema.Union([ Schema.Literal("auto"), Schema.Literal(false), Schema.Array(Schema.String), ]), supportsClientIdMetadataDocument: Schema.optional(Schema.Boolean), }); const StaticPreviewSpecOutputSchema = Schema.Struct({ title: Schema.NullOr(Schema.String), version: Schema.NullOr(Schema.String), servers: Schema.Array(StaticPreviewServerSchema), operationCount: Schema.Number, tags: Schema.Array(Schema.String), securitySchemes: Schema.Array(StaticPreviewSecuritySchemeSchema), authStrategies: Schema.Array(Schema.Struct({ schemes: Schema.Array(Schema.String) })), headerPresets: Schema.Array( Schema.Struct({ label: Schema.String, headers: Schema.Record(Schema.String, Schema.NullOr(Schema.String)), secretHeaders: Schema.Array(Schema.String), }), ), oauth2Presets: Schema.Array(StaticPreviewOAuth2PresetSchema), }); type StaticPreviewSpecOutput = typeof StaticPreviewSpecOutputSchema.Type; const OpenApiSpecInputSchema = Schema.Union([ Schema.Struct({ kind: Schema.Literal("url"), url: Schema.String }), Schema.Struct({ kind: Schema.Literal("blob"), value: Schema.String }), ]); const AuthenticationSchema = Schema.Union([ Schema.Struct({ slug: Schema.String, kind: Schema.Literal("oauth2"), authorizationUrl: Schema.String, tokenUrl: Schema.String, resource: Schema.optional(Schema.NullOr(Schema.String)), scopes: Schema.Array(Schema.String), supportsClientIdMetadataDocument: Schema.optional(Schema.Boolean), }), // Credential methods are authored request-shaped - the ONE apikey input // dialect: `{ type: "apiKey", headers: { Authorization: ["Bearer ", // variable("token")] }, queryParams: { … } }`. ApiKeyAuthTemplate, ]); const AddSourceInputSchema = Schema.Struct({ spec: OpenApiSpecInputSchema, slug: Schema.String, description: Schema.optional(Schema.String), baseUrl: Schema.optional(Schema.String), headers: Schema.optional(Schema.Record(Schema.String, Schema.String)), queryParams: Schema.optional(Schema.Record(Schema.String, Schema.String)), authenticationTemplate: Schema.optional(Schema.Array(AuthenticationSchema)), }); const AddSourceOutputSchema = Schema.Struct({ slug: Schema.String, toolCount: Schema.Number, }); const PreviewSpecInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(PreviewSpecInputSchema), ); const PreviewSpecOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(StaticPreviewSpecOutputSchema), ); const AddSourceInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddSourceInputSchema), ); const AddSourceOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddSourceOutputSchema), ); const openApiToolFailure = (code: string, message: string, details?: unknown) => ToolResult.fail({ code, message, ...(details === undefined ? {} : { details }), }); const staticPreviewOutput = (preview: SpecPreview): StaticPreviewSpecOutput => ({ title: Option.getOrNull(preview.title), version: Option.getOrNull(preview.version), servers: preview.servers.map((server) => ({ url: server.url, description: Option.getOrNull(server.description), variables: Option.getOrNull(server.variables) ? Object.fromEntries( Object.entries(Option.getOrNull(server.variables) ?? {}).map(([name, variable]) => [ name, { default: variable.default, enum: Option.getOrNull(variable.enum), description: Option.getOrNull(variable.description), }, ]), ) : null, })), operationCount: preview.operationCount, tags: preview.tags, securitySchemes: preview.securitySchemes.map((scheme) => ({ name: scheme.name, type: scheme.type, scheme: Option.getOrNull(scheme.scheme), bearerFormat: Option.getOrNull(scheme.bearerFormat), in: Option.getOrNull(scheme.in), headerName: Option.getOrNull(scheme.headerName), description: Option.getOrNull(scheme.description), flows: Option.isSome(scheme.flows) ? { authorizationCode: Option.isSome(scheme.flows.value.authorizationCode) ? { authorizationUrl: scheme.flows.value.authorizationCode.value.authorizationUrl, tokenUrl: scheme.flows.value.authorizationCode.value.tokenUrl, refreshUrl: Option.getOrNull(scheme.flows.value.authorizationCode.value.refreshUrl), scopes: scheme.flows.value.authorizationCode.value.scopes, } : null, clientCredentials: Option.isSome(scheme.flows.value.clientCredentials) ? { tokenUrl: scheme.flows.value.clientCredentials.value.tokenUrl, refreshUrl: Option.getOrNull(scheme.flows.value.clientCredentials.value.refreshUrl), scopes: scheme.flows.value.clientCredentials.value.scopes, } : null, } : null, openIdConnectUrl: Option.getOrNull(scheme.openIdConnectUrl), })), authStrategies: preview.authStrategies, headerPresets: preview.headerPresets, oauth2Presets: preview.oauth2Presets.map((preset) => ({ label: preset.label, securitySchemeName: preset.securitySchemeName, flow: preset.flow, authorizationUrl: Option.getOrNull(preset.authorizationUrl), tokenUrl: preset.tokenUrl, resource: Option.getOrNull(preset.resource), refreshUrl: Option.getOrNull(preset.refreshUrl), scopes: preset.scopes, identityScopes: preset.identityScopes, supportsClientIdMetadataDocument: preset.supportsClientIdMetadataDocument, })), }); const specInputToSourceUrl = (spec: OpenApiSpecInput): string | undefined => spec.kind === "url" ? spec.url : undefined; const OAUTH_DISCOVERED_SCHEME_NAME = "DiscoveredOAuth2"; const OPENAPI_HTTP_METHODS = new Set([ "get", "put", "post", "delete", "patch", "head", "options", "trace", ]); const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value); const maybeUrl = (value: string): URL | null => { // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: URL parsing accepts user-pasted spec/base URLs try { return new URL(value); } catch { return null; } }; const addProbeCandidate = (candidates: string[], value: string | undefined): void => { const trimmed = value?.trim(); if (!trimmed) return; const parsed = maybeUrl(trimmed); if (!parsed || (parsed.protocol !== "https:" && parsed.protocol !== "http:")) return; const normalized = parsed.toString(); const origin = parsed.origin; if (!candidates.includes(normalized)) candidates.push(normalized); if (!candidates.includes(origin)) candidates.push(origin); }; const oauthProbeCandidates = ( preview: SpecPreview, sourceUrl: string | undefined, baseUrl: string | undefined, ): readonly string[] => { const candidates: string[] = []; addProbeCandidate(candidates, baseUrl); for (const server of preview.servers) { addProbeCandidate( candidates, resolveServerUrl(server.url, Option.getOrUndefined(server.variables), {}), ); } addProbeCandidate(candidates, sourceUrl); return candidates; }; const securityRequirementScopes = ( security: unknown, targetSchemes: ReadonlySet<string>, ): readonly string[] => { if (!Array.isArray(security)) return []; const scopes = new Set<string>(); for (const requirement of security) { if (!isRecord(requirement)) continue; for (const [scheme, rawScopes] of Object.entries(requirement)) { if (targetSchemes.size > 0 && !targetSchemes.has(scheme)) continue; if (!Array.isArray(rawScopes)) continue; for (const scope of rawScopes) { if (typeof scope === "string" && scope.trim().length > 0) scopes.add(scope.trim()); } } } return [...scopes]; }; const collectDeclaredSecurityScopes = (doc: unknown, targetSchemes: ReadonlySet<string>) => { const scopes = new Set<string>(); if (!isRecord(doc)) return [] as readonly string[]; for (const scope of securityRequirementScopes(doc.security, targetSchemes)) scopes.add(scope); const paths = doc.paths; if (!isRecord(paths)) return [...scopes].sort(); for (const pathItem of Object.values(paths)) { if (!isRecord(pathItem)) continue; for (const [method, operation] of Object.entries(pathItem)) { if (!OPENAPI_HTTP_METHODS.has(method.toLowerCase()) || !isRecord(operation)) continue; for (const scope of securityRequirementScopes(operation.security, targetSchemes)) { scopes.add(scope); } } } return [...scopes].sort(); }; const nonOAuthSecuritySchemeNames = (preview: SpecPreview): ReadonlySet<string> => new Set( preview.securitySchemes .filter((scheme) => scheme.type === "http" || scheme.type === "apiKey") .map((scheme) => scheme.name), ); const discoveredOAuthPreview = (input: { readonly preview: SpecPreview; readonly authorizationUrl: string; readonly tokenUrl: string; readonly resource?: string | null; readonly scopes: readonly string[]; readonly supportsClientIdMetadataDocument?: boolean; }): SpecPreview => { const scopes = Object.fromEntries(input.scopes.map((scope) => [scope, ""])); const flow = OAuth2AuthorizationCodeFlow.make({ authorizationUrl: input.authorizationUrl, tokenUrl: input.tokenUrl, refreshUrl: Option.none(), scopes, }); const flows = OAuth2Flows.make({ authorizationCode: Option.some(flow), clientCredentials: Option.none(), }); return { ...input.preview, securitySchemes: [ ...input.preview.securitySchemes, SecurityScheme.make({ name: OAUTH_DISCOVERED_SCHEME_NAME, type: "oauth2", scheme: Option.none(), bearerFormat: Option.none(), in: Option.none(), headerName: Option.none(), description: Option.some("Discovered from OAuth authorization-server metadata"), flows: Option.some(flows), openIdConnectUrl: Option.none(), }), ], oauth2Presets: [ ...input.preview.oauth2Presets, OAuth2Preset.make({