sendChatRequest method

  1. @override
Future<Map<String, dynamic>> sendChatRequest(
  1. List<Map<String, dynamic>> messages,
  2. List<Map<String, dynamic>>? tools, {
  3. double? temperature,
  4. int? maxCompletionTokens,
  5. double? topP,
  6. bool? stream,
  7. String? reasoningEffort,
  8. dynamic stop,
  9. CancellationToken? cancellationToken,
})
override

Sends a single, complete chat request to the LLM.

Implementation

@override
Future<Map<String, dynamic>> sendChatRequest(
  List<Map<String, dynamic>> messages,
  List<Map<String, dynamic>>? tools, {
  double? temperature,
  int? maxCompletionTokens,
  double? topP,
  bool? stream,
  String? reasoningEffort,
  dynamic stop,
  CancellationToken? cancellationToken,
}) async {
  sdkLogger.info(
    'Sending chat request to Anthropic (model: $model)',
    tag: 'API',
    extra: {
      'model': model,
      'message_count': messages.length,
      'has_tools': tools != null && tools.isNotEmpty,
    },
  );

  final anthropicData = _convertToAnthropicFormat(messages, tools);

  final body = jsonEncode({
    'model': model,
    'max_tokens': maxCompletionTokens ?? maxTokens,
    if (anthropicData['system'] != null) 'system': anthropicData['system'],
    'messages': anthropicData['messages'],
    if (anthropicData['tools'] != null) 'tools': anthropicData['tools'],
    if ((temperature ?? this.temperature) != null)
      'temperature': temperature ?? this.temperature,
  });

  const int maxRetries = 3;
  const Duration baseDelay = Duration(seconds: 1);

  for (int attempt = 1; attempt <= maxRetries; attempt++) {
    if (cancellationToken?.isCancelled == true) {
      throw VanturaCancellationException();
    }

    try {
      final stopwatch = Stopwatch()..start();
      final response = await _httpClient.post(
        Uri.parse(baseUrl),
        headers: _headers,
        body: body,
      );
      stopwatch.stop();

      sdkLogger.logPerformance(
        'Anthropic API request ($model)',
        stopwatch.elapsed,
        context: {'status_code': response.statusCode},
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body) as Map<String, dynamic>;
        return _convertFromAnthropicResponse(data);
      } else if (response.statusCode == 429 || response.statusCode >= 500) {
        if (attempt == maxRetries) {
          throw VanturaApiException(
            'Anthropic API error after $maxRetries attempts',
            statusCode: response.statusCode,
            responseBody: response.body,
          );
        }

        final retrySeconds = attempt * 2;
        sdkLogger.warning(
          'Anthropic API hit ${response.statusCode}. Retrying in ${retrySeconds}s... (Attempt $attempt/$maxRetries)',
          tag: 'API',
        );

        if (onRetry != null) {
          onRetry!(
            attempt,
            Duration(seconds: retrySeconds),
            'HTTP ${response.statusCode}',
          );
        }

        await Future.delayed(Duration(seconds: retrySeconds));
        continue;
      } else {
        throw VanturaApiException(
          'Anthropic API error',
          statusCode: response.statusCode,
          responseBody: response.body,
        );
      }
    } on http.ClientException catch (_) {
      if (attempt == maxRetries) rethrow;
      await Future.delayed(baseDelay * attempt);
    }
  }

  throw Exception('Unexpected error in Anthropic request');
}