The ClientSideConnection class provides the client's view of an ACP connection, allowing clients (such as code editors) to communicate with agents. It implements the Agent interface, providing type-safe methods for all agent operations including initialization, session management, and prompt processing.
Creates a new client-side connection to an agent.
/**
* Creates a new client-side connection to an agent
* @param toClient - Factory function that creates a Client handler
* @param stream - Bidirectional message stream for communication
*/
constructor(
toClient: (agent: Agent) => Client,
stream: Stream
)Parameters:
toClient: A factory function that receives an Agent interface (the connection itself) and returns a Client implementationstream: A bidirectional stream created via ndJsonStream() or custom Stream implementationUsage Example:
import { ClientSideConnection, Client, ndJsonStream } from '@agentclientprotocol/sdk';
const client: Client = {
// ... client implementation
};
const connection = new ClientSideConnection(
(agent) => client, // Factory receives agent interface for calling agent methods
ndJsonStream(outputStream, inputStream)
);Establishes the connection with an agent and negotiates protocol capabilities.
/**
* Establishes connection and negotiates capabilities
* @param params - Initialization parameters
* @returns Agent capabilities and info
*/
async initialize(
params: InitializeRequest
): Promise<InitializeResponse>Parameters:
params.protocolVersion (number): Protocol version (currently 1)params.clientCapabilities (ClientCapabilities): Client capabilitiesparams.clientInfo (Implementation): Client name, version, and optional titleClient Capabilities:
fs: File system capabilities (readTextFile, writeTextFile)terminal: Terminal support (boolean)Returns:
protocolVersion (number): Negotiated protocol versionagentCapabilities (AgentCapabilities): Agent capabilitiesagentInfo (Implementation): Agent name and versionauthMethods (AuthMethod[] | undefined): Available authentication methodsUsage Example:
const response = await connection.initialize({
protocolVersion: 1,
clientCapabilities: {
fs: {
readTextFile: true,
writeTextFile: true
},
terminal: true
},
clientInfo: {
name: 'my-editor',
version: '1.0.0',
title: 'My Code Editor'
}
});
console.log('Agent:', response.agentInfo.name);
console.log('Supports MCP HTTP:', response.agentCapabilities?.mcpCapabilities?.http);
console.log('Supports image prompts:', response.agentCapabilities?.promptCapabilities?.image);
if (response.authMethods && response.authMethods.length > 0) {
// Authentication is required
await connection.authenticate({
methodId: response.authMethods[0].id
});
}Creates a new conversation session with the agent.
/**
* Creates a new conversation session
* @param params - Session creation parameters
* @returns Session ID and available modes
*/
async newSession(
params: NewSessionRequest
): Promise<NewSessionResponse>Parameters:
params.cwd (string): Working directory (absolute path)params.mcpServers (McpServer[]): MCP servers to connect toMCP Server Types:
{ type: "http", name: string, url: string, headers: HttpHeader[] }{ type: "sse", name: string, url: string, headers: HttpHeader[] }{ name: string, command: string, args: string[], env: EnvVariable[] }Returns:
sessionId (string): Unique session identifiermodes (SessionModeState | undefined): Available modes and current modemodels (SessionModelState | undefined): UNSTABLE - Available modelsUsage Example:
const session = await connection.newSession({
cwd: '/path/to/project',
mcpServers: [{
name: 'my-mcp-server',
command: 'mcp-server',
args: ['--port', '8080'],
env: [{
name: 'API_KEY',
value: process.env.API_KEY
}]
}]
});
console.log('Session ID:', session.sessionId);
console.log('Current mode:', session.modes?.currentModeId);
console.log('Available modes:', session.modes?.availableModes.map(m => m.name));
// Store session ID for future requests
const sessionId = session.sessionId;Error Handling:
May return an auth_required error (-32000) if the agent requires authentication before allowing session creation.
Loads an existing session to resume a previous conversation.
/**
* Loads an existing session
* @param params - Session loading parameters
* @returns Session info and state
*/
async loadSession(
params: LoadSessionRequest
): Promise<LoadSessionResponse>Parameters:
params.sessionId (string): The session ID to loadparams.cwd (string): Working directory (absolute path)params.mcpServers (McpServer[]): MCP servers to connect toReturns:
modes (SessionModeState | undefined): Available modes and current modemodels (SessionModelState | undefined): UNSTABLE - Available modelssession/update notificationsAvailability: Only available if agent advertises the loadSession capability.
Usage Example:
try {
const session = await connection.loadSession({
sessionId: 'previous-session-123',
cwd: '/path/to/project',
mcpServers: []
});
console.log('Session loaded with mode:', session.modes?.currentModeId);
// Client will receive session/update notifications with conversation history
} catch (error) {
if (error.code === -32601) {
console.error('Agent does not support loading sessions');
}
}Sets the operational mode for a session.
/**
* Sets the operational mode for a session
* @param params - Mode change parameters
* @returns Empty response on success
*/
async setSessionMode(
params: SetSessionModeRequest
): Promise<SetSessionModeResponse>Parameters:
params.sessionId (string): The session IDparams.modeId (string): The mode ID to activateReturns:
{} on successAvailability: Optional method that agents may implement.
Usage Example:
// Switch from 'code' mode to 'ask' mode
await connection.setSessionMode({
sessionId: 'session-123',
modeId: 'ask'
});
// Agent may also change modes autonomously and notify via current_mode_updateNotes:
availableModes during session creationcurrent_mode_update notificationsUNSTABLE - Selects a model for a given session.
/**
* UNSTABLE - Selects a model for the session
* @param params - Model selection parameters
* @returns Empty response on success
*/
async setSessionModel(
params: SetSessionModelRequest
): Promise<SetSessionModelResponse>Parameters:
params.sessionId (string): The session IDparams.modelId (string): The model ID to activateReturns:
{} on successAvailability: This capability is not part of the spec yet and may be removed or changed at any point.
Usage Example:
await connection.setSessionModel({
sessionId: 'session-123',
modelId: 'gpt-4'
});Authenticates the client using the specified authentication method.
/**
* Authenticates the client
* @param params - Authentication parameters
* @returns Empty response on success
*/
async authenticate(
params: AuthenticateRequest
): Promise<AuthenticateResponse>Parameters:
params.methodId (string): Authentication method ID from authMethods in InitializeResponseReturns:
{} on successUsage Example:
// After initialize, if authMethods are provided
if (initResponse.authMethods && initResponse.authMethods.length > 0) {
const authMethod = initResponse.authMethods[0];
await connection.authenticate({
methodId: authMethod.id
});
// Now can create sessions
const session = await connection.newSession({
cwd: '/path/to/project',
mcpServers: []
});
}Processes a user prompt within a session.
/**
* Processes a user prompt
* @param params - Prompt parameters
* @returns Prompt completion info
*/
async prompt(
params: PromptRequest
): Promise<PromptResponse>Parameters:
params.sessionId (string): The session IDparams.prompt (ContentBlock[]): Array of content blocks representing the user's promptContent Block Types:
{ type: "text", text: string }{ type: "image", data: string, mimeType: string, uri?: string }{ type: "audio", data: string, mimeType: string }{ type: "resource_link", uri: string, name: string }{ type: "resource", resource: { uri: string, mimeType?: string, text?: string, blob?: string } }Returns:
stopReason (string): Why the turn ended - one of "end_turn", "max_tokens", "max_turn_requests", "refusal", "cancelled"Usage Example:
// Send a simple text prompt
const response = await connection.prompt({
sessionId: 'session-123',
prompt: [{
type: 'text',
text: 'Write a function to calculate fibonacci numbers'
}]
});
console.log('Stop reason:', response.stopReason);
// Send prompt with image
const imageResponse = await connection.prompt({
sessionId: 'session-123',
prompt: [{
type: 'text',
text: 'What is in this image?'
}, {
type: 'image',
data: base64ImageData,
mimeType: 'image/png'
}]
});Lifecycle:
prompt requestsession/update notifications with progress (message chunks, tool calls, plans)session/request_permission for sensitive operationsCancels ongoing operations for a session.
/**
* Cancels ongoing operations
* @param params - Cancellation parameters
*/
async cancel(
params: CancelNotification
): Promise<void>Parameters:
params.sessionId (string): The session ID to cancelUsage Example:
// User clicks cancel button
await connection.cancel({
sessionId: 'session-123'
});
// Agent should:
// - Stop language model requests
// - Abort tool call invocations
// - Send final session/update notifications
// - Respond to prompt request with stopReason: "cancelled"Notes:
session/update notifications after sending cancelprompt request with StopReason::CancelledSends an arbitrary request that is not part of the ACP spec.
/**
* Extension method for custom requests
* @param method - Custom method name (will be prefixed with underscore)
* @param params - Method parameters
* @returns Method response
*/
async extMethod(
method: string,
params: Record<string, unknown>
): Promise<Record<string, unknown>>Parameters:
method (string): Custom method name (automatically prefixed with _ in protocol)params (object): Arbitrary method parametersReturns:
Usage Example:
// Send custom request
const result = await connection.extMethod('myapp.getStatus', {
includeDetails: true
});
console.log('Custom result:', result);Sends an arbitrary notification that is not part of the ACP spec.
/**
* Extension notification for custom events
* @param method - Custom notification name (will be prefixed with underscore)
* @param params - Notification parameters
*/
async extNotification(
method: string,
params: Record<string, unknown>
): Promise<void>Parameters:
method (string): Custom notification name (automatically prefixed with _ in protocol)params (object): Arbitrary notification parametersUsage Example:
// Send custom notification
await connection.extNotification('myapp.userAction', {
action: 'click',
target: 'button'
});/**
* AbortSignal that aborts when the connection closes
*/
get signal(): AbortSignalCan be used to listen for connection closure, check status, or pass to other APIs for automatic cancellation.
Usage Example:
connection.signal.addEventListener('abort', () => {
console.log('Connection closed');
cleanup();
});
if (connection.signal.aborted) {
console.log('Already closed');
}/**
* Promise that resolves when the connection closes
*/
get closed(): Promise<void>Usage Example:
await connection.closed;
console.log('Connection closed');import { ClientSideConnection, Client, ndJsonStream } from '@agentclientprotocol/sdk';
const client: Client = {
async requestPermission(params) {
// Show dialog to user
const approved = await showPermissionDialog(
params.title,
params.description,
params.options
);
return {
outcome: approved ? 'granted' : 'denied',
selectedOption: approved ? params.options[0].id : undefined
};
},
async sessionUpdate(params) {
switch (params.update.sessionUpdate) {
case 'agent_message_chunk':
// Display streaming content
displayContent(params.update.content);
break;
case 'tool_call':
// Show tool execution
displayToolCall(params.update);
break;
case 'plan':
// Show execution plan
displayPlan(params.update.entries);
break;
case 'current_mode_update':
// Update UI to show current mode
updateModeDisplay(params.update.currentModeId);
break;
}
},
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 {};
},
async createTerminal(params) {
const terminal = spawn(params.command, params.args, {
cwd: params.cwd,
env: Object.fromEntries(
(params.env || []).map(e => [e.name, e.value])
)
});
const terminalId = registerTerminal(terminal);
return { terminalId };
},
// ... other terminal methods
};
// Create connection
const connection = new ClientSideConnection(
(agent) => client,
ndJsonStream(outputStream, inputStream)
);
// Initialize
const initResponse = await connection.initialize({
protocolVersion: 1,
clientCapabilities: {
fs: { readTextFile: true, writeTextFile: true },
terminal: true
},
clientInfo: {
name: 'example-editor',
version: '1.0.0'
}
});
// Create session
const session = await connection.newSession({
cwd: '/path/to/project',
mcpServers: []
});
// Send prompt
const response = await connection.prompt({
sessionId: session.sessionId,
prompt: [{
type: 'text',
text: 'Refactor this function to use async/await'
}]
});
console.log('Prompt completed:', response.stopReason);
// Wait for connection to close
await connection.closed;All methods can throw RequestError for protocol-level errors:
import { RequestError } from '@agentclientprotocol/sdk';
try {
await connection.newSession({
cwd: '/path/to/project',
mcpServers: []
});
} catch (error) {
if (error instanceof RequestError) {
switch (error.code) {
case -32000: // Authentication required
console.error('Please authenticate first');
await connection.authenticate({ methodId: 'some-method' });
break;
case -32601: // Method not found
console.error('Agent does not support this method');
break;
case -32603: // Internal error
console.error('Agent internal error:', error.message);
break;
default:
console.error('Request error:', error.code, error.message);
}
}
}sessionUpdate methoddomain.method)