import { Effect, Option, Predicate, Schema } from "effect";import type { Layer } from "effect";import { HttpClient } from "effect/unstable/http"; import { ConnectionId, CredentialBindingRef, ScopeId, SecretId, SourceDetectionResult, StorageError, ToolResult, authToolFailure, defaultSourceInstallScopeId, definePlugin, tool, resolveSecretBackedMap, type CredentialBindingValue, type PluginCtx, type StorageFailure, type ToolAnnotations, type ToolRow,} from "@executor-js/sdk/core"; import { headersToConfigValues, type ConfigFileSink, type OpenApiSourceConfig,} from "@executor-js/config"; import { OpenApiAuthRequiredError, OpenApiExtractionError, OpenApiOAuthError, OpenApiParseError,} from "./errors";import { parse, resolveSpecText } from "./parse";import { extract } from "./extract";import { compileToolDefinitions, type ToolDefinition } from "./definitions";import { annotationsForOperation, invokeWithLayer } from "./invoke";import { previewSpec, type SpecPreview } from "./preview";import { openApiPresets } from "./presets";import { makeDefaultOpenapiStore, openapiSchema, type OpenapiStore, type SourceConfig, type StoredOperation, type StoredSource,} from "./store";import { HeaderValue as HeaderValueSchema, ConfiguredHeaderBinding, OAuth2SourceConfig, OperationBinding, type ConfiguredHeaderValue as ConfiguredHeaderValueValue, type HeaderValue as HeaderValueValue,} from "./types"; // ---------------------------------------------------------------------------// Plugin config// --------------------------------------------------------------------------- const STRINGIFIED_BODY_CAP = 1024;const UpstreamMessageBody = Schema.Struct({ message: Schema.String });const UpstreamErrorMessageBody = Schema.Struct({ errorMessage: Schema.String });const UpstreamNestedErrorBody = Schema.Struct({ error: UpstreamMessageBody });const UpstreamErrorsArrayBody = Schema.Struct({ errors: Schema.Array( Schema.Struct({ detail: Schema.optional(Schema.String), message: Schema.optional(Schema.String), title: Schema.optional(Schema.String), }), ),});const UpstreamDescriptionBody = Schema.Struct({ detail: Schema.optional(Schema.String), title: Schema.optional(Schema.String), description: Schema.optional(Schema.String),}); const decodeUpstreamMessageBody = Schema.decodeUnknownOption(UpstreamMessageBody);const decodeUpstreamErrorMessageBody = Schema.decodeUnknownOption(UpstreamErrorMessageBody);const decodeUpstreamNestedErrorBody = Schema.decodeUnknownOption(UpstreamNestedErrorBody);const decodeUpstreamErrorsArrayBody = Schema.decodeUnknownOption(UpstreamErrorsArrayBody);const decodeUpstreamDescriptionBody = Schema.decodeUnknownOption(UpstreamDescriptionBody); const clampedStringify = (value: unknown): string => { let s: string; // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: JSON.stringify may throw on cycles; fall back to String() so the upstream body can still be surfaced as ToolError.details fallback text try { s = JSON.stringify(value); } catch { s = String(value); } return s.length > STRINGIFIED_BODY_CAP ? `${s.slice(0, STRINGIFIED_BODY_CAP)}…` : s;}; const firstNonEmpty = (...values: readonly (string | undefined)[]): string | undefined => values.find((value) => value !== undefined && value.length > 0); // Walk known upstream error-body shapes so ToolError.message stays concise// while ToolError.details preserves the original body.const extractUpstreamMessage = (body: unknown, status: number): string => { if (typeof body === "string") { return body.length > 0 ? body : `Upstream returned HTTP ${status}`; } const nested = Option.getOrUndefined(decodeUpstreamNestedErrorBody(body)); const messageBody = Option.getOrUndefined(decodeUpstreamMessageBody(body)); const errorMessageBody = Option.getOrUndefined(decodeUpstreamErrorMessageBody(body)); const errorsBody = Option.getOrUndefined(decodeUpstreamErrorsArrayBody(body)); const descriptionBody = Option.getOrUndefined(decodeUpstreamDescriptionBody(body)); const arrayMessage = errorsBody?.errors .map(({ detail, message: upstreamMessage, title }) => firstNonEmpty(detail, upstreamMessage, title), ) .find((message) => message !== undefined); const message = firstNonEmpty( nested?.error.message, messageBody?.message, errorMessageBody?.errorMessage, arrayMessage, descriptionBody?.detail, descriptionBody?.title, descriptionBody?.description, ); if (message !== undefined) return message; if (body !== null && typeof body === "object") { return clampedStringify(body); } return `Upstream returned HTTP ${status}`;}; export type HeaderValue = HeaderValueValue;export type ConfiguredHeaderValue = ConfiguredHeaderValueValue;export type OpenApiOAuthInput = OAuth2SourceConfig; export type OpenApiSpecInput = typeof OpenApiSpecInputSchema.Type; export type OpenApiSecretShapeInput = typeof OpenApiSecretShapeInputSchema.Type; export type OpenApiConfiguredValueInput = | string | OpenApiSecretShapeInput | ConfiguredHeaderValueValue; export interface OpenApiSpecFetchCredentialsInput { readonly headers?: Record<string, OpenApiConfiguredValueInput>; readonly queryParams?: Record<string, OpenApiConfiguredValueInput>;} export interface OpenApiPreviewSpecFetchCredentialsInput { readonly headers?: Record<string, HeaderValue>; readonly queryParams?: Record<string, HeaderValue>;} export interface OpenApiPreviewInput { readonly spec: string; readonly specFetchCredentials?: OpenApiPreviewSpecFetchCredentialsInput;} export interface OpenApiSpecConfig { readonly spec: OpenApiSpecInput; readonly specFetchCredentials?: OpenApiSpecFetchCredentialsInput; /** * Executor scope id that owns this source row. Must be one of the * executor's configured scopes. Typical shape: an admin adds the * source at the outermost (organization) scope so it's visible to * every inner (per-user) scope via fall-through reads. */ readonly scope: string; readonly name: string; readonly baseUrl: string; readonly namespace: string; readonly headers?: Record<string, OpenApiConfiguredValueInput>; readonly queryParams?: Record<string, OpenApiConfiguredValueInput>; readonly oauth2?: OpenApiOAuthInput;} export interface OpenApiSourceRef { readonly id: string; readonly scope: string;} export type OpenApiConfigureCredentialInput = | string | { readonly kind: "text"; readonly text: string; readonly prefix?: string; } | { readonly kind: "secret"; readonly secretId: string; readonly secretScope?: string; readonly prefix?: string; } | { readonly kind: "connection"; readonly connectionId: string; }; /** * Errors any OpenAPI extension method may surface. The first three are * plugin-domain tagged errors that flow directly to clients (4xx, each * carrying its own `HttpApiSchema` status). `StorageFailure` covers * raw backend failures (`StorageError`) plus `UniqueViolationError`; * the HTTP edge (`@executor-js/api`'s `withCapture`) translates * `StorageError` to the opaque `InternalError({ traceId })` at Layer * composition. `UniqueViolationError` passes through — plugins can * `Effect.catchTag` it if they want a friendlier user-facing error. */export type OpenApiExtensionFailure = | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure; export interface OpenApiPluginExtension { readonly previewSpec: ( input: string | OpenApiPreviewInput, ) => Effect.Effect< SpecPreview, OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure >; readonly addSpec: ( config: OpenApiSpecConfig, ) => Effect.Effect< { readonly sourceId: string; readonly toolCount: number }, OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure >; readonly removeSpec: (namespace: string, scope: string) => Effect.Effect<void, StorageFailure>; readonly getSource: ( namespace: string, scope: string, ) => Effect.Effect<StoredSource | null, StorageFailure>; readonly configure: ( source: OpenApiSourceRef, input: OpenApiConfigureInput, ) => Effect.Effect<readonly CredentialBindingRef[], StorageFailure>;} // ---------------------------------------------------------------------------// Control-tool input/output schemas// --------------------------------------------------------------------------- const PreviewSpecInputSchema = Schema.Struct({ spec: Schema.String, specFetchCredentials: Schema.optional( Schema.Struct({ headers: Schema.optional(Schema.Record(Schema.String, HeaderValueSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, HeaderValueSchema)), }), ),}); 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, refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String),});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 OpenApiSecretShapeInputSchema = Schema.Struct({ kind: Schema.Literal("secret"), prefix: Schema.optional(Schema.String),});const OpenApiConfiguredValueInputSchema = Schema.Union([ Schema.String, OpenApiSecretShapeInputSchema,]);const OpenApiConfigureCredentialInputSchema = Schema.Union([ Schema.String, Schema.Struct({ kind: Schema.Literal("text"), text: Schema.String, prefix: Schema.optional(Schema.String), }), Schema.Struct({ kind: Schema.Literal("secret"), secretId: Schema.String, secretScope: Schema.optional(Schema.String), prefix: Schema.optional(Schema.String), }), Schema.Struct({ kind: Schema.Literal("connection"), connectionId: Schema.String, }),]);const OpenApiConfigureInputSchema = Schema.Struct({ scope: Schema.String, name: Schema.optional(Schema.String), baseUrl: Schema.optional(Schema.String), headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema)), specFetchCredentials: Schema.optional( Schema.Struct({ headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema)), queryParams: Schema.optional( Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema), ), }), ), oauth2: Schema.optional( Schema.Struct({ clientId: Schema.optional(OpenApiConfigureCredentialInputSchema), clientSecret: Schema.optional(OpenApiConfigureCredentialInputSchema), connection: Schema.optional(OpenApiConfigureCredentialInputSchema), }), ), oauth2Source: Schema.optional(OAuth2SourceConfig),});export type OpenApiConfigureInput = typeof OpenApiConfigureInputSchema.Type;const OpenApiConfigureSourceInputSchema = Schema.Struct({ source: Schema.Struct({ id: Schema.String, scope: Schema.String, }), ...OpenApiConfigureInputSchema.fields,});const OpenApiOAuthInputSchema = OAuth2SourceConfig; const AddSourceInputSchema = Schema.Struct({ spec: OpenApiSpecInputSchema, name: Schema.String, baseUrl: Schema.String, namespace: Schema.String, headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), oauth2: Schema.optional(OpenApiOAuthInputSchema), specFetchCredentials: Schema.optional( Schema.Struct({ headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), }), ),}); const AddSourceOutputSchema = Schema.Struct({ sourceId: Schema.String, source: Schema.Struct({ id: Schema.String, scope: Schema.String, }), toolCount: Schema.Number,});const GetSourceInputSchema = Schema.Struct({ namespace: Schema.String, scope: Schema.String,});const GetSourceOutputSchema = Schema.Struct({ source: Schema.NullOr(Schema.Unknown),}); 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 OpenApiConfigureSourceInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(OpenApiConfigureSourceInputSchema),);const OpenApiConfigureSourceOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(Schema.Struct({ result: Schema.Array(CredentialBindingRef) })),);const GetSourceInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(GetSourceInputSchema),);const GetSourceOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(GetSourceOutputSchema),); const openApiToolFailure = (code: string, message: string, details?: unknown) => ToolResult.fail({ code, message, ...(details === undefined ? {} : { details }), }); const openApiAuthToolFailure = (failure: OpenApiAuthRequiredError) => authToolFailure({ code: failure.code, message: failure.message, source: { id: failure.sourceId, scope: failure.sourceScope }, credential: { kind: failure.credentialKind, ...(failure.credentialLabel ? { label: failure.credentialLabel } : {}), ...(failure.slotKey ? { slotKey: failure.slotKey } : {}), ...(failure.secretId ? { secretId: failure.secretId } : {}), ...(failure.connectionId ? { connectionId: failure.connectionId } : {}), }, ...(failure.status !== undefined ? { status: failure.status } : {}), ...(failure.details !== undefined ? { upstream: { ...(failure.status !== undefined ? { status: failure.status } : {}), details: failure.details, }, } : {}), recovery: { configureSourceTool: "executor.openapi.configureSource" }, }); 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),Lines 1–500 of 1875. Load more
import { Effect, Option, Predicate, Schema } from "effect"; import type { Layer } from "effect"; import { HttpClient } from "effect/unstable/http"; import { ConnectionId, CredentialBindingRef, ScopeId, SecretId, SourceDetectionResult, StorageError, ToolResult, authToolFailure, defaultSourceInstallScopeId, definePlugin, tool, resolveSecretBackedMap, type CredentialBindingValue, type PluginCtx, type StorageFailure, type ToolAnnotations, type ToolRow, } from "@executor-js/sdk/core"; import { headersToConfigValues, type ConfigFileSink, type OpenApiSourceConfig, } from "@executor-js/config"; import { OpenApiAuthRequiredError, OpenApiExtractionError, OpenApiOAuthError, OpenApiParseError, } from "./errors"; import { parse, resolveSpecText } from "./parse"; import { extract } from "./extract"; import { compileToolDefinitions, type ToolDefinition } from "./definitions"; import { annotationsForOperation, invokeWithLayer } from "./invoke"; import { previewSpec, type SpecPreview } from "./preview"; import { openApiPresets } from "./presets"; import { makeDefaultOpenapiStore, openapiSchema, type OpenapiStore, type SourceConfig, type StoredOperation, type StoredSource, } from "./store"; import { HeaderValue as HeaderValueSchema, ConfiguredHeaderBinding, OAuth2SourceConfig, OperationBinding, type ConfiguredHeaderValue as ConfiguredHeaderValueValue, type HeaderValue as HeaderValueValue, } from "./types"; // --------------------------------------------------------------------------- // Plugin config // --------------------------------------------------------------------------- const STRINGIFIED_BODY_CAP = 1024; const UpstreamMessageBody = Schema.Struct({ message: Schema.String }); const UpstreamErrorMessageBody = Schema.Struct({ errorMessage: Schema.String }); const UpstreamNestedErrorBody = Schema.Struct({ error: UpstreamMessageBody }); const UpstreamErrorsArrayBody = Schema.Struct({ errors: Schema.Array( Schema.Struct({ detail: Schema.optional(Schema.String), message: Schema.optional(Schema.String), title: Schema.optional(Schema.String), }), ), }); const UpstreamDescriptionBody = Schema.Struct({ detail: Schema.optional(Schema.String), title: Schema.optional(Schema.String), description: Schema.optional(Schema.String), }); const decodeUpstreamMessageBody = Schema.decodeUnknownOption(UpstreamMessageBody); const decodeUpstreamErrorMessageBody = Schema.decodeUnknownOption(UpstreamErrorMessageBody); const decodeUpstreamNestedErrorBody = Schema.decodeUnknownOption(UpstreamNestedErrorBody); const decodeUpstreamErrorsArrayBody = Schema.decodeUnknownOption(UpstreamErrorsArrayBody); const decodeUpstreamDescriptionBody = Schema.decodeUnknownOption(UpstreamDescriptionBody); const clampedStringify = (value: unknown): string => { let s: string; // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: JSON.stringify may throw on cycles; fall back to String() so the upstream body can still be surfaced as ToolError.details fallback text try { s = JSON.stringify(value); } catch { s = String(value); } return s.length > STRINGIFIED_BODY_CAP ? `${s.slice(0, STRINGIFIED_BODY_CAP)}…` : s; }; const firstNonEmpty = (...values: readonly (string | undefined)[]): string | undefined => values.find((value) => value !== undefined && value.length > 0); // Walk known upstream error-body shapes so ToolError.message stays concise // while ToolError.details preserves the original body. const extractUpstreamMessage = (body: unknown, status: number): string => { if (typeof body === "string") { return body.length > 0 ? body : `Upstream returned HTTP ${status}`; } const nested = Option.getOrUndefined(decodeUpstreamNestedErrorBody(body)); const messageBody = Option.getOrUndefined(decodeUpstreamMessageBody(body)); const errorMessageBody = Option.getOrUndefined(decodeUpstreamErrorMessageBody(body)); const errorsBody = Option.getOrUndefined(decodeUpstreamErrorsArrayBody(body)); const descriptionBody = Option.getOrUndefined(decodeUpstreamDescriptionBody(body)); const arrayMessage = errorsBody?.errors .map(({ detail, message: upstreamMessage, title }) => firstNonEmpty(detail, upstreamMessage, title), ) .find((message) => message !== undefined); const message = firstNonEmpty( nested?.error.message, messageBody?.message, errorMessageBody?.errorMessage, arrayMessage, descriptionBody?.detail, descriptionBody?.title, descriptionBody?.description, ); if (message !== undefined) return message; if (body !== null && typeof body === "object") { return clampedStringify(body); } return `Upstream returned HTTP ${status}`; }; export type HeaderValue = HeaderValueValue; export type ConfiguredHeaderValue = ConfiguredHeaderValueValue; export type OpenApiOAuthInput = OAuth2SourceConfig; export type OpenApiSpecInput = typeof OpenApiSpecInputSchema.Type; export type OpenApiSecretShapeInput = typeof OpenApiSecretShapeInputSchema.Type; export type OpenApiConfiguredValueInput = | string | OpenApiSecretShapeInput | ConfiguredHeaderValueValue; export interface OpenApiSpecFetchCredentialsInput { readonly headers?: Record<string, OpenApiConfiguredValueInput>; readonly queryParams?: Record<string, OpenApiConfiguredValueInput>; } export interface OpenApiPreviewSpecFetchCredentialsInput { readonly headers?: Record<string, HeaderValue>; readonly queryParams?: Record<string, HeaderValue>; } export interface OpenApiPreviewInput { readonly spec: string; readonly specFetchCredentials?: OpenApiPreviewSpecFetchCredentialsInput; } export interface OpenApiSpecConfig { readonly spec: OpenApiSpecInput; readonly specFetchCredentials?: OpenApiSpecFetchCredentialsInput; /** * Executor scope id that owns this source row. Must be one of the * executor's configured scopes. Typical shape: an admin adds the * source at the outermost (organization) scope so it's visible to * every inner (per-user) scope via fall-through reads. */ readonly scope: string; readonly name: string; readonly baseUrl: string; readonly namespace: string; readonly headers?: Record<string, OpenApiConfiguredValueInput>; readonly queryParams?: Record<string, OpenApiConfiguredValueInput>; readonly oauth2?: OpenApiOAuthInput; } export interface OpenApiSourceRef { readonly id: string; readonly scope: string; } export type OpenApiConfigureCredentialInput = | string | { readonly kind: "text"; readonly text: string; readonly prefix?: string; } | { readonly kind: "secret"; readonly secretId: string; readonly secretScope?: string; readonly prefix?: string; } | { readonly kind: "connection"; readonly connectionId: string; }; /** * Errors any OpenAPI extension method may surface. The first three are * plugin-domain tagged errors that flow directly to clients (4xx, each * carrying its own `HttpApiSchema` status). `StorageFailure` covers * raw backend failures (`StorageError`) plus `UniqueViolationError`; * the HTTP edge (`@executor-js/api`'s `withCapture`) translates * `StorageError` to the opaque `InternalError({ traceId })` at Layer * composition. `UniqueViolationError` passes through — plugins can * `Effect.catchTag` it if they want a friendlier user-facing error. */ export type OpenApiExtensionFailure = | OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure; export interface OpenApiPluginExtension { readonly previewSpec: ( input: string | OpenApiPreviewInput, ) => Effect.Effect< SpecPreview, OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure >; readonly addSpec: ( config: OpenApiSpecConfig, ) => Effect.Effect< { readonly sourceId: string; readonly toolCount: number }, OpenApiParseError | OpenApiExtractionError | OpenApiOAuthError | StorageFailure >; readonly removeSpec: (namespace: string, scope: string) => Effect.Effect<void, StorageFailure>; readonly getSource: ( namespace: string, scope: string, ) => Effect.Effect<StoredSource | null, StorageFailure>; readonly configure: ( source: OpenApiSourceRef, input: OpenApiConfigureInput, ) => Effect.Effect<readonly CredentialBindingRef[], StorageFailure>; } // --------------------------------------------------------------------------- // Control-tool input/output schemas // --------------------------------------------------------------------------- const PreviewSpecInputSchema = Schema.Struct({ spec: Schema.String, specFetchCredentials: Schema.optional( Schema.Struct({ headers: Schema.optional(Schema.Record(Schema.String, HeaderValueSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, HeaderValueSchema)), }), ), }); 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, refreshUrl: Schema.NullOr(Schema.String), scopes: Schema.Record(Schema.String, Schema.String), }); 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 OpenApiSecretShapeInputSchema = Schema.Struct({ kind: Schema.Literal("secret"), prefix: Schema.optional(Schema.String), }); const OpenApiConfiguredValueInputSchema = Schema.Union([ Schema.String, OpenApiSecretShapeInputSchema, ]); const OpenApiConfigureCredentialInputSchema = Schema.Union([ Schema.String, Schema.Struct({ kind: Schema.Literal("text"), text: Schema.String, prefix: Schema.optional(Schema.String), }), Schema.Struct({ kind: Schema.Literal("secret"), secretId: Schema.String, secretScope: Schema.optional(Schema.String), prefix: Schema.optional(Schema.String), }), Schema.Struct({ kind: Schema.Literal("connection"), connectionId: Schema.String, }), ]); const OpenApiConfigureInputSchema = Schema.Struct({ scope: Schema.String, name: Schema.optional(Schema.String), baseUrl: Schema.optional(Schema.String), headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema)), specFetchCredentials: Schema.optional( Schema.Struct({ headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema)), queryParams: Schema.optional( Schema.Record(Schema.String, OpenApiConfigureCredentialInputSchema), ), }), ), oauth2: Schema.optional( Schema.Struct({ clientId: Schema.optional(OpenApiConfigureCredentialInputSchema), clientSecret: Schema.optional(OpenApiConfigureCredentialInputSchema), connection: Schema.optional(OpenApiConfigureCredentialInputSchema), }), ), oauth2Source: Schema.optional(OAuth2SourceConfig), }); export type OpenApiConfigureInput = typeof OpenApiConfigureInputSchema.Type; const OpenApiConfigureSourceInputSchema = Schema.Struct({ source: Schema.Struct({ id: Schema.String, scope: Schema.String, }), ...OpenApiConfigureInputSchema.fields, }); const OpenApiOAuthInputSchema = OAuth2SourceConfig; const AddSourceInputSchema = Schema.Struct({ spec: OpenApiSpecInputSchema, name: Schema.String, baseUrl: Schema.String, namespace: Schema.String, headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), oauth2: Schema.optional(OpenApiOAuthInputSchema), specFetchCredentials: Schema.optional( Schema.Struct({ headers: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), queryParams: Schema.optional(Schema.Record(Schema.String, OpenApiConfiguredValueInputSchema)), }), ), }); const AddSourceOutputSchema = Schema.Struct({ sourceId: Schema.String, source: Schema.Struct({ id: Schema.String, scope: Schema.String, }), toolCount: Schema.Number, }); const GetSourceInputSchema = Schema.Struct({ namespace: Schema.String, scope: Schema.String, }); const GetSourceOutputSchema = Schema.Struct({ source: Schema.NullOr(Schema.Unknown), }); 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 OpenApiConfigureSourceInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(OpenApiConfigureSourceInputSchema), ); const OpenApiConfigureSourceOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(Schema.Struct({ result: Schema.Array(CredentialBindingRef) })), ); const GetSourceInputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(GetSourceInputSchema), ); const GetSourceOutputStandardSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(GetSourceOutputSchema), ); const openApiToolFailure = (code: string, message: string, details?: unknown) => ToolResult.fail({ code, message, ...(details === undefined ? {} : { details }), }); const openApiAuthToolFailure = (failure: OpenApiAuthRequiredError) => authToolFailure({ code: failure.code, message: failure.message, source: { id: failure.sourceId, scope: failure.sourceScope }, credential: { kind: failure.credentialKind, ...(failure.credentialLabel ? { label: failure.credentialLabel } : {}), ...(failure.slotKey ? { slotKey: failure.slotKey } : {}), ...(failure.secretId ? { secretId: failure.secretId } : {}), ...(failure.connectionId ? { connectionId: failure.connectionId } : {}), }, ...(failure.status !== undefined ? { status: failure.status } : {}), ...(failure.details !== undefined ? { upstream: { ...(failure.status !== undefined ? { status: failure.status } : {}), details: failure.details, }, } : {}), recovery: { configureSourceTool: "executor.openapi.configureSource" }, }); 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),