Skip to main content
The ACP TypeScript SDK follows JSON-RPC 2.0 error conventions, providing structured error handling for all requests and responses.

RequestError Class

The RequestError class represents JSON-RPC errors that can be thrown from request handlers:
export class RequestError extends Error {
  constructor(
    public code: number,
    message: string,
    public data?: unknown,
  )
}

Properties

  • code: Numeric error code (see Error Codes below)
  • message: Human-readable error description
  • data: Optional additional error information
RequestError extends the standard JavaScript Error class, so it can be caught and thrown like any other error.

Error Codes

The SDK provides static factory methods for standard JSON-RPC error codes:

Parse Error (-32700)

Invalid JSON was received:
throw RequestError.parseError(
  { received: rawInput },
  'Malformed JSON in request'
);
Signature:
static parseError(
  data?: unknown,
  additionalMessage?: string
): RequestError

Invalid Request (-32600)

The JSON is valid but not a proper request object:
throw RequestError.invalidRequest(
  { missing: 'method' },
  'Request missing required method field'
);
Signature:
static invalidRequest(
  data?: unknown,
  additionalMessage?: string
): RequestError

Method Not Found (-32601)

The requested method doesn’t exist or isn’t available:
throw RequestError.methodNotFound('session/invalid');
Signature:
static methodNotFound(method: string): RequestError
Always throw methodNotFound for unknown methods in your extMethod handlers to maintain protocol compliance.

Invalid Params (-32602)

The method parameters are invalid:
throw RequestError.invalidParams(
  { field: 'sessionId', issue: 'required' },
  'Missing required sessionId parameter'
);
Signature:
static invalidParams(
  data?: unknown,
  additionalMessage?: string
): RequestError

Internal Error (-32603)

An internal server error occurred:
try {
  await processRequest();
} catch (err) {
  throw RequestError.internalError(
    { details: err.message },
    'Failed to process request'
  );
}
Signature:
static internalError(
  data?: unknown,
  additionalMessage?: string
): RequestError

Custom ACP Errors

The SDK also includes ACP-specific error codes:

Authentication Required (-32000)

if (!this.isAuthenticated) {
  throw RequestError.authRequired(
    { methods: ['oauth', 'api_key'] },
    'Please authenticate before creating a session'
  );
}
Signature:
static authRequired(
  data?: unknown,
  additionalMessage?: string
): RequestError

Resource Not Found (-32002)

const file = await this.findFile(uri);
if (!file) {
  throw RequestError.resourceNotFound(uri);
}
Signature:
static resourceNotFound(uri?: string): RequestError

Throwing Errors

Throw RequestError from any request handler to send a structured error response:
import * as acp from '@agentclientprotocol/acp';

class MyAgent implements acp.Agent {
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // Check if session exists
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw RequestError.invalidParams(
        { sessionId: params.sessionId },
        'Session not found'
      );
    }
    
    // Check if user has permission
    if (!session.isAuthorized) {
      throw RequestError.authRequired(
        { sessionId: params.sessionId },
        'Session requires authentication'
      );
    }
    
    // Process the prompt...
    return { stopReason: 'end_turn' };
  }
  
  async extMethod(
    method: string,
    params: Record<string, unknown>,
  ): Promise<Record<string, unknown>> {
    switch (method) {
      case 'example.com/my_method':
        return { result: 'ok' };
      
      default:
        // Always throw methodNotFound for unknown methods
        throw RequestError.methodNotFound(method);
    }
  }
}

Catching Errors

When calling methods on the connection, catch errors to handle failures:
try {
  const response = await connection.prompt({
    sessionId: 'abc123',
    prompt: [{ type: 'text', text: 'Hello' }],
  });
  
  console.log('Success:', response);
} catch (error) {
  // error is the ErrorResponse object from JSON-RPC
  if (typeof error === 'object' && error !== null && 'code' in error) {
    const jsonRpcError = error as acp.ErrorResponse;
    
    switch (jsonRpcError.code) {
      case -32602: // Invalid params
        console.error('Invalid parameters:', jsonRpcError.data);
        break;
      
      case -32000: // Auth required
        console.error('Authentication needed');
        await authenticate();
        break;
      
      default:
        console.error('Error:', jsonRpcError.message);
    }
  }
}
When a request fails, the promise is rejected with the JSON-RPC ErrorResponse object, not a RequestError instance.

Error Response Type

The error response follows the JSON-RPC 2.0 specification:
export type ErrorResponse = {
  code: number;
  message: string;
  data?: unknown;
};

