TypeScript implementation of the Agent Client Protocol (ACP) for standardized communication between code editors and AI-powered coding agents.
npx @tessl/cli install tessl/npm-agentclientprotocol--sdk@0.5.0The Agent Client Protocol (ACP) TypeScript SDK provides a standardized communication protocol between code editors (clients) and AI-powered coding agents. It enables bidirectional JSON-RPC 2.0-based communication over streams with full TypeScript type safety and runtime validation via Zod.
npm install @agentclientprotocol/sdkimport {
AgentSideConnection,
ClientSideConnection,
Agent,
Client,
ndJsonStream,
RequestError,
TerminalHandle
} from '@agentclientprotocol/sdk';For protocol types and interfaces:
import type {
InitializeRequest,
InitializeResponse,
NewSessionRequest,
PromptRequest,
SessionNotification,
// ... and many more
} from '@agentclientprotocol/sdk';import { AgentSideConnection, Agent, ndJsonStream } from '@agentclientprotocol/sdk';
const agent: Agent = {
async initialize(params) {
return {
protocolVersion: 1,
agentCapabilities: {
/* agent capabilities */
},
agentInfo: {
name: 'my-agent',
version: '1.0.0'
}
};
},
async newSession(params) {
const sessionId = generateSessionId();
return {
sessionId,
modes: {
availableModes: [{
id: 'code',
name: 'Code Mode',
description: 'AI-powered code editing'
}],
currentModeId: 'code'
}
};
},
async authenticate(params) {
// Handle authentication if required
return {};
},
async prompt(params) {
// Process user prompt and return response
// Send updates via connection.sessionUpdate()
return {
stopReason: 'end_turn'
};
},
async cancel(params) {
// Cancel ongoing operations
}
};
// Create connection with stdio streams
const connection = new AgentSideConnection(
() => agent,
ndJsonStream(
new WritableStream({ write: (chunk) => process.stdout.write(chunk) }),
readableStreamFromNodeStream(process.stdin)
)
);
// Wait for connection to close
await connection.closed;import { ClientSideConnection, Client, ndJsonStream } from '@agentclientprotocol/sdk';
const client: Client = {
async requestPermission(params) {
// Present permission request to user
const approved = await askUser(params);
return {
outcome: approved
? { outcome: 'selected', optionId: params.options[0].optionId }
: { outcome: 'cancelled' }
};
},
async sessionUpdate(params) {
// Handle session updates (message chunks, tool calls, etc.)
if (params.update.sessionUpdate === 'agent_message_chunk') {
displayMessageChunk(params.update.content);
} else if (params.update.sessionUpdate === 'tool_call') {
displayToolCall(params.update);
}
},
// Optional capabilities
async readTextFile(params) {
const content = await fs.readFile(params.path, 'utf-8');
return { content };
},
async writeTextFile(params) {
await fs.writeFile(params.path, params.content);
return {};
}
};
const connection = new ClientSideConnection(
() => client,
ndJsonStream(output, input)
);
// Initialize the agent
const initResponse = await connection.initialize({
protocolVersion: 1,
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } },
clientInfo: { name: 'my-editor', version: '1.0.0' }
});
// Create a session
const session = await connection.newSession({
cwd: '/path/to/project',
mcpServers: []
});
// Send a prompt
const response = await connection.prompt({
sessionId: session.sessionId,
prompt: [{ type: 'text', text: 'Hello, agent!' }]
});The ACP SDK is built around several key components:
The AgentSideConnection class provides methods for agents to interact with clients.
class AgentSideConnection {
constructor(
toAgent: (conn: AgentSideConnection) => Agent,
stream: Stream
);
async sessionUpdate(params: SessionNotification): Promise<void>;
async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse>;
async readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
async writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
async createTerminal(params: CreateTerminalRequest): Promise<TerminalHandle>;
async extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
async extNotification(method: string, params: Record<string, unknown>): Promise<void>;
get signal(): AbortSignal;
get closed(): Promise<void>;
}The ClientSideConnection class provides methods for clients to interact with agents.
class ClientSideConnection implements Agent {
constructor(
toClient: (agent: Agent) => Client,
stream: Stream
);
async initialize(params: InitializeRequest): Promise<InitializeResponse>;
async newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
async setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse>;
async authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse>;
async prompt(params: PromptRequest): Promise<PromptResponse>;
async cancel(params: CancelNotification): Promise<void>;
async extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
async extNotification(method: string, params: Record<string, unknown>): Promise<void>;
get signal(): AbortSignal;
get closed(): Promise<void>;
}The SDK defines two core interfaces that must be implemented:
interface Agent {
initialize(params: InitializeRequest): Promise<InitializeResponse>;
newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse | void>;
prompt(params: PromptRequest): Promise<PromptResponse>;
cancel(params: CancelNotification): Promise<void>;
// Optional methods
loadSession?(params: LoadSessionRequest): Promise<LoadSessionResponse>;
setSessionMode?(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void>;
setSessionModel?(params: SetSessionModelRequest): Promise<SetSessionModelResponse | void>;
extMethod?(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
extNotification?(method: string, params: Record<string, unknown>): Promise<void>;
}
interface Client {
requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse>;
sessionUpdate(params: SessionNotification): Promise<void>;
// Optional capabilities
readTextFile?(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
writeTextFile?(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
createTerminal?(params: CreateTerminalRequest): Promise<CreateTerminalResponse>;
terminalOutput?(params: TerminalOutputRequest): Promise<TerminalOutputResponse>;
releaseTerminal?(params: ReleaseTerminalRequest): Promise<ReleaseTerminalResponse | void>;
waitForTerminalExit?(params: WaitForTerminalExitRequest): Promise<WaitForTerminalExitResponse>;
killTerminal?(params: KillTerminalCommandRequest): Promise<KillTerminalResponse | void>;
extMethod?(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
extNotification?(method: string, params: Record<string, unknown>): Promise<void>;
}Complete type definitions for all protocol messages including requests, responses, and notifications.
// Initialization
interface InitializeRequest {
protocolVersion: number;
clientCapabilities?: ClientCapabilities;
clientInfo?: Implementation;
_meta?: Record<string, unknown>;
}
interface InitializeResponse {
protocolVersion: number;
agentCapabilities?: AgentCapabilities;
agentInfo?: Implementation;
authMethods?: AuthMethod[];
_meta?: Record<string, unknown>;
}
// Sessions
interface NewSessionRequest {
cwd: string;
mcpServers: McpServer[];
_meta?: Record<string, unknown>;
}
interface NewSessionResponse {
sessionId: string;
modes?: SessionModeState;
models?: SessionModelState;
_meta?: Record<string, unknown>;
}
// Prompts
interface PromptRequest {
sessionId: string;
prompt: ContentBlock[];
_meta?: Record<string, unknown>;
}
interface PromptResponse {
stopReason: "end_turn" | "max_tokens" | "max_turn_requests" | "refusal" | "cancelled";
_meta?: Record<string, unknown>;
}Utilities for creating and managing bidirectional message streams.
type Stream = {
writable: WritableStream<AnyMessage>;
readable: ReadableStream<AnyMessage>;
};
function ndJsonStream(
output: WritableStream<Uint8Array>,
input: ReadableStream<Uint8Array>
): Stream;JSON-RPC error handling with standard error codes.
class RequestError extends Error {
constructor(code: number, message: string, data?: unknown);
code: number;
data?: unknown;
static parseError(data?: unknown, additionalMessage?: string): RequestError;
static invalidRequest(data?: unknown, additionalMessage?: string): RequestError;
static methodNotFound(method: string): RequestError;
static invalidParams(data?: unknown, additionalMessage?: string): RequestError;
static internalError(data?: unknown, additionalMessage?: string): RequestError;
static authRequired(data?: unknown, additionalMessage?: string): RequestError;
static resourceNotFound(uri?: string): RequestError;
toResult<T>(): Result<T>;
toErrorResponse(): ErrorResponse;
}Create and manage terminal instances for command execution.
class TerminalHandle {
constructor(id: string, sessionId: string, conn: Connection);
id: string;
async currentOutput(): Promise<TerminalOutputResponse>;
async waitForExit(): Promise<WaitForTerminalExitResponse>;
async kill(): Promise<KillTerminalResponse>;
async release(): Promise<ReleaseTerminalResponse | void>;
async [Symbol.asyncDispose](): Promise<void>;
}Terminal management is accessed via AgentSideConnection.createTerminal() which returns a TerminalHandle. Always call release() when done with a terminal to free resources, or use await using for automatic cleanup.
const PROTOCOL_VERSION: number = 1;
const AGENT_METHODS: {
readonly authenticate: "authenticate";
readonly initialize: "initialize";
readonly session_cancel: "session/cancel";
readonly session_load: "session/load";
readonly session_new: "session/new";
readonly session_prompt: "session/prompt";
readonly session_set_mode: "session/set_mode";
readonly session_set_model: "session/set_model";
};
const CLIENT_METHODS: {
readonly fs_read_text_file: "fs/read_text_file";
readonly fs_write_text_file: "fs/write_text_file";
readonly session_request_permission: "session/request_permission";
readonly session_update: "session/update";
readonly terminal_create: "terminal/create";
readonly terminal_kill: "terminal/kill";
readonly terminal_output: "terminal/output";
readonly terminal_release: "terminal/release";
readonly terminal_wait_for_exit: "terminal/wait_for_exit";
};These constants define the method names used in the JSON-RPC protocol.
Both agents and clients can extend the protocol with custom methods and notifications:
// Agent sending extension method to client
await connection.extMethod('custom.action', { data: 'value' });
// Agent sending extension notification to client
await connection.extNotification('custom.event', { info: 'something happened' });Extension methods are automatically prefixed with underscore (_) in the protocol. To avoid conflicts, use namespaced method names like domain.method.
Both connection types provide lifecycle management:
// AbortSignal that aborts when connection closes
connection.signal.addEventListener('abort', () => {
console.log('Connection closed');
});
// Check if connection is closed
if (connection.signal.aborted) {
console.log('Connection is already closed');
}
// Wait for connection to close
await connection.closed;
console.log('Connection has closed');
// Use signal with other APIs
fetch(url, { signal: connection.signal });All protocol messages are validated at runtime using Zod schemas. Validation errors are automatically converted to JSON-RPC error responses:
InvalidParams error (-32602)MethodNotFound error (-32601)ParseError error (-32700)The package exports extensive TypeScript types for all protocol messages, organized into:
InitializeRequest, PromptRequest)InitializeResponse, PromptResponse)SessionNotification, CancelNotification)AgentCapabilities, ClientCapabilities)ContentBlock, ToolCallContent)Role, ToolKind, ToolCallStatus)See Protocol Types for the complete type reference.
setSessionModel) are marked UNSTABLE and may change