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