pub(crate) mod filters;
pub(crate) mod scenarios;
pub(crate) mod tee;
use super::per_call::ToolExecutionOutcome;
use bamboo_agent_core::tools::parse_tool_args_best_effort;
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) fn truncate_exec_session_output(
raw_result: &str,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
) -> Option<String> {
use bamboo_compression::TokenCounter;
let mut payload = serde_json::from_str::<serde_json::Value>(raw_result).ok()?;
let object = payload.as_object_mut()?;
if !matches!(
object.get("status").and_then(serde_json::Value::as_str),
Some("running" | "completed" | "killed" | "error")
) {
return None;
}
let output = object.get("output")?.as_str()?.to_owned();
let prior_omitted = object
.get("output_omitted_bytes")
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
object.insert("output".into(), serde_json::json!(""));
object.insert(
"output_omitted_bytes".into(),
serde_json::json!(prior_omitted.saturating_add(output.len() as u64)),
);
let mut kept = serde_json::to_string(object).expect("terminal result is serializable");
if counter.count_text(&kept) > max_tokens {
if let Some(feedback) = object.remove("hook_feedback") {
let omitted = feedback
.as_array()
.into_iter()
.flatten()
.filter_map(serde_json::Value::as_str)
.fold(0_u64, |bytes, text| bytes.saturating_add(text.len() as u64));
let prior = object
.get("hook_feedback_omitted_bytes")
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
object.insert(
"hook_feedback_omitted_bytes".into(),
serde_json::json!(prior.saturating_add(omitted)),
);
kept = serde_json::to_string(object).expect("terminal result is serializable");
}
}
if counter.count_text(&kept) > max_tokens && object.remove("polling").is_some() {
kept = serde_json::to_string(object).expect("terminal result is serializable");
}
if counter.count_text(&kept) > max_tokens {
return Some(kept);
}
let mut render = |end: usize| {
object.insert("output".into(), serde_json::json!(&output[..end]));
object.insert(
"output_omitted_bytes".into(),
serde_json::json!(prior_omitted.saturating_add((output.len() - end) as u64)),
);
serde_json::to_string(object).expect("terminal result is serializable")
};
let mut low = 0;
let mut high = output.len();
while low <= high {
let midpoint = low + (high - low) / 2;
let mut end = midpoint;
while !output.is_char_boundary(end) {
end -= 1;
}
let candidate = render(end);
if counter.count_text(&candidate) <= max_tokens {
kept = candidate;
low = midpoint + 1;
} else if midpoint == 0 {
break;
} else {
high = midpoint - 1;
}
}
Some(kept)
}
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>,
task_hint: Option<&TaskCompressionHint>,
) -> ToolExecutionOutcome {
let execution_name = outcome
.portable_tool
.as_ref()
.map(|tool| tool.execution_name())
.unwrap_or(tool_name);
let exec_session = matches!(execution_name, "exec_command" | "write_stdin");
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, &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 {
if exec_session {
if let Some(truncated) =
truncate_exec_session_output(&result.result, max_tool_output_tokens, &counter)
{
result.result = truncated;
return outcome;
}
}
let browser_download = tool_name
.trim()
.rsplit("::")
.next()
.is_some_and(|name| name.eq_ignore_ascii_case("browser"))
&& parse_tool_args_best_effort(args_json)
.0
.get("action")
.and_then(serde_json::Value::as_str)
== Some("download");
if result.success
&& browser_download
&& serde_json::from_str::<serde_json::Value>(&result.result)
.ok()
.is_some_and(|payload| {
payload
.get("data_base64")
.and_then(serde_json::Value::as_str)
.is_some()
})
{
result.success = false;
result.result = serde_json::json!({
"error":"download_result_exceeds_tool_output_budget",
"message":"Browser download bytes exceed the configured tool output token budget",
"limit_tokens":max_tool_output_tokens,
})
.to_string();
return outcome;
}
let truncated = truncate_to_token_budget(
&result.result,
max_tool_output_tokens,
&counter,
task_hint,
);
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
}
#[derive(Debug, Clone, Default)]
pub(crate) struct TaskCompressionHint {
keywords: Vec<String>,
}
impl TaskCompressionHint {
pub(crate) fn from_phrases<I: IntoIterator<Item = String>>(phrases: I) -> Self {
let mut keywords: Vec<String> = Vec::new();
'outer: for phrase in phrases {
for term in phrase.split(|c: char| !c.is_alphanumeric()) {
let term = term.trim().to_lowercase();
if term.len() >= 4 && !keywords.contains(&term) {
keywords.push(term);
}
if keywords.len() >= 32 {
break 'outer;
}
}
}
Self { keywords }
}
pub(crate) fn is_empty(&self) -> bool {
self.keywords.is_empty()
}
fn matches(&self, line: &str) -> bool {
let lower = line.to_lowercase();
self.keywords.iter().any(|k| lower.contains(k.as_str()))
}
}
fn collect_task_relevant_lines(
text: &str,
hint: &TaskCompressionHint,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
) -> Option<String> {
use bamboo_compression::TokenCounter;
if hint.is_empty() || max_tokens == 0 {
return None;
}
let mut kept: Vec<&str> = Vec::new();
let mut used = 0u32;
for line in text.lines() {
if !hint.matches(line) {
continue;
}
let cost = counter.count_text(line).saturating_add(1);
if used.saturating_add(cost) > max_tokens {
break;
}
used += cost;
kept.push(line);
if kept.len() >= 50 {
break;
}
}
if kept.is_empty() {
None
} else {
Some(kept.join("\n"))
}
}
fn truncate_to_token_budget(
text: &str,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
task_hint: Option<&TaskCompressionHint>,
) -> String {
use bamboo_compression::TokenCounter;
let preserved = task_hint.filter(|hint| !hint.is_empty()).and_then(|hint| {
collect_task_relevant_lines(text, hint, (max_tokens as f64 * 0.15) as u32, counter)
});
if let Some(lines) = preserved {
let head_tokens = (max_tokens as f64 * 0.5) as u32;
let tail_tokens = (max_tokens as f64 * 0.2) as u32;
let head = find_prefix_within_tokens(text, head_tokens, counter);
let tail = find_suffix_within_tokens(text, tail_tokens, counter);
return format!(
"{}\n\n[... tool output truncated: {} tokens → {} token budget; task-relevant lines preserved ...]\n{}\n\n{}",
head,
counter.count_text(text),
max_tokens,
lines,
tail,
);
}
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 {
counter.truncate_to_token_prefix(text, max_tokens)
}
fn find_suffix_within_tokens(
text: &str,
max_tokens: u32,
counter: &bamboo_compression::TiktokenTokenCounter,
) -> String {
counter.truncate_to_token_suffix(text, max_tokens)
}
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::super::policy::{ToolPolicyGuard, ToolPolicyPrecheckViolation};
use super::*;
use bamboo_agent_core::tools::{FunctionCall, ToolCall, ToolResult};
fn selected_tool_identity(
execution_name: &str,
) -> Option<super::super::per_call::PortableToolExecution> {
Some(
super::super::per_call::PortableToolExecution::selected_identity_fixture(
execution_name,
),
)
}
#[tokio::test]
async fn exec_session_caps_preserve_protocol_fields_and_count_omitted_utf8_bytes() {
use bamboo_compression::{TiktokenTokenCounter, TokenCounter};
let counter = TiktokenTokenCounter::default();
let text = "界🦀\n\"\\variable output ".repeat(500);
for (name, execution_name) in [
("exec_command", "exec_command"),
("write_stdin", "write_stdin"),
("default::exec_command", "exec_command"),
("default::write_stdin", "write_stdin"),
] {
for status in ["running", "completed", "killed", "error"] {
let mut payload = serde_json::json!({
"status":status,
"output":text,
"output_omitted_bytes":7,
});
if status == "running" {
payload["session_id"] = "retained-exec-handle".into();
} else if status == "completed" {
payload["exit_code"] = 7.into();
} else if status == "error" {
payload["error"] =
"Output capture failed; final status remains available".into();
}
let raw = payload.to_string();
let mut expected_control = payload.clone();
expected_control.as_object_mut().unwrap().remove("output");
expected_control
.as_object_mut()
.unwrap()
.remove("output_omitted_bytes");
for cap in [0, 1, 32, 96, 512] {
let outcome = ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
result: Ok(ToolResult::text(true, raw.clone())),
needs_human: None,
portable_tool: selected_tool_identity(execution_name),
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let result = maybe_compress(name, "{}", "exec-cap", outcome, cap, None, None)
.await
.result
.unwrap();
assert!(result.success);
let mut received: serde_json::Value =
serde_json::from_str(&result.result).unwrap();
let kept = received["output"].as_str().unwrap();
assert!(text.starts_with(kept));
assert_eq!(
received["output_omitted_bytes"].as_u64().unwrap(),
7 + (text.len() - kept.len()) as u64
);
if cap == 0 {
assert_eq!(result.result, raw);
} else if counter.count_text(&result.result) > cap {
assert!(kept.is_empty(), "only fixed metadata may exceed the cap");
} else if cap == 512 {
assert!(!kept.is_empty(), "available output budget should be used");
}
received.as_object_mut().unwrap().remove("output");
received
.as_object_mut()
.unwrap()
.remove("output_omitted_bytes");
assert_eq!(received, expected_control);
}
}
}
}
#[tokio::test]
async fn exec_session_cap_exception_does_not_apply_to_foreign_names_or_other_json() {
let terminal = serde_json::json!({"status":"running", "session_id":"foreign", "output":"variable output ".repeat(500)}).to_string();
let approval = serde_json::json!({"status":"awaiting_permission_approval", "output":"variable output ".repeat(500)}).to_string();
for (name, raw) in [
("vendor::exec_command", &terminal),
("vendor::write_stdin", &terminal),
("exec_command", &approval),
] {
let outcome = ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
result: Ok(ToolResult::text(true, raw.clone())),
needs_human: None,
portable_tool: selected_tool_identity(name),
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let result = maybe_compress(name, "{}", "other-cap", outcome, 1, None, None)
.await
.result
.unwrap();
assert!(result.result.contains("tool output truncated"));
}
}
#[test]
fn exec_session_caps_drop_optional_polling_before_required_metadata_exception() {
use bamboo_compression::{TiktokenTokenCounter, TokenCounter};
let counter = TiktokenTokenCounter::default();
let output = "captured output 界🦀 ".repeat(500);
let polling = serde_json::json!({
"elapsed_ms":90_000, "output_idle_ms":90_000,
"consecutive_empty_readouts":3, "exit_observed":false,
"advisory":"No new output across repeated polls. Check a command-owned progress signal before polling again; silence does not prove completion.",
});
let payload = serde_json::json!({
"status":"running", "session_id":"retained-handle", "output":output,
"polling":polling,
});
for cap in [1, 32, 128, 512] {
let compressed =
truncate_exec_session_output(&payload.to_string(), cap, &counter).unwrap();
let received: serde_json::Value = serde_json::from_str(&compressed).unwrap();
assert_eq!(received["status"], "running");
assert_eq!(received["session_id"], "retained-handle");
let kept = received["output"].as_str().unwrap();
assert!(output.starts_with(kept));
assert_eq!(received["output_omitted_bytes"], output.len() - kept.len());
if counter.count_text(&compressed) > cap {
assert!(kept.is_empty());
assert!(received.get("polling").is_none());
}
if cap == 1 {
assert!(received.get("polling").is_none());
} else if cap == 512 {
assert_eq!(received["polling"], polling);
assert!(counter.count_text(&compressed) <= cap);
}
}
}
#[test]
fn exec_session_caps_drop_optional_feedback_before_required_metadata_exception() {
use bamboo_compression::{TiktokenTokenCounter, TokenCounter};
let counter = TiktokenTokenCounter::default();
for feedback in ["review detail".to_owned(), "review 界🦀 ".repeat(1_000)] {
for cap in [1, 256] {
let output = "captured output ".repeat(1_000);
let payload = serde_json::json!({
"status":"running", "session_id":"retained-handle", "output":output,
"hook_feedback":[feedback], "hook_feedback_omitted_bytes":13,
});
let compressed =
truncate_exec_session_output(&payload.to_string(), cap, &counter).unwrap();
let received: serde_json::Value = serde_json::from_str(&compressed).unwrap();
assert_eq!(received["status"], "running");
assert_eq!(received["session_id"], "retained-handle");
let retained = received["hook_feedback"]
.as_array()
.into_iter()
.flatten()
.map(|text| text.as_str().unwrap().len())
.sum::<usize>();
assert_eq!(
received["hook_feedback_omitted_bytes"].as_u64().unwrap() as usize + retained,
13 + feedback.len()
);
let kept = received["output"].as_str().unwrap();
assert_eq!(received["output_omitted_bytes"], output.len() - kept.len());
if cap == 1 {
assert!(kept.is_empty());
assert!(received.get("hook_feedback").is_none());
} else {
assert!(counter.count_text(&compressed) <= cap);
assert_eq!(
received.get("hook_feedback").is_some(),
feedback.len() < 100
);
}
}
}
}
#[tokio::test]
async fn browser_download_over_tool_budget_returns_valid_no_bytes_failure() {
let raw = serde_json::json!({
"page_epoch":17,
"active_tab_id":"tab-1",
"url":"https://example.test/",
"filename":"archive.bin",
"byte_count":12288,
"sha256":"a".repeat(64),
"data_base64":"QUJD".repeat(4096),
})
.to_string();
let args = r##"{"action":"download","selector":"#link","expected_epoch":17}"##;
let outcome = || ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
result: Ok(ToolResult::text(true, raw.clone())),
needs_human: None,
portable_tool: None,
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
for tool_name in ["browser", "default::browser"] {
for arguments in [
args,
r##"{"action":"download","selector":"#link","expected_epoch":17"##,
] {
let output = maybe_compress(
tool_name,
arguments,
"test-session",
outcome(),
128,
None,
None,
)
.await;
let result = output.result.unwrap();
assert!(
!result.success,
"an over-budget download must not look successful"
);
let payload: serde_json::Value = serde_json::from_str(&result.result).unwrap();
assert_eq!(
payload["error"],
"download_result_exceeds_tool_output_budget"
);
assert_eq!(payload["limit_tokens"], 128);
assert!(payload.get("data_base64").is_none());
}
}
let unlimited = maybe_compress("browser", args, "test-session", outcome(), 0, None, None)
.await
.result
.unwrap();
assert!(unlimited.success);
assert_eq!(unlimited.result, raw);
let other_action = maybe_compress(
"browser",
r#"{"action":"snapshot"}"#,
"test-session",
outcome(),
128,
None,
None,
)
.await
.result
.unwrap();
assert!(other_action.success);
assert_ne!(other_action.result, raw);
}
#[tokio::test]
async fn over_budget_downloads_open_the_three_failure_circuit() {
let args = r##"{"action":"download","selector":"#link","expected_epoch":17"##;
let call = ToolCall {
id: "download-call".into(),
tool_type: "function".into(),
function: FunctionCall {
name: "default::browser".into(),
arguments: args.into(),
},
};
let mut guard = ToolPolicyGuard::default();
for _ in 0..3 {
guard.check_before_execution(&call, 0).unwrap();
let raw = serde_json::json!({ "data_base64": "QUJD".repeat(4096) }).to_string();
let outcome = ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
result: Ok(ToolResult::text(true, raw)),
needs_human: None,
portable_tool: None,
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let compressed = maybe_compress(
"default::browser",
args,
"test-session",
outcome,
128,
None,
None,
)
.await;
assert!(!compressed.result.as_ref().unwrap().success);
guard.observe_outcome(&call, &compressed.result);
}
assert!(matches!(
guard.check_before_execution(&call, 0),
Err(ToolPolicyPrecheckViolation::ToolCircuitOpen {
consecutive_failures: 3,
..
})
));
}
#[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);
}
#[test]
fn truncate_preserves_task_relevant_middle_lines() {
use bamboo_compression::TokenCounter;
let counter = bamboo_compression::TiktokenTokenCounter::default();
let mut lines: Vec<String> = (0..400)
.map(|i| format!("filler line number {i} with some padding words"))
.collect();
lines.insert(
200,
"CRITICAL zephyrqux module test FAILED here".to_string(),
);
let text = lines.join("\n");
let max_tokens = 80;
assert!(
counter.count_text(&text) > max_tokens,
"text must exceed budget"
);
let hint = TaskCompressionHint::from_phrases(vec!["zephyrqux module passes".to_string()]);
let out = truncate_to_token_budget(&text, max_tokens, &counter, Some(&hint));
assert!(
out.contains("task-relevant lines preserved"),
"preserved-section marker present"
);
assert!(
out.contains("zephyrqux"),
"the matching middle line must survive truncation"
);
}
#[test]
fn truncate_without_hint_keeps_original_format() {
let counter = bamboo_compression::TiktokenTokenCounter::default();
let text = (0..400)
.map(|i| format!("line {i} content"))
.collect::<Vec<_>>()
.join("\n");
let out = truncate_to_token_budget(&text, 50, &counter, None);
assert!(out.contains("[... tool output truncated:"));
assert!(!out.contains("task-relevant lines preserved"));
}
#[test]
fn task_hint_extracts_significant_terms_and_matches() {
let hint =
TaskCompressionHint::from_phrases(vec!["All tests pass for module X".to_string()]);
assert!(!hint.is_empty());
assert!(hint.matches("re-running the module checks"));
assert!(!hint.matches("xyz qrs unrelated"));
}
#[test]
fn skill_output_utf8_byte_ceiling_bounds_actual_bundled_and_default_counters() {
use bamboo_compression::{HeuristicTokenCounter, TiktokenTokenCounter, TokenCounter};
let bundled = TiktokenTokenCounter::default();
let heuristic = HeuristicTokenCounter::default();
let ascii = (0..=127).map(char::from).collect::<String>();
let unicode = [
"界🦀é",
"\r\n\0\"\\",
"<|endoftext|><|endofprompt|>",
"\u{10ffff}\u{10000}\u{7ff}",
];
let mut cases = vec![String::new(), ascii.clone(), "a".into(), "🦀".into()];
cases.extend(unicode.iter().map(|s| s.to_string()));
for byte in 0..=127 {
cases.push(char::from(byte).to_string());
}
let maximal = "界🦀\0\r\n\"\\<|endoftext|>".repeat(30_000);
let mut end = (512 * 1024).min(maximal.len());
while !maximal.is_char_boundary(end) {
end -= 1;
}
cases.push(maximal[..end].to_string());
for text in cases {
assert!(text.len() <= 512 * 1024);
for count in [bundled.count_text(&text), heuristic.count_text(&text)] {
assert!(
count as usize <= text.len(),
"actual count {count} > {} bytes",
text.len()
);
}
}
}
#[tokio::test]
async fn skill_output_byte_sized_json_survives_generic_compression_and_zero_cap() {
use bamboo_compression::{TiktokenTokenCounter, TokenCounter};
let raw = serde_json::json!({"contents":"界🦀\0\r\n\"\\<|endoftext|>".repeat(12),"next_cursor":null}).to_string();
let counter = TiktokenTokenCounter::default();
assert!(counter.count_text(&raw) as usize <= raw.len());
for cap in [raw.len() as u32, 0] {
let outcome = ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
portable_tool: None,
result: Ok(ToolResult::text(true, raw.clone())),
needs_human: None,
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let result =
maybe_compress("skills_read", "{}", "output-test", outcome, cap, None, None)
.await
.result
.unwrap();
assert!(result.success);
assert_eq!(result.result, raw);
serde_json::from_str::<serde_json::Value>(&result.result).unwrap();
}
}
#[tokio::test]
async fn skill_output_name_and_failed_ok_result_never_bypass_generic_hard_cap() {
use bamboo_compression::{TiktokenTokenCounter, TokenCounter};
let raw = serde_json::json!({"contents":"🦀\"\\ variable content ".repeat(2_000),"next_cursor":"later"}).to_string();
assert!(TiktokenTokenCounter::default().count_text(&raw) > 128);
for name in [
"skills_read",
"default::skills_read",
"untrusted::skills_read",
"unrelated",
] {
for success in [true, false] {
let outcome = ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
portable_tool: None,
result: Ok(ToolResult::text(success, raw.clone())),
needs_human: None,
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let result = maybe_compress(name, "{}", "output-test", outcome, 128, None, None)
.await
.result
.unwrap();
assert_eq!(result.success, success);
assert_ne!(result.result, raw);
assert!(result.result.contains("tool output truncated"));
}
}
let error = "genuine execution error ".repeat(200);
let outcome = ToolExecutionOutcome {
output_cap: None,
permission_replay_origin: None,
portable_tool: None,
result: Err(error.clone()),
needs_human: None,
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let result = maybe_compress("skills_read", "{}", "output-test", outcome, 1, None, None)
.await
.result
.unwrap_err();
assert_eq!(result, error);
}
#[tokio::test]
async fn scoped_cap_metadata_never_bypasses_generic_compressor_argument_or_error_path() {
let raw = "🦀 variable escaped \" \\ ".repeat(1000);
for observation in [None, Some(None), Some(Some(0)), Some(Some(64))] {
for name in ["skills_read", "ordinary_custom_tool"] {
let make_outcome = |result| ToolExecutionOutcome {
output_cap: observation,
permission_replay_origin: None,
portable_tool: None,
result,
needs_human: None,
post_tool_hook_eligible: true,
managed_running_violation: false,
tool_duration: std::time::Duration::ZERO,
};
let compressed = maybe_compress(
name,
"{}",
"scoped-compressor",
make_outcome(Ok(ToolResult::text(true, raw.clone()))),
64,
None,
None,
)
.await;
assert_eq!(compressed.output_cap, observation);
let result = compressed.result.unwrap();
assert!(result.success);
assert!(result.result.contains("tool output truncated"));
assert_ne!(result.result, raw);
let unbounded = maybe_compress(
name,
"{}",
"scoped-compressor",
make_outcome(Ok(ToolResult::text(true, raw.clone()))),
0,
None,
None,
)
.await;
assert_eq!(unbounded.output_cap, observation);
assert_eq!(unbounded.result.unwrap().result, raw);
let error = "genuine Reader failure".repeat(100);
let failed = maybe_compress(
name,
"{}",
"scoped-compressor",
make_outcome(Err(error.clone())),
1,
None,
None,
)
.await;
assert_eq!(failed.output_cap, observation);
assert_eq!(failed.result.unwrap_err(), error);
}
}
}
}