Skip to main content

matrixcode_core/compress/
phase_detector.rs

1//! Conversation phase detection for dynamic weight adjustment.
2//!
3//! Analyzes message history to determine the current conversation phase,
4//! which affects scoring weights during compression.
5
6use crate::providers::{ContentBlock, Message, MessageContent, Role};
7
8use super::types::ConversationPhase;
9
10/// Detector for conversation phase.
11pub struct PhaseDetector;
12
13impl PhaseDetector {
14    /// Detect the current conversation phase from message history.
15    pub fn detect(messages: &[Message]) -> ConversationPhase {
16        // Few messages means initial request phase
17        if messages.len() <= 3 {
18            return ConversationPhase::InitialRequest;
19        }
20
21        // Analyze recent messages (last 10)
22        let recent_start = messages.len().saturating_sub(10);
23        let recent = &messages[recent_start..];
24
25        // Check for tool activity
26        let has_tools = recent.iter().any(|m| has_tool_use(m));
27
28        if has_tools {
29            // Check for finalizing signals
30            if has_finalizing_signals(recent) {
31                return ConversationPhase::Finalizing;
32            }
33            return ConversationPhase::ActiveDevelopment;
34        }
35
36        // Default to initial request if no tools
37        ConversationPhase::InitialRequest
38    }
39
40    /// Detect phase with custom window size.
41    pub fn detect_with_window(messages: &[Message], window_size: usize) -> ConversationPhase {
42        if messages.len() <= 3 {
43            return ConversationPhase::InitialRequest;
44        }
45
46        let recent_start = messages.len().saturating_sub(window_size);
47        let recent = &messages[recent_start..];
48
49        let has_tools = recent.iter().any(|m| has_tool_use(m));
50
51        if has_tools {
52            if has_finalizing_signals(recent) {
53                return ConversationPhase::Finalizing;
54            }
55            return ConversationPhase::ActiveDevelopment;
56        }
57
58        ConversationPhase::InitialRequest
59    }
60}
61
62/// Check if a message contains ToolUse block.
63fn has_tool_use(message: &Message) -> bool {
64    match &message.content {
65        MessageContent::Blocks(blocks) => blocks
66            .iter()
67            .any(|b| matches!(b, ContentBlock::ToolUse { .. })),
68        _ => false,
69    }
70}
71
72/// Check for signals indicating task is nearing completion.
73fn has_finalizing_signals(messages: &[Message]) -> bool {
74    for msg in messages {
75        // Check for ask tool (user decisions)
76        if has_ask_tool(msg) {
77            return true;
78        }
79
80        // Check for todo completion signals
81        if has_todo_completion(msg) {
82            return true;
83        }
84
85        // Check for user confirmation patterns
86        if has_user_confirmation(msg) {
87            return true;
88        }
89    }
90    false
91}
92
93/// Check if message contains ask tool.
94fn has_ask_tool(message: &Message) -> bool {
95    match &message.content {
96        MessageContent::Blocks(blocks) => blocks.iter().any(|b| {
97            if let ContentBlock::ToolUse { name, .. } = b {
98                name == "ask"
99            } else {
100                false
101            }
102        }),
103        MessageContent::Text(text) => text.contains("AskUserQuestion"),
104    }
105}
106
107/// Check if message indicates todo completion.
108fn has_todo_completion(message: &Message) -> bool {
109    match &message.content {
110        MessageContent::Blocks(blocks) => blocks.iter().any(|b| {
111            match b {
112                ContentBlock::ToolUse { name, .. } => name == "todo_write",
113                ContentBlock::ToolResult { content, .. } => {
114                    content.contains("completed") || content.contains("done")
115                }
116                _ => false,
117            }
118        }),
119        MessageContent::Text(text) => {
120            text.contains("任务完成") || text.contains("task completed") || text.contains("all done")
121        }
122    }
123}
124
125/// Check if message contains user confirmation.
126fn has_user_confirmation(message: &Message) -> bool {
127    if message.role != Role::User {
128        return false;
129    }
130
131    match &message.content {
132        MessageContent::Text(text) => {
133            let lower = text.to_lowercase();
134            lower.contains("好的")
135                || lower.contains("可以")
136                || lower.contains("继续")
137                || lower.contains("yes")
138                || lower.contains("ok")
139                || lower.contains("confirm")
140                || lower.contains("done")
141        }
142        _ => false,
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_detect_initial_request() {
152        let messages = vec![
153            Message {
154                role: Role::User,
155                content: MessageContent::Text("Hello".to_string()),
156            },
157        ];
158        assert_eq!(PhaseDetector::detect(&messages), ConversationPhase::InitialRequest);
159    }
160
161    #[test]
162    fn test_detect_active_development() {
163        let messages = vec![
164            Message {
165                role: Role::User,
166                content: MessageContent::Text("Read file".to_string()),
167            },
168            Message {
169                role: Role::Assistant,
170                content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
171                    id: "t1".to_string(),
172                    name: "read".to_string(),
173                    input: serde_json::json!({"path": "test.rs"}),
174                }]),
175            },
176            Message {
177                role: Role::Tool,
178                content: MessageContent::Blocks(vec![ContentBlock::ToolResult {
179                    tool_use_id: "t1".to_string(),
180                    content: "file content".to_string(),
181                }]),
182            },
183        ];
184        assert_eq!(PhaseDetector::detect(&messages), ConversationPhase::InitialRequest);
185    }
186
187    #[test]
188    fn test_detect_finalizing() {
189        let messages = vec![
190            Message {
191                role: Role::User,
192                content: MessageContent::Text("Start task".to_string()),
193            },
194            Message {
195                role: Role::Assistant,
196                content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
197                    id: "t1".to_string(),
198                    name: "read".to_string(),
199                    input: serde_json::json!({"path": "test.rs"}),
200                }]),
201            },
202            Message {
203                role: Role::Assistant,
204                content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
205                    id: "t2".to_string(),
206                    name: "ask".to_string(),
207                    input: serde_json::json!({"question": "Confirm?"}),
208                }]),
209            },
210        ];
211        // With 3 messages, still InitialRequest
212        assert_eq!(PhaseDetector::detect(&messages), ConversationPhase::InitialRequest);
213    }
214}