import { Effect, Option, Schema } from "effect";import type { Layer } from "effect";import { HttpClient } from "effect/unstable/http"; import { IntegrationAlreadyExistsError, IntegrationDetectionResult, IntegrationNotFoundError, IntegrationSlug, ToolResult, definePlugin, HealthCheckSpec, mergeAuthTemplates, sha256Hex, tool, type AuthMethodDescriptor, type Integration, type IntegrationConfig, type IntegrationPreset, type IntegrationRecord, type OrgWriteDeniedError, type PluginCtx, type StorageFailure,} from "@executor-js/sdk/core"; import { decodeOpenApiIntegrationConfig, type OpenApiIntegrationConfig } from "./config";import { OpenApiExtractionError, OpenApiOAuthError, OpenApiParseError, OpenApiSpecOverrideError,} from "./errors";import { parse, parseSpecObject, 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 { resolveSpecFormatAdapter, type ConvertedSpec, type SpecFormatAdapter,} from "./spec-format";import type { Authentication } from "./types";import { normalizeOpenApiAuthInputs, type AuthenticationInput } from "./types";import { ApiKeyAuthTemplate, describeApiKeyAuthMethod } from "@executor-js/sdk/http-auth";import { checkHealthOpenApi, compileAndPersistOpenApiSpecStreaming, compileOpenApiSpec, invokeOpenApiBackedTool, listHealthCheckCandidatesOpenApi, openApiStoredOperationsFromCompiled, resolveOpenApiBackedAnnotations, resolveOpenApiBackedTools, validateOpenApiBackedToolArgs,} from "./backing";import type { InvokeOptions } from "./invoke";import { resolveServerUrl } from "./openapi-utils";import { applySpecOverrides, decodeOpenApiSpecOverrides, SpecOverridesSchema, type SpecOverrides,} from "./spec-overrides"; const encodeJsonText = Schema.encodeUnknownSync(Schema.UnknownFromJsonString); // ---------------------------------------------------------------------------// Extension input shapes// --------------------------------------------------------------------------- export type OpenApiSpecInput = typeof OpenApiSpecInputSchema.Type; export interface OpenApiPreviewInput { readonly spec: string; readonly specFormat?: string; readonly specOverrides?: SpecOverrides;} /** 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>; readonly specFormat?: string; /** Ordered RFC 6902 operations applied after fetching/conversion and before parsing. */ readonly specOverrides?: SpecOverrides; readonly family?: string; readonly healthCheck?: HealthCheckSpec; /** 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`, and update request routing metadata. 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"; readonly baseUrl?: string;} /** 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 * `specUrl`. */ readonly spec?: OpenApiSpecInput; /** Replacement override list. Omit to keep existing overrides; pass [] to clear them. */ readonly specOverrides?: SpecOverrides;} export interface OpenApiPluginExtension { readonly previewSpec: ( input: string | OpenApiPreviewInput, ) => Effect.Effect< SpecPreview, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | OpenApiSpecOverrideError | StorageFailure >; readonly addSpec: ( config: OpenApiSpecConfig, ) => Effect.Effect< { readonly slug: IntegrationSlug; readonly toolCount: number }, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | OpenApiSpecOverrideError | IntegrationAlreadyExistsError | OrgWriteDeniedError | 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 | OpenApiSpecOverrideError | IntegrationNotFoundError | OrgWriteDeniedError | StorageFailure >; readonly removeSpec: (slug: string) => Effect.Effect<void, OrgWriteDeniedError | 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[], OrgWriteDeniedError | StorageFailure>;} // ---------------------------------------------------------------------------// Control-tool input/output schemas// --------------------------------------------------------------------------- const PreviewSpecInputSchema = Schema.Struct({ spec: Schema.String, specFormat: Schema.optional(Schema.String), specOverrides: Schema.optional(SpecOverridesSchema),}); 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 AddIntegrationInputSchema = Schema.Struct({ spec: OpenApiSpecInputSchema, slug: Schema.optional(Schema.String), name: Schema.optional(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)), specFormat: Schema.optional(Schema.String), specOverrides: Schema.optional(SpecOverridesSchema), family: Schema.optional(Schema.String), healthCheck: Schema.optional(HealthCheckSpec), authenticationTemplate: Schema.optional(Schema.Array(AuthenticationSchema)),}); const AddIntegrationOutputSchema = Schema.Struct({ slug: Schema.String, toolCount: Schema.Number,}); const PreviewSpecInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(PreviewSpecInputSchema),);const PreviewSpecOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(StaticPreviewSpecOutputSchema),);const AddIntegrationInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddIntegrationInputSchema),);const AddIntegrationOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddIntegrationOutputSchema),); 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 specInputToSpecUrl = (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, specUrl: 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, specUrl); 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); } }Lines 1–500 of 1355. 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, HealthCheckSpec, mergeAuthTemplates, sha256Hex, tool, type AuthMethodDescriptor, type Integration, type IntegrationConfig, type IntegrationPreset, type IntegrationRecord, type OrgWriteDeniedError, type PluginCtx, type StorageFailure, } from "@executor-js/sdk/core"; import { decodeOpenApiIntegrationConfig, type OpenApiIntegrationConfig } from "./config"; import { OpenApiExtractionError, OpenApiOAuthError, OpenApiParseError, OpenApiSpecOverrideError, } from "./errors"; import { parse, parseSpecObject, 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 { resolveSpecFormatAdapter, type ConvertedSpec, type SpecFormatAdapter, } from "./spec-format"; import type { Authentication } from "./types"; import { normalizeOpenApiAuthInputs, type AuthenticationInput } from "./types"; import { ApiKeyAuthTemplate, describeApiKeyAuthMethod } from "@executor-js/sdk/http-auth"; import { checkHealthOpenApi, compileAndPersistOpenApiSpecStreaming, compileOpenApiSpec, invokeOpenApiBackedTool, listHealthCheckCandidatesOpenApi, openApiStoredOperationsFromCompiled, resolveOpenApiBackedAnnotations, resolveOpenApiBackedTools, validateOpenApiBackedToolArgs, } from "./backing"; import type { InvokeOptions } from "./invoke"; import { resolveServerUrl } from "./openapi-utils"; import { applySpecOverrides, decodeOpenApiSpecOverrides, SpecOverridesSchema, type SpecOverrides, } from "./spec-overrides"; const encodeJsonText = Schema.encodeUnknownSync(Schema.UnknownFromJsonString); // --------------------------------------------------------------------------- // Extension input shapes // --------------------------------------------------------------------------- export type OpenApiSpecInput = typeof OpenApiSpecInputSchema.Type; export interface OpenApiPreviewInput { readonly spec: string; readonly specFormat?: string; readonly specOverrides?: SpecOverrides; } /** 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>; readonly specFormat?: string; /** Ordered RFC 6902 operations applied after fetching/conversion and before parsing. */ readonly specOverrides?: SpecOverrides; readonly family?: string; readonly healthCheck?: HealthCheckSpec; /** 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`, and update request routing metadata. 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"; readonly baseUrl?: string; } /** 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 * `specUrl`. */ readonly spec?: OpenApiSpecInput; /** Replacement override list. Omit to keep existing overrides; pass [] to clear them. */ readonly specOverrides?: SpecOverrides; } export interface OpenApiPluginExtension { readonly previewSpec: ( input: string | OpenApiPreviewInput, ) => Effect.Effect< SpecPreview, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | OpenApiSpecOverrideError | StorageFailure >; readonly addSpec: ( config: OpenApiSpecConfig, ) => Effect.Effect< { readonly slug: IntegrationSlug; readonly toolCount: number }, | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | OpenApiSpecOverrideError | IntegrationAlreadyExistsError | OrgWriteDeniedError | 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 | OpenApiSpecOverrideError | IntegrationNotFoundError | OrgWriteDeniedError | StorageFailure >; readonly removeSpec: (slug: string) => Effect.Effect<void, OrgWriteDeniedError | 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[], OrgWriteDeniedError | StorageFailure>; } // --------------------------------------------------------------------------- // Control-tool input/output schemas // --------------------------------------------------------------------------- const PreviewSpecInputSchema = Schema.Struct({ spec: Schema.String, specFormat: Schema.optional(Schema.String), specOverrides: Schema.optional(SpecOverridesSchema), }); 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 AddIntegrationInputSchema = Schema.Struct({ spec: OpenApiSpecInputSchema, slug: Schema.optional(Schema.String), name: Schema.optional(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)), specFormat: Schema.optional(Schema.String), specOverrides: Schema.optional(SpecOverridesSchema), family: Schema.optional(Schema.String), healthCheck: Schema.optional(HealthCheckSpec), authenticationTemplate: Schema.optional(Schema.Array(AuthenticationSchema)), }); const AddIntegrationOutputSchema = Schema.Struct({ slug: Schema.String, toolCount: Schema.Number, }); const PreviewSpecInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(PreviewSpecInputSchema), ); const PreviewSpecOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(StaticPreviewSpecOutputSchema), ); const AddIntegrationInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddIntegrationInputSchema), ); const AddIntegrationOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(AddIntegrationOutputSchema), ); 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 specInputToSpecUrl = (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, specUrl: 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, specUrl); 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); } }