Skip to main content

Overview

The RPC unions module provides sealed union types that enable exhaustive, type-safe handling of ACP requests, responses, and notifications. These unions ensure you handle all message types correctly and support both standard protocol methods and custom extensions.

Key Benefits

Type Safety

Each message type is strongly typed with full IDE support and compile-time checking

Exhaustive Handling

Pattern matching ensures all message types are handled without gaps

Forward Compatibility

Unknown methods are captured as extension types for graceful handling

Method Discovery

Automatic routing from JSON-RPC method names to typed objects

Union Types

Agent Unions

Types used by agents when sending or handling messages:
AgentNotificationUnion
abstract class
Notifications sent by agents to clients
AgentRequestUnion
abstract class
Requests initiated by agents (handled by clients)
AgentResponseUnion
abstract class
Responses produced by agents for client requests

Client Unions

Types used by clients when sending or handling messages:
ClientRequestUnion
abstract class
Requests initiated by clients (handled by agents)
ClientResponseUnion
abstract class
Responses produced by clients for agent requests
ClientNotificationUnion
abstract class
Notifications sent by clients to agents

Usage Examples

Handling Agent Requests

import 'package:acp_dart/acp_dart.dart';

Future<dynamic> handleAgentRequest(String method, dynamic params) async {
  final request = AgentRequestUnion.fromMethod(method, params);
  
  switch (request) {
    case AgentReadTextFileRequest(params: final p):
      // Handle file read
      final content = await readFile(p.path);
      return ReadTextFileResponse(content: content);
    
    case AgentWriteTextFileRequest(params: final p):
      // Handle file write
      await writeFile(p.path, p.content);
      return WriteTextFileResponse();
    
    case AgentRequestPermissionRequest(params: final p):
      // Handle permission request
      final optionId = await promptUser(p.message, p.options);
      return RequestPermissionResponse(optionId: optionId);
    
    case AgentCreateTerminalRequest(params: final p):
      // Handle terminal creation
      final terminalId = await createTerminal(p.command);
      return CreateTerminalResponse(terminalId: terminalId);
    
    case AgentExtensionMethodRequest(methodName: final m, rawParams: final p):
      // Handle custom extension
      return handleCustomMethod(m, p);
    
    // All other terminal operations...
    default:
      throw RequestError.methodNotFound(method);
  }
}

Handling Client Requests

import 'package:acp_dart/acp_dart.dart';

Future<dynamic> handleClientRequest(String method, dynamic params) async {
  final request = ClientRequestUnion.fromMethod(method, params);
  
  switch (request) {
    case ClientInitializeRequest(params: final p):
      return InitializeResponse(
        protocolVersion: '0.1.0',
        capabilities: AgentCapabilities(loadSession: false, auth: []),
      );
    
    case ClientNewSessionRequest(params: final p):
      final sessionId = createSession(p.mcpServers);
      return NewSessionResponse(sessionId: sessionId, availableModes: []);
    
    case ClientPromptRequest(params: final p):
      final result = await processPrompt(p.sessionId, p.message);
      return PromptResponse(stopReason: StopReason.completed);
    
    case ClientSetSessionModeRequest(params: final p):
      await setMode(p.sessionId, p.mode);
      return SetSessionModeResponse();
    
    case ClientExtensionMethodRequest(methodName: final m, rawParams: final p):
      return handleCustomMethod(m, p);
    
    default:
      throw RequestError.methodNotFound(method);
  }
}

Creating Union Instances

import 'package:acp_dart/acp_dart.dart';

// Create a request to read a file
final readRequest = AgentReadTextFileRequest(
  ReadTextFileRequest(
    sessionId: 'session-123',
    path: '/path/to/file.dart',
  ),
);

// Get the method name for JSON-RPC
print(readRequest.method); // 'fs/read_text_file'

// Serialize to JSON
final json = readRequest.toJson();

Pattern Matching with Notifications

import 'package:acp_dart/acp_dart.dart';

void handleAgentNotification(dynamic payload) {
  final notification = AgentNotificationUnion.fromJson(payload);
  
  switch (notification) {
    case SessionAgentNotification(notification: final n):
      // Handle session update
      handleSessionUpdate(n);
    
    case AgentExtensionNotification(rawPayload: final p):
      // Handle custom notification
      handleCustomNotification(p);
  }
}

Method Routing

Each union provides a fromMethod factory that automatically routes JSON-RPC methods to the correct union variant:
// Automatically creates the right request type
final request = AgentRequestUnion.fromMethod(
  'fs/read_text_file',
  {'sessionId': 'session-1', 'path': '/file.dart'},
);
// Returns: AgentReadTextFileRequest

// Unknown methods become extensions
final extRequest = AgentRequestUnion.fromMethod(
  '_custom/method',
  {'custom': 'params'},
);
// Returns: AgentExtensionMethodRequest

Type Safety Benefits

Using RPC unions ensures compile-time safety:
  • No missing cases: The compiler warns if you don’t handle all variants
  • No typos: Method names are checked at compile time
  • IDE support: Full autocomplete and type hints
  • Refactoring: Easy to update when the protocol changes

Requests

All request types

Responses

All response types

Notifications

All notification types

Build docs developers (and LLMs) love