251 lines · 9.3 KB · TypeScript
RawDownload
import { Effect } from "effect";import { Data } from "effect";import type { InvokeOptions } from "@executor-js/sdk"; import type { HandleBridge, HandleRootSpec } from "../seams/tool-sandbox";import { ToolSandboxError } from "../seams/tool-sandbox";import type { IntegrationDecl } from "../pipeline/descriptor"; // ---------------------------------------------------------------------------// Integration DI: declare in source, choose at invocation.//// A tool descriptor declares `integrations` (role -> integration slug). The// caller supplies one connection address per role in the tool-call payload, and// this layer peels those role properties off before the handler runs. If an org// has exactly one connection for a role's integration, that connection is used// as the default. Missing, unknown, or wrong-integration choices are typed// BindingError failures. The handler receives clients under the declared role// names.// --------------------------------------------------------------------------- export class BindingError extends Data.TaggedError("BindingError")<{  readonly message: string;  readonly role: string;  readonly integration: string;  readonly requestedConnection?: string;}> {} export interface ConnectionCandidate {  readonly address: string;  readonly integration: string;  readonly name?: string;  readonly owner?: string;} /** The resolved connections for a tool invocation: role -> connection address. */export type RoleBindings = Readonly<Record<string, string>>; /** Resolves connection inventory and method calls for declared integration *  roles. Host wiring will back this with core connection lookup and normal *  policy/credential invocation. */export interface ClientResolver {  readonly listConnections: (input: {    readonly integration: string;  }) => Effect.Effect<readonly ConnectionCandidate[], BindingError>;  readonly resolveConnection: (input: {    readonly connection: string;  }) => Effect.Effect<ConnectionCandidate | null, BindingError>;  readonly call: (input: {    readonly integration: string;    readonly connection: string;    readonly path: readonly string[];    readonly args: readonly unknown[];    readonly invokeOptions?: InvokeOptions;  }) => Effect.Effect<unknown, BindingError>;} export interface BindingContext {  /** Declared integration roles from the tool descriptor. */  readonly declared: Readonly<Record<string, IntegrationDecl>>;  /** Resolved role -> connection address choices for this invocation. */  readonly bindings: RoleBindings;  /** Routes a bound method call to the real integration. */  readonly resolver: ClientResolver;  /** Caller-supplied invoke options to preserve approval/elicitation context. */  readonly invokeOptions?: InvokeOptions;} const isRecord = (value: unknown): value is Record<string, unknown> =>  value !== null && typeof value === "object" && !Array.isArray(value); const findDefaultConnection = (  role: string,  decl: IntegrationDecl,  resolver: ClientResolver,): Effect.Effect<string, BindingError> =>  resolver.listConnections({ integration: decl.integration }).pipe(    Effect.flatMap((connections) => {      if (connections.length === 1) return Effect.succeed(connections[0]!.address);      return Effect.fail(        new BindingError({          role,          integration: decl.integration,          message:            connections.length === 0              ? `missing required connection for role "${role}" (${decl.integration}); no connections are available`              : `missing required connection for role "${role}" (${decl.integration}); choose one of ${connections                  .map((c) => c.address)                  .join(", ")}`,        }),      );    }),  ); const resolveRequestedConnection = (  role: string,  decl: IntegrationDecl,  requested: string,  resolver: ClientResolver,): Effect.Effect<string, BindingError> =>  Effect.gen(function* () {    const connection = yield* resolver.resolveConnection({ connection: requested });    if (connection) {      if (connection.integration !== decl.integration) {        return yield* new BindingError({          role,          integration: decl.integration,          message: `connection "${requested}" belongs to integration "${connection.integration}", not "${decl.integration}" for role "${role}"`,          requestedConnection: requested,        });      }      return connection.address;    }     const connections = yield* resolver.listConnections({ integration: decl.integration });    return yield* new BindingError({      role,      integration: decl.integration,      message: `unknown connection "${requested}" for role "${role}" (${decl.integration})${connections.length > 0 ? `; choose one of ${connections.map((candidate) => candidate.address).join(", ")}` : ""}`,      requestedConnection: requested,    });  }); export interface ResolvedIntegrationBindings {  readonly input: unknown;  readonly bindings: RoleBindings;} /** Peel platform-added role properties from the caller payload and resolve them *  to concrete connection addresses. */export const resolveIntegrationBindings = (  declared: Readonly<Record<string, IntegrationDecl>>,  args: unknown,  resolver: ClientResolver,): Effect.Effect<ResolvedIntegrationBindings, BindingError> =>  Effect.gen(function* () {    const roles = Object.entries(declared);    if (roles.length === 0) return { input: args, bindings: {} };     const payload = isRecord(args) ? args : {};    const input: Record<string, unknown> = {};    for (const [key, value] of Object.entries(payload)) {      if (!Object.prototype.hasOwnProperty.call(declared, key)) input[key] = value;    }     const bindings: Record<string, string> = {};    for (const [role, decl] of roles) {      const raw = payload[role];      if (raw === undefined) {        bindings[role] = yield* findDefaultConnection(role, decl, resolver);        continue;      }      if (typeof raw !== "string" || raw.length === 0) {        return yield* new BindingError({          role,          integration: decl.integration,          message: `connection for role "${role}" (${decl.integration}) must be a non-empty string`,          ...(typeof raw === "string" ? { requestedConnection: raw } : {}),        });      }      bindings[role] = yield* resolveRequestedConnection(role, decl, raw, resolver);    }     return { input, bindings };  }); /** Compute the sandbox handle roots: one single root per declared integration *  role. */export const rootsFor = (  declared: Readonly<Record<string, IntegrationDecl>>,): Readonly<Record<string, HandleRootSpec>> => {  const roots: Record<string, HandleRootSpec> = {};  for (const role of Object.keys(declared)) {    roots[role] = { kind: "single" };  }  return roots;}; // Parse an indexed root name. Indexed roots are not supported in v1, but the// parser lets the bridge reject `role#0` explicitly instead of misrouting it.const parseRoot = (root: string): { role: string; index?: number } => {  const hash = root.indexOf("#");  if (hash === -1) return { role: root };  return { role: root.slice(0, hash), index: Number(root.slice(hash + 1)) };}; const RESERVED_ROOTS = new Set(["__proto__", "constructor", "prototype"]); const invokeErr = (message: string): ToolSandboxError =>  new ToolSandboxError({ kind: "invoke", message }); /** * Build the HandleBridge the sandbox calls out through. A declared role routes * to its resolved connection through the `ClientResolver`. The dispatch is * STRICT: an empty/malformed path, a reserved root, an undeclared root, or an * indexed root is an error, never a silent success. A `.account` read on a * client returns bound-connection metadata without a round-trip. */export const buildBridge = (context: BindingContext): HandleBridge => ({  call: ({ root, path, args }) => {    if (typeof root !== "string" || root.length === 0) {      return Effect.fail(invokeErr("bridge call is missing a handle root"));    }    if (!Array.isArray(path) || path.length === 0) {      return Effect.fail(invokeErr(`bridge call to "${root}" has an empty method path`));    }    if (RESERVED_ROOTS.has(root)) {      return Effect.fail(invokeErr(`reserved handle root is not callable: ${root}`));    }     const { role, index } = parseRoot(root);    if (index !== undefined) {      return Effect.fail(        invokeErr(`single integration role "${role}" was addressed with an index`),      );    }     const decl = context.declared[role];    if (!decl) {      return Effect.fail(invokeErr(`undeclared handle root: ${root}`));    }     const connectionName = context.bindings[role];    if (!connectionName) {      return Effect.fail(invokeErr(`no binding for role ${role}`));    }    if (path.length === 1 && path[0] === "account") {      // Connection-derived placeholders only; this is not verified upstream      // account identity.      return Effect.succeed({ email: connectionName, login: connectionName, name: connectionName });    }     return context.resolver      .call({        integration: decl.integration,        connection: connectionName,        path,        args,        invokeOptions: context.invokeOptions,      })      .pipe(        Effect.mapError(          (cause) =>            new ToolSandboxError({              kind: "invoke",              message: "integration call failed",              cause,            }),        ),      );  },});