ai_agent/services/compact/
reactive_compact.rs1use crate::compact::estimate_token_count;
9use crate::services::compact::grouping::group_messages_by_api_round;
10use crate::types::Message;
11
12#[derive(Debug, Clone)]
14pub struct ReactiveCompactResult {
15 pub messages: Vec<Message>,
17 pub compacted: bool,
19}
20
21pub fn run_reactive_compact(
26 messages: &[Message],
27 model: &str,
28) -> Result<ReactiveCompactResult, String> {
29 let effective_window = crate::compact::get_effective_context_window_size(model) as usize;
30 let token_count = estimate_token_count(messages, effective_window as u32) as usize;
31
32 if token_count <= effective_window {
33 return Ok(ReactiveCompactResult {
34 messages: messages.to_vec(),
35 compacted: false,
36 });
37 }
38
39 let groups = group_messages_by_api_round(messages);
40 if groups.len() <= 1 {
41 return Ok(ReactiveCompactResult {
42 messages: messages.to_vec(),
43 compacted: false,
44 });
45 }
46
47 let mut remaining: Vec<Message> = messages.to_vec();
49 for group in &groups {
50 if remaining.len() <= 4 {
51 break;
52 }
53
54 let new_len = remaining.len().saturating_sub(group.len());
55 if new_len < 4 {
56 break;
57 }
58
59 remaining = remove_group(&remaining, group);
61
62 let new_tokens = estimate_token_count(&remaining, effective_window as u32) as usize;
63 if new_tokens < effective_window {
64 break;
65 }
66 }
67
68 Ok(ReactiveCompactResult {
69 messages: remaining,
70 compacted: true,
71 })
72}
73
74fn remove_group(all: &[Message], group: &[Message]) -> Vec<Message> {
76 if group.len() >= all.len() {
77 return Vec::new();
78 }
79
80 for i in 0..=all.len().saturating_sub(group.len()) {
82 if all[i..i + group.len()]
83 .iter()
84 .zip(group.iter())
85 .all(|(a, b)| a.content == b.content && a.role == b.role)
86 {
87 let mut result = all[..i].to_vec();
88 result.extend_from_slice(&all[i + group.len()..]);
89 return result;
90 }
91 }
92
93 let keep = all.len().saturating_sub(group.len());
95 all[keep..].to_vec()
96}
97
98pub fn is_reactive_compact_enabled() -> bool {
100 !crate::utils::env_utils::is_env_truthy(Some("DISABLE_REACTIVE_COMPACT"))
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_reactive_compact_below_threshold() {
109 let messages = vec![Message {
110 role: crate::types::MessageRole::User,
111 content: "Hello".to_string(),
112 ..Default::default()
113 }];
114 let result = run_reactive_compact(&messages, "claude-sonnet-4-6");
115 assert!(result.is_ok());
116 assert!(!result.unwrap().compacted);
117 }
118
119 #[test]
120 fn test_reactive_compact_enabled() {
121 assert!(is_reactive_compact_enabled());
122 }
123}