use std::sync::Arc;
use async_stream::try_stream;
use futures::stream::{BoxStream, StreamExt};
use crate::error::Error;
use crate::provider::Provider;
use crate::stream_event::StreamEvent;
use crate::text::request::{TextRequest, ToolChoice};
use crate::text::response::{Step, TextResponse};
use crate::tool_loop::execute_tools;
use crate::value_objects::{
AssistantMessage, FinishReason, Message, Meta, ToolCall, ToolResultMessage, Usage,
};
pub fn stream_text(
provider: Arc<dyn Provider>,
mut request: TextRequest,
) -> BoxStream<'static, Result<StreamEvent, Error>> {
let stream = try_stream! {
let mut steps: Vec<Step> = Vec::new();
loop {
let mut inner = provider.stream_text_once(&request).await?;
let mut text = String::new();
let mut tool_calls: Vec<ToolCall> = Vec::new();
let mut usage = Usage::default();
let mut finish_reason = FinishReason::Stop;
let mut meta = Meta::default();
while let Some(event) = inner.next().await {
let event = event?;
match &event {
StreamEvent::StreamStart { meta: step_meta } => meta = step_meta.clone(),
StreamEvent::TextDelta { text: delta } => text.push_str(delta),
StreamEvent::ToolCall(call) => tool_calls.push(call.clone()),
StreamEvent::StepFinish {
usage: step_usage,
finish_reason: step_finish_reason,
} => {
usage = *step_usage;
finish_reason = *step_finish_reason;
}
StreamEvent::ToolCallDelta { .. } | StreamEvent::ToolResult(_) => {}
StreamEvent::StreamEnd { .. } => {}
}
yield event;
}
let has_tool_calls = finish_reason == FinishReason::ToolCalls && !tool_calls.is_empty();
steps.push(Step {
text: if text.is_empty() { None } else { Some(text) },
tool_calls: tool_calls.clone(),
finish_reason,
usage,
meta,
});
let stop_condition_met = request
.stop_when
.iter()
.any(|condition| condition.should_stop(&steps));
if !has_tool_calls || steps.len() as u32 >= request.max_steps || stop_condition_met {
yield StreamEvent::StreamEnd {
response: TextResponse::from_steps(steps),
};
break;
}
let results =
execute_tools(&request.tools, &tool_calls, request.approval_handler.as_ref())
.await;
for result in &results {
yield StreamEvent::ToolResult(result.clone());
}
let last_text = steps.last().and_then(|step| step.text.clone());
request.messages.push(Message::Assistant(AssistantMessage {
content: last_text,
tool_calls,
}));
request.messages.push(Message::ToolResult(ToolResultMessage {
tool_results: results,
}));
request.tool_choice = ToolChoice::Auto;
}
};
stream.boxed()
}