Skip to main content

deepstrike_core/context/
summarizer.rs

1use crate::context::pressure::PressureAction;
2use crate::types::message::{Message, Role};
3
4pub trait Summarizer: Send + Sync {
5    /// Produce a summary of `messages` that fits within `max_tokens`.
6    fn summarize(&self, messages: &[Message], action: PressureAction, max_tokens: u32) -> String;
7}
8
9/// Deterministic rule-based summariser — no LLM required.
10pub struct RuleSummarizer;
11
12impl Summarizer for RuleSummarizer {
13    fn summarize(&self, messages: &[Message], action: PressureAction, _max_tokens: u32) -> String {
14        let n = messages.len();
15        let tokens: u32 = messages.iter().map(|m| m.token_count.unwrap_or(0)).sum();
16        let mut tool_names: Vec<String> = messages
17            .iter()
18            .flat_map(|m| m.tool_calls.iter().map(|tc| tc.name.to_string()))
19            .collect();
20        tool_names.sort();
21        tool_names.dedup();
22
23        let last_assistant = messages
24            .iter()
25            .rev()
26            .find(|m| m.role == Role::Assistant)
27            .and_then(|m| m.content.as_text())
28            .map(|t| {
29                if t.len() > 200 {
30                    format!("{}...", &t[..200])
31                } else {
32                    t.to_string()
33                }
34            })
35            .unwrap_or_default();
36
37        let action_str = action.label();
38
39        let mut s =
40            format!("[Compressed: {action_str}]\n{n} messages / {tokens} tokens archived\n");
41        if !tool_names.is_empty() {
42            s.push_str(&format!("tools used: {}\n", tool_names.join(", ")));
43        }
44        if !last_assistant.is_empty() {
45            s.push_str(&format!("last assistant output: {last_assistant}"));
46        }
47        s
48    }
49}