use bytes::Bytes;
use futures::StreamExt;
use tokio::sync::mpsc;
use bamboo_llm::provider::LLMStream;
use bamboo_llm::types::LLMChunk;
use bamboo_metrics::{ForwardStatus, MetricsCollector};
use bamboo_agent_core::tools::ToolCallAccumulator;
use super::super::super::types::ResponsesOutputItem;
use super::super::output::{build_completed_response, build_output_items};
use super::super::usage::ResponsesUsageAccumulator;
use super::events::{
completed_event, created_event, done_sse_bytes, event_to_sse_bytes, failed_sse_bytes,
function_call_item_events, message_content_part_added_event, message_item_added_event,
message_item_done_events, output_text_delta_event, raw_event_to_sse_bytes, sequence_event,
};
use crate::handlers::llm_compat::usage::estimate_completion_tokens;
pub(super) struct StreamWorkerArgs {
pub(super) stream_result: LLMStream,
pub(super) tx: mpsc::Sender<Result<Bytes, anyhow::Error>>,
pub(super) metrics: MetricsCollector,
pub(super) forward_id: String,
pub(super) fallback_response_id: String,
pub(super) message_id: String,
pub(super) created_at: u64,
pub(super) resolved_model: String,
pub(super) estimated_prompt_tokens: u64,
}
pub(super) fn spawn_stream_worker(args: StreamWorkerArgs) {
tokio::spawn(async move {
run_stream_worker(args).await;
});
}
pub(super) async fn run_stream_worker(mut args: StreamWorkerArgs) {
let mut had_error = false;
let mut error_message: Option<String> = None;
let mut content = String::new();
let mut reasoning_content = String::new();
let mut tool_calls = ToolCallAccumulator::new();
let mut response_id: Option<String> = None;
let mut created_sent = false;
let mut message_started = false;
let mut next_sequence_number = 0u64;
let mut saw_done = false;
let mut provider_usage = ResponsesUsageAccumulator::default();
let mut raw_responses_mode = false;
async fn ensure_created_event(
args: &mut StreamWorkerArgs,
response_id: &str,
created_sent: &mut bool,
next_sequence_number: &mut u64,
) -> bool {
if *created_sent {
return true;
}
let event = sequence_event(
created_event(
response_id.to_string(),
args.resolved_model.clone(),
args.created_at,
),
next_sequence_number,
);
if args.tx.send(Ok(event_to_sse_bytes(&event))).await.is_err() {
return false;
}
*created_sent = true;
true
}
async fn ensure_message_started(
args: &mut StreamWorkerArgs,
response_id: &str,
created_sent: &mut bool,
message_started: &mut bool,
next_sequence_number: &mut u64,
) -> bool {
if *message_started {
return true;
}
if !ensure_created_event(args, response_id, created_sent, next_sequence_number).await {
return false;
}
let added = sequence_event(
message_item_added_event(response_id, &args.message_id),
next_sequence_number,
);
if args.tx.send(Ok(event_to_sse_bytes(&added))).await.is_err() {
return false;
}
let content_added = sequence_event(
message_content_part_added_event(response_id, &args.message_id),
next_sequence_number,
);
if args
.tx
.send(Ok(event_to_sse_bytes(&content_added)))
.await
.is_err()
{
return false;
}
*message_started = true;
true
}
while let Some(chunk_result) = args.stream_result.next().await {
match chunk_result {
Ok(LLMChunk::ResponsesEvent { event_type, data }) => {
raw_responses_mode = true;
let bytes = raw_event_to_sse_bytes(
event_type.as_str(),
data.as_ref(),
&mut next_sequence_number,
);
if args.tx.send(Ok(bytes)).await.is_err() {
break;
}
}
Ok(LLMChunk::ResponseId(id)) => {
response_id = Some(id.clone());
if !raw_responses_mode
&& !ensure_created_event(
&mut args,
&id,
&mut created_sent,
&mut next_sequence_number,
)
.await
{
break;
}
}
Ok(LLMChunk::Token(text)) => {
content.push_str(&text);
let active_response_id = response_id
.clone()
.unwrap_or_else(|| args.fallback_response_id.clone());
if !raw_responses_mode
&& !ensure_message_started(
&mut args,
&active_response_id,
&mut created_sent,
&mut message_started,
&mut next_sequence_number,
)
.await
{
break;
}
if !raw_responses_mode {
let event = sequence_event(
output_text_delta_event(&active_response_id, &args.message_id, text),
&mut next_sequence_number,
);
if args.tx.send(Ok(event_to_sse_bytes(&event))).await.is_err() {
break;
}
}
}
Ok(LLMChunk::ReasoningToken(text)) => {
reasoning_content.push_str(&text);
}
Ok(LLMChunk::ToolCalls(calls)) => {
let active_response_id = response_id
.clone()
.unwrap_or_else(|| args.fallback_response_id.clone());
if !raw_responses_mode
&& !ensure_created_event(
&mut args,
&active_response_id,
&mut created_sent,
&mut next_sequence_number,
)
.await
{
break;
}
tool_calls.extend(calls)
}
Ok(LLMChunk::ToolCallsIndexed(indexed)) => {
let active_response_id = response_id
.clone()
.unwrap_or_else(|| args.fallback_response_id.clone());
if !raw_responses_mode
&& !ensure_created_event(
&mut args,
&active_response_id,
&mut created_sent,
&mut next_sequence_number,
)
.await
{
break;
}
tool_calls.extend_indexed(indexed)
}
Ok(LLMChunk::Done) => {
saw_done = true;
break;
}
Ok(LLMChunk::ProviderUsage {
input_tokens,
output_tokens,
total_tokens,
reasoning_tokens,
cache_read_input_tokens,
cache_write_input_tokens,
..
}) => provider_usage.record(
input_tokens,
output_tokens,
total_tokens,
reasoning_tokens,
cache_read_input_tokens,
cache_write_input_tokens,
),
Ok(LLMChunk::TransportActivity)
| Ok(LLMChunk::CacheUsage { .. })
| Ok(LLMChunk::UsageSummary { .. })
| Ok(LLMChunk::ReasoningSignature(_)) => {}
Err(error) => {
had_error = true;
error_message = Some(error.to_string());
tracing::error!("Stream error: {}", error);
args.metrics.forward_completed(
args.forward_id.clone(),
chrono::Utc::now(),
None,
ForwardStatus::Error,
None,
Some(error.to_string()),
);
break;
}
}
}
if !had_error && !saw_done {
had_error = true;
error_message = Some("Stream ended before a protocol completion event".to_string());
args.metrics.forward_completed(
args.forward_id.clone(),
chrono::Utc::now(),
None,
ForwardStatus::Error,
None,
error_message.clone(),
);
}
if had_error {
let message = error_message.as_deref().unwrap_or("upstream stream error");
let _ = args
.tx
.send(Ok(failed_sse_bytes(message, next_sequence_number)))
.await;
let _ = args.tx.send(Ok(done_sse_bytes())).await;
return;
}
let completion_tokens = estimate_completion_tokens(&content)
.saturating_add(estimate_completion_tokens(&reasoning_content));
let response_usage = provider_usage.response_usage();
let (metrics_usage, metrics_details) =
provider_usage.metrics_usage(args.estimated_prompt_tokens, completion_tokens);
let final_response_id = response_id.unwrap_or_else(|| args.fallback_response_id.clone());
if raw_responses_mode {
let _ = args.tx.send(Ok(done_sse_bytes())).await;
args.metrics.forward_completed_with_details(
args.forward_id,
chrono::Utc::now(),
Some(200),
ForwardStatus::Success,
Some(metrics_usage),
metrics_details,
None,
);
return;
}
if !ensure_created_event(
&mut args,
&final_response_id,
&mut created_sent,
&mut next_sequence_number,
)
.await
{
return;
}
let fragment_groups = tool_calls.parts().len();
let finalized_calls = tool_calls.finalize();
if finalized_calls.len() < fragment_groups {
tracing::warn!(
dropped = fragment_groups - finalized_calls.len(),
"Dropping incomplete streamed tool call(s) whose name never arrived"
);
}
let output = build_output_items(&args.message_id, content, finalized_calls);
let mut client_gone = false;
'items: for (output_index, item) in output.iter().enumerate() {
let events = match item {
ResponsesOutputItem::Message(message) => {
if !ensure_message_started(
&mut args,
&final_response_id,
&mut created_sent,
&mut message_started,
&mut next_sequence_number,
)
.await
{
client_gone = true;
break 'items;
}
message_item_done_events(&final_response_id, message)
}
ResponsesOutputItem::FunctionCall(fc) => {
function_call_item_events(&final_response_id, fc, output_index as u32)
}
};
for event in events {
let event = sequence_event(event, &mut next_sequence_number);
if args.tx.send(Ok(event_to_sse_bytes(&event))).await.is_err() {
client_gone = true;
break 'items;
}
}
}
if !client_gone {
let response = build_completed_response(
final_response_id,
args.created_at,
args.resolved_model,
output,
response_usage,
);
let complete = sequence_event(completed_event(response), &mut next_sequence_number);
let _ = args.tx.send(Ok(event_to_sse_bytes(&complete))).await;
let _ = args.tx.send(Ok(done_sse_bytes())).await;
}
args.metrics.forward_completed_with_details(
args.forward_id,
chrono::Utc::now(),
Some(200),
ForwardStatus::Success,
Some(metrics_usage),
metrics_details,
None,
);
}