Skip to main content

matrixcode_core/compress/
progressive.rs

1//! Progressive Compression Strategy: Multi-stage compression for optimal token management.
2//!
3//! This module implements a progressive compression approach that applies
4//! compression in stages, preserving more information when possible.
5
6use crate::providers::Message;
7use crate::compress::CoherenceDetector;
8use crate::compress::ConversationFocus;
9use crate::compress::complexity::{ComplexityAnalyzer, ComplexityLevel};
10use crate::compress::focus_point::{FocusManager};
11use crate::compress::hardcode_config::HardcodeConfig;
12use anyhow::Result;
13
14/// Progressive compression stages.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CompressionStage {
17    /// Stage 1: Remove low-priority messages (greetings, simple questions)
18    RemoveLowPriority,
19    /// Stage 2: Summarize medium-priority messages
20    SummarizeMedium,
21    /// Stage 3: Compress high-priority but verbose messages
22    CompressHighPriority,
23    /// Stage 4: Emergency compression (aggressive summarization)
24    EmergencyCompression,
25}
26
27impl CompressionStage {
28    /// Get stage name.
29    pub fn name(&self) -> &str {
30        match self {
31            CompressionStage::RemoveLowPriority => "Remove Low Priority",
32            CompressionStage::SummarizeMedium => "Summarize Medium",
33            CompressionStage::CompressHighPriority => "Compress High Priority",
34            CompressionStage::EmergencyCompression => "Emergency Compression",
35        }
36    }
37
38    /// Get stage priority (higher = more aggressive).
39    pub fn priority(&self) -> u8 {
40        match self {
41            CompressionStage::RemoveLowPriority => 1,
42            CompressionStage::SummarizeMedium => 2,
43            CompressionStage::CompressHighPriority => 3,
44            CompressionStage::EmergencyCompression => 4,
45        }
46    }
47}
48
49/// Progressive compression controller.
50#[derive(Debug, Clone)]
51pub struct ProgressiveCompressor {
52    /// Coherence detector
53    coherence: CoherenceDetector,
54    /// Focus manager (optional)
55    focus_manager: Option<FocusManager>,
56    /// Configuration
57    config: ProgressiveConfig,
58    /// Hardcode configuration
59    hardcode_config: HardcodeConfig,
60}
61
62/// Configuration for progressive compression.
63#[derive(Debug, Clone)]
64pub struct ProgressiveConfig {
65    /// Token budget target
66    target_budget: u32,
67    /// Threshold to trigger stage 1
68    stage1_threshold: u32,
69    /// Threshold to trigger stage 2
70    stage2_threshold: u32,
71    /// Threshold to trigger stage 3
72    stage3_threshold: u32,
73    /// Threshold to trigger emergency
74    emergency_threshold: u32,
75    /// Preserve last N messages always
76    preserve_last_n: usize,
77    /// Minimum coherence threshold to keep together
78    coherence_threshold: f32,
79}
80
81impl Default for ProgressiveConfig {
82    fn default() -> Self {
83        Self {
84            target_budget: 8000,
85            stage1_threshold: 12000,  // Remove low priority when >12k tokens
86            stage2_threshold: 16000,  // Summarize medium when >16k tokens
87            stage3_threshold: 20000,  // Compress high when >20k tokens
88            emergency_threshold: 25000, // Emergency when >25k tokens
89            preserve_last_n: 3,
90            coherence_threshold: 0.7,
91        }
92    }
93}
94
95impl ProgressiveConfig {
96    /// 创建基于对话复杂度的自适应配置
97    /// 
98    /// # Arguments
99    /// * `messages` - 对话历史
100    /// 
101    /// # Returns
102    /// 根据复杂度调整的压缩阈值配置
103    pub fn adaptive_configure(messages: &[Message]) -> Self {
104        let complexity = ComplexityAnalyzer::analyze(messages);
105        
106        // 基于复杂度动态调整阈值
107        let (stage1, stage2, stage3, emergency, preserve_n) = match complexity {
108            ComplexityLevel::High => {
109                // 技术讨论密集 → 提前压缩,保留更多上下文
110                log::info!("检测到高复杂度对话,采用激进压缩策略");
111                (10000, 14000, 18000, 22000, 5)
112            },
113            ComplexityLevel::Medium => {
114                // 普通对话 → 默认阈值
115                log::info!("检测到中等复杂度对话,采用标准压缩策略");
116                (12000, 16000, 20000, 25000, 3)
117            },
118            ComplexityLevel::Low => {
119                // 闲聊 → 延迟压缩,减少 AI 调用
120                log::info!("检测到低复杂度对话,采用保守压缩策略");
121                (15000, 20000, 25000, 30000, 2)
122            },
123        };
124        
125        Self {
126            target_budget: 8000,
127            stage1_threshold: stage1,
128            stage2_threshold: stage2,
129            stage3_threshold: stage3,
130            emergency_threshold: emergency,
131            preserve_last_n: preserve_n,
132            coherence_threshold: 0.7,
133        }
134    }
135    
136    /// 获取复杂度描述
137    pub fn complexity_description(messages: &[Message]) -> &'static str {
138        let complexity = ComplexityAnalyzer::analyze(messages);
139        ComplexityAnalyzer::complexity_description(complexity)
140    }
141}
142
143impl ProgressiveCompressor {
144    /// Create a new progressive compressor.
145    pub fn new(config: ProgressiveConfig) -> Self {
146        Self {
147            coherence: CoherenceDetector::default(),
148            focus_manager: None,
149            config,
150            hardcode_config: HardcodeConfig::default(),
151        }
152    }
153
154    /// Create with default configuration.
155    pub fn default_config() -> Self {
156        Self::new(ProgressiveConfig::default())
157    }
158    
159    /// 创建自适应配置的压缩器(基于对话复杂度)
160    pub fn adaptive_create(messages: &[Message]) -> Self {
161        let config = ProgressiveConfig::adaptive_configure(messages);
162        let mut instance = Self::new(config);
163        // 根据复杂度配置 hardcode_config
164        let complexity = ComplexityAnalyzer::analyze(messages);
165        instance.hardcode_config = HardcodeConfig::from_complexity(complexity);
166        instance
167    }
168
169    /// Set focus manager.
170    pub fn set_focus_manager(&mut self, manager: FocusManager) {
171        self.focus_manager = Some(manager);
172    }
173    
174    /// Set custom hardcode config.
175    pub fn with_hardcode_config(mut self, config: HardcodeConfig) -> Self {
176        self.hardcode_config = config;
177        self
178    }
179
180    /// Compress messages using progressive strategy.
181    pub async fn compress(&mut self, messages: &[Message], provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
182        let current_tokens = estimate_tokens(messages);
183        
184        // If under budget, no compression needed
185        if current_tokens <= self.config.target_budget {
186            return Ok(messages.to_vec());
187        }
188
189        let mut result = messages.to_vec();
190        let mut applied_stages = Vec::new();
191
192        // Apply stages in order until target budget is reached
193        for stage in &[
194            CompressionStage::RemoveLowPriority,
195            CompressionStage::SummarizeMedium,
196            CompressionStage::CompressHighPriority,
197            CompressionStage::EmergencyCompression,
198        ] {
199            let tokens = estimate_tokens(&result);
200            
201            // Check if we need this stage
202            let threshold = self.get_threshold_for_stage(*stage);
203            if tokens <= threshold && tokens <= self.config.target_budget {
204                break;
205            }
206
207            // Apply stage
208            result = self.apply_stage(result.clone(), *stage, provider).await?;
209            applied_stages.push(*stage);
210
211            // Check if we reached target
212            if estimate_tokens(&result) <= self.config.target_budget {
213                break;
214            }
215        }
216
217        // Log compression result
218        log::info!(
219            "Progressive compression: {} -> {} tokens, stages applied: {}",
220            current_tokens,
221            estimate_tokens(&result),
222            applied_stages.iter().map(|s| s.name()).collect::<Vec<_>>().join(", ")
223        );
224
225        Ok(result)
226    }
227
228    /// Get threshold for a compression stage.
229    fn get_threshold_for_stage(&self, stage: CompressionStage) -> u32 {
230        match stage {
231            CompressionStage::RemoveLowPriority => self.config.stage1_threshold,
232            CompressionStage::SummarizeMedium => self.config.stage2_threshold,
233            CompressionStage::CompressHighPriority => self.config.stage3_threshold,
234            CompressionStage::EmergencyCompression => self.config.emergency_threshold,
235        }
236    }
237
238    /// Apply a single compression stage.
239    async fn apply_stage(
240        &mut self,
241        messages: Vec<Message>,
242        stage: CompressionStage,
243        provider: Option<&dyn crate::providers::Provider>,
244    ) -> Result<Vec<Message>> {
245        match stage {
246            CompressionStage::RemoveLowPriority => {
247                self.remove_low_priority(messages)
248            }
249            CompressionStage::SummarizeMedium => {
250                self.summarize_medium(messages, provider).await
251            }
252            CompressionStage::CompressHighPriority => {
253                self.compress_high_priority(messages, provider).await
254            }
255            CompressionStage::EmergencyCompression => {
256                self.emergency_compress(messages, provider).await
257            }
258        }
259    }
260
261    /// Stage 1: Remove low-priority messages (结合 Focus 相关性).
262    fn remove_low_priority(&self, messages: Vec<Message>) -> Result<Vec<Message>> {
263        let mut result = Vec::new();
264        let mut removed_count = 0;
265
266        // Segment messages by coherence
267        let segments = self.coherence.segment_messages(&messages);
268
269        for segment in segments {
270            // 结合 Focus 相关性的优先级判断
271            let filtered = segment.iter()
272                .enumerate()
273                .filter(|(i, msg)| {
274                    // Always preserve last N messages
275                    if messages.len() - i <= self.config.preserve_last_n {
276                        return true;
277                    }
278                    
279                    // Check Focus relevance if available
280                    if let Some(focus_manager) = &self.focus_manager {
281                        let content = self.get_message_content(msg);
282                        let relevance = self.calculate_message_focus_relevance(&content, focus_manager);
283                        
284                        // High relevance (> 0.7) - always keep
285                        if relevance > 0.7 {
286                            return true;
287                        }
288                        
289                        // Medium relevance (0.3-0.7) - keep if has code/questions
290                        if relevance > 0.3 {
291                            if content.contains("```") || content.contains("?") || content.contains("?") {
292                                return true;
293                            }
294                        }
295                        
296                        // Low relevance (< 0.3) - can remove if short
297                        if content.len() < 50 && !content.contains("```") {
298                            removed_count += 1;
299                            return false;
300                        }
301                        
302                        return true;
303                    } else {
304                        // No focus manager, use simple heuristic
305                        let content = self.get_message_content(msg);
306                        if content.contains("```") || content.contains("function") || content.contains("fn ") {
307                            return true;
308                        }
309                        
310                        if content.contains("?") || content.contains("?") || 
311                           content.to_lowercase().contains("how") || content.to_lowercase().contains("如何") {
312                            return true;
313                        }
314                        
315                        if content.len() < 50 {
316                            removed_count += 1;
317                            return false;
318                        }
319                        
320                        true
321                    }
322                })
323                .map(|(_, msg)| msg.clone())
324                .collect::<Vec<_>>();
325
326            result.extend(filtered);
327        }
328
329        log::debug!("Stage 1: Removed {} low-priority messages", removed_count);
330        Ok(result)
331    }
332    
333    /// Calculate message relevance to active focus points.
334    fn calculate_message_focus_relevance(&self, content: &str, focus_manager: &FocusManager) -> f32 {
335        if let Some(current_focus) = focus_manager.current_focus() {
336            // Calculate relevance to current focus
337            let mut score = 0.0_f32;
338            
339            // Keyword matching
340            let content_lower = content.to_lowercase();
341            for keyword in &current_focus.keywords {
342                if content_lower.contains(&keyword.to_lowercase()) {
343                    score += 0.2;
344                }
345            }
346            
347            // Entity matching (higher weight)
348            for entity in &current_focus.entities {
349                if content_lower.contains(&entity.to_lowercase()) {
350                    score += 0.3;
351                }
352            }
353            
354            // File path matching
355            for file in &current_focus.related_files {
356                if content.contains(&*file.to_string_lossy()) {
357                    score += 0.4;
358                }
359            }
360            
361            // Apply importance weighting
362            score *= current_focus.importance;
363            
364            // Apply confidence weighting
365            score *= current_focus.confidence;
366            
367            return score.min(1.0);
368        }
369        
370        0.5 // Neutral if no active focus
371    }
372
373    /// Stage 2: Summarize medium-priority messages.
374    async fn summarize_medium(&self, messages: Vec<Message>, provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
375        let mut result = Vec::new();
376        let mut summarized_count = 0;
377
378        // Segment messages by coherence
379        let segments = self.coherence.segment_messages(&messages);
380
381        for segment in segments {
382            // Find medium-length messages (100-500 chars)
383            let medium_indices = segment.iter()
384                .enumerate()
385                .filter(|(_, msg)| {
386                    let content = self.get_message_content(msg);
387                    content.len() >= 100 && content.len() <= 500 && !content.contains("```")
388                })
389                .map(|(i, _)| i)
390                .collect::<Vec<_>>();
391
392            if medium_indices.len() >= 2 {
393                // Summarize medium-priority messages together
394                let medium_messages = medium_indices.iter()
395                    .map(|&i| segment[i].clone())
396                    .collect::<Vec<_>>();
397
398                if let Some(p) = provider {
399                    let summary = self.generate_summary(&medium_messages, p).await?;
400                    
401                    // Create summary message
402                    let summary_msg = Message {
403                        role: crate::providers::Role::Assistant,
404                        content: crate::providers::MessageContent::Text(format!("[摘要] {}", summary)),
405                    };
406
407                    // Replace medium messages with summary
408                    let mut new_segment = Vec::new();
409                    for (i, msg) in segment.iter().enumerate() {
410                        if medium_indices.contains(&i) {
411                            if i == medium_indices[0] {
412                                new_segment.push(summary_msg.clone());
413                                summarized_count += medium_indices.len();
414                            }
415                        } else {
416                            new_segment.push(msg.clone());
417                        }
418                    }
419                    result.extend(new_segment);
420                } else {
421                    // No provider, just compress inline
422                    result.extend(self.compress_inline(&segment, &medium_indices));
423                }
424            } else {
425                // No medium-priority messages in this segment
426                result.extend(segment);
427            }
428        }
429
430        log::debug!("Stage 2: Summarized {} medium-priority messages", summarized_count);
431        Ok(result)
432    }
433
434    /// Stage 3: Compress high-priority but verbose messages.
435    async fn compress_high_priority(&self, messages: Vec<Message>, provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
436        let mut result = Vec::new();
437        let mut compressed_count = 0;
438
439        for msg in messages {
440            // Compress verbose messages (> threshold) with code or details
441            let content = self.get_message_content(&msg);
442            if content.len() > self.hardcode_config.code_content_threshold && (content.contains("```") || content.contains("fn ") || content.contains("function")) {
443                if let Some(p) = provider {
444                    let compressed = self.compress_single_message(&msg, p).await?;
445                    result.push(compressed);
446                    compressed_count += 1;
447                } else {
448                    result.push(self.trim_verbose_message(msg));
449                }
450            } else {
451                result.push(msg);
452            }
453        }
454
455        log::debug!("Stage 3: Compressed {} high-priority verbose messages", compressed_count);
456        Ok(result)
457    }
458
459    /// Stage 4: Emergency compression (aggressive).
460    async fn emergency_compress(&self, messages: Vec<Message>, provider: Option<&dyn crate::providers::Provider>) -> Result<Vec<Message>> {
461        // Emergency: Summarize entire conversation
462        if let Some(p) = provider {
463            let emergency_summary = self.generate_emergency_summary(&messages, p).await?;
464            
465            // Keep only last 3 messages + summary
466            let last_n = messages.iter().rev().take(self.config.preserve_last_n).rev().cloned().collect::<Vec<_>>();
467            
468            let summary_msg = Message {
469                role: crate::providers::Role::Assistant,
470                content: crate::providers::MessageContent::Text(format!("[对话摘要] {}", emergency_summary)),
471            };
472
473            let result = vec![summary_msg]
474                .into_iter()
475                .chain(last_n.into_iter())
476                .collect();
477
478            log::warn!("Emergency compression applied: {} messages -> summary + last {}", messages.len(), self.config.preserve_last_n);
479            Ok(result)
480        } else {
481            // No provider, keep only last N messages
482            Ok(messages.iter().rev().take(self.config.preserve_last_n).rev().cloned().collect())
483        }
484    }
485
486    /// Generate summary for a group of messages.
487    async fn generate_summary(&self, messages: &[Message], provider: &dyn crate::providers::Provider) -> Result<String> {
488        let context = messages.iter()
489            .map(|m| match &m.content {
490                crate::providers::MessageContent::Text(text) => text.clone(),
491                crate::providers::MessageContent::Blocks(blocks) => {
492                    blocks.iter()
493                        .filter_map(|b| {
494                            if let crate::providers::ContentBlock::Text { text } = b {
495                                Some(text.clone())
496                            } else {
497                                None
498                            }
499                        })
500                        .collect::<Vec<_>>()
501                        .join("\n")
502                }
503            })
504            .collect::<Vec<_>>()
505            .join("\n\n");
506
507        let request = crate::providers::ChatRequest {
508            messages: vec![crate::providers::Message {
509                role: crate::providers::Role::User,
510                content: crate::providers::MessageContent::Text(format!(
511                    "请简洁总结以下对话要点(不超过200字):\n\n{}",
512                    context
513                )),
514            }],
515            tools: vec![],
516            system: Some("你是对话摘要助手,生成简洁准确的总结。".to_string()),
517            think: false,
518            max_tokens: 300,
519            server_tools: vec![],
520            enable_caching: false,
521        };
522
523        let response = provider.chat(request).await?;
524        
525        let summary = response.content.iter()
526            .filter_map(|b| {
527                if let crate::providers::ContentBlock::Text { text } = b {
528                    Some(text.clone())
529                } else {
530                    None
531                }
532            })
533            .collect::<Vec<_>>()
534            .join("");
535
536        Ok(summary)
537    }
538
539    /// Generate emergency summary for entire conversation.
540    async fn generate_emergency_summary(&self, messages: &[Message], provider: &dyn crate::providers::Provider) -> Result<String> {
541        let context = messages.iter()
542            .map(|m| match &m.content {
543                crate::providers::MessageContent::Text(text) => text.clone(),
544                crate::providers::MessageContent::Blocks(blocks) => {
545                    blocks.iter()
546                        .filter_map(|b| {
547                            if let crate::providers::ContentBlock::Text { text } = b {
548                                Some(text.clone())
549                            } else {
550                                None
551                            }
552                        })
553                        .collect::<Vec<_>>()
554                        .join("\n")
555                }
556            })
557            .collect::<Vec<_>>()
558            .join("\n\n");
559
560        // Truncate if too long
561        let truncated = if context.len() > self.hardcode_config.max_context_length {
562            context.chars().take(self.hardcode_config.max_context_length).collect::<String>()
563        } else {
564            context
565        };
566
567        let request = crate::providers::ChatRequest {
568            messages: vec![crate::providers::Message {
569                role: crate::providers::Role::User,
570                content: crate::providers::MessageContent::Text(format!(
571                    "请生成紧急摘要,包含以下关键信息:\n1. 主要讨论主题\n2. 重要决策\n3. 待解决问题\n4. 当前状态\n\n对话内容:\n{}",
572                    truncated
573                )),
574            }],
575            tools: vec![],
576            system: Some("你是紧急摘要助手,在对话过长时生成关键信息摘要。".to_string()),
577            think: false,
578            max_tokens: 500,
579            server_tools: vec![],
580            enable_caching: false,
581        };
582
583        let response = provider.chat(request).await?;
584        
585        let summary = response.content.iter()
586            .filter_map(|b| {
587                if let crate::providers::ContentBlock::Text { text } = b {
588                    Some(text.clone())
589                } else {
590                    None
591                }
592            })
593            .collect::<Vec<_>>()
594            .join("");
595
596        Ok(summary)
597    }
598
599    /// Compress a single verbose message.
600    async fn compress_single_message(&self, msg: &Message, provider: &dyn crate::providers::Provider) -> Result<Message> {
601        let content = match &msg.content {
602            crate::providers::MessageContent::Text(text) => text.clone(),
603            crate::providers::MessageContent::Blocks(blocks) => {
604                blocks.iter()
605                    .filter_map(|b| {
606                        if let crate::providers::ContentBlock::Text { text } = b {
607                            Some(text.clone())
608                        } else {
609                            None
610                        }
611                    })
612                    .collect::<Vec<_>>()
613                    .join("\n")
614            }
615        };
616
617        // Request concise version
618        let request = crate::providers::ChatRequest {
619            messages: vec![crate::providers::Message {
620                role: crate::providers::Role::User,
621                content: crate::providers::MessageContent::Text(format!(
622                    "请将以下内容精简,保留核心信息:\n\n{}",
623                    content
624                )),
625            }],
626            tools: vec![],
627            system: Some("你是内容精简助手,去除冗余保留要点。".to_string()),
628            think: false,
629            max_tokens: 200,
630            server_tools: vec![],
631            enable_caching: false,
632        };
633
634        let response = provider.chat(request).await?;
635        
636        let compressed = response.content.iter()
637            .filter_map(|b| {
638                if let crate::providers::ContentBlock::Text { text } = b {
639                    Some(text.clone())
640                } else {
641                    None
642                }
643            })
644            .collect::<Vec<_>>()
645            .join("");
646
647        Ok(Message {
648            role: msg.role,
649            content: crate::providers::MessageContent::Text(format!("[精简] {}", compressed)),
650        })
651    }
652
653    /// Get text content from message.
654    fn get_message_content(&self, msg: &Message) -> String {
655        match &msg.content {
656            crate::providers::MessageContent::Text(text) => text.clone(),
657            crate::providers::MessageContent::Blocks(blocks) => {
658                blocks.iter()
659                    .filter_map(|b| {
660                        if let crate::providers::ContentBlock::Text { text } = b {
661                            Some(text.clone())
662                        } else {
663                            None
664                        }
665                    })
666                    .collect::<Vec<_>>()
667                    .join("\n")
668            }
669        }
670    }
671
672    /// Trim verbose message inline (without AI).
673    fn trim_verbose_message(&self, msg: Message) -> Message {
674        let content = match &msg.content {
675            crate::providers::MessageContent::Text(text) => text.clone(),
676            crate::providers::MessageContent::Blocks(blocks) => {
677                blocks.iter()
678                    .filter_map(|b| {
679                        if let crate::providers::ContentBlock::Text { text } = b {
680                            Some(text.clone())
681                        } else {
682                            None
683                        }
684                    })
685                    .collect::<Vec<_>>()
686                    .join("\n")
687            }
688        };
689
690        // Simple trim: keep first N chars
691        let trimmed = if content.len() > self.hardcode_config.max_trimmed_content_length {
692            format!("[精简] {}...", content.chars().take(self.hardcode_config.max_trimmed_content_length).collect::<String>())
693        } else {
694            content
695        };
696
697        Message {
698            role: msg.role,
699            content: crate::providers::MessageContent::Text(trimmed),
700        }
701    }
702
703    /// Compress segments while maintaining coherence and focus priority.
704    ///
705    /// This method is designed to work with the integrated processor,
706    /// compressing pre-segmented messages while considering focus relevance.
707    ///
708    /// # Arguments
709    /// * `segments` - Pre-segmented message groups from CoherenceDetector.
710    /// * `focus` - Current conversation focus.
711    /// * `coherence` - Coherence detector for scoring.
712    ///
713    /// # Returns
714    /// Compressed messages with focus-aware prioritization.
715    pub fn compress_segments(
716        &self,
717        segments: Vec<Vec<Message>>,
718        focus: &ConversationFocus,
719        coherence: &CoherenceDetector,
720    ) -> Result<Vec<Message>> {
721        let mut result = Vec::new();
722
723        for segment in segments {
724            // Calculate coherence score for this segment
725            let coherence_score = coherence.calculate_coherence(&segment);
726
727            // Calculate focus score for this segment
728            let focus_score = self.calculate_segment_focus_score(&segment, focus);
729
730            // Decision logic based on coherence and focus
731            if coherence_score > self.config.coherence_threshold && focus_score > 0.5 {
732                // High coherence + High focus relevance: preserve intact
733                log::debug!(
734                    "Segment preserved: coherence={}, focus={}",
735                    coherence_score, focus_score
736                );
737                result.extend(segment);
738            } else if coherence_score > self.config.coherence_threshold {
739                // High coherence but lower focus: mostly preserve
740                if segment.len() <= 3 {
741                    result.extend(segment);
742                } else {
743                    // Keep first and last, summarize middle
744                    result.push(segment[0].clone());
745                    // Middle messages can be summarized (inline compression)
746                    let middle_indices: Vec<usize> = (1..segment.len() - 1).collect();
747                    let compressed_middle = self.compress_inline(&segment, &middle_indices);
748                    result.extend(compressed_middle.into_iter().skip(1).take(segment.len() - 3));
749                    result.push(segment[segment.len() - 1].clone());
750                }
751            } else if focus_score > 0.5 {
752                // Low coherence but high focus: keep key messages
753                for msg in &segment {
754                    let msg_focus = self.calculate_message_focus_score(msg, focus);
755                    if msg_focus > 0.3 {
756                        result.push(msg.clone());
757                    }
758                }
759            } else {
760                // Low coherence + Low focus: aggressive compression
761                if !segment.is_empty() {
762                    // Create inline summary for the segment
763                    let all_indices: Vec<usize> = (0..segment.len()).collect();
764                    let compressed = self.compress_inline(&segment, &all_indices);
765                    result.extend(compressed);
766                }
767            }
768        }
769
770        Ok(result)
771    }
772
773    /// Calculate focus score for a segment of messages.
774    fn calculate_segment_focus_score(&self, segment: &[Message], focus: &ConversationFocus) -> f32 {
775        if segment.is_empty() {
776            return 0.0;
777        }
778
779        let mut total_score = 0.0;
780        for msg in segment {
781            total_score += self.calculate_message_focus_score(msg, focus);
782        }
783
784        total_score / segment.len() as f32
785    }
786
787    /// Calculate focus score for a single message using keywords.
788    fn calculate_message_focus_score(&self, message: &Message, focus: &ConversationFocus) -> f32 {
789        // Get message content
790        let content = self.get_message_content(message);
791        let content_lower = content.to_lowercase();
792
793        let mut score: f32 = 0.0;
794
795        // Check if message matches current topic
796        if let Some(topic) = &focus.current_topic {
797            let topic_keywords: Vec<&str> = topic.split(", ").collect();
798            for kw in topic_keywords {
799                if content_lower.contains(&kw.to_lowercase()) {
800                    score += 0.2;
801                }
802            }
803        }
804
805        // Check if message matches current question keywords
806        if let Some(question) = &focus.current_question {
807            let question_lower = question.to_lowercase();
808            for word in question_lower.split_whitespace() {
809                if word.len() > 3 && content_lower.contains(word) {
810                    score += 0.1;
811                }
812            }
813        }
814
815        // Check focus manager if available
816        if let Some(focus_manager) = &self.focus_manager {
817            let relevance = self.calculate_message_focus_relevance(&content, focus_manager);
818            score = score.max(relevance);
819        }
820
821        score.min(1.0)
822    }
823
824    /// Compress inline without AI.
825    fn compress_inline(&self, messages: &[Message], indices: &[usize]) -> Vec<Message> {
826        let mut result = Vec::new();
827        let mut summary_parts = Vec::new();
828
829        for (i, msg) in messages.iter().enumerate() {
830            if indices.contains(&i) {
831                // Collect summary parts
832                let content = match &msg.content {
833                    crate::providers::MessageContent::Text(text) => text.chars().take(100).collect::<String>(),
834                    crate::providers::MessageContent::Blocks(_) => "...".to_string(),
835                };
836                summary_parts.push(content);
837                
838                if i == indices[indices.len() - 1] {
839                    // Create summary
840                    let summary = format!("[摘要] {}", summary_parts.join(" | "));
841                    result.push(Message {
842                        role: crate::providers::Role::Assistant,
843                        content: crate::providers::MessageContent::Text(summary),
844                    });
845                }
846            } else {
847                result.push(msg.clone());
848            }
849        }
850
851        result
852    }
853}
854
855/// Estimate tokens in messages (simple approximation).
856fn estimate_tokens(messages: &[Message]) -> u32 {
857    messages.iter()
858        .map(|m| {
859            let content = match &m.content {
860                crate::providers::MessageContent::Text(text) => text.clone(),
861                crate::providers::MessageContent::Blocks(blocks) => {
862                    blocks.iter()
863                        .filter_map(|b| {
864                            if let crate::providers::ContentBlock::Text { text } = b {
865                                Some(text.clone())
866                            } else {
867                                None
868                            }
869                        })
870                        .collect::<Vec<_>>()
871                        .join("\n")
872                }
873            };
874            // Rough estimation: 4 chars per token
875            (content.len() / 4) as u32 + 50 // +50 for metadata
876        })
877        .sum()
878}
879
880#[cfg(test)]
881mod tests {
882    use super::*;
883
884    #[test]
885    fn test_progressive_config_default() {
886        let config = ProgressiveConfig::default();
887        assert_eq!(config.target_budget, 8000);
888        assert_eq!(config.preserve_last_n, 3);
889    }
890
891    #[test]
892    fn test_compressor_creation() {
893        let compressor = ProgressiveCompressor::default_config();
894        assert!(compressor.focus_manager.is_none());
895    }
896
897    #[test]
898    fn test_stage_ordering() {
899        assert!(CompressionStage::RemoveLowPriority.priority() < CompressionStage::SummarizeMedium.priority());
900        assert!(CompressionStage::SummarizeMedium.priority() < CompressionStage::CompressHighPriority.priority());
901        assert!(CompressionStage::CompressHighPriority.priority() < CompressionStage::EmergencyCompression.priority());
902    }
903
904    #[test]
905    fn test_estimate_tokens() {
906        use crate::providers::{Message, MessageContent, Role};
907        
908        let messages = vec![
909            Message {
910                role: Role::User,
911                content: MessageContent::Text("This is a test message".to_string()),
912            },
913        ];
914        
915        let tokens = estimate_tokens(&messages);
916        assert!(tokens > 0);
917    }
918}