use crate::tools::config_tools::{
BASH_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_WRITE_TOOL_NAME, GLOB_TOOL_NAME,
GREP_TOOL_NAME, NOTEBOOK_EDIT_TOOL_NAME, POWERSHELL_TOOL_NAME, WEB_FETCH_TOOL_NAME,
WEB_SEARCH_TOOL_NAME,
};
use crate::utils::env_utils;
const DEFAULT_MAX_INPUT_TOKENS: usize = 180_000;
const DEFAULT_TARGET_INPUT_TOKENS: usize = 40_000;
fn tools_clearable_results() -> Vec<&'static str> {
vec![
BASH_TOOL_NAME,
POWERSHELL_TOOL_NAME,
GLOB_TOOL_NAME,
GREP_TOOL_NAME,
FILE_READ_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
WEB_SEARCH_TOOL_NAME,
]
}
fn tools_clearable_uses() -> Vec<&'static str> {
vec![
FILE_EDIT_TOOL_NAME,
FILE_WRITE_TOOL_NAME,
NOTEBOOK_EDIT_TOOL_NAME,
]
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContextEditStrategy {
ClearToolUses20250919 {
#[serde(skip_serializing_if = "Option::is_none")]
trigger: Option<TriggerConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
keep: Option<KeepConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
clear_tool_inputs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
exclude_tools: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
clear_at_least: Option<TriggerConfig>,
},
ClearThinking20251015 {
keep: ThinkingKeepConfig,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TriggerConfig {
#[serde(rename = "type")]
pub trigger_type: String,
pub value: usize,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KeepConfig {
#[serde(rename = "type")]
pub keep_type: String,
pub value: usize,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum ThinkingKeepConfig {
KeepAll,
KeepTurns {
#[serde(rename = "type")]
keep_type: String,
value: usize,
},
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ContextManagementConfig {
pub edits: Vec<ContextEditStrategy>,
}
pub struct ContextManagementOptions {
pub has_thinking: bool,
pub is_redact_thinking_active: bool,
pub clear_all_thinking: bool,
}
impl Default for ContextManagementOptions {
fn default() -> Self {
Self {
has_thinking: false,
is_redact_thinking_active: false,
clear_all_thinking: false,
}
}
}
pub fn get_api_context_management(
options: Option<ContextManagementOptions>,
) -> Option<ContextManagementConfig> {
let opts = options.unwrap_or_default();
let mut strategies = Vec::new();
if opts.has_thinking && !opts.is_redact_thinking_active {
let keep = if opts.clear_all_thinking {
ThinkingKeepConfig::KeepTurns {
keep_type: "thinking_turns".to_string(),
value: 1,
}
} else {
ThinkingKeepConfig::KeepAll
};
strategies.push(ContextEditStrategy::ClearThinking20251015 { keep });
}
let use_clear_tool_results =
env_utils::is_env_truthy(std::env::var("USE_API_CLEAR_TOOL_RESULTS").ok().as_deref());
let use_clear_tool_uses =
env_utils::is_env_truthy(std::env::var("USE_API_CLEAR_TOOL_USES").ok().as_deref());
if !use_clear_tool_results && !use_clear_tool_uses {
if strategies.is_empty() {
return None;
}
return Some(ContextManagementConfig { edits: strategies });
}
if use_clear_tool_results {
let trigger_threshold = std::env::var("API_MAX_INPUT_TOKENS")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(DEFAULT_MAX_INPUT_TOKENS);
let keep_target = std::env::var("API_TARGET_INPUT_TOKENS")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(DEFAULT_TARGET_INPUT_TOKENS);
strategies.push(ContextEditStrategy::ClearToolUses20250919 {
trigger: Some(TriggerConfig {
trigger_type: "input_tokens".to_string(),
value: trigger_threshold,
}),
keep: None,
clear_tool_inputs: Some(
tools_clearable_results()
.into_iter()
.map(|s| s.to_string())
.collect(),
),
exclude_tools: None,
clear_at_least: Some(TriggerConfig {
trigger_type: "input_tokens".to_string(),
value: trigger_threshold.saturating_sub(keep_target),
}),
});
}
if use_clear_tool_uses {
let trigger_threshold = std::env::var("API_MAX_INPUT_TOKENS")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(DEFAULT_MAX_INPUT_TOKENS);
let keep_target = std::env::var("API_TARGET_INPUT_TOKENS")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(DEFAULT_TARGET_INPUT_TOKENS);
strategies.push(ContextEditStrategy::ClearToolUses20250919 {
trigger: Some(TriggerConfig {
trigger_type: "input_tokens".to_string(),
value: trigger_threshold,
}),
keep: None,
clear_tool_inputs: None,
exclude_tools: Some(
tools_clearable_uses()
.into_iter()
.map(|s| s.to_string())
.collect(),
),
clear_at_least: Some(TriggerConfig {
trigger_type: "input_tokens".to_string(),
value: trigger_threshold.saturating_sub(keep_target),
}),
});
}
if strategies.is_empty() {
None
} else {
Some(ContextManagementConfig { edits: strategies })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_api_context_management_no_thinking() {
let result = get_api_context_management(None);
assert!(result.is_none());
}
#[test]
fn test_get_api_context_management_with_thinking() {
let opts = ContextManagementOptions {
has_thinking: true,
..Default::default()
};
let result = get_api_context_management(Some(opts));
assert!(result.is_some());
let config = result.unwrap();
assert!(!config.edits.is_empty());
assert!(matches!(
&config.edits[0],
ContextEditStrategy::ClearThinking20251015 { .. }
));
}
#[test]
fn test_get_api_context_management_clear_all_thinking() {
let opts = ContextManagementOptions {
has_thinking: true,
clear_all_thinking: true,
..Default::default()
};
let result = get_api_context_management(Some(opts));
assert!(result.is_some());
let config = result.unwrap();
match &config.edits[0] {
ContextEditStrategy::ClearThinking20251015 { keep } => match keep {
ThinkingKeepConfig::KeepTurns { value, .. } => {
assert_eq!(*value, 1);
}
_ => panic!("Expected KeepTurns"),
},
_ => panic!("Expected ClearThinking20251015"),
}
}
}