Skip to main content
The ACP Dart SDK provides full support for protocol extensions, allowing you to implement custom methods and notifications that go beyond the standard ACP specification.

Overview

Extension methods enable you to:
  • Add domain-specific functionality to your agent or client
  • Experiment with new features before they’re added to ACP
  • Create proprietary features for specialized use cases
  • Bridge to other protocols or systems
ACP reserves extension methods under the _ prefix. All custom method names should start with an underscore.

Extension Methods

Extension methods are request-response operations that aren’t part of the standard ACP specification.

Agent-Side Extensions

Implement custom methods that clients can call on your agent.
import 'package:acp_dart/acp_dart.dart';

class MyAgent implements Agent {
  // ... standard ACP methods ...
  
  @override
  Future<Map<String, dynamic>>? extMethod(
    String method,
    Map<String, dynamic> params,
  ) async {
    switch (method) {
      case '_analyze_codebase':
        return await analyzeCodebase(params);
      
      case '_get_project_stats':
        return await getProjectStats(params);
      
      case '_custom_tool':
        return await executeCustomTool(params);
      
      default:
        // Return null to indicate method not found
        return null;
    }
  }
  
  Future<Map<String, dynamic>> analyzeCodebase(
    Map<String, dynamic> params,
  ) async {
    final path = params['path'] as String;
    final depth = params['depth'] as int? ?? 3;
    
    // Your custom logic
    final analysis = await performAnalysis(path, depth);
    
    return {
      'totalFiles': analysis.totalFiles,
      'languages': analysis.languages,
      'complexity': analysis.complexity,
    };
  }
}

Client-Side Extensions

Implement custom methods that agents can call on your client.
class MyClient implements Client {
  // ... standard ACP methods ...
  
  @override
  Future<Map<String, dynamic>>? extMethod(
    String method,
    Map<String, dynamic> params,
  ) async {
    switch (method) {
      case '_open_browser':
        return await openBrowser(params);
      
      case '_show_notification':
        return await showNotification(params);
      
      default:
        return null;
    }
  }
  
  Future<Map<String, dynamic>> openBrowser(
    Map<String, dynamic> params,
  ) async {
    final url = params['url'] as String;
    final newTab = params['newTab'] as bool? ?? true;
    
    await launchUrl(url, newTab: newTab);
    
    return {'success': true};
  }
}

Calling Extension Methods

// Client calls agent extension method
final response = await connection.extMethod(
  '_analyze_codebase',
  {
    'path': '/project/src',
    'depth': 5,
  },
);

print('Total files: ${response['totalFiles']}');
print('Languages: ${response['languages']}');

Extension Notifications

Extension notifications are one-way messages that don’t expect a response.

Sending Notifications

class MyAgent implements Agent {
  // ... standard methods ...
  
  @override
  Future<void>? extNotification(
    String method,
    Map<String, dynamic> params,
  ) async {
    switch (method) {
      case '_progress_update':
        await handleProgressUpdate(params);
        break;
      
      case '_custom_event':
        await handleCustomEvent(params);
        break;
    }
  }
}

// Send extension notification from the opposite side
await connection.extNotification(
  '_progress_update',
  {
    'stage': 'analyzing',
    'progress': 0.45,
    'message': 'Analyzing dependencies...',
  },
);

Protocol Integration

Extension methods are fully integrated into the SDK’s connection handling:
// From lib/src/acp.dart (lines 524-535)
Future<dynamic> requestHandler(String method, dynamic params) async {
  switch (method) {
    // ... standard ACP methods ...
    
    default:
      if (method.startsWith('_')) {
        final result = await agent.extMethod(
          method,
          params as Map<String, dynamic>,
        );
        if (result == null) {
          throw RequestError.methodNotFound(method);
        }
        return result;
      }
      throw RequestError.methodNotFound(method);
  }
}
Key Behaviors:
  • Method names are passed through exactly as provided
  • The leading _ is not automatically added
  • Returning null from extMethod results in a Method not found error
  • Extension methods follow the same error handling as standard methods

Type Safety with Extensions

While extension methods use generic Map<String, dynamic> for parameters and responses, you can create type-safe wrappers:
class AnalyzeCodebaseRequest {
  final String path;
  final int depth;
  final List<String>? excludePatterns;
  
  AnalyzeCodebaseRequest({
    required this.path,
    this.depth = 3,
    this.excludePatterns,
  });
  
  Map<String, dynamic> toJson() => {
    'path': path,
    'depth': depth,
    if (excludePatterns != null) 'excludePatterns': excludePatterns,
  };
  
