use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContextManagement {
pub edits: Vec<ContextEdit>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContextEdit {
#[serde(rename = "clear_tool_uses_20250919")]
ClearToolUses {
#[serde(skip_serializing_if = "Option::is_none")]
trigger: Option<TokenThreshold>,
#[serde(skip_serializing_if = "Option::is_none")]
keep: Option<TokenThreshold>,
#[serde(skip_serializing_if = "Option::is_none")]
clear_at_least: Option<TokenThreshold>,
#[serde(skip_serializing_if = "Option::is_none")]
exclude_tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
clear_tool_inputs: Option<bool>,
},
#[serde(rename = "clear_thinking_20251015")]
ClearThinking {
#[serde(skip_serializing_if = "Option::is_none")]
keep: Option<ThinkingKeep>,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenThreshold {
#[serde(rename = "type")]
pub threshold_type: String,
pub value: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ThinkingKeep {
All(String),
Turns(TokenThreshold),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextManagementResponse {
#[serde(default)]
pub applied_edits: Vec<AppliedEdit>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum AppliedEdit {
#[serde(rename = "clear_tool_uses_20250919")]
ClearToolUses { cleared_tool_uses: u32, cleared_input_tokens: u32 },
#[serde(rename = "clear_thinking_20251015")]
ClearThinking { cleared_thinking_turns: u32, cleared_input_tokens: u32 },
}
impl ContextManagement {
pub fn clear_tool_uses() -> Self {
Self {
edits: vec![ContextEdit::ClearToolUses {
trigger: None,
keep: None,
clear_at_least: None,
exclude_tools: None,
clear_tool_inputs: None,
}],
}
}
pub fn clear_thinking() -> Self {
Self { edits: vec![ContextEdit::ClearThinking { keep: None }] }
}
}
impl TokenThreshold {
pub fn input_tokens(value: u32) -> Self {
Self { threshold_type: "input_tokens".to_string(), value }
}
pub fn tool_uses(value: u32) -> Self {
Self { threshold_type: "tool_uses".to_string(), value }
}
}
impl ThinkingKeep {
pub fn all() -> Self {
Self::All("all".to_string())
}
pub fn turns(n: u32) -> Self {
Self::Turns(TokenThreshold { threshold_type: "thinking_turns".to_string(), value: n })
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn clear_tool_uses_minimal() {
let cm = ContextManagement::clear_tool_uses();
let j = serde_json::to_value(&cm).unwrap();
assert_eq!(j, json!({"edits": [{"type": "clear_tool_uses_20250919"}]}));
}
#[test]
fn clear_tool_uses_full() {
let cm = ContextManagement {
edits: vec![ContextEdit::ClearToolUses {
trigger: Some(TokenThreshold::input_tokens(30000)),
keep: Some(TokenThreshold::tool_uses(3)),
clear_at_least: Some(TokenThreshold::input_tokens(5000)),
exclude_tools: Some(vec!["web_search".to_string()]),
clear_tool_inputs: Some(true),
}],
};
let j = serde_json::to_value(&cm).unwrap();
assert_eq!(j["edits"][0]["trigger"]["value"], 30000);
assert_eq!(j["edits"][0]["exclude_tools"][0], "web_search");
}
#[test]
fn clear_thinking_with_turns() {
let cm = ContextManagement {
edits: vec![ContextEdit::ClearThinking { keep: Some(ThinkingKeep::turns(2)) }],
};
let j = serde_json::to_value(&cm).unwrap();
assert_eq!(
j,
json!({"edits": [{"type": "clear_thinking_20251015", "keep": {"type": "thinking_turns", "value": 2}}]})
);
}
#[test]
fn clear_thinking_keep_all() {
let cm = ContextManagement {
edits: vec![ContextEdit::ClearThinking { keep: Some(ThinkingKeep::all()) }],
};
let j = serde_json::to_value(&cm).unwrap();
assert_eq!(j, json!({"edits": [{"type": "clear_thinking_20251015", "keep": "all"}]}));
}
#[test]
fn applied_edit_roundtrip() {
let j = json!({"type": "clear_tool_uses_20250919", "cleared_tool_uses": 8, "cleared_input_tokens": 50000});
let edit: AppliedEdit = serde_json::from_value(j.clone()).unwrap();
assert_eq!(serde_json::to_value(&edit).unwrap(), j);
}
#[test]
fn combined_strategies() {
let cm = ContextManagement {
edits: vec![
ContextEdit::ClearThinking { keep: Some(ThinkingKeep::turns(2)) },
ContextEdit::ClearToolUses {
trigger: Some(TokenThreshold::input_tokens(50000)),
keep: Some(TokenThreshold::tool_uses(5)),
clear_at_least: None,
exclude_tools: None,
clear_tool_inputs: None,
},
],
};
let j = serde_json::to_value(&cm).unwrap();
assert_eq!(j["edits"].as_array().unwrap().len(), 2);
assert_eq!(j["edits"][0]["type"], "clear_thinking_20251015");
assert_eq!(j["edits"][1]["type"], "clear_tool_uses_20250919");
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompactionMetadata {
pub compacted_token_count: u32,
pub summary_token_count: u32,
pub context_window_remaining: u32,
}