use futures::StreamExt;
use crate::brain::provider::{
ContentBlock, ContentDelta, LLMResponse, ProviderError, ProviderStream, StreamEvent, TokenUsage,
};
pub(crate) async fn collect_stream(
mut stream: ProviderStream,
) -> std::result::Result<LLMResponse, ProviderError> {
let mut id = String::new();
let mut model = String::new();
let mut text = String::new();
let mut usage = TokenUsage::default();
let mut stop_reason = None;
while let Some(item) = stream.next().await {
match item? {
StreamEvent::MessageStart { message } => {
id = message.id;
model = message.model;
usage = message.usage;
}
StreamEvent::ContentBlockStart {
content_block: ContentBlock::Text { text: opening },
..
} => text.push_str(&opening),
StreamEvent::ContentBlockStart { .. } => {}
StreamEvent::ContentBlockDelta {
delta: ContentDelta::TextDelta { text: delta },
..
} => text.push_str(&delta),
StreamEvent::ContentBlockDelta { .. } => {}
StreamEvent::ContentBlockStop { .. } | StreamEvent::Ping => {}
StreamEvent::MessageDelta {
delta,
usage: final_usage,
} => {
if delta.stop_reason.is_some() {
stop_reason = delta.stop_reason;
}
if final_usage.input_tokens > 0 {
usage.input_tokens = final_usage.input_tokens;
}
if final_usage.output_tokens > 0 {
usage.output_tokens = final_usage.output_tokens;
}
}
StreamEvent::MessageStop => break,
StreamEvent::Error { error } => return Err(ProviderError::StreamError(error)),
}
}
Ok(LLMResponse {
id,
model,
content: vec![ContentBlock::Text { text }],
stop_reason,
usage,
streaming_active_secs: None,
tool_text_leak: false,
})
}