Package Exports
- @acp-kit/core
- @acp-kit/core/node
Readme
ACP Kit
ACP Kit is a runtime for building applications on top of the Agent Client Protocol.
It launches an ACP agent process, manages the protocol connection, handles authentication, exposes host adapters for permissions / files / terminals, and turns raw session/update traffic into normalized turn, message, reasoning, and tool events. Your application chooses an agent profile, attaches a host, opens a session, and consumes stable events.
Contents
- Install
- Quick Start
- Examples
- What ACP Kit Does
- API Overview
- Built-in Agent Profiles
- How It Compares to
@agentclientprotocol/sdk - Compatibility
- Status
- Documentation
- Development
- License
Install
npm install @acp-kit/coreRequirements:
- Node.js >= 20.11 (required for
await using/Symbol.asyncDisposeused in the examples below; if you cannot upgrade, callacp.shutdown()andsession.dispose()explicitly and Node 18 still works) - A reachable ACP agent CLI (for example Copilot CLI, Claude ACP, Codex ACP) installed on the machine running the runtime
Quick Start
Open a session and subscribe to normalized events. Each event has a stable type,
stable correlation ids (messageId, toolCallId, turnId), and a typed payload —
the runtime aggregates raw session/update traffic for you.
import { createAcpRuntime } from '@acp-kit/core';
await using acp = createAcpRuntime({
profile: 'copilot',
host: {
requestPermission: async () => 'allow_once',
chooseAuthMethod: async ({ methods }) => methods[0]?.id ?? null,
},
});
await using session = await acp.newSession({ cwd: process.cwd() });
session.on({
messageDelta: (e) => process.stdout.write(e.delta),
toolStart: (e) => console.log(`\n[tool ${e.toolCallId}] ${e.title ?? e.name}`),
toolEnd: (e) => console.log(`[tool ${e.toolCallId}] ${e.status}`),
turnCompleted: (e) => console.log(`\n(turn ${e.turnId} done: ${e.stopReason})`),
});
await session.prompt('Summarize this repository.');The handler keys are camelCase; each callback receives the matching event variant
with full type narrowing — no string literals to remember.
For the full list see RuntimeEventKind.
If you only need to watch a single event type, use a typed listener directly:
session.on('tool.start', (e) => console.log(e.toolCallId, e.title)); // e: ToolStartEvent
session.on('message.delta', (e) => process.stdout.write(e.delta)); // e: MessageDeltaEventsession.on(...) is overloaded: pass a handler map for camelCase dispatch, a
literal event type to narrow a single listener, or 'event' to receive every
RuntimeSessionEvent.
One runtime owns one agent subprocess and can host many sessions over different
cwds — see examples/pair-programming/.
Examples
The repository ships with four runnable examples under examples/. Each one is a standalone npm package that installs the published @acp-kit/core from npm:
| Example | Runs without an agent installed | What it shows |
|---|---|---|
quick-start/ |
No | Minimal single-prompt script. |
pair-programming/ |
No | Two sessions in one runtime as AUTHOR + REVIEWER, looping until the reviewer says APPROVED. |
mock-runtime/ |
Yes | Self-contained mock ACP server. Use this to see the full event flow without installing an agent. |
real-agent-cli/ |
No | Interactive CLI driver for real agents (copilot, claude, codex) with prompts for auth and permission decisions. |
cd examples/mock-runtime
npm install
npm startSee examples/README.md for details.
What ACP Kit Does
A real ACP client has to do all of this before it can hold a useful conversation:
- choose which agent implementation to talk to and where it lives on disk
- spawn that agent in a platform-safe way (Windows quirks, login shells, env propagation)
- detect startup failure and surface stderr / exit reasons clearly
- bootstrap an ACP connection with
initialize - handle
auth_requiredduringsession/new, run an auth method, retry - create sessions
- expose host adapters (permission prompts, file access, terminal access)
- turn raw
session/updatetraffic into stable message / reasoning / tool / usage events - decide when a turn is actually complete
ACP Kit packages all of the above behind createAcpRuntime({...}).newSession({ cwd }) (or the runOneShotPrompt one-shot helper).
API Overview
RuntimeSession emits normalized RuntimeSessionEvents — stable per-message,
per-tool, and per-turn events with correlation ids (messageId, toolCallId,
turnId). They drive transcripts, UI state, and multi-agent orchestration.
If you need raw protocol traffic (debuggers, protocol bridges), install
@acp-kit/core's composeWireMiddleware / normalizeWireMiddleware to observe
the exact JSON-RPC frames before / after normalization.
import {
createAcpRuntime,
type RuntimeHost,
type RuntimeSessionEvent,
type AgentProfile,
} from '@acp-kit/core';
await using acp = createAcpRuntime({
profile: 'copilot', // built-in id, or a custom AgentProfile object
host: {
requestPermission: async (req) => 'allow_once',
chooseAuthMethod: async ({ methods }) => methods[0]?.id ?? null,
// Optional: file system + terminal capabilities are advertised to the
// agent only when the corresponding host methods are provided.
// readTextFile / writeTextFile take ACP request/response objects from
// @agentclientprotocol/sdk; createTerminal must be paired with
// terminalOutput / waitForTerminalExit / killTerminal / releaseTerminal.
} satisfies RuntimeHost,
});
await using session = await acp.newSession({ cwd: '/path/to/workspace' });
// Subscribe with a handler map (camelCase keys, full type narrowing per handler)
session.on({
messageDelta: (e) => process.stdout.write(e.delta),
toolStart: (e) => console.log(`[tool ${e.toolCallId}] ${e.title ?? e.name}`),
toolEnd: (e) => console.log(`[tool ${e.toolCallId}] ${e.status}`),
turnCompleted: (e) => console.log(`done: ${e.stopReason}`),
});
const result = await session.prompt('Refactor utils.ts'); // Promise<PromptResult>
await session.cancel(); // optional: cancel the in-flight turn
// session and runtime are disposed automatically by `await using`Lifecycle helpers:
acp.shutdown()— explicit teardown if you cannot useawait using.acp.reconnect()— drop the current agent process and reconnect without losing application-level state.session.setMode(modeId)/session.setModel(modelId)— switch agent mode or model mid-session when the agent advertises options.
One-shot helper (spawn agent + run one prompt + auto-dispose):
runOneShotPrompt({ profile, cwd, prompt })— yieldsRuntimeSessionEvents.
The full surface is exported from a single entry point: @acp-kit/core.
Built-in Agent Profiles
| Profile id | Agent |
|---|---|
copilot |
GitHub Copilot CLI in ACP mode |
claude |
Claude ACP |
codex |
Codex ACP |
Custom profiles are plain objects:
const myProfile: AgentProfile = {
id: 'my-agent',
displayName: 'My Agent',
command: 'my-agent-cli',
args: ['--acp'],
env: { /* optional */ },
};
await using acp = createAcpRuntime({ profile: myProfile, host });How It Compares to @agentclientprotocol/sdk
ACP Kit is built on top of @agentclientprotocol/sdk, not as a replacement.
@agentclientprotocol/sdkis the protocol toolkit. It gives youClientSideConnection,ndJsonStream, typed request/response/notification payloads, and JSON-RPC framing — once you already have a connection to an ACP server.- ACP Kit is the client runtime. It launches the agent, manages the connection lifecycle, runs auth, exposes host adapters, normalizes raw protocol updates into stable events, and tracks turn state.
The protocol layer underneath stays exactly @agentclientprotocol/sdk. ACP Kit does not fork it, replace it, or hide it — it depends on it as a regular npm dependency.
┌──────────────────────────────────────────────────────────────────┐
│ Your Product (editor extension, desktop shell, daemon, web …) │
│ - product UI / state │
│ - product persistence and remote sync │
│ - cross-session orchestration │
└───────────────────────────────▲──────────────────────────────────┘
│ normalized events:
│ message.delta / reasoning.delta
│ tool.start / tool.update / tool.end
│ turn.started / turn.completed / turn.failed
│
┌───────────────────────────────┴──────────────────────────────────┐
│ ACP Kit │
│ agent profiles · process spawn · startup diagnostics │
│ auth orchestration · session creation │
│ host adapters: permission, fs, terminal │
│ session/update normalization · transcript reduction │
│ turn lifecycle (start / complete / cancel / fail) │
└───────────────────────────────▲──────────────────────────────────┘
│ uses
│
┌───────────────────────────────┴──────────────────────────────────┐
│ @agentclientprotocol/sdk │
│ ClientSideConnection · ndJsonStream · JSON-RPC framing │
│ initialize · session/new · session/prompt · session/update │
└───────────────────────────────▲──────────────────────────────────┘
│ bytes over a transport
│ (this repo: child-process stdio)
│
┌───────────────────────────────┴──────────────────────────────────┐
│ ACP Server (Copilot CLI --acp, Claude ACP, Codex ACP, …) │
└──────────────────────────────────────────────────────────────────┘For a deeper walkthrough see docs/acp-sdk-vs-runtime.md.
Compatibility
| Dependency | Version |
|---|---|
@agentclientprotocol/sdk |
^0.18 |
| Node.js | >= 20.11 recommended (for await using); >= 18 works if you dispose manually |
| TypeScript (consumers) | >= 5.2 (for using / await using syntax) |
| OS | Windows, macOS, Linux |
ACP Kit aims to track the latest stable @agentclientprotocol/sdk minor release. Breaking changes in the SDK will be matched by a minor or major bump in @acp-kit/core while v0.x is in effect.
Status
ACP Kit is experimental (v0.x). The public API may change between minor versions until v1.0.
Implemented today:
- Built-in agent profiles for Copilot, Claude, Codex; custom profiles via plain objects
- Cross-platform process spawn with startup timeout, stderr capture, and exit diagnostics
- ACP connection bootstrap on top of
@agentclientprotocol/sdk - Auth retry when
session/newreturnsauth_required - Host adapters for permission, file system, and terminal (advertised by capability)
- Normalized
RuntimeSessionEventsurface (message.*,reasoning.*,tool.*,turn.*,status.changed,session.*.updated) with handler-map dispatch viasession.on({ messageDelta, toolStart, ... }) - Multiple sessions per runtime over different
cwds, each withSymbol.asyncDispose(await using) - Idempotent
acp.shutdown()andacp.reconnect();runOneShotPromptone-shot helper - Transcript reducer with pending-stream completion flushing
Not implemented yet:
session/loadresume flows- Higher-level collaboration semantics (delegation, sub-agents)
See docs/migration-plan.md for how downstream products can adopt the runtime incrementally.
Documentation
docs/acp-sdk-vs-runtime.md— the boundary between the official SDK and ACP Kitdocs/architecture.md— runtime layers and design principlesdocs/package-plan.md— why ACP Kit ships as a single package today and when to splitdocs/migration-plan.md— incremental adoption path for existing ACP products
Development
npm install # install workspace deps (packages/core only)
npm run build # tsc -b packages/core
npm test # vitest runTo try an example:
cd examples/mock-runtime
npm install
npm startRepository layout:
packages/core/ @acp-kit/core source, tests, build output
docs/ architecture and design notes
examples/ standalone npm packages that depend on the published @acp-kit/coreContributions are welcome. Please open an issue to discuss non-trivial changes before sending a PR.