120 lines · 5.6 KB · TypeScript
RawDownload
import { Option, Schema } from "effect";import {  ApiKeyAuthMethod,  TOKEN_VARIABLE,  renderAuthPlacements,  requiredPlacementVariables,} from "@executor-js/sdk/http-auth"; import type { Authentication } from "./types";import { SpecOverridesSchema, type SpecOverrides } from "./spec-overrides"; // ---------------------------------------------------------------------------// OpenAPI integration config — the opaque blob stored on the catalog// `integration.config` column (D1). Core never parses it; the plugin writes it// at register time and reads it back in `resolveTools` / `invokeTool`.//// In v2 there are NO credential bindings, NO per-source secret slots, and NO// StoredSource credential config. The config carries only://   - the content hash of the spec blob and/or the source URL to (re)fetch//     from,//   - optional RFC 6902 overrides and the raw source hash they apply to,//   - the optional base URL override,//   - the auth templates a connection's value is rendered through.// The resolved spec text itself lives in the plugin blob store, keyed// `spec/<specHash>` — it's a build input for resolveTools/refresh, not data// any list/invoke path should pay to load. Rows that predate the blob store// (inline `spec` text) are rewritten before this schema sees them: cloud by// the out-of-band migrate-specs-to-blobs script, the libSQL hosts by the// boot-time ledger migration.// --------------------------------------------------------------------------- const OAuthAuthenticationSchema = 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),}); export const AuthenticationSchema = Schema.Union([OAuthAuthenticationSchema, ApiKeyAuthMethod]); export const OpenApiIntegrationConfigSchema = Schema.Struct({  /** Hex SHA-256 of the resolved spec text — the content address of the spec   *  blob (`spec/<hash>` in the plugin blob store). */  specHash: Schema.optional(Schema.String),  /** Hex SHA-256 of the unmodified source text when spec overrides are active. */  sourceSpecHash: Schema.optional(Schema.String),  /** Origin URL the spec was fetched from, when known. Enables refresh. */  specUrl: Schema.optional(Schema.String),  /** Optional base URL override. */  baseUrl: Schema.optional(Schema.String),  /** Static headers applied to every request (no secret material). */  headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),  /** Static query params applied to every request (no secret material). */  queryParams: Schema.optional(Schema.Record(Schema.String, Schema.String)),  /** Adapter id used to fetch/convert the source format, when not plain OpenAPI. */  specFormat: Schema.optional(Schema.String),  /** Catalog family for grouped integration display. */  family: Schema.optional(Schema.String),  /** Ordered RFC 6902 operations reapplied whenever the source spec is refreshed. */  specOverrides: Schema.optional(SpecOverridesSchema),  /** The auth methods a connection's value can be applied through. */  authenticationTemplate: Schema.optional(Schema.Array(AuthenticationSchema)),}); export type OpenApiIntegrationConfig = Omit<  typeof OpenApiIntegrationConfigSchema.Type,  "authenticationTemplate" | "specOverrides"> & {  /** Branded over the schema's structural form so the template renderer can   *  treat `slug` as an `AuthTemplateSlug`. */  readonly authenticationTemplate?: readonly Authentication[];  readonly specOverrides?: SpecOverrides;}; const decodeConfig = Schema.decodeUnknownOption(OpenApiIntegrationConfigSchema); /** Decode the opaque integration config blob into the openapi shape. *  Returns null when the blob is missing/incompatible. */export const decodeOpenApiIntegrationConfig = (value: unknown): OpenApiIntegrationConfig | null =>  Option.getOrNull(decodeConfig(value)) as OpenApiIntegrationConfig | null; // ---------------------------------------------------------------------------// Template rendering — "auth state derived into the auth-template format"// (D11). An apiKey method renders through the shared placements renderer; an// oauth template (no explicit placement) renders a bearer `authorization`// header from the `token` input (the access token).// --------------------------------------------------------------------------- export interface RenderedAuth {  readonly headers: Record<string, string>;  readonly queryParams: Record<string, string>;} /** Render an auth template against a connection's resolved input `values` *  (`variable → value`). Each placement substitutes from its own entry, so a *  method with two distinct inputs (e.g. Datadog) fills each header from a *  different value. */export const renderAuthTemplate = (  template: Authentication,  values: Record<string, string | null>,): RenderedAuth => {  if (template.kind === "oauth2") {    return {      headers: { authorization: `Bearer ${values[TOKEN_VARIABLE] ?? ""}` },      queryParams: {},    };  }  return renderAuthPlacements(template.placements, values);}; /** The distinct input variables a template references — the inputs a connection *  must supply. An oauth template needs `token`; an apiKey method needs every *  variable across its placements. */export const requiredTemplateVariables = (template: Authentication): readonly string[] => {  if (template.kind === "oauth2") return [TOKEN_VARIABLE];  return requiredPlacementVariables(template.placements);};