use super::ccr;
use crate::core::tokens::count_tokens;
use crate::core::web::distill;
const RESEARCH_PROSE_CAP: usize = 20_000;
const RESEARCH_PROSE_CAP_ENV: &str = "LEAN_CTX_RESEARCH_PROSE_CAP";
fn research_prose_cap() -> usize {
std::env::var(RESEARCH_PROSE_CAP_ENV)
.ok()
.and_then(|v| v.trim().parse::<usize>().ok())
.filter(|cap| *cap > 0)
.unwrap_or(RESEARCH_PROSE_CAP)
}
pub fn compress_tool_result(content: &str, tool_name: Option<&str>) -> String {
let compressed = compress_inner(content, tool_name);
attach_ccr(content, compressed)
}
fn attach_ccr(original: &str, result: String) -> String {
if original.len() < ccr::MIN_TEE_BYTES
|| original.len().saturating_sub(result.len()) < ccr::MIN_TEE_BYTES
{
return result;
}
match ccr::persist(original) {
Some(handle) => match ccr::inband_locator(&handle) {
Some(marker) => format!(
"{result}\n[lean-ctx: full original elided to save tokens — echo {marker} \
on your next turn to get the verbatim original spliced back inline]"
),
None => format!(
"{result}\n[lean-ctx: full original at {handle} — read it, or \
ctx_expand(id=\"{handle}\", head=N|search=\"…\"|json_path=\"…\") for a slice]"
),
},
None => result,
}
}
fn compress_inner(content: &str, tool_name: Option<&str>) -> String {
if content.trim().is_empty() || content.len() < 200 {
return content.to_string();
}
if tool_name.is_some_and(is_lean_ctx_tool) {
return content.to_string();
}
if crate::core::protect::has_markers(content) {
return crate::core::protect::compress_preserving(content, |seg| {
compress_inner(seg, tool_name)
});
}
if is_cited_research_output(content) {
return content.to_string();
}
if extract_command_hint(content).is_none()
&& looks_like_prose(content)
&& let Some(out) = squeeze_research_prose(content)
{
return out;
}
let cmd = infer_command(content, tool_name);
let generic_command = cmd.is_empty() || cmd == "shell";
if generic_command
&& (output_looks_like_test_run(content) || output_looks_like_build_failure(content))
{
return crate::shell::compress::engine::preserve_verbatim_pub(content);
}
crate::shell::compress::engine::compress_if_beneficial(&cmd, content)
}
fn output_looks_like_test_run(content: &str) -> bool {
const NEEDLES: &[&str] = &[
"test result:", "short test summary info", " passed in ", " failed in ", "=== RUN", "--- FAIL:", "--- PASS:", "Test Suites:", " examples, ", "FAILED", ];
NEEDLES.iter().any(|n| content.contains(n))
}
fn output_looks_like_build_failure(content: &str) -> bool {
const NEEDLES: &[&str] = &[
"error[", ": error:", "fatal error:", "undefined reference to", "panicked at", "could not compile", "Traceback (most recent call last)", "AssertionError", "make: ***", "Build FAILED",
"BUILD FAILED",
"Segmentation fault",
];
NEEDLES.iter().any(|n| content.contains(n))
}
fn is_cited_research_output(content: &str) -> bool {
content.contains("· Retrieved: ") && content.contains("\nSource: ")
}
const CODE_SYMBOLS: &str = "{}<>;=|\\$`";
fn looks_like_prose(content: &str) -> bool {
let sample: String = content.chars().take(4000).collect();
let total = sample.chars().count();
if total < 600 {
return false;
}
let total_f = total as f32;
let alpha = sample.chars().filter(|c| c.is_alphabetic()).count() as f32;
let spaces = sample.chars().filter(|c| *c == ' ').count() as f32;
let symbols = sample.chars().filter(|c| CODE_SYMBOLS.contains(*c)).count() as f32;
if alpha / total_f < 0.6 || spaces / total_f < 0.12 || symbols / total_f > 0.06 {
return false;
}
if sample.matches(['.', '!', '?']).count() < 4 {
return false;
}
let non_empty: Vec<&str> = sample.lines().filter(|l| !l.trim().is_empty()).collect();
if non_empty.is_empty() {
return false;
}
let avg_len =
non_empty.iter().map(|l| l.chars().count()).sum::<usize>() as f32 / non_empty.len() as f32;
avg_len >= 40.0
}
fn squeeze_research_prose(content: &str) -> Option<String> {
let before = count_tokens(content);
let squeezed = squeeze_research_prose_body(content);
if squeezed.trim().is_empty() {
return None;
}
let after = count_tokens(&squeezed);
if after + 2 >= before {
return None;
}
Some(crate::core::protocol::append_savings_with_info(
&squeezed,
before,
after,
Some("research"),
None,
))
}
fn squeeze_research_prose_body(content: &str) -> String {
let cap = research_prose_cap();
if content.len() > cap {
return super::prose_ranker::squeeze(content, cap);
}
distill::squeeze_prose(content, cap)
}
fn is_lean_ctx_tool(name: &str) -> bool {
let bare = name
.rsplit("__")
.next()
.unwrap_or(name)
.rsplit([':', '/', '.'])
.next()
.unwrap_or(name);
bare.starts_with("ctx_") || name.starts_with("ctx_")
}
fn infer_command(content: &str, tool_name: Option<&str>) -> String {
if let Some(cmd) = extract_command_hint(content) {
return cmd;
}
if let Some(name) = tool_name {
let nl = name.to_lowercase();
if nl.contains("bash") || nl.contains("shell") || nl.contains("terminal") {
return "shell".to_string();
}
if nl.contains("search") || nl.contains("grep") || nl.contains("find") {
return "grep".to_string();
}
}
String::new()
}
fn extract_command_hint(content: &str) -> Option<String> {
for line in content.lines().take(3) {
let trimmed = line.trim();
if let Some(cmd) = trimmed.strip_prefix("$ ") {
return Some(cmd.to_string());
}
if let Some(cmd) = trimmed.strip_prefix("% ") {
return Some(cmd.to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
fn short_content_unchanged() {
let short = "hello world";
assert_eq!(compress_tool_result(short, None), short);
}
#[test]
fn empty_content_unchanged() {
assert_eq!(compress_tool_result("", None), "");
assert_eq!(compress_tool_result(" ", None), " ");
}
#[test]
fn command_hint_extraction() {
assert_eq!(
extract_command_hint("$ cargo build\nCompiling foo"),
Some("cargo build".to_string())
);
assert_eq!(extract_command_hint("no prefix here"), None);
}
#[test]
fn tool_name_inference() {
assert_eq!(infer_command("some text", Some("bash_execute")), "shell");
assert_eq!(infer_command("some text", Some("search_files")), "grep");
assert_eq!(infer_command("some text", Some("unknown_tool")), "");
}
#[test]
fn lean_ctx_tool_results_pass_through_verbatim() {
let raw = (1..=120)
.map(|i| format!("Line {i:04}: the quick brown fox jumps over the lazy dog"))
.collect::<Vec<_>>()
.join("\n");
assert!(raw.len() > 200);
for tool in [
"ctx_shell",
"ctx_read",
"ctx_search",
"ctx_grep",
"mcp__lean-ctx__ctx_shell",
"lean-ctx:ctx_read",
] {
assert_eq!(
compress_tool_result(&raw, Some(tool)),
raw,
"{tool} output must pass through the proxy verbatim"
);
}
assert_ne!(
compress_tool_result(&raw, Some("bash")),
raw,
"foreign-tool output should still be compressed by the proxy"
);
}
#[test]
fn cited_research_output_is_preserved_verbatim() {
let cited = format!(
"Rust is a language.\n\n---\nSource: Rust — https://x.com/a\n\
Site: x.com · Retrieved: 2026-06-06T00:00:00Z\n{}",
"Extra body line that would otherwise be touched. ".repeat(20)
);
assert_eq!(compress_tool_result(&cited, Some("ctx_url_read")), cited);
}
#[test]
fn prose_is_squeezed_and_deduped() {
let para = "Rust is a multi-paradigm systems programming language that \
emphasizes performance, type safety, and fearless concurrency, \
achieving memory safety without a garbage collector at runtime.";
let input = format!("{}\n", [para; 8].join("\n\n"));
assert!(input.len() > 600);
let out = compress_tool_result(&input, Some("web_fetch"));
assert_eq!(out.matches("fearless concurrency").count(), 1);
assert!(out.contains("performance, type safety"));
}
#[test]
#[serial]
fn research_prose_cap_env_overrides_default() {
let _lock = crate::core::data_dir::test_env_lock();
crate::test_env::set_var(RESEARCH_PROSE_CAP_ENV, "1234");
assert_eq!(research_prose_cap(), 1234);
crate::test_env::remove_var(RESEARCH_PROSE_CAP_ENV);
}
#[test]
#[serial]
fn research_prose_cap_env_invalid_falls_back() {
let _lock = crate::core::data_dir::test_env_lock();
for value in ["", "not_a_number", "0"] {
crate::test_env::set_var(RESEARCH_PROSE_CAP_ENV, value);
assert_eq!(research_prose_cap(), RESEARCH_PROSE_CAP);
}
crate::test_env::remove_var(RESEARCH_PROSE_CAP_ENV);
}
#[test]
fn code_output_is_not_treated_as_prose() {
let code = "fn main() {\n let x = vec![1, 2, 3];\n \
for i in &x { println!(\"{}\", i); }\n}\n"
.repeat(20);
assert!(!looks_like_prose(&code));
}
#[test]
fn shell_log_is_not_treated_as_prose() {
let log = "$ cargo build\n Compiling foo v0.1.0\n Finished dev\n".repeat(20);
assert!(!looks_like_prose(&log));
}
#[test]
fn foreign_shell_build_failure_preserved_verbatim() {
let mut log = String::from("gcc -O2 -c src/versioncmp.c -o versioncmp.o\n");
log.push_str("src/versioncmp.c: In function 'version_cmp':\n");
log.push_str(
"src/versioncmp.c:142:17: error: invalid operands to binary < (have 'char *' and 'int')\n",
);
for i in 0..40 {
log.push_str(&format!(" note: expansion context line {i}\n"));
}
log.push_str("make: *** [Makefile:23: versioncmp.o] Error 1\n");
let out = compress_tool_result(&log, Some("shell"));
assert!(
out.contains("versioncmp.c:142:17: error:"),
"compiler error must survive the proxy"
);
assert!(
out.contains("make: ***"),
"make failure summary must survive"
);
}
#[test]
fn foreign_shell_test_failure_preserved_verbatim() {
let mut log = String::from("running 3 tests\n");
log.push_str("test version::tests::sorts_numeric ... FAILED\n");
for i in 0..40 {
log.push_str(&format!("note line {i} with some filler content here\n"));
}
log.push_str("test result: FAILED. 2 passed; 1 failed; 0 ignored\n");
let out = compress_tool_result(&log, Some("bash"));
assert!(
out.contains("test result: FAILED"),
"test summary must survive the proxy"
);
assert!(out.contains("sorts_numeric ... FAILED"));
}
#[test]
fn plain_shell_log_not_forced_verbatim() {
let log = "Listening on port 8080\nRequest received from 10.0.0.2\n".repeat(20);
assert!(!output_looks_like_test_run(&log));
assert!(!output_looks_like_build_failure(&log));
}
fn big_compressible_log() -> String {
(1..=400)
.map(|i| format!("[info] processed item {i:04} ok"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn live_compression_is_recoverable_via_ccr_handle() {
let _lock = crate::core::data_dir::test_env_lock();
let log = big_compressible_log();
let out = compress_tool_result(&log, Some("bash"));
assert!(
out.len() < log.len(),
"a large foreign log must be compressed"
);
let handle = ccr::persist(&log).expect("same content -> same handle");
assert!(out.contains(&handle), "CCR handle must be embedded: {out}");
let recovered = std::fs::read_to_string(&handle).expect("tee file readable");
assert!(
recovered.contains("processed item 0007 ok")
&& recovered.contains("processed item 0400 ok"),
"verbatim original must be fully recoverable"
);
}
#[test]
fn live_compression_output_is_byte_stable_across_turns() {
let _lock = crate::core::data_dir::test_env_lock();
let log = big_compressible_log();
let a = compress_tool_result(&log, Some("bash"));
let b = compress_tool_result(&log, Some("bash"));
assert_eq!(
a, b,
"the CCR handle is content-addressed, so the rewritten result must be \
byte-identical across turns (provider cache prefix stays valid, #448)"
);
}
#[test]
fn small_or_passthrough_output_gets_no_ccr_handle() {
let _lock = crate::core::data_dir::test_env_lock();
let tiny = "ok\n".repeat(10);
assert!(!compress_tool_result(&tiny, Some("bash")).contains("full original at"));
let raw = (1..=120)
.map(|i| format!("Line {i:04}: lorem ipsum dolor sit amet consectetur"))
.collect::<Vec<_>>()
.join("\n");
let out = compress_tool_result(&raw, Some("ctx_shell"));
assert_eq!(out, raw, "lean-ctx tool result must stay verbatim (no CCR)");
}
}