import { ClientSideConnection, SessionConfigOption, SessionMode, Client } from '@agentclientprotocol/sdk'; export { SessionConfigOption, SessionMode } from '@agentclientprotocol/sdk'; import { S as StreamEvent, A as AcpPermissionResolution } from '../types-DUWAu0g6.js'; /** * AIP-44 ACP client — drives a subprocess agent via stdio JSON-RPC. * * Wraps `@agentclientprotocol/sdk`'s `ClientSideConnection`. Owns the * connection lifecycle, runs `initialize` on construction, and exposes * a session API where each `prompt()` call returns an * `AsyncIterable` fed by the SDK's `session/update` * notification handler. */ /** * Map our internal `AcpMcpServer` (`{ name, transport, ref, headers, credentialRef, args, env }`) * onto the `@agentclientprotocol/sdk` wire shape expected by * `session/new.mcpServers` (and `session/load.mcpServers`). * * The ACP `McpServer` union is transport-tagged and field-named * differently from our compact internal form: * - http → `{ type: "http", name, url, headers: [{ name, value }, …] }` * - sse → `{ type: "sse", name, url, headers: [{ name, value }, …] }` * - stdio → `{ name, command, args, env: [{ name, value }, …] }` (untagged variant) * * Without this mapping the raw `{ transport, ref }` entry reaches the * agent verbatim and `session/new` rejects with `Invalid params` — the * symptom that broke orchestrator spawns (the auto-injected scoped * gateway is an `http` entry). * * Retro-compat: an entry that already carries an ACP-native discriminator * (`type`/`url`/`command`) is passed through untouched, so callers that * pre-shape their entries keep working. */ declare function toAcpMcpServers(servers: readonly unknown[]): unknown[]; interface AcpClientOptions { /** Output bytes go here (subprocess stdin). */ output: WritableStream; /** Input bytes come from here (subprocess stdout). */ input: ReadableStream; /** Client identity advertised during `initialize`. */ clientInfo?: { name: string; title?: string; version?: string; }; /** Client capabilities advertised during `initialize`. */ capabilities?: { fs?: { readTextFile?: boolean; writeTextFile?: boolean; }; terminal?: boolean; /** * Advertise the AIR `asyncTasks` extension (default true), so an agent * that implements it (claude-agent-acp) publishes its background tasks' * lifecycle — surfaced as `background-task` StreamEvents. Pass `false` * to opt out; an agent that doesn't know the extension ignores it. */ asyncTasks?: boolean; }; /** Optional handlers for agent-initiated requests beyond fs / terminal. */ handlers?: Partial; /** * When true, every `session/request_permission` the agent raises is * SURFACED (emitted as an `agent-prompt` StreamEvent on the session's * stream) and HELD — the RPC does not resolve until the host calls * `respondPermission(requestId, ...)`. `handlers.requestPermission` is * bypassed while this is set. Undefined/false (the default) is unchanged: * requests go straight to `handlers.requestPermission`. Used by the * daemon's cross-session permission inbox. */ permissionHold?: boolean; /** Protocol version to negotiate. Defaults to the SDK's current. */ protocolVersion?: number; /** * Called on ANY JSON-RPC traffic with the child agent process — every * incoming `session/update` notification (even ones that * `translateSessionUpdate` maps to `null`, e.g. a `tool_call_update` * still in progress) and every outbound RPC call this client makes * (`newSession`, `loadSession`, `setSessionConfigOption`, `prompt`, * `cancel`). Distinct from the `StreamEvent` stream: fires even * during gaps where no event is produced, so a host can track * liveness independent of what the agent actually says. */ onActivity?: () => void; /** * Turn-idle watchdog: if a `prompt()` call goes this many ms with NO * activity signal (see `onActivity` above — incoming `session/update` * notifications, outbound RPCs) and the underlying `connection.prompt()` * promise still hasn't resolved, synthesize a `turn-end` event with * `reason: "watchdog-timeout"` so the caller's async iterator completes * instead of hanging forever. The timer resets on every activity signal * observed DURING that turn, so a legitimately long tool-call chain * (still producing activity, just no user-visible output) never * false-positives — this is "N ms of true silence," not "N ms since the * turn started." * * Undefined (the default) disables the watchdog entirely — existing * callers see no behavior change. If the real `connection.prompt()` * eventually settles after the synthetic turn-end was already emitted, * the late result is logged and discarded (no crash, no duplicate * turn-end for the same logical turn). */ turnIdleTimeoutMs?: number; /** * Orphaned-prompt recovery (default {@link DEFAULT_ORPHANED_PROMPT_TIMEOUT_MS}; * `0` disables). A prompt sent while the agent is mid-way through an * AUTONOMOUS cycle (Claude Code's task-notification wake) can be folded * into that cycle as a queued command: the model answers it, but the * cycle's only result carries the autonomous origin, which claude-agent-acp * keeps off the user-turn lane — so `session/prompt` never resolves (seen * live: a finished session stayed "busy" ~50 min until a manual cancel). * When an in-flight prompt sees an autonomous-origin result frame followed * by this many ms of silence, the client cancels it — a real active turn * on the agent side, so the cancel settles it — and reports the turn * `completed`: its work was done inside the cycle. */ orphanedPromptTimeoutMs?: number; } declare const DEFAULT_ORPHANED_PROMPT_TIMEOUT_MS = 60000; /** The ACP steering extension request (claude-agent-acp, codex-acp): * inject a follow-up into the turn that is currently running. */ declare const ACP_STEER_METHOD = "_session/steering"; /** What `AcpClientSession.steer` did: * - `"steered"` — injected into the running turn; * - `"promptRequired"` — no turn in flight (checked here first: we only * steer while OUR `prompt()` is in flight, never during an agent's * autonomous cycle), so nothing was sent — deliver it as a normal prompt; * - `"unsupported"` — the agent doesn't advertise steering, or refused. */ type SteerOutcome = "steered" | "promptRequired" | "unsupported"; interface AcpClient { readonly connection: ClientSideConnection; /** * Whether the underlying JSON-RPC connection is still open. Reads the SDK * connection's own abort signal (`ClientSideConnection.signal`), which is * aborted synchronously the moment the transport closes — so this is a * FRESH fact at call time, never a cached flag that can go stale. * * The pull counterpart to {@link AcpClient.onDisconnect}: a sweep (the * daemon's crash-detect pass) can ask "is this session's adapter actually * reachable?" without waiting for a prompt to fail. */ isConnected(): boolean; /** * Subscribe to transport death. `listener` fires at most once, when the * JSON-RPC connection to the agent goes away — the subprocess's stdout * ended, the transport errored, or the SDK closed the connection for any * other reason — and fires SYNCHRONOUSLY if it's already gone by the time * you subscribe (so there is no window where a late subscriber misses it). * * This is the only push signal for the failure mode a pid probe cannot * see: the wrapper process is still alive (`process.kill(pid, 0)` * succeeds) but its ACP stream is dead, so every subsequent RPC rejects * with "ACP connection closed" while the host still believes the session * is running. * * Also fires for an ORDERLY teardown (the host's own `close()` aborts the * same controller), so a consumer that only cares about UNEXPECTED death * must gate on its own "did I ask for this?" state — the daemon does that * via the session's status (`markCrashed` only acts on a `running` row). */ onDisconnect(listener: (err: Error) => void): void; /** Negotiated agent capabilities returned from `initialize`. */ readonly agentCapabilities: Record | undefined; /** The TOP-LEVEL `_meta` of the `initialize` response (sibling of * `agentCapabilities`) — where extension capabilities such as * `steering.supported` are advertised. `undefined` when absent. */ readonly initMeta: Record | undefined; /** True when the agent advertised `_meta.steering.supported` at * initialize — i.e. it accepts `_session/steering`. */ readonly steeringSupported: boolean; newSession(params: { cwd: string; mcpServers?: unknown[]; /** * Model to select via `session/set_config_option` immediately after * `newSession`. The claude-agent-acp wrapper supports this as * `configId:"model"`. Omit to keep the agent's own default. * * Best-effort: a value the agent can't resolve (a stale/gated/typo'd id) * is warned about and dropped — the session still starts on the agent's * default model rather than crashing the spawn. See the reject handler * in `newSession` for the reasoning (agentproto#186). */ model?: string; /** * Effort level to select via `session/set_config_option` after * `newSession`. Effort is model-dependent — same label ≠ same * budget across models; defaults differ too (see AgentCliConnectOptions). * Omit to keep the model's own default. */ effort?: string; /** * Mode to select via `session/set_config_option` after `newSession`, * for ACP servers that model mode selection as a session config * option (`configId:"mode"`) instead of a CLI flag — e.g. opencode. * Best-effort, same as `model`/`effort`: a rejection is warned about * and dropped rather than failing the spawn. Omit to keep the * agent's own default mode. */ mode?: string; /** * Extra `_meta` for the `session/new` request (vendor-extension bag — * e.g. claude-agent-acp's `_meta.systemPrompt = { append: text }`). * Forwarded verbatim as the request's `_meta`, only when set. Omit to * send no `_meta` at all (unchanged). */ meta?: Record; }): Promise; /** * Reattach to an existing session by id. Available only when the * agent advertised `loadSession: true` in its capabilities (callers * should check `agentCapabilities` first; we forward the SDK error * verbatim if they don't). * * Drives the AIP-45 native-resume continuation strategy: the host * persists the `sessionId` returned by `newSession`, then on a * cold start (process restart, fresh sandbox, different machine) * spawns a new subprocess and calls `loadSession` to pick up * the conversation from the stored history. */ loadSession(params: { sessionId: string; cwd: string; mcpServers?: unknown[]; /** * Model to re-assert via `session/set_config_option` immediately after * `loadSession`. A native-resume spawns a fresh subprocess whose session * config starts from the harness default; some harnesses (opencode) * PERSIST and restore the session's own model, so a resumed session that * omits this silently runs the harness default — a different provider on a * different bill for a derived-from-model adapter. Same best-effort * contract as {@link newSession}'s `model` (a rejection is recorded on * `modelApplyRejection`, never fatal here). Omit to keep the resumed * session's own model. */ model?: string; /** Effort level to re-assert on the resumed session — same contract as * {@link newSession}'s `effort`. Omit to keep the model's own default. */ effort?: string; /** Mode to re-assert on the resumed session via * `session/set_config_option(configId:"mode")` — same contract as * {@link newSession}'s `mode`. Omit to keep the resumed session's own * mode. */ mode?: string; /** * Extra `_meta` for the `session/load` request — same contract as * {@link newSession}'s `meta` (claude-agent-acp re-reads e.g. * `_meta.systemPrompt` on load too, so a channel-backed text must be * re-sent every cold resume or it is lost after turn 1). */ meta?: Record; }): Promise; /** * Resolve a permission request parked by permission-hold mode. `requestId` * is the `toolCallId` carried on the `agent-prompt` StreamEvent that * surfaced the request. Returns true when a matching parked request was * found and resolved, false when the id is unknown or already resolved * (idempotent — a duplicate response is a no-op). No-op returning false * when `permissionHold` was never enabled. */ respondPermission(requestId: string, resolution: AcpPermissionResolution): boolean; close(): Promise; } /** * Result of a mid-session `setConfigOption` call — mirrors the best-effort * contract `newSession`'s own `model`/`effort`/`mode` params already use * (see the reject handler below): a rejected config option is reported * structurally, never thrown, so a caller can surface "didn't apply" to a * human without the live session (or its in-flight turn) ever being at risk. */ interface SetConfigOptionResult { applied: boolean; /** Present only when `applied` is false — the server's rejection detail * (see `configOptionErrorDetail`), or a client-side reason. */ reason?: string; } interface AcpClientSession { readonly sessionId: string; /** * Set when `newSession`'s connect-time `model` apply was REJECTED by the * server — the session is live but running on the agent's own default * model, not the requested one. The apply itself stays non-fatal here * (agentproto#186: a rejected model must never crash the spawn with an * opaque error), but silently-on-the-wrong-model is exactly the failure * a caller may need to escalate: for a derived-from-model adapter * (opencode & co.) the model IS the route, so "kept the default" means * a different provider and a different bill. This field is the * structural signal that lets the host decide — warn for free-routing * adapters, fail loudly for derived-from-model ones. `undefined` when no * model was requested, the apply succeeded, or the session was resumed * (`loadSession` applies no model). */ readonly modelApplyRejection?: { requested: string; reason: string; }; /** * The wrapper's advertised session configuration options (SDK * `SessionConfigOption[]`), captured verbatim from `newSession`'s / * `loadSession`'s response at connect time — the per-model value lists * (e.g. which model/effort ids the wrapper will actually accept) that * today are otherwise discoverable only by calling `setConfigOption` * and catching a reject. A read surface so a caller can resolve what's * offerable BEFORE attempting to apply it. Empty when the wrapper * advertises nothing (`configOptions` absent/`null` on the wire) — * never inferred or guessed. Snapshot at connect time; not live-updated * by a later `setConfigOption`/`setSessionMode` call. */ readonly availableConfigOptions: SessionConfigOption[]; /** * The wrapper's advertised session modes (SDK `SessionModeState. * availableModes`), captured from the same connect-time response. * Empty when the wrapper has no native mode registry (`modes` * absent/`null` on the wire) — the read-surface signal a caller uses to * decide there's no native posture to switch, only a portable/ * prompt-injected one. Snapshot at connect time, same as * `availableConfigOptions`. */ readonly availableModes: SessionMode[]; /** * The mode id the wrapper reported as active (`SessionModeState. * currentModeId`) at connect time. `undefined` when the wrapper * advertised no mode state at all. */ readonly currentModeId: string | undefined; prompt(input: { messages: unknown[]; signal?: AbortSignal; }): AsyncIterable; cancel(): Promise; /** * Apply a session config option (e.g. `configId:"model"`) on this LIVE, * already-connected session — the mid-session counterpart to the * `model`/`effort`/`mode` params `newSession` accepts at connect time. * Same non-fatal contract: a server rejection resolves * `{applied:false, reason}` instead of throwing, so a bad or gated model * id can never kill an otherwise-healthy session. */ setConfigOption(configId: string, value: string): Promise; /** * Switch this session's mode on this LIVE, already-connected session via * ACP `session/set_mode` (SDK `setSessionMode`) — the native-posture * counterpart to `setConfigOption`, distinct axis (SDK models mode and * config-option as separate RPCs). Same best-effort, non-fatal contract: * a mode the wrapper doesn't recognize (not in `availableModes`, a stale * offer, or a version mismatch) resolves `{applied:false, reason}` * instead of throwing — a rejected mode switch must never kill an * otherwise-healthy session, mirroring `setConfigOption`'s contract * exactly. */ setSessionMode(modeId: string): Promise; /** * Receive the events the agent emits while NO `prompt()` is in flight. * * An agent can work without being prompted: Claude Code wakes the model on * its own when a background task settles (an "autonomous" task-notification * cycle) and streams that work as ordinary `session/update`s. With no turn * iterator to land in, those events used to be dropped on the floor — the * host saw an idle session while the agent kept working. They are delivered * here instead (in-turn events keep flowing through `prompt()`'s iterator * only). Returns an unsubscribe function. With no listener registered, an * out-of-turn event is dropped, as before. */ onOutOfTurnEvent(listener: (event: StreamEvent) => void): () => void; /** Whether this session's agent accepts `_session/steering` (see * `AcpClient.steeringSupported`). */ readonly steeringSupported: boolean; /** * Steer the turn in flight: inject `content` into it (ACP steering * extension, `_session/steering`) instead of queueing a new prompt. * Always sends `idleBehavior:"promptRequired"`, and never sends at all * unless OUR `prompt()` is in flight — so it can't start a detached turn * the host never sees, or fold into an agent's autonomous cycle. Never * throws; see {@link SteerOutcome}. */ steer(content: unknown): Promise; close(): Promise; } declare function createAcpClient(options: AcpClientOptions): Promise; /** * The AIR extension's `_meta` shape, as claude-agent-acp reads it * (`air-extension.js` `clientSupportsAirCapability`): an integer `version` * >= 1 and a `capabilities` list, under `_meta.jetbrains.air`. */ declare const AIR_ASYNC_TASKS_CAPABILITY = "asyncTasks"; /** `InitializeResponse._meta.steering.supported === true`. */ declare function isSteeringAdvertised(initMeta: Record | undefined): boolean; /** * The ACP SDK maps a server-side thrown error to a JSON-RPC `-32603` whose * `message` is JUST "Internal error" — `RequestError.internalError` never * embeds the cause in the message; the useful text travels in * `error.data.details` (e.g. a managed cloud adapter naming the credential * that is missing, or the vendor's API error). Surface that detail in the * message AND carry `code`/`data`/`name` through, so a host that reads only * `error.message` shows the real cause instead of a bare "Internal error", * and a host that inspects `error.code` still sees -32603. Returns the * original error untouched when it isn't such an opaque re-wrapping. */ declare function enrichInternalAcpError(err: T): T | Error; export { ACP_STEER_METHOD, AIR_ASYNC_TASKS_CAPABILITY, type AcpClient, type AcpClientOptions, type AcpClientSession, AcpPermissionResolution, DEFAULT_ORPHANED_PROMPT_TIMEOUT_MS, type SetConfigOptionResult, type SteerOutcome, createAcpClient, enrichInternalAcpError, isSteeringAdvertised, toAcpMcpServers };