Skip to main content

matrixcode_core/compress/
config.rs

1//! Compression configuration and bias settings.
2
3use anyhow::Result;
4
5// ============================================================================
6// Constants
7// ============================================================================
8
9/// Compression trigger threshold (percentage of context window).
10pub const DEFAULT_COMPRESSION_THRESHOLD: f64 = 0.75;
11
12/// Minimum messages to keep after compression.
13pub const MIN_MESSAGES_TO_KEEP: usize = 8;
14
15/// Target ratio after compression (keep this fraction of tokens).
16pub const DEFAULT_TARGET_RATIO: f64 = 0.4;
17
18/// Default model for summarization.
19pub const DEFAULT_COMPRESSOR_MODEL: &str = "claude-3-5-haiku-20241022";
20
21// ============================================================================
22// Helper Functions
23// ============================================================================
24
25/// Format token count for display.
26pub fn format_tokens(n: u32) -> String {
27    if n < 1_000 {
28        n.to_string()
29    } else if n < 10_000 {
30        format!("{:.1}K", n as f64 / 1_000.0)
31    } else {
32        format!("{:.0}K", n as f64 / 1_000.0)
33    }
34}
35
36// ============================================================================
37// Compression Bias
38// ============================================================================
39
40/// Compression bias - controls what to prioritize during compression.
41#[derive(Debug, Clone, Default)]
42pub struct CompressionBias {
43    /// Preserve tool calls and their results.
44    pub preserve_tools: bool,
45    /// Preserve thinking blocks.
46    pub preserve_thinking: bool,
47    /// Preserve user questions.
48    pub preserve_user_questions: bool,
49    /// Compact long outputs instead of removing.
50    pub compact_long_outputs: bool,
51    /// Aggressive mode - remove more content.
52    pub aggressive: bool,
53    /// Custom keywords to preserve.
54    pub preserve_keywords: Vec<String>,
55}
56
57impl CompressionBias {
58    /// Default bias - balanced preservation.
59    pub fn balanced() -> Self {
60        Self {
61            preserve_tools: true,
62            preserve_thinking: false,
63            preserve_user_questions: true,
64            compact_long_outputs: false,
65            aggressive: false,
66            preserve_keywords: vec![
67                "决定".to_string(),
68                "decision".to_string(),
69                "重要".to_string(),
70                "important".to_string(),
71                "关键".to_string(),
72                "key".to_string(),
73            ],
74        }
75    }
76
77    /// Preserve all important content.
78    pub fn preserve_important() -> Self {
79        Self {
80            preserve_tools: true,
81            preserve_thinking: true,
82            preserve_user_questions: true,
83            compact_long_outputs: true,
84            aggressive: false,
85            preserve_keywords: vec![
86                "决定".to_string(),
87                "decision".to_string(),
88                "重要".to_string(),
89                "important".to_string(),
90                "关键".to_string(),
91                "key".to_string(),
92                "完成".to_string(),
93                "done".to_string(),
94                "成功".to_string(),
95                "success".to_string(),
96            ],
97        }
98    }
99
100    /// Aggressive compression.
101    pub fn aggressive() -> Self {
102        Self {
103            preserve_tools: false,
104            preserve_thinking: false,
105            preserve_user_questions: false,
106            compact_long_outputs: false,
107            aggressive: true,
108            preserve_keywords: vec![],
109        }
110    }
111
112    /// Focus on preserving tool operations.
113    pub fn tool_focused() -> Self {
114        Self {
115            preserve_tools: true,
116            preserve_thinking: false,
117            preserve_user_questions: false,
118            compact_long_outputs: false,
119            aggressive: false,
120            preserve_keywords: vec![
121                "工具".to_string(),
122                "tool".to_string(),
123                "执行".to_string(),
124                "execute".to_string(),
125                "文件".to_string(),
126                "file".to_string(),
127            ],
128        }
129    }
130
131    /// Parse bias from a string specification.
132    pub fn parse(spec: &str) -> Result<Self> {
133        let spec = spec.trim().to_lowercase();
134
135        if spec == "balanced" || spec == "default" || spec.is_empty() {
136            return Ok(Self::balanced());
137        }
138        if spec == "aggressive" {
139            return Ok(Self::aggressive());
140        }
141        if spec == "preserve_important" || spec == "important" {
142            return Ok(Self::preserve_important());
143        }
144        if spec == "tool_focused" || spec == "tools" {
145            return Ok(Self::tool_focused());
146        }
147
148        // Parse custom specification
149        let mut bias = Self::default();
150
151        for part in spec.split_whitespace() {
152            if let Some(preserve_list) = part.strip_prefix("preserve:") {
153                for item in preserve_list.split(',') {
154                    match item.trim() {
155                        "tools" | "tool" => bias.preserve_tools = true,
156                        "thinking" | "think" => bias.preserve_thinking = true,
157                        "user" | "questions" => bias.preserve_user_questions = true,
158                        "compact" | "long" => bias.compact_long_outputs = true,
159                        _ => {}
160                    }
161                }
162            } else if let Some(keyword_list) = part.strip_prefix("keywords:") {
163                bias.preserve_keywords = keyword_list
164                    .split(',')
165                    .map(|k| k.trim().to_string())
166                    .filter(|k| !k.is_empty())
167                    .collect();
168            } else if part == "aggressive" {
169                bias.aggressive = true;
170            }
171        }
172
173        Ok(bias)
174    }
175
176    /// Format bias for display.
177    pub fn format(&self) -> String {
178        let mut parts: Vec<String> = Vec::new();
179
180        if self.preserve_tools {
181            parts.push("tools".to_string());
182        }
183        if self.preserve_thinking {
184            parts.push("thinking".to_string());
185        }
186        if self.preserve_user_questions {
187            parts.push("user".to_string());
188        }
189        if self.compact_long_outputs {
190            parts.push("compact".to_string());
191        }
192        if self.aggressive {
193            parts.push("aggressive".to_string());
194        }
195
196        if !self.preserve_keywords.is_empty() {
197            parts.push(format!("keywords:{}", self.preserve_keywords.join(",")));
198        }
199
200        if parts.is_empty() {
201            "default".to_string()
202        } else {
203            parts.join(", ")
204        }
205    }
206}
207
208// ============================================================================
209// Compression Configuration
210// ============================================================================
211
212/// Configuration for context compression.
213#[derive(Debug, Clone)]
214pub struct CompressionConfig {
215    /// Threshold (0.0-1.0) at which to trigger compression.
216    pub threshold: f64,
217    /// Maximum tokens to target after compression.
218    pub target_ratio: f64,
219    /// Minimum recent messages to always preserve.
220    pub min_preserve_messages: usize,
221    /// Whether to use AI summarization.
222    pub use_summarization: bool,
223    /// Optional model name for summarization.
224    pub compressor_model: Option<String>,
225    /// Compression bias.
226    pub bias: CompressionBias,
227}
228
229impl Default for CompressionConfig {
230    fn default() -> Self {
231        Self {
232            threshold: DEFAULT_COMPRESSION_THRESHOLD,
233            target_ratio: DEFAULT_TARGET_RATIO,
234            min_preserve_messages: MIN_MESSAGES_TO_KEEP,
235            use_summarization: true,
236            compressor_model: None,
237            bias: CompressionBias::balanced(),
238        }
239    }
240}
241
242impl CompressionConfig {
243    /// Get the compressor model name.
244    pub fn compressor_model_name(&self) -> &str {
245        self.compressor_model
246            .as_deref()
247            .unwrap_or(DEFAULT_COMPRESSOR_MODEL)
248    }
249}