pub(crate) mod filters;
pub(crate) mod scenarios;
pub(crate) mod tee;
use super::per_call::ToolExecutionOutcome;
pub(crate) struct ContextPressure {
pub usage_percent: u8,
pub remaining_tokens: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CompressionTier {
Passthrough,
Light,
Standard,
Aggressive,
}
const TIER_LIGHT_TOKENS: u32 = 1_000;
const TIER_STANDARD_TOKENS: u32 = 10_000;
const TIER_AGGRESSIVE_TOKENS: u32 = 25_000;
fn classify_tier(text: &str) -> CompressionTier {
use bamboo_compression::TokenCounter;
let counter = bamboo_compression::TiktokenTokenCounter::default();
let tokens = counter.count_text(text);
if tokens < TIER_LIGHT_TOKENS {
CompressionTier::Passthrough
} else if tokens < TIER_STANDARD_TOKENS {
CompressionTier::Light
} else if tokens < TIER_AGGRESSIVE_TOKENS {
CompressionTier::Standard
} else {
CompressionTier::Aggressive
}
}
fn adjust_tier_for_pressure(
tier: CompressionTier,
pressure: Option<&ContextPressure>,
) -> CompressionTier {
let Some(p) = pressure else {
return tier; };
match p.usage_percent {
0..=49 => {
match tier {
CompressionTier::Light => CompressionTier::Passthrough,
other => other,
}
}
50..=80 => {
tier
}
_ => {
match tier {
CompressionTier::Light => CompressionTier::Standard,
other => other,
}
}
}
}
fn apply_light_compression(raw_result: &str) -> CompressionResult {
let parsed: serde_json::Value = match serde_json::from_str(raw_result) {
Ok(v) => v,
Err(_) => {
let clean = filters::strip_ansi(raw_result);
let collapsed = filters::collapse_blank_lines(&clean);
let deduped = filters::collapse_duplicate_lines(&collapsed, 3);
let was_compressed = deduped.len() != raw_result.len();
return CompressionResult {
compressed: deduped,
was_compressed,
};
}
};
let stdout = parsed.get("stdout").and_then(|v| v.as_str()).unwrap_or("");
let stderr = parsed.get("stderr").and_then(|v| v.as_str()).unwrap_or("");
let clean_stdout = filters::strip_ansi(stdout);
let clean_stderr = filters::strip_ansi(stderr);
let collapsed_stdout = filters::collapse_blank_lines(&clean_stdout);
let collapsed_stderr = filters::collapse_blank_lines(&clean_stderr);
let deduped_stdout = filters::collapse_duplicate_lines(&collapsed_stdout, 3);
let deduped_stderr = filters::collapse_duplicate_lines(&collapsed_stderr, 3);
let was_compressed = deduped_stdout.len() != stdout.len()
|| deduped_stderr.len() != stderr.len()
|| clean_stdout.len() != stdout.len()
|| clean_stderr.len() != stderr.len();
if !was_compressed {
return CompressionResult {
compressed: raw_result.to_string(),
was_compressed: false,
};
}
let mut obj = parsed.clone();
if let Some(map) = obj.as_object_mut() {
map.insert(
"stdout".to_string(),
serde_json::Value::String(deduped_stdout),
);
map.insert(
"stderr".to_string(),
serde_json::Value::String(deduped_stderr),
);
}
let compressed = serde_json::to_string(&obj).unwrap_or_else(|_| raw_result.to_string());
CompressionResult {
compressed,
was_compressed: true,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OutputScenario {
BashTest,
BashBuild,
BashGit,
BashPackage,
BashGeneric,
ReadCode,
GrepResults,
WebFetchHtml,
PassThrough,
}
pub(crate) fn detect_scenario(tool_name: &str, args_json: &str) -> OutputScenario {
match tool_name {
"Bash" | "BashOutput" => detect_bash_scenario(args_json),
"Read" | "read_file" => OutputScenario::ReadCode,
"Grep" => OutputScenario::GrepResults,
"WebFetch" => OutputScenario::WebFetchHtml,
_ => OutputScenario::PassThrough,
}
}
fn detect_bash_scenario(args_json: &str) -> OutputScenario {
let command = extract_command(args_json);
let cmd = command.trim();
if is_test_command(cmd) {
OutputScenario::BashTest
} else if is_build_command(cmd) {
OutputScenario::BashBuild
} else if is_git_command(cmd) {
OutputScenario::BashGit
} else if is_package_command(cmd) {
OutputScenario::BashPackage
} else {
OutputScenario::BashGeneric
}
}
fn extract_command(args_json: &str) -> String {
serde_json::from_str::<serde_json::Value>(args_json)
.ok()
.and_then(|v| v.get("command").and_then(|c| c.as_str()).map(String::from))
.unwrap_or_default()
}
fn is_test_command(cmd: &str) -> bool {
let patterns = [
"cargo test",
"cargo nextest",
"pytest",
"python -m pytest",
"npm test",
"npm run test",
"npx vitest",
"npx jest",
"yarn test",
"pnpm test",
"go test",
"dotnet test",
"mvn test",
"gradle test",
"./gradlew test",
"phpunit",
"rspec",
"bundle exec rspec",
];
patterns.iter().any(|p| cmd_contains(cmd, p))
}
fn is_build_command(cmd: &str) -> bool {
let patterns = [
"cargo check",
"cargo clippy",
"cargo build",
"tsc",
"eslint",
"npm run build",
"npm run lint",
"npx tsc",
"go build",
"go vet",
"dotnet build",
"dotnet publish",
"mvn compile",
"mvn package",
"mvn verify",
"mvn install",
"mvn clean",
"mvn deploy",
"mvn site",
"gradle build",
"gradle assemble",
"gradle compileJava",
"gradle compileKotlin",
"./gradlew build",
"./gradlew assemble",
"./gradlew compileJava",
"docker build",
"docker compose build",
"docker-compose build",
"terraform plan",
"terraform apply",
"terraform init",
];
patterns.iter().any(|p| cmd_contains(cmd, p))
}
fn is_git_command(cmd: &str) -> bool {
let patterns = [
"git status",
"git diff",
"git log",
"git show",
"git branch",
"git stash",
];
patterns.iter().any(|p| cmd_contains(cmd, p))
}
fn is_package_command(cmd: &str) -> bool {
let patterns = [
"npm install",
"npm ci",
"yarn install",
"yarn add",
"pnpm install",
"pnpm add",
"pip install",
"pip3 install",
"poetry install",
"cargo install",
"go mod download",
"go mod tidy",
"go get ",
"composer install",
"composer update",
"composer require",
"bundle install",
"bundler install",
"gem install",
"apt install",
"apt-get install",
"brew install",
"mvn dependency:",
"gradle dependencies",
"./gradlew dependencies",
"dotnet restore",
"nuget install",
];
patterns.iter().any(|p| cmd_contains(cmd, p))
}
fn cmd_contains(cmd: &str, pattern: &str) -> bool {
cmd.contains(pattern)
}
pub(crate) struct CompressionResult {
pub compressed: String,
pub was_compressed: bool,
}
pub(super) async fn maybe_compress(
tool_name: &str,
args_json: &str,
session_id: &str,
mut outcome: ToolExecutionOutcome,
max_tool_output_tokens: u32,
context_pressure: Option<ContextPressure>,
) -> ToolExecutionOutcome {
let result = match outcome.result {
Ok(ref mut result) => result,
Err(_) => return outcome, };
let scenario = detect_scenario(tool_name, args_json);
if scenario != OutputScenario::PassThrough {
let original = result.result.clone();
let base_tier = classify_tier(&original);
let tier = adjust_tier_for_pressure(base_tier, context_pressure.as_ref());
let compressed = match tier {
CompressionTier::Passthrough => CompressionResult {
compressed: original.clone(),
was_compressed: false,
},
CompressionTier::Light => apply_light_compression(&original),
CompressionTier::Standard | CompressionTier::Aggressive => {
compress_by_scenario(scenario, &original, args_json, tier)
}
};
if compressed.was_compressed {
let tee_note =
tee::tee_save_if_needed(session_id, args_json, &original, &compressed.compressed)
.await;
result.result = match tee_note {
Some(note) => format!("{}\n\n{}", compressed.compressed, note),
None => compressed.compressed,
};
}
}
if max_tool_output_tokens > 0 {
use bamboo_compression::TokenCounter;
let counter = bamboo_compression::TiktokenTokenCounter::default();
let tokens = counter.count_text(&result.result);
if tokens > max_tool_output_tokens {
let truncated =
truncate_to_token_budget(&result.result, max_tool_output_tokens, &counter);
tracing::info!(
"[{}] Tool output truncated: {} tokens > {} limit, {} chars → {} chars",
session_id,
tokens,
max_tool_output_tokens,
result.result.len(),
truncated.len(),
);
result.result = truncated;
}
}
outcome
}
fn truncate_to_token_budget(
text: &str,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
) -> String {
use bamboo_compression::TokenCounter;
let head_tokens = (max_tokens as f64 * 0.7) as u32;
let tail_tokens = max_tokens.saturating_sub(head_tokens);
let head = find_prefix_within_tokens(text, head_tokens, counter);
let tail = find_suffix_within_tokens(text, tail_tokens, counter);
format!(
"{}\n\n[... tool output truncated: {} tokens → {} token budget ...]\n\n{}",
head,
counter.count_text(text),
max_tokens,
tail,
)
}
fn find_prefix_within_tokens(
text: &str,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
) -> String {
use bamboo_compression::TokenCounter;
let mut end = text.len();
for (i, _) in text.char_indices() {
if counter.count_text(&text[..i]) > max_tokens {
end = i;
break;
}
}
text[..end].to_string()
}
fn find_suffix_within_tokens(
text: &str,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
) -> String {
use bamboo_compression::TokenCounter;
let mut start = 0;
for (i, _) in text.char_indices().rev() {
if counter.count_text(&text[i..]) > max_tokens {
break;
}
start = i;
}
text[start..].to_string()
}
fn compress_by_scenario(
scenario: OutputScenario,
raw_result: &str,
_args_json: &str,
tier: CompressionTier,
) -> CompressionResult {
match scenario {
OutputScenario::BashTest => scenarios::bash_test::compress(raw_result, tier),
OutputScenario::BashBuild => scenarios::bash_build::compress(raw_result, tier),
OutputScenario::BashGit => scenarios::bash_git::compress(raw_result, tier),
OutputScenario::BashPackage => scenarios::bash_package::compress(raw_result, tier),
OutputScenario::BashGeneric => scenarios::bash_generic::compress(raw_result, tier),
OutputScenario::ReadCode => scenarios::read_code::compress(raw_result, tier),
OutputScenario::GrepResults => scenarios::grep_results::compress(raw_result, tier),
OutputScenario::WebFetchHtml => scenarios::web_fetch::compress(raw_result, tier),
OutputScenario::PassThrough => CompressionResult {
compressed: raw_result.to_string(),
was_compressed: false,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_cargo_test() {
let args = r#"{"command": "cargo test --workspace"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_npm_test() {
let args = r#"{"command": "npm test"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_pytest() {
let args = r#"{"command": "cd myproject && pytest -v"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_cargo_check() {
let args = r#"{"command": "cargo check"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_cargo_clippy() {
let args = r#"{"command": "cargo clippy -- -D warnings"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_git_status() {
let args = r#"{"command": "git status"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashGit);
}
#[test]
fn detect_git_diff() {
let args = r#"{"command": "git diff --stat"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashGit);
}
#[test]
fn detect_npm_install() {
let args = r#"{"command": "npm install"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn detect_generic_bash() {
let args = r#"{"command": "ls -la"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashGeneric);
}
#[test]
fn detect_chained_cargo_test() {
let args = r#"{"command": "cd bamboo && cargo test --lib"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_read_tool() {
assert_eq!(detect_scenario("Read", "{}"), OutputScenario::ReadCode);
assert_eq!(detect_scenario("read_file", "{}"), OutputScenario::ReadCode);
}
#[test]
fn detect_grep_tool() {
assert_eq!(detect_scenario("Grep", "{}"), OutputScenario::GrepResults);
}
#[test]
fn detect_webfetch_tool() {
assert_eq!(
detect_scenario("WebFetch", "{}"),
OutputScenario::WebFetchHtml
);
}
#[test]
fn detect_passthrough_tools() {
assert_eq!(detect_scenario("Edit", "{}"), OutputScenario::PassThrough);
assert_eq!(detect_scenario("Write", "{}"), OutputScenario::PassThrough);
assert_eq!(detect_scenario("Task", "{}"), OutputScenario::PassThrough);
assert_eq!(
detect_scenario("conclusion_with_options", "{}"),
OutputScenario::PassThrough
);
}
#[test]
fn detect_invalid_args_json() {
assert_eq!(
detect_scenario("Bash", "not-json"),
OutputScenario::BashGeneric
);
}
#[test]
fn detect_mvn_compile() {
let args = r#"{"command": "mvn compile"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_mvn_clean_install() {
let args = r#"{"command": "mvn clean install -DskipTests"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_mvn_package() {
let args = r#"{"command": "mvn package -Dmaven.test.skip=true"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_mvn_verify() {
let args = r#"{"command": "mvn verify"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_mvn_test() {
let args = r#"{"command": "mvn test"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_mvn_dependency() {
let args = r#"{"command": "mvn dependency:tree"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn detect_gradle_build() {
let args = r#"{"command": "gradle build"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_gradlew_build() {
let args = r#"{"command": "./gradlew build"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_gradlew_test() {
let args = r#"{"command": "./gradlew test"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_docker_build() {
let args = r#"{"command": "docker build -t myapp ."}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_terraform_plan() {
let args = r#"{"command": "terraform plan"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashBuild);
}
#[test]
fn detect_go_mod_download() {
let args = r#"{"command": "go mod download"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn detect_composer_install() {
let args = r#"{"command": "composer install"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn detect_bundle_install() {
let args = r#"{"command": "bundle install"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn detect_dotnet_restore() {
let args = r#"{"command": "dotnet restore"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn detect_poetry_install() {
let args = r#"{"command": "poetry install"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashPackage);
}
#[test]
fn pressure_low_skips_light_tier() {
let pressure = ContextPressure {
usage_percent: 30,
remaining_tokens: 100_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Passthrough
);
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Standard, Some(&pressure)),
CompressionTier::Standard
);
}
#[test]
fn pressure_normal_uses_base_tier() {
let pressure = ContextPressure {
usage_percent: 65,
remaining_tokens: 50_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Light
);
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Standard, Some(&pressure)),
CompressionTier::Standard
);
}
#[test]
fn pressure_high_promotes_light_to_standard() {
let pressure = ContextPressure {
usage_percent: 90,
remaining_tokens: 5_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Standard
);
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Passthrough, Some(&pressure)),
CompressionTier::Passthrough
);
}
#[test]
fn pressure_none_uses_base_tier() {
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, None),
CompressionTier::Light
);
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Passthrough, None),
CompressionTier::Passthrough
);
}
#[test]
fn classify_tier_empty_string() {
assert_eq!(classify_tier(""), CompressionTier::Passthrough);
}
#[test]
fn classify_tier_short_string() {
assert_eq!(classify_tier("hello world"), CompressionTier::Passthrough);
}
#[test]
fn classify_tier_aggressive_is_reachable() {
let words: Vec<String> = (0..30_000).map(|i| format!("word{}", i)).collect();
let big = words.join(" ");
let tier = classify_tier(&big);
assert_eq!(tier, CompressionTier::Aggressive);
}
#[test]
fn pressure_boundary_50_percent() {
let pressure = ContextPressure {
usage_percent: 50,
remaining_tokens: 50_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Light
);
}
#[test]
fn pressure_boundary_80_percent() {
let pressure = ContextPressure {
usage_percent: 80,
remaining_tokens: 20_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Light
);
}
#[test]
fn pressure_boundary_81_percent() {
let pressure = ContextPressure {
usage_percent: 81,
remaining_tokens: 19_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Standard
);
}
#[test]
fn pressure_boundary_49_percent() {
let pressure = ContextPressure {
usage_percent: 49,
remaining_tokens: 51_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Passthrough
);
}
#[test]
fn pressure_does_not_change_aggressive() {
let low = ContextPressure {
usage_percent: 10,
remaining_tokens: 90_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Aggressive, Some(&low)),
CompressionTier::Aggressive
);
}
#[test]
fn pressure_does_not_change_standard_at_low() {
let low = ContextPressure {
usage_percent: 10,
remaining_tokens: 90_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Standard, Some(&low)),
CompressionTier::Standard
);
}
#[test]
fn pressure_zero_percent() {
let pressure = ContextPressure {
usage_percent: 0,
remaining_tokens: 200_000,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Passthrough
);
}
#[test]
fn pressure_100_percent() {
let pressure = ContextPressure {
usage_percent: 100,
remaining_tokens: 0,
};
assert_eq!(
adjust_tier_for_pressure(CompressionTier::Light, Some(&pressure)),
CompressionTier::Standard
);
}
#[test]
fn light_compression_plain_text_no_changes() {
let input = "hello\nworld\n";
let result = apply_light_compression(input);
assert!(!result.was_compressed);
}
#[test]
fn light_compression_strips_ansi_plain_text() {
let input = "\x1b[32mgreen text\x1b[0m\n";
let result = apply_light_compression(input);
assert!(result.was_compressed);
assert!(!result.compressed.contains("\x1b["));
}
#[test]
fn light_compression_json_envelope() {
let json = serde_json::json!({
"command": "echo",
"stdout": "\x1b[32mhello\x1b[0m",
"stderr": "",
"exit_code": 0,
})
.to_string();
let result = apply_light_compression(&json);
assert!(result.was_compressed);
assert!(!result.compressed.contains("\x1b["));
let parsed: serde_json::Value = serde_json::from_str(&result.compressed).unwrap();
let stdout_val = parsed["stdout"].as_str().unwrap();
assert!(stdout_val.starts_with("hello"));
}
#[test]
fn light_compression_json_no_ansi_not_compressed() {
let json = serde_json::json!({
"command": "echo",
"stdout": "clean output\n",
"stderr": "no ansi here\n",
"exit_code": 0,
})
.to_string();
let result = apply_light_compression(&json);
assert!(!result.was_compressed);
}
#[test]
fn detect_piped_cargo_test() {
let args = r#"{"command": "cargo test 2>&1 | tee output.log"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
#[test]
fn detect_bashoutput_uses_bash_detection() {
let args = r#"{"command": "git status"}"#;
assert_eq!(detect_scenario("BashOutput", args), OutputScenario::BashGit);
}
#[test]
fn detect_command_with_env_prefix() {
let args = r#"{"command": "CI=true cargo test"}"#;
assert_eq!(detect_scenario("Bash", args), OutputScenario::BashTest);
}
}