@stackbone/sdk 0.1.0-alpha.6 → 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../libs/sdk/src/surfaces/external/connect/connect-transport.ts","../../../libs/sdk/src/surfaces/external/connect/connect.ts","../../../libs/sdk/src/surfaces/external/connect/channel-helpers.ts","../../../libs/sdk/src/surfaces/external/connect/call-connector.ts"],"names":["TIMESTAMP_HEADER","SIGNATURE_HEADER","signBrokerHeaders","secret","process","env","timestamp","String","Date","now","signature","createHmac","update","digest","brokerBaseUrl","url","Error","replace","installationId","id","TOKEN_PATH","brokerCodeToEveError","connectorId","code","message","ConnectionAuthorizationRequiredError","undefined","ConnectionAuthorizationFailedError","reason","retryable","readBrokerError","response","body","json","catch","status","fetchBrokerToken","JSON","stringify","connector","principal","type","fetch","method","headers","cause","ok","error","minted","token","expiresAt","evictBrokerToken","connect","principalType","stackboneConnect","getToken","_opts","evict","withConnect","definition","auth","connectHeaders","headerName","connection","EXECUTE_PATH","connectorCallError","errorFromBody","callConnector","operation","args","opts","parsed","output","connectorHandle","Proxy","Object","create","get","_target","prop"],"mappings":"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","file":"connect.cjs","sourcesContent":["// Shared transport primitives for the Stackbone Connect runtime calls (feature\n// 74). Both the connection-auth ADAPTER (`connect.ts` — the eve `getToken`/`evict`\n// strategy) and the direct workflow→connector CALL helper (`call-connector.ts`)\n// hit the SAME agent-facing broker under `STACKBONE_API_URL` with the SAME HMAC\n// scheme-A signature, so the signing algorithm, header names, and env contract\n// live here ONCE. Duplicating the HMAC in two places risks the two diverging\n// (one signing the raw string, the other a number/Buffer) and the broker's\n// constant-time verifier would then reject one of them.\n//\n// This module is a PLAIN node module: it statically imports nothing but\n// `node:crypto`, so it is safe for the eve-importing `connect.ts` AND the\n// eve-free `call-connector.ts` to share.\n\nimport { createHmac } from 'node:crypto';\n\n/**\n * WHO a connector call is made on behalf of — the scoping principal the broker\n * keys its credential cache/grant against. A STRUCTURAL mirror of the broker\n * contract's `@stackbone/connect-contract` `Principal` union (app / user /\n * client-credentials / jwt-bearer); defined locally rather than imported so the\n * SDK connect surface stays a self-contained leaf (the sibling eve adapter follows\n * the same discipline — it never pulls the platform contract into the published\n * SDK tarball). It is structurally compatible, so a value typed against the\n * contract's `Principal` is assignable here and vice-versa.\n */\nexport type Principal =\n | { readonly type: 'app' }\n | {\n readonly type: 'user';\n readonly id: string;\n readonly issuer: string;\n readonly attributes?: Readonly<Record<string, string | readonly string[]>>;\n }\n | { readonly type: 'client-credentials'; readonly serviceAccountId: string }\n | { readonly type: 'jwt-bearer'; readonly subject: string; readonly issuer: string };\n\n/** The header the agent-facing broker request stamps the raw timestamp under. */\nexport const TIMESTAMP_HEADER = 'x-stackbone-timestamp';\n/** The header the agent-facing broker request stamps the HMAC signature under. */\nexport const SIGNATURE_HEADER = 'x-stackbone-workflow-signature';\n\n/**\n * Sign an agent-facing broker request so the broker's HMAC verifier accepts it.\n * The signature is `hex(HMAC-SHA256(HMAC_SECRET, timestamp))` over the RAW\n * timestamp string. This MUST stay byte-identical to the agent's\n * `hmacWorkflowAuth` verifier and the emulator's `verifyTokenRequestSignature`\n * (same algorithm, same raw-string input, same header names, 5-minute drift\n * window on the verifier) — do not sign a number/Buffer, bind the body, or\n * rename the headers. An empty secret produces a signature the fail-closed\n * verifier rejects, which is the correct behaviour for a misconfigured runtime.\n */\nexport function signBrokerHeaders(): Record<string, string> {\n const secret = process.env['HMAC_SECRET'] ?? '';\n const timestamp = String(Date.now());\n const signature = createHmac('sha256', secret).update(timestamp).digest('hex');\n return {\n [TIMESTAMP_HEADER]: timestamp,\n [SIGNATURE_HEADER]: signature,\n };\n}\n\n/**\n * Resolve the base URL of the Stackbone Connect broker. The runtime injects it on\n * the agent / workflow process's env (never hardcode a host — the broker is\n * deployable per env: cloud / emulator / self-host). The trailing slash(es) are\n * trimmed so callers append `/api/connect/...` without doubling the separator.\n */\nexport function brokerBaseUrl(): string {\n const url = process.env['STACKBONE_API_URL'];\n if (!url) {\n throw new Error(\n 'STACKBONE_API_URL is not set. The Stackbone runtime injects the broker base URL; run this agent through `stackbone dev`.',\n );\n }\n return url.replace(/\\/+$/, '');\n}\n\n/**\n * Read the install id the broker request asserts as its scoping claim. The\n * emulator (and, in production, the client's VM) injects it on the process's env;\n * absent → the caller is not running under a Stackbone runtime.\n */\nexport function installationId(): string {\n const id = process.env['STACKBONE_INSTALLATION_ID'];\n if (!id) {\n throw new Error(\n 'STACKBONE_INSTALLATION_ID is not set. The Stackbone runtime injects it; run this agent through `stackbone dev`.',\n );\n }\n return id;\n}\n","// `connect()` — the Stackbone Connect eve ADAPTER (feature 74, Phase G).\n//\n// A connection authored under `agent/connections/<slug>.ts` declares an `auth`\n// strategy that eve probes before every tool call. `connect(connectorId)` returns\n// that strategy, pre-wired to fetch a short-lived credential from the Stackbone\n// Connect BROKER instead of holding a static key — so the operator registers the\n// connector + its OAuth app ONCE in Studio and every agent's connection resolves\n// its token through the broker at call time.\n//\n// SCOPE — DEV-ONLY, APP-SCOPED, NON-INTERACTIVE. This adapter implements ONLY the\n// `principalType: 'app'`, `getToken`-only path: the broker mints the agent's OWN\n// service-account credential out of band (the operator completes the OAuth dance\n// in Studio), and `getToken` just hands it over. There is NO `startAuthorization`\n// / `completeAuthorization` and NO `principalType: 'user'` here — interactive,\n// per-user OAuth rides eve's durable hooks and is a FUTURE phase. `getToken`\n// THROWS to signal \"needs authorization\" (the operator must finish the dance);\n// it never returns null.\n//\n// eve is an OPTIONAL EXTERNAL PEER (kept external by the SDK build; pinned only in\n// the creator's eve workspace), so:\n// - types come from the `eve-connections-peer.d.ts` shim, NOT a hard dependency;\n// - the two eve error classes are MATCHED/PRODUCED BY `err.name`, never\n// `instanceof` — under bundling the adapter and eve can end up with different\n// class identities (dual-instance hazard), so name is the only stable signal.\n// Importing the classes from `'eve/connections'` (external) means at runtime\n// they ARE the creator's eve classes, so eve's\n// `isConnectionAuthorizationRequiredError`/`...Failed` guards (which test\n// `err.name`) recognise what the adapter throws.\n//\n// CALLER IDENTITY — HMAC scheme A (install identity), byte-identical to\n// `surfaces/external/agents/eve-agent.ts signWorkflowHeaders()`: the broker token\n// request carries `hex(HMAC-SHA256(HMAC_SECRET, String(Date.now())))` over the RAW\n// timestamp string under `x-stackbone-timestamp` + `x-stackbone-workflow-signature`.\n// HMAC proves POSSESSION of the shared per-workspace secret, NOT which install is\n// calling, so the request body ALSO carries the explicit `installationId`\n// (`STACKBONE_INSTALLATION_ID`) + connector + `principal: { type: 'app' }`.\n\nimport {\n ConnectionAuthorizationFailedError,\n ConnectionAuthorizationRequiredError,\n type ConnectionAuthDefinition,\n type ConnectionAuthorizationContext,\n type ConnectionPrincipal,\n type TokenResult,\n} from 'eve/connections';\n\nimport { brokerBaseUrl, installationId, signBrokerHeaders } from './connect-transport';\n\n/** The broker route the adapter POSTs a token request to (under `STACKBONE_API_URL`). */\nconst TOKEN_PATH = '/api/connect/token';\n\n/**\n * The auth strategy `connect()` returns, widened with the Stackbone attribution\n * marker. Structurally a `getToken`-only eve `ConnectionAuthDefinition` (so it\n * drops straight into `defineMcpClientConnection({ auth })` /\n * `defineOpenAPIConnection({ auth })`), plus a `stackboneConnect` marker — the\n * sibling of eve's `vercelConnect` — that lets build/Studio tooling detect a\n * broker-backed connection without inspecting the `getToken` closure.\n */\nexport interface StackboneConnectAuth extends ConnectionAuthDefinition {\n /** App-scoped: the adapter only ever emits the non-interactive `'app'` path. */\n readonly principalType: 'app';\n /** Compile-time attribution marker; ignored by eve's token path. */\n readonly stackboneConnect: { readonly connector: string };\n}\n\n/** The broker's token-mint success body — byte-aligned with eve's `TokenResult`. */\ninterface BrokerTokenResponse {\n /** The short-lived scoped token (always applied as `Authorization: Bearer`). */\n readonly token: string;\n /** Absolute ms-since-epoch advisory expiry — same unit as eve's `expiresAt`. */\n readonly expiresAt?: number;\n}\n\n/** The broker's error envelope — discriminated by the string `code`, never a class. */\ninterface BrokerErrorBody {\n readonly code: string;\n readonly message?: string;\n}\n\n/**\n * Translate a broker error code (read off the error body) into the eve error the\n * runtime understands. The mapping is matched/produced BY NAME — the eve classes\n * imported from `'eve/connections'` set `this.name` to their own class name and\n * eve's guards switch on that, so a constructed instance is recognised across the\n * peer boundary.\n *\n * - `no_token` / `user_authorization_required` → `ConnectionAuthorizationRequiredError`\n * (the principal must authorise out of band; eve emits `authorization.required`).\n * - `connector_installation_required` / `app_not_installed` →\n * `ConnectionAuthorizationFailedError(reason: 'app_not_installed', retryable: false)`\n * (terminal: the operator never installed the connector's credentials).\n * - `principal_required` → terminal `ConnectionAuthorizationFailedError`.\n * - any other broker code → a NON-retryable failure (a credential-layer error the\n * adapter cannot map more precisely; do not re-prompt on a wrong-token-shape bug).\n */\nfunction brokerCodeToEveError(connectorId: string, code: string, message?: string): Error {\n switch (code) {\n case 'no_token':\n case 'user_authorization_required':\n return new ConnectionAuthorizationRequiredError(\n connectorId,\n message ? { message } : undefined,\n );\n case 'connector_installation_required':\n case 'app_not_installed':\n return new ConnectionAuthorizationFailedError(connectorId, {\n ...(message ? { message } : {}),\n reason: 'app_not_installed',\n retryable: false,\n });\n case 'principal_required':\n return new ConnectionAuthorizationFailedError(connectorId, {\n ...(message ? { message } : {}),\n reason: 'principal_required',\n retryable: false,\n });\n default:\n return new ConnectionAuthorizationFailedError(connectorId, {\n message: message ?? `Stackbone Connect broker rejected the token request (code \"${code}\").`,\n reason: code,\n retryable: false,\n });\n }\n}\n\n/**\n * Read the broker's `{ code, message }` error envelope off a non-2xx response,\n * tolerating an empty / non-JSON body (a proxy 502 may carry no JSON).\n */\nasync function readBrokerError(response: Response): Promise<BrokerErrorBody> {\n const body = (await response.json().catch(() => null)) as Partial<BrokerErrorBody> | null;\n if (body && typeof body.code === 'string') {\n // Omit `message` when absent rather than setting it to `undefined`:\n // `exactOptionalPropertyTypes: true` forbids an explicit `undefined` on the\n // optional `message` prop (matches the spread pattern in brokerCodeToEveError).\n return { code: body.code, ...(body.message !== undefined ? { message: body.message } : {}) };\n }\n return { code: 'credential_error', message: `Broker responded ${response.status}.` };\n}\n\n/**\n * POST a broker token request and return the eve `TokenResult` VERBATIM (the\n * broker's `{ token, expiresAt }` is byte-aligned with eve's `TokenResult`, and\n * `expiresAt` is already absolute ms — no unit conversion).\n *\n * A broker error body is translated to the matching eve error BY NAME. A\n * transport-level failure (DNS / connection refused / the fetch itself rejects)\n * is a TRANSIENT condition → a RETRYABLE `ConnectionAuthorizationFailedError` so\n * eve re-prompts on a flaky broker rather than treating it as a hard auth failure.\n */\nasync function fetchBrokerToken(connectorId: string): Promise<TokenResult> {\n const body = JSON.stringify({\n connector: connectorId,\n principal: { type: 'app' as const },\n installationId: installationId(),\n });\n let response: Response;\n try {\n response = await fetch(`${brokerBaseUrl()}${TOKEN_PATH}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...signBrokerHeaders(),\n },\n body,\n });\n } catch (cause) {\n // Network / transport failure → transient, retryable.\n throw new ConnectionAuthorizationFailedError(connectorId, {\n message: `Stackbone Connect broker is unreachable: ${\n cause instanceof Error ? cause.message : String(cause)\n }`,\n reason: 'execute_failed',\n retryable: true,\n });\n }\n\n if (!response.ok) {\n const error = await readBrokerError(response);\n throw brokerCodeToEveError(connectorId, error.code, error.message);\n }\n\n const minted = (await response.json().catch(() => null)) as BrokerTokenResponse | null;\n if (!minted || typeof minted.token !== 'string') {\n throw new ConnectionAuthorizationFailedError(connectorId, {\n message: 'Stackbone Connect broker returned a malformed token response.',\n reason: 'invalid_output',\n retryable: false,\n });\n }\n // Return the eve TokenResult verbatim: `{ token, expiresAt? }`, ms-aligned.\n return minted.expiresAt !== undefined\n ? { token: minted.token, expiresAt: minted.expiresAt }\n : { token: minted.token };\n}\n\n/**\n * Best-effort: tell the broker to drop its cached token for this connector +\n * (app) principal so the NEXT `getToken` re-mints. Called by eve's `evict` hook\n * right after a provider 401. Swallows its own errors — eviction is a recovery\n * optimisation, never a failure path.\n */\nasync function evictBrokerToken(connectorId: string): Promise<void> {\n try {\n await fetch(`${brokerBaseUrl()}${TOKEN_PATH.replace(/\\/token$/, '/evict')}`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...signBrokerHeaders(),\n },\n body: JSON.stringify({\n connector: connectorId,\n principal: { type: 'app' as const },\n installationId: process.env['STACKBONE_INSTALLATION_ID'],\n }),\n });\n } catch {\n // Best-effort: a failed evict just means the next getToken may reuse a stale\n // cached token; it never breaks the tool call.\n }\n}\n\n/**\n * `connect(connectorId)` — wire a connection's `auth` to the Stackbone Connect\n * broker for the agent's OWN (app-scoped) credential.\n *\n * Use it as the `auth` field of a connection definition:\n *\n * ```ts\n * import { defineOpenAPIConnection } from 'eve/connections';\n * import { connect } from '@stackbone/sdk/connect';\n *\n * export default defineOpenAPIConnection({\n * spec: 'https://api.example.com/openapi.json',\n * auth: connect('example'),\n * });\n * ```\n *\n * `getToken` fetches a short-lived bearer from the broker (HMAC-signed,\n * install-scoped). When the broker reports the connector's credentials are not yet\n * installed it THROWS `ConnectionAuthorizationRequiredError` (eve emits\n * `authorization.required`); a hard broker failure throws\n * `ConnectionAuthorizationFailedError(reason, retryable)`. `evict` purges the\n * broker's cached token on a 401 so the next `getToken` re-mints.\n *\n * @param connectorId The connector id registered in Studio (matches\n * `Connector.connectorId`). Stamped onto the returned `stackboneConnect` marker.\n */\nexport function connect(connectorId: string): StackboneConnectAuth {\n return {\n principalType: 'app',\n stackboneConnect: { connector: connectorId },\n getToken(_opts: {\n readonly principal: ConnectionPrincipal;\n readonly connection: ConnectionAuthorizationContext;\n }): Promise<TokenResult> {\n // App-scoped: the principal is always `{ type: 'app' }` for this adapter —\n // the broker request asserts it explicitly (not derived from `_opts`).\n return fetchBrokerToken(connectorId);\n },\n evict(_opts: {\n readonly principal: ConnectionPrincipal;\n readonly connection: ConnectionAuthorizationContext;\n }): Promise<void> {\n return evictBrokerToken(connectorId);\n },\n };\n}\n\n/**\n * Re-export the two eve connection-auth error classes the adapter PRODUCES, so a\n * creator authoring a custom `getToken` (or a test asserting the adapter's\n * behaviour) constructs the SAME classes the broker maps to — and matches them BY\n * `err.name`, never `instanceof`.\n */\nexport { ConnectionAuthorizationRequiredError, ConnectionAuthorizationFailedError };\n","// Stackbone Connect — channel/connection helpers (feature 74, Phase G).\n//\n// `connect(connectorId)` returns the `auth` strategy for a single connection. The\n// helpers here are the thin glue that ATTACHES that broker-backed auth (and any\n// broker-resolved request headers) onto an eve connection/channel definition,\n// without the creator threading `connect()` into every `define*` call by hand.\n//\n// Like `connect()`, these are app-scoped + non-interactive only and produce eve\n// shapes structurally (eve stays an OPTIONAL external peer — types come from the\n// `eve-connections-peer.d.ts` shim, no hard dependency).\n//\n// CREDENTIAL PLACEMENT — the de-risk notes flag that eve carries ONLY a Bearer\n// token through `auth.getToken` (it hardcodes `Authorization: Bearer <token>`).\n// Anything else — an API-key header (`X-Api-Key`), a custom header — has no slot\n// on eve's `TokenResult`; it rides the connection's SEPARATE `headers` closure.\n// `withConnect` covers the Bearer path (sets `auth`); `connectHeaders` covers the\n// non-Bearer path (produces the `headers` closure).\n\nimport { connect, type StackboneConnectAuth } from './connect';\nimport type { ConnectionAuthDefinition } from 'eve/connections';\n\n/**\n * A connection definition with an `auth` slot — the common shape of what eve's\n * `defineMcpClientConnection` / `defineOpenAPIConnection` accept and return. Kept\n * structural so the helpers compose over either factory's output without importing\n * the concrete definition types.\n */\nexport interface ConnectionLike {\n /** The auth strategy eve probes before every tool call. */\n auth?: ConnectionAuthDefinition;\n}\n\n/**\n * Attach a broker-backed `auth` to an existing connection definition.\n *\n * Sugar over `{ ...definition, auth: connect(connectorId) }` for creators who\n * build the connection with `defineMcpClientConnection` / `defineOpenAPIConnection`\n * first and want to bolt Stackbone Connect on without re-typing the `auth` field.\n * The returned definition carries the `stackboneConnect` attribution marker so\n * build/Studio tooling detects it.\n *\n * @param definition The eve connection definition to wrap.\n * @param connectorId The connector id registered in Studio.\n */\nexport function withConnect<T extends ConnectionLike>(\n definition: T,\n connectorId: string,\n): T & { auth: StackboneConnectAuth } {\n return { ...definition, auth: connect(connectorId) };\n}\n\n/**\n * Resolve the broker-managed request headers for a connector (the non-Bearer\n * credential-placement path: API-key headers, custom headers). eve carries Bearer\n * tokens through `auth.getToken`, but anything else rides the connection's\n * separate `headers` closure — this helper produces that closure, pre-wired to the\n * broker.\n *\n * The returned closure runs fresh per request (eve re-resolves `headers` on every\n * call), so a rotated credential is always current. It reuses `connect()`'s\n * broker round-trip via the connection's `getToken`, then writes the minted token\n * under `headerName` (defaults to `X-Api-Key`, the common provider convention). If\n * the broker signals the credential is missing, the underlying eve error\n * propagates verbatim (matched by name downstream) — the header closure never\n * masks it.\n *\n * @param connectorId The connector id registered in Studio.\n * @param headerName The header to place the minted token under (default `X-Api-Key`).\n */\nexport function connectHeaders(\n connectorId: string,\n headerName = 'X-Api-Key',\n): () => Promise<Record<string, string>> {\n const auth = connect(connectorId);\n return async (): Promise<Record<string, string>> => {\n // Drive the same broker round-trip `auth.getToken` performs. The `connection`\n // url is unused on the app-scoped path (the broker request asserts the\n // principal explicitly), so pass an empty context.\n const { token } = await auth.getToken({\n principal: { type: 'app' },\n connection: { url: '' },\n });\n return { [headerName]: token };\n };\n}\n","// `callConnector()` / `connection()` — call a Stackbone Connect connector DIRECTLY\n// from a workflow, with NO agent in the loop (feature 74).\n//\n// A workflow `\"use step\"` often needs to perform a single provider action (send a\n// Slack message, create a GitHub issue) without spinning up an agent turn. This\n// helper POSTs the action to the agent-facing broker `POST /api/connect/execute`\n// (the emulator in `stackbone dev`, the client's VM in production) which runs ONE\n// `ConnectActionExecutor.execute` against the provider on the caller's behalf and\n// returns the operation output.\n//\n// It is a PLAIN HMAC fetch client: unlike the sibling `connect.ts` (the eve\n// connection-auth ADAPTER), this file imports NO eve types — a workflow that calls\n// a connector directly need not author an eve connection. It shares the SAME\n// transport (HMAC scheme A + env contract) as the adapter via `connect-transport`,\n// so the broker's one verifier accepts both byte-for-byte.\n//\n// CALLER IDENTITY — HMAC scheme A (install identity), byte-identical to\n// `surfaces/external/agents/eve-agent.ts signWorkflowHeaders()`: the request\n// carries `hex(HMAC-SHA256(HMAC_SECRET, String(Date.now())))` over the RAW\n// timestamp string under `x-stackbone-timestamp` + `x-stackbone-workflow-signature`.\n// HMAC proves POSSESSION of the shared per-workspace secret, NOT which install is\n// calling, so the body ALSO carries the explicit `installationId`\n// (`STACKBONE_INSTALLATION_ID`) + connector + operation + principal scoping claims.\n\nimport {\n brokerBaseUrl,\n installationId,\n signBrokerHeaders,\n type Principal,\n} from './connect-transport';\n\n/** The broker route the direct connector call POSTs to (under `STACKBONE_API_URL`). */\nconst EXECUTE_PATH = '/api/connect/execute';\n\n/** Options accepted by `callConnector` / `connection(...).call`. */\nexport interface CallConnectorOptions {\n /**\n * WHO the connector call is made on behalf of. Defaults to `{ type: 'app' }`\n * (the agent's own service-account credential). A `user` principal scopes the\n * broker token issuer to that end user; `client-credentials` / `jwt-bearer` are\n * the broker's M2M auth modes.\n */\n readonly principal?: Principal;\n}\n\n/**\n * The broker's `POST /api/connect/execute` success envelope. The executor's\n * operation output rides under `output` (arbitrary JSON the provider returned).\n */\ninterface ExecuteSuccessBody {\n readonly ok: true;\n readonly output: unknown;\n}\n\n/** The broker's error envelope — discriminated by the string `code`, never a class. */\ninterface ExecuteErrorBody {\n readonly ok: false;\n readonly error: { readonly code: string; readonly message?: string };\n}\n\n/**\n * An `Error` carrying the broker's machine-readable `code` so a workflow matches\n * the failure BY CODE STRING (never `instanceof` — under bundling the SDK and the\n * workflow can hold different class identities; the code is the stable signal).\n */\nexport interface ConnectorCallError extends Error {\n /** The broker error code (`invalid_args`, `credential_error`, `timeout`, …). */\n readonly code: string;\n}\n\n/**\n * Build the `ConnectorCallError` thrown on a broker error. The `code` is carried\n * as an own property so `err.code` reads back the broker taxonomy.\n */\nfunction connectorCallError(code: string, message: string): ConnectorCallError {\n const error = new Error(message) as Error & { code: string };\n error.code = code;\n return error;\n}\n\n/**\n * Read the broker's `{ ok: false, error: { code, message } }` envelope off a\n * non-2xx (or `ok: false`) response, tolerating an empty / non-JSON body (a proxy\n * 502 may carry no JSON). Falls back to a `credential_error` keyed off the HTTP\n * status when no structured code is present.\n */\nfunction errorFromBody(status: number, body: ExecuteErrorBody | null): ConnectorCallError {\n if (body && body.ok === false && body.error && typeof body.error.code === 'string') {\n return connectorCallError(\n body.error.code,\n body.error.message ??\n `Stackbone Connect broker rejected the call (code \"${body.error.code}\").`,\n );\n }\n return connectorCallError('credential_error', `Stackbone Connect broker responded ${status}.`);\n}\n\n/**\n * Call a Stackbone Connect connector operation directly from a workflow.\n *\n * POSTs `{ connector, operation, args, principal, installationId }` to the\n * agent-facing broker (`POST /api/connect/execute`) with HMAC scheme-A headers.\n * On `{ ok: true, output }` it returns the operation `output`; on\n * `{ ok: false, error }` (or any non-2xx) it THROWS a `ConnectorCallError` whose\n * `.code` is the broker error code and `.message` the broker message.\n *\n * ```ts\n * const channels = await callConnector('slack', 'conversations.list');\n * await callConnector('slack', 'chat.postMessage', { channel: 'C123', text: 'hi' });\n * ```\n *\n * @param connector The connect connector id registered in Studio.\n * @param operation The operation / tool id to run on that connector.\n * @param args The operation arguments (path/query/body params). Defaults `{}`.\n * @param opts Optional `{ principal }` — defaults to `{ type: 'app' }`.\n */\nexport async function callConnector(\n connector: string,\n operation: string,\n args?: Readonly<Record<string, unknown>>,\n opts?: CallConnectorOptions,\n): Promise<unknown> {\n const principal: Principal = opts?.principal ?? { type: 'app' };\n // Resolve the env-derived URL + install id BEFORE the fetch try-block so a\n // missing `STACKBONE_API_URL` / `STACKBONE_INSTALLATION_ID` surfaces its own\n // explicit error instead of being swallowed into the transport `execute_failed`.\n const url = `${brokerBaseUrl()}${EXECUTE_PATH}`;\n const body = JSON.stringify({\n connector,\n operation,\n args: args ?? {},\n principal,\n installationId: installationId(),\n });\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n ...signBrokerHeaders(),\n },\n body,\n });\n } catch (cause) {\n // Network / transport failure → a retryable execute failure surfaced by code.\n throw connectorCallError(\n 'execute_failed',\n `Stackbone Connect broker is unreachable: ${\n cause instanceof Error ? cause.message : String(cause)\n }`,\n );\n }\n\n const parsed = (await response.json().catch(() => null)) as\n | ExecuteSuccessBody\n | ExecuteErrorBody\n | null;\n\n if (!response.ok || (parsed && parsed.ok === false)) {\n throw errorFromBody(response.status, (parsed as ExecuteErrorBody | null) ?? null);\n }\n\n if (!parsed || parsed.ok !== true) {\n // 2xx but a body that is neither `{ ok: true }` nor a structured error —\n // treat as a malformed broker response rather than silently returning it.\n throw connectorCallError(\n 'invalid_output',\n 'Stackbone Connect broker returned a malformed execute response.',\n );\n }\n\n return parsed.output;\n}\n\n/** The dynamic escape hatch present on every `connection(id)` handle. */\nexport interface ConnectorHandle {\n /**\n * Run ANY operation on this connector by id — the untyped escape hatch, always\n * available (use it for an operation id that is not a JS identifier, e.g. a\n * dotted `chat.postMessage`, or a connector with no generated types). Sugar for\n * `callConnector(id, operation, args, opts)`.\n */\n call(\n operation: string,\n args?: Readonly<Record<string, unknown>>,\n opts?: CallConnectorOptions,\n ): Promise<unknown>;\n}\n\n/**\n * Augmentable registry of TYPED connector operations, keyed by the VERBATIM\n * connector id (`'stub-mail'`, `'slack'`). The Stackbone Connect type generator\n * (`stackbone dev` writes `.stackbone/connect.d.ts`) MERGES one member per connector\n * into this interface via a `declare module` augmentation, each a map of the\n * connector's operations → typed `(args) => Promise<output>`, derived from the\n * introspected JSON Schema. Empty by default — with no generated types,\n * `connection(id)` offers only the dynamic `.call(...)`.\n *\n * Same id-keyed-registry mechanism as `eveAgent`'s `AgentRegistry`: the selection\n * style is unified — address an entity by its verbatim id, get a typed handle.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type\nexport interface StackboneConnections {}\n\n/**\n * The typed handle for a connector id: its generated operations (when the id is a\n * known key of `StackboneConnections`) intersected with the dynamic `.call` escape\n * hatch (always present). An unknown id resolves to just `ConnectorHandle`.\n */\nexport type ConnectorOf<Id extends string> = (Id extends keyof StackboneConnections\n ? StackboneConnections[Id]\n : unknown) &\n ConnectorHandle;\n\n/**\n * The exported `connection`: select a connector by its verbatim id and get a typed\n * handle. A known id (a key of the generated `StackboneConnections`) yields its\n * typed operations + `.call`; any other string yields the dynamic `ConnectorHandle`.\n */\nexport interface ConnectionAccessor {\n <Id extends keyof StackboneConnections>(id: Id): ConnectorOf<Id>;\n (id: string): ConnectorHandle;\n}\n\n/**\n * Build the per-id handle: a `Proxy` where `.call` is the dynamic escape hatch and\n * EVERY other property is treated as an operation id, dispatched verbatim to\n * `callConnector(id, operation, args, opts)`. So `connection('stub-mail').sendMail(a)`\n * is `callConnector('stub-mail', 'sendMail', a)` — the typed operations and the\n * dynamic `.call` hit the same broker path, with zero per-connector runtime code.\n */\nconst connectorHandle = (id: string): ConnectorHandle =>\n new Proxy(Object.create(null) as ConnectorHandle, {\n get(_target, prop) {\n // Never look thenable (an accidental `await connection(id)` must not hang).\n if (typeof prop !== 'string' || prop === 'then') return undefined;\n if (prop === 'call') {\n return (\n operation: string,\n args?: Readonly<Record<string, unknown>>,\n opts?: CallConnectorOptions,\n ): Promise<unknown> => callConnector(id, operation, args, opts);\n }\n return (args?: Readonly<Record<string, unknown>>, opts?: CallConnectorOptions): Promise<unknown> =>\n callConnector(id, prop, args, opts);\n },\n });\n\n/**\n * Call Stackbone Connect connectors. Select by verbatim id, then call an operation —\n * typed when `.stackbone/connect.d.ts` is generated, dynamic via `.call` always:\n *\n * ```ts\n * await connection('stub-mail').sendMail({ to, subject, body }); // typed\n * await connection('slack').call('chat.postMessage', { ... }); // dynamic escape hatch\n * ```\n *\n * Mirrors `eveAgent('agent-name')`: one id-keyed selection style across the SDK.\n */\nexport const connection: ConnectionAccessor = ((id: string) =>\n connectorHandle(id)) as ConnectionAccessor;\n"]}
package/connect.d.cts ADDED
@@ -0,0 +1,94 @@
1
+ import { ConnectionAuthDefinition } from 'eve/connections';
2
+ export { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError } from 'eve/connections';
3
+ export { C as CallConnectorOptions, a as ConnectionAccessor, b as ConnectorCallError, c as ConnectorHandle, P as Principal, S as StackboneConnections, d as callConnector, e as connection } from './call-connector-CYDw_yG5.cjs';
4
+
5
+ /**
6
+ * The auth strategy `connect()` returns, widened with the Stackbone attribution
7
+ * marker. Structurally a `getToken`-only eve `ConnectionAuthDefinition` (so it
8
+ * drops straight into `defineMcpClientConnection({ auth })` /
9
+ * `defineOpenAPIConnection({ auth })`), plus a `stackboneConnect` marker — the
10
+ * sibling of eve's `vercelConnect` — that lets build/Studio tooling detect a
11
+ * broker-backed connection without inspecting the `getToken` closure.
12
+ */
13
+ interface StackboneConnectAuth extends ConnectionAuthDefinition {
14
+ /** App-scoped: the adapter only ever emits the non-interactive `'app'` path. */
15
+ readonly principalType: 'app';
16
+ /** Compile-time attribution marker; ignored by eve's token path. */
17
+ readonly stackboneConnect: {
18
+ readonly connector: string;
19
+ };
20
+ }
21
+ /**
22
+ * `connect(connectorId)` — wire a connection's `auth` to the Stackbone Connect
23
+ * broker for the agent's OWN (app-scoped) credential.
24
+ *
25
+ * Use it as the `auth` field of a connection definition:
26
+ *
27
+ * ```ts
28
+ * import { defineOpenAPIConnection } from 'eve/connections';
29
+ * import { connect } from '@stackbone/sdk/connect';
30
+ *
31
+ * export default defineOpenAPIConnection({
32
+ * spec: 'https://api.example.com/openapi.json',
33
+ * auth: connect('example'),
34
+ * });
35
+ * ```
36
+ *
37
+ * `getToken` fetches a short-lived bearer from the broker (HMAC-signed,
38
+ * install-scoped). When the broker reports the connector's credentials are not yet
39
+ * installed it THROWS `ConnectionAuthorizationRequiredError` (eve emits
40
+ * `authorization.required`); a hard broker failure throws
41
+ * `ConnectionAuthorizationFailedError(reason, retryable)`. `evict` purges the
42
+ * broker's cached token on a 401 so the next `getToken` re-mints.
43
+ *
44
+ * @param connectorId The connector id registered in Studio (matches
45
+ * `Connector.connectorId`). Stamped onto the returned `stackboneConnect` marker.
46
+ */
47
+ declare function connect(connectorId: string): StackboneConnectAuth;
48
+
49
+ /**
50
+ * A connection definition with an `auth` slot — the common shape of what eve's
51
+ * `defineMcpClientConnection` / `defineOpenAPIConnection` accept and return. Kept
52
+ * structural so the helpers compose over either factory's output without importing
53
+ * the concrete definition types.
54
+ */
55
+ interface ConnectionLike {
56
+ /** The auth strategy eve probes before every tool call. */
57
+ auth?: ConnectionAuthDefinition;
58
+ }
59
+ /**
60
+ * Attach a broker-backed `auth` to an existing connection definition.
61
+ *
62
+ * Sugar over `{ ...definition, auth: connect(connectorId) }` for creators who
63
+ * build the connection with `defineMcpClientConnection` / `defineOpenAPIConnection`
64
+ * first and want to bolt Stackbone Connect on without re-typing the `auth` field.
65
+ * The returned definition carries the `stackboneConnect` attribution marker so
66
+ * build/Studio tooling detects it.
67
+ *
68
+ * @param definition The eve connection definition to wrap.
69
+ * @param connectorId The connector id registered in Studio.
70
+ */
71
+ declare function withConnect<T extends ConnectionLike>(definition: T, connectorId: string): T & {
72
+ auth: StackboneConnectAuth;
73
+ };
74
+ /**
75
+ * Resolve the broker-managed request headers for a connector (the non-Bearer
76
+ * credential-placement path: API-key headers, custom headers). eve carries Bearer
77
+ * tokens through `auth.getToken`, but anything else rides the connection's
78
+ * separate `headers` closure — this helper produces that closure, pre-wired to the
79
+ * broker.
80
+ *
81
+ * The returned closure runs fresh per request (eve re-resolves `headers` on every
82
+ * call), so a rotated credential is always current. It reuses `connect()`'s
83
+ * broker round-trip via the connection's `getToken`, then writes the minted token
84
+ * under `headerName` (defaults to `X-Api-Key`, the common provider convention). If
85
+ * the broker signals the credential is missing, the underlying eve error
86
+ * propagates verbatim (matched by name downstream) — the header closure never
87
+ * masks it.
88
+ *
89
+ * @param connectorId The connector id registered in Studio.
90
+ * @param headerName The header to place the minted token under (default `X-Api-Key`).
91
+ */
92
+ declare function connectHeaders(connectorId: string, headerName?: string): () => Promise<Record<string, string>>;
93
+
94
+ export { type ConnectionLike, type StackboneConnectAuth, connect, connectHeaders, withConnect };
package/connect.d.ts ADDED
@@ -0,0 +1,94 @@
1
+ import { ConnectionAuthDefinition } from 'eve/connections';
2
+ export { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError } from 'eve/connections';
3
+ export { C as CallConnectorOptions, a as ConnectionAccessor, b as ConnectorCallError, c as ConnectorHandle, P as Principal, S as StackboneConnections, d as callConnector, e as connection } from './call-connector-CYDw_yG5.js';
4
+
5
+ /**
6
+ * The auth strategy `connect()` returns, widened with the Stackbone attribution
7
+ * marker. Structurally a `getToken`-only eve `ConnectionAuthDefinition` (so it
8
+ * drops straight into `defineMcpClientConnection({ auth })` /
9
+ * `defineOpenAPIConnection({ auth })`), plus a `stackboneConnect` marker — the
10
+ * sibling of eve's `vercelConnect` — that lets build/Studio tooling detect a
11
+ * broker-backed connection without inspecting the `getToken` closure.
12
+ */
13
+ interface StackboneConnectAuth extends ConnectionAuthDefinition {
14
+ /** App-scoped: the adapter only ever emits the non-interactive `'app'` path. */
15
+ readonly principalType: 'app';
16
+ /** Compile-time attribution marker; ignored by eve's token path. */
17
+ readonly stackboneConnect: {
18
+ readonly connector: string;
19
+ };
20
+ }
21
+ /**
22
+ * `connect(connectorId)` — wire a connection's `auth` to the Stackbone Connect
23
+ * broker for the agent's OWN (app-scoped) credential.
24
+ *
25
+ * Use it as the `auth` field of a connection definition:
26
+ *
27
+ * ```ts
28
+ * import { defineOpenAPIConnection } from 'eve/connections';
29
+ * import { connect } from '@stackbone/sdk/connect';
30
+ *
31
+ * export default defineOpenAPIConnection({
32
+ * spec: 'https://api.example.com/openapi.json',
33
+ * auth: connect('example'),
34
+ * });
35
+ * ```
36
+ *
37
+ * `getToken` fetches a short-lived bearer from the broker (HMAC-signed,
38
+ * install-scoped). When the broker reports the connector's credentials are not yet
39
+ * installed it THROWS `ConnectionAuthorizationRequiredError` (eve emits
40
+ * `authorization.required`); a hard broker failure throws
41
+ * `ConnectionAuthorizationFailedError(reason, retryable)`. `evict` purges the
42
+ * broker's cached token on a 401 so the next `getToken` re-mints.
43
+ *
44
+ * @param connectorId The connector id registered in Studio (matches
45
+ * `Connector.connectorId`). Stamped onto the returned `stackboneConnect` marker.
46
+ */
47
+ declare function connect(connectorId: string): StackboneConnectAuth;
48
+
49
+ /**
50
+ * A connection definition with an `auth` slot — the common shape of what eve's
51
+ * `defineMcpClientConnection` / `defineOpenAPIConnection` accept and return. Kept
52
+ * structural so the helpers compose over either factory's output without importing
53
+ * the concrete definition types.
54
+ */
55
+ interface ConnectionLike {
56
+ /** The auth strategy eve probes before every tool call. */
57
+ auth?: ConnectionAuthDefinition;
58
+ }
59
+ /**
60
+ * Attach a broker-backed `auth` to an existing connection definition.
61
+ *
62
+ * Sugar over `{ ...definition, auth: connect(connectorId) }` for creators who
63
+ * build the connection with `defineMcpClientConnection` / `defineOpenAPIConnection`
64
+ * first and want to bolt Stackbone Connect on without re-typing the `auth` field.
65
+ * The returned definition carries the `stackboneConnect` attribution marker so
66
+ * build/Studio tooling detects it.
67
+ *
68
+ * @param definition The eve connection definition to wrap.
69
+ * @param connectorId The connector id registered in Studio.
70
+ */
71
+ declare function withConnect<T extends ConnectionLike>(definition: T, connectorId: string): T & {
72
+ auth: StackboneConnectAuth;
73
+ };
74
+ /**
75
+ * Resolve the broker-managed request headers for a connector (the non-Bearer
76
+ * credential-placement path: API-key headers, custom headers). eve carries Bearer
77
+ * tokens through `auth.getToken`, but anything else rides the connection's
78
+ * separate `headers` closure — this helper produces that closure, pre-wired to the
79
+ * broker.
80
+ *
81
+ * The returned closure runs fresh per request (eve re-resolves `headers` on every
82
+ * call), so a rotated credential is always current. It reuses `connect()`'s
83
+ * broker round-trip via the connection's `getToken`, then writes the minted token
84
+ * under `headerName` (defaults to `X-Api-Key`, the common provider convention). If
85
+ * the broker signals the credential is missing, the underlying eve error
86
+ * propagates verbatim (matched by name downstream) — the header closure never
87
+ * masks it.
88
+ *
89
+ * @param connectorId The connector id registered in Studio.
90
+ * @param headerName The header to place the minted token under (default `X-Api-Key`).
91
+ */
92
+ declare function connectHeaders(connectorId: string, headerName?: string): () => Promise<Record<string, string>>;
93
+
94
+ export { type ConnectionLike, type StackboneConnectAuth, connect, connectHeaders, withConnect };
package/connect.js ADDED
@@ -0,0 +1,257 @@
1
+ import { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError } from 'eve/connections';
2
+ export { ConnectionAuthorizationFailedError, ConnectionAuthorizationRequiredError } from 'eve/connections';
3
+ import { createHmac } from 'crypto';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var TIMESTAMP_HEADER = "x-stackbone-timestamp";
8
+ var SIGNATURE_HEADER = "x-stackbone-workflow-signature";
9
+ function signBrokerHeaders() {
10
+ const secret = process.env["HMAC_SECRET"] ?? "";
11
+ const timestamp = String(Date.now());
12
+ const signature = createHmac("sha256", secret).update(timestamp).digest("hex");
13
+ return {
14
+ [TIMESTAMP_HEADER]: timestamp,
15
+ [SIGNATURE_HEADER]: signature
16
+ };
17
+ }
18
+ __name(signBrokerHeaders, "signBrokerHeaders");
19
+ function brokerBaseUrl() {
20
+ const url = process.env["STACKBONE_API_URL"];
21
+ if (!url) {
22
+ throw new Error("STACKBONE_API_URL is not set. The Stackbone runtime injects the broker base URL; run this agent through `stackbone dev`.");
23
+ }
24
+ return url.replace(/\/+$/, "");
25
+ }
26
+ __name(brokerBaseUrl, "brokerBaseUrl");
27
+ function installationId() {
28
+ const id = process.env["STACKBONE_INSTALLATION_ID"];
29
+ if (!id) {
30
+ throw new Error("STACKBONE_INSTALLATION_ID is not set. The Stackbone runtime injects it; run this agent through `stackbone dev`.");
31
+ }
32
+ return id;
33
+ }
34
+ __name(installationId, "installationId");
35
+
36
+ // src/surfaces/external/connect/connect.ts
37
+ var TOKEN_PATH = "/api/connect/token";
38
+ function brokerCodeToEveError(connectorId, code, message) {
39
+ switch (code) {
40
+ case "no_token":
41
+ case "user_authorization_required":
42
+ return new ConnectionAuthorizationRequiredError(connectorId, message ? {
43
+ message
44
+ } : void 0);
45
+ case "connector_installation_required":
46
+ case "app_not_installed":
47
+ return new ConnectionAuthorizationFailedError(connectorId, {
48
+ ...message ? {
49
+ message
50
+ } : {},
51
+ reason: "app_not_installed",
52
+ retryable: false
53
+ });
54
+ case "principal_required":
55
+ return new ConnectionAuthorizationFailedError(connectorId, {
56
+ ...message ? {
57
+ message
58
+ } : {},
59
+ reason: "principal_required",
60
+ retryable: false
61
+ });
62
+ default:
63
+ return new ConnectionAuthorizationFailedError(connectorId, {
64
+ message: message ?? `Stackbone Connect broker rejected the token request (code "${code}").`,
65
+ reason: code,
66
+ retryable: false
67
+ });
68
+ }
69
+ }
70
+ __name(brokerCodeToEveError, "brokerCodeToEveError");
71
+ async function readBrokerError(response) {
72
+ const body = await response.json().catch(() => null);
73
+ if (body && typeof body.code === "string") {
74
+ return {
75
+ code: body.code,
76
+ ...body.message !== void 0 ? {
77
+ message: body.message
78
+ } : {}
79
+ };
80
+ }
81
+ return {
82
+ code: "credential_error",
83
+ message: `Broker responded ${response.status}.`
84
+ };
85
+ }
86
+ __name(readBrokerError, "readBrokerError");
87
+ async function fetchBrokerToken(connectorId) {
88
+ const body = JSON.stringify({
89
+ connector: connectorId,
90
+ principal: {
91
+ type: "app"
92
+ },
93
+ installationId: installationId()
94
+ });
95
+ let response;
96
+ try {
97
+ response = await fetch(`${brokerBaseUrl()}${TOKEN_PATH}`, {
98
+ method: "POST",
99
+ headers: {
100
+ "content-type": "application/json",
101
+ ...signBrokerHeaders()
102
+ },
103
+ body
104
+ });
105
+ } catch (cause) {
106
+ throw new ConnectionAuthorizationFailedError(connectorId, {
107
+ message: `Stackbone Connect broker is unreachable: ${cause instanceof Error ? cause.message : String(cause)}`,
108
+ reason: "execute_failed",
109
+ retryable: true
110
+ });
111
+ }
112
+ if (!response.ok) {
113
+ const error = await readBrokerError(response);
114
+ throw brokerCodeToEveError(connectorId, error.code, error.message);
115
+ }
116
+ const minted = await response.json().catch(() => null);
117
+ if (!minted || typeof minted.token !== "string") {
118
+ throw new ConnectionAuthorizationFailedError(connectorId, {
119
+ message: "Stackbone Connect broker returned a malformed token response.",
120
+ reason: "invalid_output",
121
+ retryable: false
122
+ });
123
+ }
124
+ return minted.expiresAt !== void 0 ? {
125
+ token: minted.token,
126
+ expiresAt: minted.expiresAt
127
+ } : {
128
+ token: minted.token
129
+ };
130
+ }
131
+ __name(fetchBrokerToken, "fetchBrokerToken");
132
+ async function evictBrokerToken(connectorId) {
133
+ try {
134
+ await fetch(`${brokerBaseUrl()}${TOKEN_PATH.replace(/\/token$/, "/evict")}`, {
135
+ method: "POST",
136
+ headers: {
137
+ "content-type": "application/json",
138
+ ...signBrokerHeaders()
139
+ },
140
+ body: JSON.stringify({
141
+ connector: connectorId,
142
+ principal: {
143
+ type: "app"
144
+ },
145
+ installationId: process.env["STACKBONE_INSTALLATION_ID"]
146
+ })
147
+ });
148
+ } catch {
149
+ }
150
+ }
151
+ __name(evictBrokerToken, "evictBrokerToken");
152
+ function connect(connectorId) {
153
+ return {
154
+ principalType: "app",
155
+ stackboneConnect: {
156
+ connector: connectorId
157
+ },
158
+ getToken(_opts) {
159
+ return fetchBrokerToken(connectorId);
160
+ },
161
+ evict(_opts) {
162
+ return evictBrokerToken(connectorId);
163
+ }
164
+ };
165
+ }
166
+ __name(connect, "connect");
167
+
168
+ // src/surfaces/external/connect/channel-helpers.ts
169
+ function withConnect(definition, connectorId) {
170
+ return {
171
+ ...definition,
172
+ auth: connect(connectorId)
173
+ };
174
+ }
175
+ __name(withConnect, "withConnect");
176
+ function connectHeaders(connectorId, headerName = "X-Api-Key") {
177
+ const auth = connect(connectorId);
178
+ return async () => {
179
+ const { token } = await auth.getToken({
180
+ principal: {
181
+ type: "app"
182
+ },
183
+ connection: {
184
+ url: ""
185
+ }
186
+ });
187
+ return {
188
+ [headerName]: token
189
+ };
190
+ };
191
+ }
192
+ __name(connectHeaders, "connectHeaders");
193
+
194
+ // src/surfaces/external/connect/call-connector.ts
195
+ var EXECUTE_PATH = "/api/connect/execute";
196
+ function connectorCallError(code, message) {
197
+ const error = new Error(message);
198
+ error.code = code;
199
+ return error;
200
+ }
201
+ __name(connectorCallError, "connectorCallError");
202
+ function errorFromBody(status, body) {
203
+ if (body && body.ok === false && body.error && typeof body.error.code === "string") {
204
+ return connectorCallError(body.error.code, body.error.message ?? `Stackbone Connect broker rejected the call (code "${body.error.code}").`);
205
+ }
206
+ return connectorCallError("credential_error", `Stackbone Connect broker responded ${status}.`);
207
+ }
208
+ __name(errorFromBody, "errorFromBody");
209
+ async function callConnector(connector, operation, args, opts) {
210
+ const principal = opts?.principal ?? {
211
+ type: "app"
212
+ };
213
+ const url = `${brokerBaseUrl()}${EXECUTE_PATH}`;
214
+ const body = JSON.stringify({
215
+ connector,
216
+ operation,
217
+ args: args ?? {},
218
+ principal,
219
+ installationId: installationId()
220
+ });
221
+ let response;
222
+ try {
223
+ response = await fetch(url, {
224
+ method: "POST",
225
+ headers: {
226
+ "content-type": "application/json",
227
+ ...signBrokerHeaders()
228
+ },
229
+ body
230
+ });
231
+ } catch (cause) {
232
+ throw connectorCallError("execute_failed", `Stackbone Connect broker is unreachable: ${cause instanceof Error ? cause.message : String(cause)}`);
233
+ }
234
+ const parsed = await response.json().catch(() => null);
235
+ if (!response.ok || parsed && parsed.ok === false) {
236
+ throw errorFromBody(response.status, parsed ?? null);
237
+ }
238
+ if (!parsed || parsed.ok !== true) {
239
+ throw connectorCallError("invalid_output", "Stackbone Connect broker returned a malformed execute response.");
240
+ }
241
+ return parsed.output;
242
+ }
243
+ __name(callConnector, "callConnector");
244
+ var connectorHandle = /* @__PURE__ */ __name((id) => new Proxy(/* @__PURE__ */ Object.create(null), {
245
+ get(_target, prop) {
246
+ if (typeof prop !== "string" || prop === "then") return void 0;
247
+ if (prop === "call") {
248
+ return (operation, args, opts) => callConnector(id, operation, args, opts);
249
+ }
250
+ return (args, opts) => callConnector(id, prop, args, opts);
251
+ }
252
+ }), "connectorHandle");
253
+ var connection = /* @__PURE__ */ __name((id) => connectorHandle(id), "connection");
254
+
255
+ export { callConnector, connect, connectHeaders, connection, withConnect };
256
+ //# sourceMappingURL=connect.js.map
257
+ //# sourceMappingURL=connect.js.map