matrixcode_core/compress/
config.rs1use anyhow::Result;
4
5pub const DEFAULT_COMPRESSION_THRESHOLD: f64 = 0.5;
12
13pub const MIN_MESSAGES_TO_KEEP: usize = 20;
16
17pub const DEFAULT_TARGET_RATIO: f64 = 0.4;
19
20pub const DEFAULT_COMPRESSOR_MODEL: &str = "claude-3-5-haiku-20241022";
22
23pub const MAX_CONSECUTIVE_FAILURES: u32 = 3;
30
31pub const AUTOCOMPACT_BUFFER_TOKENS: u32 = 13_000;
33pub const WARNING_THRESHOLD_BUFFER_TOKENS: u32 = 20_000;
34pub const ERROR_THRESHOLD_BUFFER_TOKENS: u32 = 20_000;
35pub const MANUAL_COMPACT_BUFFER_TOKENS: u32 = 3_000;
36
37pub const TIME_BASED_MC_GAP_THRESHOLD_MINUTES: u32 = 5;
40
41pub const TIME_BASED_MC_CLEARED_MESSAGE: &str = "[Old tool result content cleared]";
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ThresholdLevel {
51 Normal,
53 Warning,
55 Error,
57 Blocking,
59}
60
61pub fn format_tokens(n: u32) -> String {
67 if n < 1_000 {
68 n.to_string()
69 } else if n < 10_000 {
70 format!("{:.1}K", n as f64 / 1_000.0)
71 } else {
72 format!("{:.0}K", n as f64 / 1_000.0)
73 }
74}
75
76#[derive(Debug, Clone, Default)]
82pub struct CompressionBias {
83 pub preserve_tools: bool,
85 pub preserve_thinking: bool,
87 pub preserve_user_questions: bool,
89 pub compact_long_outputs: bool,
91 pub aggressive: bool,
93 pub preserve_keywords: Vec<String>,
95}
96
97impl CompressionBias {
98 pub fn balanced() -> Self {
100 Self {
101 preserve_tools: true,
102 preserve_thinking: false,
103 preserve_user_questions: true,
104 compact_long_outputs: false,
105 aggressive: false,
106 preserve_keywords: vec![
107 "决定".to_string(),
108 "decision".to_string(),
109 "重要".to_string(),
110 "important".to_string(),
111 "关键".to_string(),
112 "key".to_string(),
113 ],
114 }
115 }
116
117 pub fn preserve_important() -> Self {
119 Self {
120 preserve_tools: true,
121 preserve_thinking: true,
122 preserve_user_questions: true,
123 compact_long_outputs: true,
124 aggressive: false,
125 preserve_keywords: vec![
126 "决定".to_string(),
127 "decision".to_string(),
128 "重要".to_string(),
129 "important".to_string(),
130 "关键".to_string(),
131 "key".to_string(),
132 "完成".to_string(),
133 "done".to_string(),
134 "成功".to_string(),
135 "success".to_string(),
136 ],
137 }
138 }
139
140 pub fn aggressive() -> Self {
142 Self {
143 preserve_tools: false,
144 preserve_thinking: false,
145 preserve_user_questions: false,
146 compact_long_outputs: false,
147 aggressive: true,
148 preserve_keywords: vec![],
149 }
150 }
151
152 pub fn tool_focused() -> Self {
154 Self {
155 preserve_tools: true,
156 preserve_thinking: false,
157 preserve_user_questions: false,
158 compact_long_outputs: false,
159 aggressive: false,
160 preserve_keywords: vec![
161 "工具".to_string(),
162 "tool".to_string(),
163 "执行".to_string(),
164 "execute".to_string(),
165 "文件".to_string(),
166 "file".to_string(),
167 ],
168 }
169 }
170
171 pub fn parse(spec: &str) -> Result<Self> {
173 let spec = spec.trim().to_lowercase();
174
175 if spec == "balanced" || spec == "default" || spec.is_empty() {
176 return Ok(Self::balanced());
177 }
178 if spec == "aggressive" {
179 return Ok(Self::aggressive());
180 }
181 if spec == "preserve_important" || spec == "important" {
182 return Ok(Self::preserve_important());
183 }
184 if spec == "tool_focused" || spec == "tools" {
185 return Ok(Self::tool_focused());
186 }
187
188 let mut bias = Self::default();
190
191 for part in spec.split_whitespace() {
192 if let Some(preserve_list) = part.strip_prefix("preserve:") {
193 for item in preserve_list.split(',') {
194 match item.trim() {
195 "tools" | "tool" => bias.preserve_tools = true,
196 "thinking" | "think" => bias.preserve_thinking = true,
197 "user" | "questions" => bias.preserve_user_questions = true,
198 "compact" | "long" => bias.compact_long_outputs = true,
199 _ => {}
200 }
201 }
202 } else if let Some(keyword_list) = part.strip_prefix("keywords:") {
203 bias.preserve_keywords = keyword_list
204 .split(',')
205 .map(|k| k.trim().to_string())
206 .filter(|k| !k.is_empty())
207 .collect();
208 } else if part == "aggressive" {
209 bias.aggressive = true;
210 }
211 }
212
213 Ok(bias)
214 }
215
216 pub fn format(&self) -> String {
218 let mut parts: Vec<String> = Vec::new();
219
220 if self.preserve_tools {
221 parts.push("tools".to_string());
222 }
223 if self.preserve_thinking {
224 parts.push("thinking".to_string());
225 }
226 if self.preserve_user_questions {
227 parts.push("user".to_string());
228 }
229 if self.compact_long_outputs {
230 parts.push("compact".to_string());
231 }
232 if self.aggressive {
233 parts.push("aggressive".to_string());
234 }
235
236 if !self.preserve_keywords.is_empty() {
237 parts.push(format!("keywords:{}", self.preserve_keywords.join(",")));
238 }
239
240 if parts.is_empty() {
241 "default".to_string()
242 } else {
243 parts.join(", ")
244 }
245 }
246}
247
248#[derive(Debug, Clone)]
254pub struct CompressionConfig {
255 pub threshold: f64,
257 pub target_ratio: f64,
259 pub min_preserve_messages: usize,
261 pub use_summarization: bool,
263 pub compressor_model: Option<String>,
265 pub bias: CompressionBias,
267}
268
269impl Default for CompressionConfig {
270 fn default() -> Self {
271 Self {
272 threshold: DEFAULT_COMPRESSION_THRESHOLD,
273 target_ratio: DEFAULT_TARGET_RATIO,
274 min_preserve_messages: MIN_MESSAGES_TO_KEEP,
275 use_summarization: true,
276 compressor_model: None,
277 bias: CompressionBias::balanced(),
278 }
279 }
280}
281
282impl CompressionConfig {
283 pub fn compressor_model_name(&self) -> &str {
285 self.compressor_model
286 .as_deref()
287 .unwrap_or(DEFAULT_COMPRESSOR_MODEL)
288 }
289
290 pub fn calculate_threshold_level(
293 token_usage: u32,
294 context_window: u32,
295 ) -> (ThresholdLevel, u32) {
296 let percent_left = if context_window > 0 {
297 ((context_window - token_usage) as f64 / context_window as f64 * 100.0) as u32
298 } else {
299 0
300 };
301
302 let auto_threshold = context_window.saturating_sub(AUTOCOMPACT_BUFFER_TOKENS);
304 let warning_threshold = auto_threshold.saturating_sub(WARNING_THRESHOLD_BUFFER_TOKENS);
305 let error_threshold = auto_threshold.saturating_sub(ERROR_THRESHOLD_BUFFER_TOKENS);
306 let blocking_threshold = context_window.saturating_sub(MANUAL_COMPACT_BUFFER_TOKENS);
307
308 let level = if token_usage >= blocking_threshold {
309 ThresholdLevel::Blocking
310 } else if token_usage >= error_threshold {
311 ThresholdLevel::Error
312 } else if token_usage >= warning_threshold {
313 ThresholdLevel::Warning
314 } else {
315 ThresholdLevel::Normal
316 };
317
318 (level, percent_left.max(0))
319 }
320}
321
322#[derive(Debug, Clone, Default)]
328pub struct CircuitBreakerState {
329 pub consecutive_failures: u32,
331 pub is_tripped: bool,
333 pub last_failure_time: Option<u64>,
335}
336
337impl CircuitBreakerState {
338 pub fn new() -> Self {
340 Self::default()
341 }
342
343 pub fn record_failure(&mut self) -> bool {
345 self.consecutive_failures += 1;
346 self.last_failure_time = Some(std::time::SystemTime::now()
347 .duration_since(std::time::UNIX_EPOCH)
348 .unwrap_or_default()
349 .as_secs());
350
351 if self.consecutive_failures >= MAX_CONSECUTIVE_FAILURES {
352 self.is_tripped = true;
353 return true;
354 }
355 false
356 }
357
358 pub fn record_success(&mut self) {
360 self.consecutive_failures = 0;
361 self.is_tripped = false;
362 self.last_failure_time = None;
363 }
364
365 pub fn should_skip(&self) -> bool {
367 self.is_tripped
368 }
369
370 pub fn reset(&mut self) {
372 self.consecutive_failures = 0;
373 self.is_tripped = false;
374 self.last_failure_time = None;
375 }
376}