Automatic Error Handling

The SDK automatically handles certain errors:

Zod Validation Errors

If a request handler receives invalid parameters, the SDK catches Zod validation errors and converts them to invalidParams errors:
// This is handled automatically by the SDK
const validatedParams = validate.zPromptRequest.parse(params);
// If validation fails, SDK throws RequestError.invalidParams(error.format())

Unexpected Errors

If an unexpected error is thrown from a request handler, the SDK converts it to an internalError:
class MyAgent implements acp.Agent {
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // If this throws an unexpected error:
    const result = await someUnreliableOperation();
    
    // The SDK automatically catches it and returns:
    // RequestError.internalError({ details: error.message })
  }
}

Best Practices

Do:
  • Use the appropriate error code for each error type
  • Include helpful error messages
  • Add structured data for debugging
  • Throw methodNotFound for unknown extension methods
Don’t:
  • Throw generic Error objects (use RequestError instead)
  • Include sensitive data in error messages
  • Swallow errors without handling them
  • Use error codes outside the JSON-RPC specification

Custom Error Codes

For application-specific errors, use custom error codes in the range -32000 to -32099 (reserved for implementation-defined errors):
class CustomErrors {
  static rateLimitExceeded(): RequestError {
    return new RequestError(
      -32001,
      'Rate limit exceeded',
      {
        retryAfter: 60,
        limit: 100,
      }
    );
  }
  
  static quotaExceeded(): RequestError {
    return new RequestError(
      -32003,
      'Quota exceeded',
      {
        quotaType: 'tokens',
        limit: 1000000,
        used: 1000000,
      }
    );
  }
}

// Use in your handlers
class MyAgent implements acp.Agent {
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    if (this.isRateLimited(params.sessionId)) {
      throw CustomErrors.rateLimitExceeded();
    }
    
    // Process prompt...
  }
}

Error Handling Example

Complete example showing comprehensive error handling:
import * as acp from '@agentclientprotocol/acp';

class RobustAgent implements acp.Agent {
  private sessions = new Map<string, Session>();
  
  async initialize(params: acp.InitializeRequest): Promise<acp.InitializeResponse> {
    return {
      protocolVersion: acp.PROTOCOL_VERSION,
      agentCapabilities: { loadSession: false },
    };
  }
  
  async newSession(params: acp.NewSessionRequest): Promise<acp.NewSessionResponse> {
    // Validate working directory exists
    if (!await this.directoryExists(params.cwd)) {
      throw RequestError.invalidParams(
        { cwd: params.cwd },
        'Working directory does not exist'
      );
    }
    
    const sessionId = crypto.randomUUID();
    this.sessions.set(sessionId, new Session(params));
    
    return { sessionId };
  }
  
  async authenticate(params: acp.AuthenticateRequest): Promise<void> {
    // Check if authentication method is supported
    const supportedMethods = ['api_key', 'oauth'];
    if (!supportedMethods.includes(params.authMethod.id)) {
      throw RequestError.invalidParams(
        { supportedMethods },
        `Unsupported authentication method: ${params.authMethod.id}`
      );
    }
    
    // Validate credentials
    try {
      await this.validateCredentials(params.authMethod);
    } catch (err) {
      throw RequestError.authRequired(
        { reason: 'invalid_credentials' },
        'Invalid or expired credentials'
      );
    }
  }
  
  async prompt(params: acp.PromptRequest): Promise<acp.PromptResponse> {
    // Validate session
    const session = this.sessions.get(params.sessionId);
    if (!session) {
      throw RequestError.invalidParams(
        { sessionId: params.sessionId },
        'Session not found or expired'
      );
    }
    
    try {
      // Process the prompt
      await session.processPrompt(params.prompt);
      return { stopReason: 'end_turn' };
    } catch (err) {
      // Convert known errors to RequestError
      if (err instanceof QuotaExceededError) {
        throw RequestError.internalError(
          { quotaType: err.quotaType },
          'Quota exceeded'
        );
      }
      
      // Re-throw RequestErrors as-is
      if (err instanceof RequestError) {
        throw err;
      }
      
      // Wrap unexpected errors
      throw RequestError.internalError(
        { error: err.message },
        'Failed to process prompt'
      );
    }
  }
  
  async cancel(params: acp.CancelNotification): Promise<void> {
    const session = this.sessions.get(params.sessionId);
    if (session) {
      session.cancel();
    }
    // Don't throw errors in notification handlers
  }
}

Build docs developers (and LLMs) love