or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

agent-connection.mdclient-connection.mderrors.mdindex.mdinterfaces.mdprotocol-types.mdstream.md
tile.json

client-connection.mddocs/

Client-Side Connection

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.

Capabilities

ClientSideConnection Constructor

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 implementation
  • stream: A bidirectional stream created via ndJsonStream() or custom Stream implementation

Usage 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)
);

Initialize

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 capabilities
  • params.clientInfo (Implementation): Client name, version, and optional title

Client Capabilities:

  • fs: File system capabilities (readTextFile, writeTextFile)
  • terminal: Terminal support (boolean)

Returns:

  • protocolVersion (number): Negotiated protocol version
  • agentCapabilities (AgentCapabilities): Agent capabilities
  • agentInfo (Implementation): Agent name and version
  • authMethods (AuthMethod[] | undefined): Available authentication methods

Usage 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
  });
}

New Session

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 to

MCP Server Types:

  • HTTP: { type: "http", name: string, url: string, headers: HttpHeader[] }
  • SSE: { type: "sse", name: string, url: string, headers: HttpHeader[] }
  • Stdio: { name: string, command: string, args: string[], env: EnvVariable[] }

Returns:

  • sessionId (string): Unique session identifier
  • modes (SessionModeState | undefined): Available modes and current mode
  • models (SessionModelState | undefined): UNSTABLE - Available models

Usage 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.

Load Session

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 load
  • params.cwd (string): Working directory (absolute path)
  • params.mcpServers (McpServer[]): MCP servers to connect to

Returns:

  • modes (SessionModeState | undefined): Available modes and current mode
  • models (SessionModelState | undefined): UNSTABLE - Available models
  • The agent will stream the entire conversation history back via session/update notifications

Availability: 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');
  }
}

Set Session Mode

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 ID
  • params.modeId (string): The mode ID to activate

Returns:

  • Empty object {} on success

Availability: 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_update

Notes:

  • The mode must be one of the modes advertised in availableModes during session creation
  • Can be called at any time, even while agent is generating a turn
  • Agents may change modes autonomously and notify via current_mode_update notifications

Set Session Model

UNSTABLE - 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 ID
  • params.modelId (string): The model ID to activate

Returns:

  • Empty object {} on success

Availability: 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'
});

Authenticate

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 InitializeResponse

Returns:

  • Empty object {} on success

Usage 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: []
  });
}

Prompt

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 ID
  • params.prompt (ContentBlock[]): Array of content blocks representing the user's prompt

Content Block Types:

  • Text: { type: "text", text: string }
  • Image: { type: "image", data: string, mimeType: string, uri?: string }
  • Audio: { type: "audio", data: string, mimeType: string }
  • Resource link: { type: "resource_link", uri: string, name: string }
  • Resource: { 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:

  1. Client sends prompt request
  2. Agent processes prompt using language models
  3. Agent sends session/update notifications with progress (message chunks, tool calls, plans)
  4. Agent requests permission via session/request_permission for sensitive operations
  5. Agent returns final response with stop reason

Cancel

Cancels 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 cancel

Usage 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:

  • This is a notification (fire-and-forget)
  • The client SHOULD continue accepting session/update notifications after sending cancel
  • The agent SHOULD respond to the pending prompt request with StopReason::Cancelled

Extension Method

Sends 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 parameters

Returns:

  • Arbitrary response object

Usage Example:

// Send custom request
const result = await connection.extMethod('myapp.getStatus', {
  includeDetails: true
});

console.log('Custom result:', result);

Extension Notification

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 parameters

Usage Example:

// Send custom notification
await connection.extNotification('myapp.userAction', {
  action: 'click',
  target: 'button'
});

Connection Lifecycle Properties

AbortSignal

/**
 * AbortSignal that aborts when the connection closes
 */
get signal(): AbortSignal

Can 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');
}

Closed Promise

/**
 * Promise that resolves when the connection closes
 */
get closed(): Promise<void>

Usage Example:

await connection.closed;
console.log('Connection closed');

Complete Example

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;

Error Handling

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);
    }
  }
}

Notes

  • All methods are async and return Promises
  • The connection automatically handles JSON-RPC message routing and validation
  • Session updates are received via the client's sessionUpdate method
  • Permission requests must be answered promptly
  • Extension methods/notifications are prefixed with underscore in the protocol
  • Use namespaced method names for extensions (e.g., domain.method)