use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use theway_transport::feed::{Feed, FeedUpdate};
use tokio::sync::mpsc;
pub type ThinkingSummarizerFn = Arc<
dyn Fn(String) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send>> + Send + Sync,
>;
#[derive(Clone)]
pub struct ThinkingSummarySettings {
pub min_chars: usize,
pub summarizer: ThinkingSummarizerFn,
}
const MAX_IN_FLIGHT: usize = 4;
#[derive(Default)]
pub struct ThinkingBurst {
pub open: bool,
pub in_flight: usize,
}
pub fn apply(
session_id: &str,
feed: &mut Feed,
burst: &mut ThinkingBurst,
settings: Option<&ThinkingSummarySettings>,
feed_tx: &mpsc::UnboundedSender<(String, FeedUpdate)>,
update: FeedUpdate,
) {
match update {
FeedUpdate::ThinkingDelta(_) => {
burst.open = true;
feed.apply(update);
}
FeedUpdate::ThinkingSummary { .. } => {
feed.apply(update);
burst.in_flight = burst.in_flight.saturating_sub(1);
}
other => {
let was_open = burst.open;
burst.open = false;
feed.apply(other);
if was_open
&& let Some(settings) = settings
&& burst.in_flight < MAX_IN_FLIGHT
&& let Some((index, text)) = feed.last_thinking_block()
&& text.len() >= settings.min_chars
{
burst.in_flight += 1;
let summarizer = settings.summarizer.clone();
let feed_tx = feed_tx.clone();
let session_id = session_id.to_string();
tokio::spawn(async move {
let summary = summarizer(text)
.await
.unwrap_or_else(|_| "(thinking summary unavailable)".to_string());
let _ = feed_tx.send((
session_id,
FeedUpdate::ThinkingSummary {
block_index: index,
summary,
},
));
});
}
}
}
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("turn/thinking_summary");