4966 lines · 203.5 KB · TypeScript
RawDownload
import { Effect, Inspectable, Layer, Option, Predicate, Schema } from "effect";import { FetchHttpClient, type HttpClient } from "effect/unstable/http";import { fumadb } from "@executor-js/fumadb";import { memoryAdapter } from "@executor-js/fumadb/adapters/memory";import { withQueryContext, type Condition, type ConditionBuilder } from "@executor-js/fumadb/query";import { schema as fumaSchema, type RelationsMap } from "@executor-js/fumadb/schema";import type { AnyColumn } from "@executor-js/fumadb/schema";import {  StorageError,  afterCommit,  isStorageFailure,  makeFumaClient,  type FumaDb,  type FumaRow,  type FumaTables,  type StorageFailure,} from "./fuma-runtime";import { makeFumaBlobStore, pluginBlobStore, type BlobStore, type OwnerPartitions } from "./blob";import { makePendingApprovalStore, type PendingApprovalStore } from "./pending-approval";import { coreToolsPlugin } from "./core-tools";import type {  Connection,  ConnectionInputOrigin,  ConnectionRef,  CreateConnectionInput,  ConnectionValueInput,  UpdateConnectionInput,  ValidateConnectionInput,} from "./connection";import { HealthCheckResult, HealthCheckSpec } from "./health-check";import type { HealthCheckCandidate } from "./health-check";import {  ARTIFACT_SUMMARY_COLUMNS,  coreSchema,  isToolPolicyAction,  TOOL_INVOCATION_COLUMNS,  type ConnectionRow,  type CoreSchema,  type IntegrationRow,  type OAuthClientRow,  type ToolInvocationRow,  type ToolRow,  type ToolPolicyRow,} from "./core-schema";import {  ElicitationDeclinedError,  ElicitationResponse,  FormElicitation,  type ElicitationHandler,  type ElicitationRequest,  type OnElicitation,  type InvokeOptions,} from "./elicitation"; export type { OnElicitation, InvokeOptions } from "./elicitation";import {  rowToArtifact,  rowToArtifactSummary,  type Artifact,  type ArtifactSummary,  type RemoveArtifactInput,  type RenameArtifactInput,  type SaveArtifactInput,  type SetArtifactPreviewInput,} from "./artifact";import {  ArtifactNotFoundError,  ConnectionNotFoundError,  CredentialProviderNotRegisteredError,  CredentialResolutionError,  IntegrationNotFoundError,  InvalidConnectionInputError,  IntegrationRemovalNotAllowedError,  NoHandlerError,  PluginNotLoadedError,  ToolBlockedError,  ToolInvocationError,  ToolNotFoundError,  type ExecuteError,} from "./errors";import {  ArtifactId,  AuthTemplateSlug,  ConnectionAddress,  ConnectionName,  IntegrationSlug,  NO_AUTH_TEMPLATE,  OAuthClientSlug,  Owner,  PolicyId,  ProviderItemId,  ProviderKey,  Subject,  Tenant,  ToolAddress,  ToolName,} from "./ids";import type {  AuthMethodDescriptor,  Integration,  IntegrationChangeEvent,  IntegrationConfig,  IntegrationDisplayDescriptor,  RegisterIntegrationInput,} from "./integration";import {  makeOAuthService,  type MintOAuthConnectionInput,  type OAuthScopePolicy,} from "./oauth-service";import type { OAuthService } from "./oauth-client";import {  comparePolicyRow,  isValidPattern,  matchPattern,  positionForNewPattern,  resolveEffectivePolicy,  rowToToolPolicy,  type CreateToolPolicyInput,  type EffectivePolicy,  type RemoveToolPolicyInput,  type ToolPolicy,  type UpdateToolPolicyInput,} from "./policies";import type { CredentialProvider, ProviderEntry } from "./provider";import { touchSubject } from "./subject-registry";import type {  AnyPlugin,  Elicit,  IntegrationConfigureSchema,  IntegrationPresetCatalogEntry,  IntegrationRecord,  OwnerBinding,  PluginCtx,  PluginExtensions,  ResolveToolsResult,  StaticIntegrationDecl,  StaticToolDecl,  StorageDeps,  ToolPolicyProvider,  ToolPolicyProviderRule,  ToolInvocationCredential,} from "./plugin";import {  pluginStorageId,  type PluginStorageCollectionData,  type PluginStorageCollectionDefinition,  type PluginStorageCollectionQueryInput,  type PluginStorageEntry,  type PluginStorageFacade,  type PluginStorageRuntimeCollectionDefinition,  type PluginStorageRuntimeIndexSpec,} from "./plugin-storage";import {  assertExecutorOwnerPolicyTable,  ORG_SUBJECT,  type ExecutorOwnerPolicyContext,} from "./owner-policy";import { ToolSchemaView, type IntegrationDetectionResult } from "./types";import { type Tool, type ToolAnnotations, type ToolDef, type ToolListFilter } from "./tool";import { buildToolTypeScriptPreview } from "./schema-types";import { collectReferencedDefinitions } from "./schema-refs";import {  refreshAccessToken,  exchangeClientCredentials,  shouldRefreshToken,  type OAuthEndpointUrlPolicy,} from "./oauth-helpers";import { connectionIdentifier } from "./connection-name-identifier";import { annotateToolResultOutcome } from "./tool-result";import { isUnauthorizedToolFailure } from "./auth-tool-failure"; const PLUGIN_STORAGE_DELETE_KEY_BATCH_SIZE = 90;const PLUGIN_STORAGE_CREATE_ROW_BATCH_SIZE = 90;const MAX_APPROVAL_ARGUMENT_PREVIEW_CHARS = 4_000; // ---------------------------------------------------------------------------// Elicitation handler — resolved once at `createExecutor({ onElicitation })`// and overridable per `execute`. A tool that requests user input mid-execution// suspends the fiber and the handler decides how to respond. The "accept-all"// sentinel auto-accepts (tests / non-interactive hosts).// --------------------------------------------------------------------------- const acceptAllHandler: ElicitationHandler = () =>  Effect.succeed(ElicitationResponse.make({ action: "accept" })); const resolveElicitationHandler = (onElicitation: OnElicitation): ElicitationHandler =>  onElicitation === "accept-all" ? acceptAllHandler : onElicitation; // ---------------------------------------------------------------------------// Address scheme — `tools.<integration>.<owner>.<connection>.<tool>`.// --------------------------------------------------------------------------- const ADDRESS_PREFIX = "tools"; export interface ParsedToolAddress {  readonly integration: IntegrationSlug;  readonly owner: Owner;  readonly connection: ConnectionName;  readonly tool: ToolName;} const isOwner = (value: string): value is Owner => value === "org" || value === "user"; /** Parse a callable address; null when it's not a well-formed *  `tools.<integration>.<owner>.<connection>.<tool>`. * *  The four leading segments (prefix, integration, owner, connection) are *  slug-like and never contain a `.`; the `<tool>` segment is the *entire* *  remainder after the 4th dot, so it may itself contain dots. That lets a tool *  whose name is a structured `group.leaf` path (e.g. an OpenAPI *  `aliases.deleteAlias`) address naturally as *  `tools.<integration>.<owner>.<connection>.aliases.deleteAlias` — the same *  dotted nesting the sandbox `tools` proxy produces from property access. */export const parseToolAddress = (address: string): ParsedToolAddress | null => {  // Walk to the 4th dot; everything past it is the tool (dots and all).  let cut = -1;  for (let i = 0; i < 4; i++) {    cut = address.indexOf(".", cut + 1);    if (cut === -1) return null;  }  const [prefix, integration, owner, connection] = address.slice(0, cut).split(".") as [    string,    string,    string,    string,  ];  const tool = address.slice(cut + 1);  if (prefix !== ADDRESS_PREFIX) return null;  if (!isOwner(owner)) return null;  if (integration.length === 0 || connection.length === 0 || tool.length === 0) {    return null;  }  return {    integration: IntegrationSlug.make(integration),    owner,    connection: ConnectionName.make(connection),    tool: ToolName.make(tool),  };}; export const connectionAddress = (  owner: Owner,  integration: IntegrationSlug,  connection: ConnectionName,): ConnectionAddress =>  ConnectionAddress.make(`${ADDRESS_PREFIX}.${integration}.${owner}.${connection}`); export const toolAddress = (  owner: Owner,  integration: IntegrationSlug,  connection: ConnectionName,  tool: ToolName,): ToolAddress =>  ToolAddress.make(`${ADDRESS_PREFIX}.${integration}.${owner}.${connection}.${tool}`); // ---------------------------------------------------------------------------// Owner key helpers — every owned-row write stamps `tenant`, `owner`,// `subject` (org → subject="").// --------------------------------------------------------------------------- interface OwnedKeys {  readonly tenant: string;  readonly owner: Owner;  readonly subject: string;} // ---------------------------------------------------------------------------// Executor — public surface. Every list/execute/schema call is a direct// core-table query unioned with the in-memory static pool.// --------------------------------------------------------------------------- export type Executor<TPlugins extends readonly AnyPlugin[] = readonly []> = {  readonly integrations: {    readonly list: () => Effect.Effect<readonly Integration[], StorageFailure>;    readonly get: (slug: IntegrationSlug) => Effect.Effect<Integration | null, StorageFailure>;    readonly update: (      slug: IntegrationSlug,      patch: { readonly name?: string; readonly description?: string },    ) => Effect.Effect<void, IntegrationNotFoundError | StorageFailure>;    readonly remove: (      slug: IntegrationSlug,    ) => Effect.Effect<void, IntegrationRemovalNotAllowedError | StorageFailure>;    readonly detect: (      url: string,    ) => Effect.Effect<readonly IntegrationDetectionResult[], StorageFailure>;    /** The integration's declared health check: which authenticated operation a     *  connection runs to prove its credential is alive and surface whose     *  account it is. Configured by the user the same way auth methods are. */    readonly healthCheck: {      /** The currently declared check, or null when none is configured (or the       *  owning plugin has no health-check capability). */      readonly get: (        slug: IntegrationSlug,      ) => Effect.Effect<HealthCheckSpec | null, StorageFailure>;      /** The operations a user can pick from, ranked non-destructive-first then       *  fewest required arguments. Empty when the plugin has no candidates. */      readonly candidates: (        slug: IntegrationSlug,      ) => Effect.Effect<        readonly HealthCheckCandidate[],        IntegrationNotFoundError | StorageFailure      >;      /** Declare (or clear, with `null`) the health check for the integration. */      readonly set: (        slug: IntegrationSlug,        spec: HealthCheckSpec | null,      ) => Effect.Effect<void, IntegrationNotFoundError | StorageFailure>;    };  };   readonly connections: {    readonly create: (      input: CreateConnectionInput,    ) => Effect.Effect<      Connection,      | IntegrationNotFoundError      | CredentialProviderNotRegisteredError      | InvalidConnectionInputError      | StorageFailure    >;    readonly list: (filter?: {      readonly integration?: IntegrationSlug;      readonly owner?: Owner;    }) => Effect.Effect<readonly Connection[], StorageFailure>;    readonly get: (ref: ConnectionRef) => Effect.Effect<Connection | null, StorageFailure>;    /** Edit user-curated metadata (description, identityLabel). Credentials and     *  OAuth lifecycle fields are not editable here. */    readonly update: (      ref: ConnectionRef,      input: UpdateConnectionInput,    ) => Effect.Effect<Connection, ConnectionNotFoundError | StorageFailure>;    readonly remove: (      ref: ConnectionRef,    ) => Effect.Effect<void, ConnectionNotFoundError | StorageFailure>;    readonly refresh: (      ref: ConnectionRef,    ) => Effect.Effect<      readonly Tool[],      ConnectionNotFoundError | IntegrationNotFoundError | StorageFailure    >;    /** Run the integration's declared health check against a saved connection:     *  classify the credential (healthy / expired / degraded / unknown) and     *  extract its identity for display. Never throws on an auth wall or upstream     *  error: those come back as a `HealthCheckResult` with the matching status. */    readonly checkHealth: (      ref: ConnectionRef,      options?: {        /** Return the persisted verdict when younger than this; probe         *  otherwise. Omit to always probe. */        readonly ifStaleMs?: number;      },    ) => Effect.Effect<      HealthCheckResult,      ConnectionNotFoundError | IntegrationNotFoundError | StorageFailure    >;    /** Validate an in-flight credential WITHOUT saving it (key-first connect):     *  resolve the pasted value(s), run the health check, and return the result     *  so the caller can confirm the key works and derive a name from the     *  identity before creating the connection. */    readonly validate: (      input: ValidateConnectionInput,    ) => Effect.Effect<HealthCheckResult, IntegrationNotFoundError | StorageFailure>;  };   /** Shared OAuth service. Hosts use this through the core HTTP OAuth group;   *  plugins see the same service as `ctx.oauth`. */  readonly oauth: OAuthService;   readonly tools: {    readonly list: (filter?: ToolListFilter) => Effect.Effect<readonly Tool[], StorageFailure>;    readonly schema: (address: ToolAddress) => Effect.Effect<ToolSchemaView | null, StorageFailure>;  };   readonly providers: {    readonly list: () => Effect.Effect<readonly ProviderKey[]>;    readonly items: (key: ProviderKey) => Effect.Effect<readonly ProviderEntry[], StorageFailure>;  };   readonly policies: {    readonly list: () => Effect.Effect<readonly ToolPolicy[], StorageFailure>;    readonly create: (input: CreateToolPolicyInput) => Effect.Effect<ToolPolicy, StorageFailure>;    readonly update: (input: UpdateToolPolicyInput) => Effect.Effect<ToolPolicy, StorageFailure>;    readonly remove: (input: RemoveToolPolicyInput) => Effect.Effect<void, StorageFailure>;    readonly resolve: (address: ToolAddress) => Effect.Effect<EffectivePolicy, StorageFailure>;  };   /**   * The PLATFORM VIEW: read-only, tenant-wide reads across every subject.   * Present only when the executor was built with `platformView: true`   * (default off) — every other surface on this executor stays bound to the   * single `{ tenant, subject }` product view and is unaffected by this one.   * Internal admin surface; the public HTTP shape is a separate concern.   */  readonly admin?: ExecutorAdmin;  /** Saved generative-UI artifacts, visible to the bound owner scope. */  readonly artifacts: {    /** Newest first, without the JSX source — lists stay light. */    readonly list: () => Effect.Effect<readonly ArtifactSummary[], StorageFailure>;    readonly get: (id: string) => Effect.Effect<Artifact, ArtifactNotFoundError | StorageFailure>;    /** Create, or overwrite an existing artifact in place when `id` is given. */    readonly save: (      input: SaveArtifactInput,    ) => Effect.Effect<Artifact, ArtifactNotFoundError | StorageFailure>;    readonly rename: (      input: RenameArtifactInput,    ) => Effect.Effect<Artifact, ArtifactNotFoundError | StorageFailure>;    readonly remove: (input: RemoveArtifactInput) => Effect.Effect<void, StorageFailure>;    /** Upgrade the stored preview to a snapshot of a settled render. Touches     *  only `preview`, and never `updated_at`. */    readonly setPreview: (      input: SetArtifactPreviewInput,    ) => Effect.Effect<void, ArtifactNotFoundError | StorageFailure>;  };   /**   * Approvals recorded for artifact-originated calls that paused on a human.   *   * Scoped to this executor's owner, so a record is only readable by the caller   * who created it — the ownership check on resume is the same read that fetches   * it. See `pending-approval.ts` for why an artifact pause is reconstructible   * when a general codemode pause is not.   */  readonly pendingApprovals: PendingApprovalStore;   readonly execute: (    address: ToolAddress,    args: unknown,    options?: InvokeOptions,  ) => Effect.Effect<unknown, ExecuteError>;   readonly close: () => Effect.Effect<void, StorageFailure>;} & PluginExtensions<TPlugins>; // ---------------------------------------------------------------------------// The platform view — internal admin reads.//// Everything else on the executor is the PRODUCT view: bound to one// { tenant, subject }. This is the read-only escape hatch beside it, reading// the same store through a `reach: "tenant"` context so it can answer// "who exists under this tenant, and what have they connected".//// Vocabulary note: internal code says subject/tenant/reach. Anything// public-facing says users/owners — the translation happens at the HTTP edge,// not here.//// FIELD DISCIPLINE: these shapes are a hand-picked allowlist, not a projection// of the row. Nothing secret-bearing may appear — no `item_ids`, no// `refresh_item_id`, no oauth client secrets, no credential material of any// kind. Adding a field here is a deliberate act.// --------------------------------------------------------------------------- /** One principal seen under the tenant (a row of the `subject` table). */export interface AdminSubject {  /** The host-auth principal id. Opaque — it also carries host sentinels   *  like "local", so nothing may parse it. */  readonly externalId: string;  readonly createdAt: Date;  /** Epoch ms of the last sighting on the request path; null when never seen   *  (a subject can be created at connection-create before any sighting). */  readonly lastSeenAt: number | null;  readonly status: string | null;} /** A connection as the platform view sees it: enough to answer "what has this *  user connected and is it healthy", and nothing that could resolve a *  credential. */export interface AdminConnection {  readonly owner: Owner;  /** The owning principal — `null` for org-owned connections, which belong to   *  the tenant rather than to any one user. */  readonly subject: string | null;  readonly integration: IntegrationSlug;  readonly name: ConnectionName;  /** The scope set the provider actually granted, space-delimited as recorded   *  at connect/refresh. Null for static credentials. A summary of ACCESS —   *  never a token. */  readonly oauthScope: string | null;  readonly lastHealth: HealthCheckResult | null;} /** A subject together with every connection it owns in the tenant. */export interface AdminSubjectWithConnections extends AdminSubject {  readonly connections: readonly AdminConnection[];} export interface AdminListSubjectsOptions {  readonly limit?: number;  readonly offset?: number;} /** * Page size applied when a caller names none. Every admin list is BOUNDED: * `listSubjects()` with no arguments is the obvious call, and unbounded it * returns every subject in the tenant — which `listSubjectsWithConnections` * then turns into one sequential connection query PER SUBJECT, inside a single * request, over a per-request socket on cloud. A default is what keeps the * no-args call honest; a caller who wants more asks for more, up to * {@link ADMIN_MAX_PAGE_SIZE}. *

Lines 1–500 of 4966.