stream method

  1. @override
Stream<String> stream(
  1. List<ChatMessage> messages, {
  2. String? system,
  3. int? maxTokens,
})
override

Streams the response to messages as text deltas.

Implementation

@override
Stream<String> stream(
  List<ChatMessage> messages, {
  String? system,
  int? maxTokens,
}) async* {
  String? stop;
  Object? details;
  var finished = false;
  final events = _http.postSse(
    _endpoint,
    _headers,
    _body(messages, system, maxTokens ?? streamMaxTokens, stream: true),
  );
  await for (final e in events) {
    if (e.event == 'ping') continue;
    final Object? json;
    try {
      json = jsonDecode(e.data);
    } on FormatException {
      continue;
    }
    if (json is! Map) continue;
    switch (json['type']) {
      case 'content_block_delta':
        final delta = json['delta'];
        if (delta is Map && delta['type'] == 'text_delta') {
          final text = delta['text'] as String? ?? '';
          if (text.isNotEmpty) yield text;
        }
      case 'message_delta':
        final delta = json['delta'];
        if (delta is Map) {
          stop = delta['stop_reason'] as String? ?? stop;
          details = delta['stop_details'] ?? details;
        }
      case 'error':
        final error = json['error'];
        final map = error is Map ? error : const {};
        final type = map['type'] as String?;
        throw LlmHttpException(
          type == 'overloaded_error' ? 529 : 500,
          '${map['message'] ?? 'stream error'}',
          errorType: type,
        );
      case 'message_stop':
        // The answer is complete. Stop reading rather than waiting for the
        // server to close the connection: a gateway that keeps it open
        // would otherwise stall until the idle timeout and then report an
        // error after a perfectly good answer.
        finished = true;
    }
    if (finished) break;
  }
  if (stop == 'refusal') throw _refusal(details);
  if (!finished) {
    // No `message_stop` means the connection ended mid-answer. Reporting it
    // is the only way a caller can tell a short answer from a cut one.
    throw const LlmException(
      'the response stream ended before Claude finished; '
      'discard the partial text and retry',
    );
  }
}