  factory AnalyzeCodebaseRequest.fromJson(Map<String, dynamic> json) =>
    AnalyzeCodebaseRequest(
      path: json['path'] as String,
      depth: json['depth'] as int? ?? 3,
      excludePatterns: (json['excludePatterns'] as List?)?.cast<String>(),
    );
}

Best Practices

Naming Conventions

  • Always prefix with _
  • Use lowercase with underscores
  • Be descriptive: _analyze_codebase not _analyze
  • Namespace for clarity: _myapp_custom_feature

Error Handling

  • Return null for unsupported methods
  • Throw RequestError for known error cases
  • Use standard JSON-RPC error codes
  • Include helpful error messages

Versioning

  • Include version in method name if needed: _myfeature_v2
  • Document schema changes clearly
  • Support backward compatibility when possible
  • Deprecate old versions gracefully

Documentation

  • Document expected parameters
  • Document response format
  • Provide usage examples
  • Note any side effects

Error Handling

@override
Future<Map<String, dynamic>>? extMethod(
  String method,
  Map<String, dynamic> params,
) async {
  try {
    switch (method) {
      case '_risky_operation':
        // Validate parameters
        if (params['path'] == null) {
          throw RequestError.invalidParams(
            {'error': 'path parameter is required'},
          );
        }
        
        // Execute operation
        return await performOperation(params);
      
      default:
        return null;  // Method not found
    }
  } catch (e) {
    if (e is RequestError) {
      rethrow;
    }
    // Map unexpected errors to internal error
    throw RequestError.internalError(
      {'message': e.toString()},
    );
  }
}

Capability Discovery

While ACP doesn’t have a standard mechanism for advertising extension methods, you can implement your own:
@override
Future<Map<String, dynamic>>? extMethod(
  String method,
  Map<String, dynamic> params,
) async {
  switch (method) {
    case '_list_capabilities':
      return {
        'extensions': [
          {
            'method': '_analyze_codebase',
            'description': 'Analyzes codebase structure and complexity',
            'params': {
              'path': 'string (required)',
              'depth': 'int (optional, default: 3)',
            },
          },
          {
            'method': '_get_project_stats',
            'description': 'Returns project statistics',
            'params': {},
          },
        ],
      };
    
    // ... other methods ...
  }
}

Testing Extensions

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

void main() {
  group('Extension Methods', () {
    test('_analyze_codebase returns analysis', () async {
      final agent = TestAgent();
      
      final result = await agent.extMethod(
        '_analyze_codebase',
        {'path': '/test/path'},
      );
      
      expect(result?['totalFiles'], greaterThan(0));
      expect(result?['languages'], isA<Map>());
    });
    
    test('unknown extension returns null', () async {
      final agent = TestAgent();
      
      final result = await agent.extMethod(
        '_unknown_method',
        {},
      );
      
      expect(result, isNull);
    });
  });
}

Real-World Examples

// Extension for IDE-specific features
@override
Future<Map<String, dynamic>>? extMethod(
  String method,
  Map<String, dynamic> params,
) async {
  switch (method) {
    case '_jump_to_definition':
      return await jumpToDefinition(
        file: params['file'],
        line: params['line'],
        column: params['column'],
      );
    
    case '_find_references':
      return await findReferences(
        symbol: params['symbol'],
      );
    
    case '_trigger_refactor':
      return await triggerRefactor(
        type: params['type'],
        location: params['location'],
      );
  }
}
// Extension for project management features
@override
Future<Map<String, dynamic>>? extMethod(
  String method,
  Map<String, dynamic> params,
) async {
  switch (method) {
    case '_create_task':
      final taskId = await createTask(
        title: params['title'],
        description: params['description'],
        assignee: params['assignee'],
      );
      return {'taskId': taskId};
    
    case '_list_tasks':
      final tasks = await listTasks(
        project: params['project'],
        status: params['status'],
      );
      return {'tasks': tasks};
  }
}
// Extension for analytics and telemetry
@override
Future<void>? extNotification(
  String method,
  Map<String, dynamic> params,
) async {
  switch (method) {
    case '_log_event':
      await logAnalyticsEvent(
        event: params['event'],
        properties: params['properties'],
      );
      break;
    
    case '_track_error':
      await trackError(
        error: params['error'],
        stackTrace: params['stackTrace'],
        context: params['context'],
      );
      break;
  }
}

Protocol Compliance

Extension methods complement but don’t replace standard ACP methods:
Do:
  • Use extensions for optional, specialized features
  • Implement all required ACP methods
  • Prefix all custom methods with _
  • Handle unsupported methods gracefully
Don’t:
  • Use extensions to avoid implementing standard methods
  • Expect all clients to support your extensions
  • Break ACP semantics with custom methods
  • Use non-prefixed names for extensions

Back to Support Matrix

Review all supported protocol features

Build docs developers (and LLMs) love