streamMessage method

  1. @override
Stream<String> streamMessage({
  1. required String systemPrompt,
  2. required List<Map<String, String>> messages,
  3. int maxTokens = 8192,
})
override

Stream a response, yielding text deltas as they arrive.

Implementation

@override
Stream<String> streamMessage({
  required String systemPrompt,
  required List<Map<String, String>> messages,
  int maxTokens = 8192,
}) async* {
  final token = await _auth.getAccessToken();
  final model = await _getModel();

  dev.log(
    'Gemini API stream request: model=$model, '
    'systemPrompt=${systemPrompt.length} chars, '
    'messages=${messages.length}',
    name: 'GeminiDatasource',
  );

  final url =
      '${_provider.apiUrl}/models/$model:streamGenerateContent?alt=sse';

  final response = await _dio.post<ResponseBody>(
    url,
    options: Options(
      headers: {
        'Authorization': 'Bearer $token',
        'content-type': 'application/json',
      },
      responseType: ResponseType.stream,
    ),
    data: {
      'systemInstruction': {
        'parts': [
          {'text': systemPrompt},
        ],
      },
      'contents': _buildContents(messages),
      'generationConfig': {'maxOutputTokens': maxTokens},
    },
  );

  final stream = response.data?.stream;
  if (stream == null) {
    throw Exception('No stream in Gemini API response.');
  }

  String buffer = '';

  await for (final chunk in stream) {
    buffer += utf8.decode(chunk);

    while (buffer.contains('\n\n')) {
      final eventEnd = buffer.indexOf('\n\n');
      final eventBlock = buffer.substring(0, eventEnd);
      buffer = buffer.substring(eventEnd + 2);

      for (final line in eventBlock.split('\n')) {
        if (!line.startsWith('data: ')) continue;
        final dataStr = line.substring(6).trim();

        try {
          final json = jsonDecode(dataStr) as Map<String, dynamic>;
          final text = _extractText(json);
          if (text.isNotEmpty) {
            yield text;
          }
        } catch (_) {
          // Skip malformed lines.
        }
      }
    }
  }
}