use std::sync::Arc;
use async_trait::async_trait;
use crate::providers::ChatMessage;
#[derive(Debug, Clone)]
pub struct ContextInfo {
pub message_count: usize,
pub total_chars: usize,
pub max_chars: usize,
pub compactions_done: usize,
}
#[derive(Debug, Clone)]
pub struct CompressResult {
pub did_compact: bool,
pub messages: Vec<ChatMessage>,
}
#[async_trait]
pub trait Compactor: Send + Sync {
fn name(&self) -> &str;
fn should_compress(&self, info: &ContextInfo) -> bool;
async fn compress(&self, messages: &[ChatMessage], task: Option<&str>) -> CompressResult;
}
pub struct DefaultCompactor;
impl DefaultCompactor {
pub fn new() -> Self {
Self
}
}
impl Default for DefaultCompactor {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Compactor for DefaultCompactor {
fn name(&self) -> &str {
"default_compactor"
}
fn should_compress(&self, info: &ContextInfo) -> bool {
let threshold = (0.75 * info.max_chars as f64) as usize;
info.total_chars > threshold
}
async fn compress(&self, messages: &[ChatMessage], task: Option<&str>) -> CompressResult {
let keep_recent = 6;
if messages.len() <= keep_recent + 2 {
return CompressResult {
did_compact: false,
messages: messages.to_vec(),
};
}
let system_msgs: Vec<&ChatMessage> =
messages.iter().filter(|m| m.role == "system").collect();
let non_system: Vec<&ChatMessage> =
messages.iter().filter(|m| m.role != "system").collect();
if non_system.len() <= keep_recent {
return CompressResult {
did_compact: false,
messages: messages.to_vec(),
};
}
let (old_msgs, recent_msgs) = non_system.split_at(non_system.len() - keep_recent);
let mut summary_input = String::new();
for m in old_msgs {
let role_label = match m.role.as_str() {
"user" => "User",
"assistant" | "assistant_tool_use" => "Assistant",
"tool_result" => "Tool Result",
_ => &m.role,
};
let content = if m.content.len() > 500 {
format!("{}...", &m.content[..500])
} else {
m.content.clone()
};
summary_input.push_str(&format!("[{}]: {}\n", role_label, content));
}
let original_task = task.unwrap_or("unknown");
let compact_prompt = format!(
"Summarize this conversation concisely. Original task: \"{}\"\n\n\
Focus on: what was accomplished, key results, what's still pending.\n\n\
Conversation:\n{}",
original_task, summary_input
);
let _compact_msgs = [ChatMessage {
role: "user".into(),
content: compact_prompt,
tool_use_id: None,
}];
let summary = format!(
"[Compacted — {} earlier messages. Prior context summarized.]",
old_msgs.len()
);
let mut compacted = Vec::new();
for sm in &system_msgs {
compacted.push((*sm).clone());
}
compacted.push(ChatMessage {
role: "user".into(),
content: format!(
"[Compacted — {} earlier messages summarized]\n\n{}",
old_msgs.len(),
summary
),
tool_use_id: None,
});
compacted.push(ChatMessage {
role: "assistant".into(),
content: "Understood, continuing from summary.".into(),
tool_use_id: None,
});
for rm in recent_msgs {
compacted.push((*rm).clone());
}
CompressResult {
did_compact: old_msgs.len() > 2,
messages: compacted,
}
}
}
pub fn default_compactor() -> Arc<dyn Compactor> {
Arc::new(DefaultCompactor::new())
}