use std::sync::{Arc, Mutex};
#[allow(deprecated)]
use swink_agent::sliding_window;
use swink_agent::{AgentMessage, AssistantMessage, ContentBlock, LlmMessage};
#[non_exhaustive]
#[derive(Debug)]
pub struct CompactionResult {
pub messages: Vec<AgentMessage>,
pub removed_count: usize,
pub summary: Option<String>,
}
impl CompactionResult {
#[must_use]
pub fn new(messages: Vec<AgentMessage>, removed_count: usize, summary: Option<String>) -> Self {
Self {
messages,
removed_count,
summary,
}
}
}
pub struct SummarizingCompactor {
summary: Arc<Mutex<Option<String>>>,
normal_budget: usize,
overflow_budget: usize,
anchor: usize,
}
impl SummarizingCompactor {
pub fn new(normal_budget: usize, overflow_budget: usize, anchor: usize) -> Self {
Self {
summary: Arc::new(Mutex::new(None)),
normal_budget,
overflow_budget,
anchor,
}
}
pub fn compaction_fn(&self) -> impl Fn(&mut Vec<AgentMessage>, bool) + Send + Sync {
let summary = Arc::clone(&self.summary);
#[allow(deprecated)]
let base = sliding_window(self.normal_budget, self.overflow_budget, self.anchor);
let anchor = self.anchor;
move |messages: &mut Vec<AgentMessage>, overflow: bool| {
let len_before = messages.len();
base(messages, overflow);
let len_after = messages.len();
if len_after < len_before {
let mut guard = summary
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(text) = guard.take() {
let summary_msg = AgentMessage::Llm(LlmMessage::Assistant(
AssistantMessage::new(
vec![ContentBlock::Text {
text: format!("[Context summary of earlier conversation]\n{text}"),
}],
String::new(),
String::new(),
)
.with_timestamp(0),
));
let insert_pos = anchor.min(messages.len());
messages.insert(insert_pos, summary_msg);
}
}
}
}
pub fn set_summary(&self, text: impl Into<String>) {
let mut guard = self
.summary
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*guard = Some(text.into());
}
pub fn clear_summary(&self) {
let mut guard = self
.summary
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*guard = None;
}
pub fn has_summary(&self) -> bool {
let guard = self
.summary
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.is_some()
}
}
#[cfg(test)]
#[path = "compaction_tests.rs"]
mod tests;