matrixcode_core/compress/
phase_detector.rs1use crate::providers::{ContentBlock, Message, MessageContent, Role};
7
8use super::types::ConversationPhase;
9
10pub struct PhaseDetector;
12
13impl PhaseDetector {
14 pub fn detect(messages: &[Message]) -> ConversationPhase {
16 if messages.len() <= 3 {
18 return ConversationPhase::InitialRequest;
19 }
20
21 let recent_start = messages.len().saturating_sub(10);
23 let recent = &messages[recent_start..];
24
25 let has_tools = recent.iter().any(has_tool_use);
27
28 if has_tools {
29 if has_finalizing_signals(recent) {
31 return ConversationPhase::Finalizing;
32 }
33 return ConversationPhase::ActiveDevelopment;
34 }
35
36 ConversationPhase::InitialRequest
38 }
39
40 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(has_tool_use);
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
62fn 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
72fn has_finalizing_signals(messages: &[Message]) -> bool {
74 for msg in messages {
75 if has_ask_tool(msg) {
77 return true;
78 }
79
80 if has_todo_completion(msg) {
82 return true;
83 }
84
85 if has_user_confirmation(msg) {
87 return true;
88 }
89 }
90 false
91}
92
93fn 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
107fn has_todo_completion(message: &Message) -> bool {
109 match &message.content {
110 MessageContent::Blocks(blocks) => blocks.iter().any(|b| match b {
111 ContentBlock::ToolUse { name, .. } => name == "todo_write",
112 ContentBlock::ToolResult { content, .. } => {
113 content.contains("completed") || content.contains("done")
114 }
115 _ => false,
116 }),
117 MessageContent::Text(text) => {
118 text.contains("任务完成")
119 || text.contains("task completed")
120 || text.contains("all done")
121 }
122 }
123}
124
125fn 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![Message {
153 role: Role::User,
154 content: MessageContent::Text("Hello".to_string()),
155 }];
156 assert_eq!(
157 PhaseDetector::detect(&messages),
158 ConversationPhase::InitialRequest
159 );
160 }
161
162 #[test]
163 fn test_detect_active_development() {
164 let messages = vec![
165 Message {
166 role: Role::User,
167 content: MessageContent::Text("Read file".to_string()),
168 },
169 Message {
170 role: Role::Assistant,
171 content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
172 id: "t1".to_string(),
173 name: "read".to_string(),
174 input: serde_json::json!({"path": "test.rs"}),
175 }]),
176 },
177 Message {
178 role: Role::Tool,
179 content: MessageContent::Blocks(vec![ContentBlock::ToolResult {
180 tool_use_id: "t1".to_string(),
181 content: "file content".to_string(),
182 }]),
183 },
184 ];
185 assert_eq!(
186 PhaseDetector::detect(&messages),
187 ConversationPhase::InitialRequest
188 );
189 }
190
191 #[test]
192 fn test_detect_finalizing() {
193 let messages = vec![
194 Message {
195 role: Role::User,
196 content: MessageContent::Text("Start task".to_string()),
197 },
198 Message {
199 role: Role::Assistant,
200 content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
201 id: "t1".to_string(),
202 name: "read".to_string(),
203 input: serde_json::json!({"path": "test.rs"}),
204 }]),
205 },
206 Message {
207 role: Role::Assistant,
208 content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
209 id: "t2".to_string(),
210 name: "ask".to_string(),
211 input: serde_json::json!({"question": "Confirm?"}),
212 }]),
213 },
214 ];
215 assert_eq!(
217 PhaseDetector::detect(&messages),
218 ConversationPhase::InitialRequest
219 );
220 }
221}