stream method
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',
);
}
}