deepstrike_core/context/
summarizer.rs1use crate::context::pressure::PressureAction;
2use crate::types::message::{Message, Role};
3
4pub struct RuleSummarizer;
7
8impl RuleSummarizer {
9 pub fn summarize(&self, messages: &[Message], action: PressureAction, _max_tokens: u32) -> String {
11 let n = messages.len();
12 let tokens: u32 = messages.iter().map(|m| m.token_count.unwrap_or(0)).sum();
13 let mut tool_names: Vec<String> = messages
14 .iter()
15 .flat_map(|m| m.tool_calls.iter().map(|tc| tc.name.to_string()))
16 .collect();
17 tool_names.sort();
18 tool_names.dedup();
19
20 let last_assistant = messages
21 .iter()
22 .rev()
23 .find(|m| m.role == Role::Assistant)
24 .and_then(|m| m.content.as_text())
25 .map(|t| {
26 if t.len() > 200 {
27 format!(
30 "{}...",
31 crate::context::text::truncate_bytes_at_char_boundary(t, 200)
32 )
33 } else {
34 t.to_string()
35 }
36 })
37 .unwrap_or_default();
38
39 let action_str = action.label();
40
41 let mut s =
42 format!("[Compressed: {action_str}]\n{n} messages / {tokens} tokens archived\n");
43 if !tool_names.is_empty() {
44 s.push_str(&format!("tools used: {}\n", tool_names.join(", ")));
45 }
46 if !last_assistant.is_empty() {
47 s.push_str(&format!("last assistant output: {last_assistant}"));
48 }
49 s
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::types::message::Content;
57
58 #[test]
61 fn summarize_does_not_panic_on_cjk_boundary() {
62 let long_cjk = "规范".repeat(100);
64 assert!(!long_cjk.is_char_boundary(200));
65
66 let msg = Message {
67 role: Role::Assistant,
68 content: Content::Text(long_cjk),
69 tool_calls: vec![],
70 token_count: None,
71 };
72 let out = RuleSummarizer.summarize(&[msg], PressureAction::AutoCompact, 1000);
73 assert!(out.contains("规范"));
74 assert!(out.contains("..."));
75 }
76}