Skip to main content

Overview

The ACP Dart SDK provides comprehensive error handling through the RequestError class and automatic error mapping between Dart exceptions and JSON-RPC error codes.

RequestError

The RequestError class represents errors that occur during ACP communication. It implements the Exception interface and maps to JSON-RPC 2.0 error codes.
class RequestError implements Exception {
  final int code;
  final String message;
  final dynamic data;

  RequestError(this.code, this.message, [this.data]);
}
code
int
required
The JSON-RPC 2.0 error code
message
String
required
A human-readable error message
data
dynamic
Optional additional error data providing context

Standard Error Factories

The SDK provides factory methods for all standard JSON-RPC errors:

Protocol Errors

RequestError.parseError([data])
RequestError
Code: -32700
Invalid JSON was received. An error occurred while parsing the JSON text.
RequestError.invalidRequest([data])
RequestError
Code: -32600
The JSON sent is not a valid request object.
RequestError.methodNotFound(String method)
RequestError
Code: -32601
The method does not exist or is not available.
RequestError.invalidParams([data])
RequestError
Code: -32602
Invalid method parameters. Used for validation failures.
RequestError.internalError([data])
RequestError
Code: -32603
Internal JSON-RPC error. Used for unexpected runtime failures.

Application Errors

RequestError.authRequired([data])
RequestError
Code: -32000
Authentication is required before this operation can be performed.
RequestError.resourceNotFound([String? uri])
RequestError
Code: -32002
The requested resource (such as a file) was not found.
RequestError.requestCancelled([data])
RequestError
Code: -32800
The request was cancelled by the client or due to $/cancel_request.

Error Code Reference

CodeNameDescriptionWhen to Use
-32700Parse errorInvalid JSONMalformed JSON in message
-32600Invalid RequestInvalid request objectMissing required fields
-32601Method not foundUnknown methodUnsupported method name
-32602Invalid paramsInvalid parametersType/validation errors
-32603Internal errorInternal errorUnexpected exceptions
-32000Auth requiredAuthentication neededUser must authenticate
-32002Resource not foundResource missingFile/session not found
-32800CancelledRequest cancelledClient cancellation

Automatic Error Mapping

The SDK automatically maps Dart exceptions to appropriate JSON-RPC errors:
// In Connection._mapRequestError()
if (error is RequestError) {
  return error; // Already a RequestError
}

if (error is TypeError || 
    error is FormatException || 
    error is ArgumentError) {
  return RequestError.invalidParams(errorData);
}

return RequestError.internalError();
Parameter validation failures (TypeError, FormatException, ArgumentError) automatically become -32602 Invalid params errors.Unexpected runtime failures automatically become -32603 Internal error errors without exposing internal details.

Usage Examples

Throwing Errors in Handlers

import 'package:acp_dart/acp_dart.dart';

class MyAgent implements Agent {
  @override
  Future<PromptResponse> prompt(PromptRequest params) async {
    // Validate session exists
    if (!sessions.containsKey(params.sessionId)) {
      throw RequestError.resourceNotFound(params.sessionId);
    }
    
    // Check authentication
    if (!isAuthenticated) {
      throw RequestError.authRequired({'sessionId': params.sessionId});
    }
    
    // Validate prompt
    if (params.message.isEmpty) {
      throw RequestError.invalidParams({
        'field': 'message',
        'error': 'Message cannot be empty',
      });
    }
    
    // Process prompt
    return await processPrompt(params);
  }
}

Catching Errors

import 'package:acp_dart/acp_dart.dart';

try {
  final response = await connection.sendRequest(
    'session/prompt',
    promptRequest.toJson(),
  );
} on RequestError catch (e) {
  if (e.code == RequestError.requestCancelledCode) {
    print('Request was cancelled');
  } else if (e.code == -32000) {
    print('Authentication required');
  } else {
    print('Error ${e.code}: ${e.message}');
  }
} catch (e) {
  print('Unexpected error: $e');
}

Custom Error Data

import 'package:acp_dart/acp_dart.dart';

class MyClient implements Client {
  @override
  Future<ReadTextFileResponse> readTextFile(ReadTextFileRequest params) async {
    final file = File(params.path);
    
    if (!file.existsSync()) {
      throw RequestError.resourceNotFound(params.path);
    }
    
    if (!await hasPermission(params.path)) {
      throw RequestError(-32003, 'Permission denied', {
        'path': params.path,
        'reason': 'File is outside allowed directory',
      });
    }
    
    final content = await file.readAsString();
    return ReadTextFileResponse(content: content);
  }
}

Validation Errors

import 'package:acp_dart/acp_dart.dart';

Future<NewSessionResponse> newSession(NewSessionRequest params) async {
  // Dart will throw TypeError/ArgumentError for invalid types
  // These automatically become -32602 Invalid params
  
  // Manual validation
  if (params.mcpServers.length > 10) {
    throw RequestError.invalidParams({
      'field': 'mcpServers',
      'error': 'Too many MCP servers (max 10)',
      'value': params.mcpServers.length,
    });
  }
  
  return NewSessionResponse(
    sessionId: createSession(params),
    availableModes: [],
  );
}

Error Response Structure

Errors are serialized to JSON-RPC error responses:
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {
      "field": "message",
      "error": "Message cannot be empty"
    }
  }
}

ErrorResponse Class

class ErrorResponse {
  final int code;
  final String message;
  final dynamic data;

  Map<String, dynamic> toJson() => {
    'code': code,
    'message': message,
    if (data != null) 'data': data,
  };
}

Converting Errors

import 'package:acp_dart/acp_dart.dart';

final error = RequestError.invalidParams({'field': 'path'});

// Convert to error response
final errorResponse = error.toErrorResponse();

// Convert to JSON-RPC result (error variant)
final result = error.toResult();
// {'error': {'code': -32602, 'message': 'Invalid params', 'data': {...}}}

Best Practices

Use Specific Errors

Use the appropriate factory method for each error type rather than creating generic errors

Include Context

Provide helpful error data to aid debugging (field names, invalid values, etc.)

Never Expose Internals

Use internalError() for unexpected exceptions to avoid exposing implementation details

Validate Early

Validate parameters early and throw invalidParams before processing begins

Error Handling Patterns

Retry Logic

import 'package:acp_dart/acp_dart.dart';

Future<T> withRetry<T>(Future<T> Function() fn) async {
  for (var attempt = 0; attempt < 3; attempt++) {
    try {
      return await fn();
    } on RequestError catch (e) {
      if (e.code == -32603 && attempt < 2) {
        await Future.delayed(Duration(seconds: 1 << attempt));
        continue;
      }
      rethrow;
    }
  }
  throw RequestError.internalError('All retry attempts failed');
}

Error Logging

import 'dart:io';
import 'package:acp_dart/acp_dart.dart';

void logError(RequestError error, String context) {
  stderr.writeln('[$context] Error ${error.code}: ${error.message}');
  if (error.data != null) {
    stderr.writeln('  Data: ${error.data}');
  }
}

Connection

Error handling in connections

Error Handling Guide

Comprehensive error handling patterns

Protocol Spec

JSON-RPC 2.0 specification

Build docs developers (and LLMs) love