use crate::compact::estimate_token_count;
use crate::services::compact::grouping::group_messages_by_api_round;
use crate::types::Message;
#[derive(Debug, Clone)]
pub struct ReactiveCompactResult {
pub messages: Vec<Message>,
pub compacted: bool,
}
pub fn run_reactive_compact(
messages: &[Message],
model: &str,
) -> Result<ReactiveCompactResult, String> {
let effective_window = crate::compact::get_effective_context_window_size(model) as usize;
let token_count = estimate_token_count(messages, effective_window as u32) as usize;
if token_count <= effective_window {
return Ok(ReactiveCompactResult {
messages: messages.to_vec(),
compacted: false,
});
}
let groups = group_messages_by_api_round(messages);
if groups.len() <= 1 {
return Ok(ReactiveCompactResult {
messages: messages.to_vec(),
compacted: false,
});
}
let mut remaining: Vec<Message> = messages.to_vec();
for group in &groups {
if remaining.len() <= 4 {
break;
}
let new_len = remaining.len().saturating_sub(group.len());
if new_len < 4 {
break;
}
remaining = remove_group(&remaining, group);
let new_tokens = estimate_token_count(&remaining, effective_window as u32) as usize;
if new_tokens < effective_window {
break;
}
}
Ok(ReactiveCompactResult {
messages: remaining,
compacted: true,
})
}
fn remove_group(all: &[Message], group: &[Message]) -> Vec<Message> {
if group.len() >= all.len() {
return Vec::new();
}
for i in 0..=all.len().saturating_sub(group.len()) {
if all[i..i + group.len()]
.iter()
.zip(group.iter())
.all(|(a, b)| a.content == b.content && a.role == b.role)
{
let mut result = all[..i].to_vec();
result.extend_from_slice(&all[i + group.len()..]);
return result;
}
}
let keep = all.len().saturating_sub(group.len());
all[keep..].to_vec()
}
pub fn is_reactive_compact_enabled() -> bool {
!crate::utils::env_utils::is_env_truthy(Some("DISABLE_REACTIVE_COMPACT"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reactive_compact_below_threshold() {
let messages = vec![Message {
role: crate::types::MessageRole::User,
content: "Hello".to_string(),
..Default::default()
}];
let result = run_reactive_compact(&messages, "claude-sonnet-4-6");
assert!(result.is_ok());
assert!(!result.unwrap().compacted);
}
#[test]
fn test_reactive_compact_enabled() {
assert!(is_reactive_compact_enabled());
}
}