Skip to main content

opendev_context/compaction/
mod.rs

1//! Auto-compaction of conversation history when approaching context limits.
2//!
3//! Implements staged context optimization with proactive reduction:
4//! - Sliding window: For 500+ message sessions, keep recent N + compressed summary
5//! - 70%: Warning logged, tracking begins
6//! - 80%: Progressive observation masking + verbose tool output summarization
7//! - 85%: Fast pruning of old tool outputs (skips small outputs < 200 chars)
8//! - 90%: Aggressive masking + trimming
9//! - 99%: Full LLM-powered compaction (summarize middle messages)
10
11mod artifacts;
12mod compactor;
13mod levels;
14mod preview;
15mod tokens;
16
17pub use artifacts::{ArtifactEntry, ArtifactIndex};
18pub use compactor::ContextCompactor;
19pub use levels::OptimizationLevel;
20pub use preview::{CompactionPreview, StagePreview, compact_preview};
21pub use tokens::count_tokens;
22
23/// Staged compaction thresholds (fraction of context window).
24pub const STAGE_WARNING: f64 = 0.70;
25pub const STAGE_MASK: f64 = 0.80;
26pub const STAGE_PRUNE: f64 = 0.85;
27pub const STAGE_AGGRESSIVE: f64 = 0.90;
28pub const STAGE_COMPACT: f64 = 0.99;
29
30/// Token budget to protect from pruning (recent tool outputs).
31pub const PRUNE_PROTECTED_TOKENS: u64 = 40_000;
32
33/// Tool types whose outputs survive compaction pruning.
34pub const PROTECTED_TOOL_TYPES: &[&str] = &[
35    "skill",
36    "invoke_skill",
37    "present_plan",
38    "read_file",
39    "web_screenshot",
40    "vlm",
41];
42
43/// Minimum output length below which pruning is skipped (not worth it).
44pub const PRUNE_MIN_LENGTH: usize = 200;
45
46/// Sliding window: number of recent messages to keep verbatim.
47pub const SLIDING_WINDOW_RECENT: usize = 50;
48
49/// Sliding window: message count threshold to activate.
50pub const SLIDING_WINDOW_THRESHOLD: usize = 500;
51
52/// Minimum length of tool output before summarization kicks in.
53pub const TOOL_OUTPUT_SUMMARIZE_THRESHOLD: usize = 500;
54
55/// A message in API format (role + content + optional tool_calls).
56///
57/// This is a lightweight representation for compaction operations,
58/// working with raw JSON-like dicts rather than the full ChatMessage model.
59pub type ApiMessage = serde_json::Map<String, serde_json::Value>;
60
61/// Test helpers shared across sub-module tests.
62#[cfg(test)]
63pub(crate) mod tests {
64    use super::ApiMessage;
65
66    pub fn make_msg(role: &str, content: &str) -> ApiMessage {
67        let mut msg = ApiMessage::new();
68        msg.insert(
69            "role".to_string(),
70            serde_json::Value::String(role.to_string()),
71        );
72        msg.insert(
73            "content".to_string(),
74            serde_json::Value::String(content.to_string()),
75        );
76        msg
77    }
78
79    pub fn make_tool_msg(tool_call_id: &str, content: &str) -> ApiMessage {
80        let mut msg = ApiMessage::new();
81        msg.insert(
82            "role".to_string(),
83            serde_json::Value::String("tool".to_string()),
84        );
85        msg.insert(
86            "tool_call_id".to_string(),
87            serde_json::Value::String(tool_call_id.to_string()),
88        );
89        msg.insert(
90            "content".to_string(),
91            serde_json::Value::String(content.to_string()),
92        );
93        msg
94    }
95
96    pub fn make_assistant_with_tc(tool_calls: Vec<(&str, &str)>) -> ApiMessage {
97        let mut msg = ApiMessage::new();
98        msg.insert(
99            "role".to_string(),
100            serde_json::Value::String("assistant".to_string()),
101        );
102        msg.insert(
103            "content".to_string(),
104            serde_json::Value::String(String::new()),
105        );
106        let tcs: Vec<serde_json::Value> = tool_calls
107            .into_iter()
108            .map(|(id, name)| {
109                serde_json::json!({
110                    "id": id,
111                    "function": { "name": name, "arguments": "{}" }
112                })
113            })
114            .collect();
115        msg.insert("tool_calls".to_string(), serde_json::Value::Array(tcs));
116        msg
117    }
118}