/** * agent.ts, the AGENT side of the Agent Client Protocol (ACP). * * Exposes a GoodVibes session as an ACP agent so ACP-capable editors (Zed and * others) can drive GoodVibes over stdio: initialize/authenticate → session * lifecycle → streamed content + tool-call updates → permission requests * mapped onto the platform permission callback. * * The substrate is the SDK Embedding API (`createEmbeddedSession`): each ACP * session boots an embedded GoodVibes session against the request's `cwd` and * bridges its runtime-event bus onto ACP `session/update` notifications. No new * engine, the adapter is a protocol mapping over the embed surface. * * Honest capability surface (see `initialize`): anything the platform does not * support is reported `false`, never stubbed, * - `loadSession: false` (no session restore over ACP) * - prompt `image`/`audio`/`embeddedContext`: false (input is text; the * submit seam takes a text body) * - `mcpCapabilities.http`/`sse`: false (our MCP client is stdio JSON-RPC * only). STDIO `mcpServers` declared in `session/new` ARE wired into the * embedded session's tool surface; an http/sse entry is rejected by name * (a compliant client never sends one, given the advertised capabilities). * Turn cancellation is real: `session/cancel` aborts the session's active * agent(s) in-flight (`EmbeddedSession.cancelActive` → the agent's cancellation * signal, which the agent runner threads into the provider call), so an * already-executing provider call is stopped mid-flight and the turn emits its * cancelled outcome. A still-queued input (not yet delivered to an agent) is * additionally cancelled via the broker's `cancelInput`, and the in-flight * prompt resolves with stop reason `cancelled`. */ import type { AgentSideConnection, Agent } from '@agentclientprotocol/sdk'; import type { AuthenticateRequest, AuthenticateResponse, CancelNotification, ContentBlock, InitializeRequest, InitializeResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, StopReason } from '@agentclientprotocol/sdk'; import { type EmbeddedSession } from '../embed/session.js'; import type { PermissionPromptDecision, PermissionRequestHandler } from '../permissions/prompt.js'; import type { TurnEvent } from '../../events/turn.js'; import type { McpServerConfig } from '../mcp/config.js'; /** Factory seam for the embedded-session substrate (tests inject fakes). */ export type EmbeddedSessionFactory = (options: { readonly workspace: string; readonly requestPermission: PermissionRequestHandler; /** stdio MCP servers the ACP client declared, translated for the embedded session. */ readonly mcpServers?: readonly McpServerConfig[] | undefined; }) => Promise; /** * The ACP `mcpServers` entry shapes (from the ACP schema): a stdio server * (untagged, carrying `command`) or an http/sse server (tagged `type`, carrying * `url`). Modeled locally and read defensively because the vendor types degrade * to `any` when the ACP SDK types are absent. */ interface AcpEnvVariable { readonly name: string; readonly value: string; } interface AcpMcpServerDeclaration { readonly type?: string | undefined; readonly name?: string | undefined; readonly command?: string | undefined; readonly args?: readonly string[] | undefined; readonly env?: readonly AcpEnvVariable[] | undefined; readonly url?: string | undefined; } /** * Translate a client's declared ACP MCP servers into our stdio `McpServerConfig` * shape. Returns the supported stdio configs and the names of servers we * genuinely cannot support (http/sse, no such transport in our MCP client, * which is stdio JSON-RPC only). We advertise `mcpCapabilities.http/sse: false` * in `initialize`, so a compliant client never sends those; when one arrives * anyway it is reported by name rather than silently dropped. */ export declare function translateAcpMcpServers(servers: readonly AcpMcpServerDeclaration[]): { readonly configs: McpServerConfig[]; readonly unsupported: string[]; }; export interface AcpAgentOptions { /** Home directory handed to the embedded daemon. Defaults to $HOME. */ readonly homeDirectory?: string | undefined; /** Substrate override; defaults to `createEmbeddedSession`. */ readonly sessionFactory?: EmbeddedSessionFactory | undefined; } /** Extract the text of a prompt: text blocks verbatim, resource links by URI. */ export declare function promptText(blocks: readonly ContentBlock[]): string; /** Map a terminal GoodVibes turn event onto an ACP stop reason. */ export declare function mapStopReason(event: TurnEvent): StopReason | null; /** Map an ACP permission outcome back onto the platform decision shape. */ export declare function mapPermissionOutcome(outcome: { outcome: 'cancelled'; } | { outcome: 'selected'; optionId: string; }): PermissionPromptDecision; /** * The ACP `Agent` implementation backed by embedded GoodVibes sessions. * One instance serves one connection; each `session/new` boots one embedded * session against the request's `cwd`. */ export declare class GoodVibesAcpAgent implements Agent { private readonly conn; private readonly options; private readonly sessions; constructor(conn: Pick, options?: AcpAgentOptions); initialize(params: InitializeRequest): Promise; /** No authentication required: the embedded daemon is process-local. */ authenticate(_params: AuthenticateRequest): Promise; newSession(params: NewSessionRequest): Promise; prompt(params: PromptRequest): Promise; cancel(params: CancelNotification): Promise; /** Tear down every embedded session (used by serveAcpAgent on stream end). */ dispose(): Promise; private bridgePermission; private forwardToolEvent; } /** * Serve a GoodVibes ACP agent over stdio. Call from a headless entry point; * returns the connection and a dispose handle (the caller owns process exit). * * Async because `@agentclientprotocol/sdk` is an optionalDependency and the * connection cannot be built before it resolves. When the package is absent * the returned promise rejects with an error naming it, which is the honest * answer for an entry point whose whole job is to speak that protocol. */ export declare function serveAcpAgent(options?: AcpAgentOptions): Promise<{ connection: AgentSideConnection; dispose: () => Promise; }>; export {}; //# sourceMappingURL=agent.d.ts.map