Skip to main content

winx_code_agent/utils/
output_compress.rs

1//! Conscious compression of noisy shell output.
2//!
3//! The goal is token economy **without losing context**. We never summarize,
4//! paraphrase, or drop unique information. The only thing collapsed is
5//! *mechanical repetition*, which carries no extra meaning:
6//!   - runs of byte-identical consecutive lines  -> one line + an `[×N]` marker
7//!   - runs of 3+ blank lines                    -> a single blank line
8//!
9//! Deliberately conservative choices so we never eat real context:
10//!   - lines that merely differ (e.g. "Test 1 passed" / "Test 2 passed", or a
11//!     compiler's per-file diagnostics) are left untouched — those are content;
12//!   - progress bars that redraw with `\r` are already collapsed upstream by the
13//!     PTY ring buffer, so there's nothing speculative to guess at here;
14//!   - an `[×N]` marker is fully reversible information — the reader knows the
15//!     exact line and how many times it repeated.
16//!
17//! Set `WINX_NO_COMPRESS=1` to disable entirely.
18
19/// Don't bother compressing output shorter than this many lines.
20const MIN_LINES: usize = 30;
21/// Only collapse a run of identical lines once it repeats at least this often.
22const RUN_MIN: usize = 3;
23/// Only return a compressed result if it removes at least this many lines —
24/// otherwise the footer isn't worth the noise.
25const MIN_SAVED_LINES: usize = 8;
26
27/// Collapse mechanical repetition in `output`. Returns `None` when compression
28/// is disabled, the output is too short, or there's nothing meaningful to save —
29/// callers should fall back to the original text in that case.
30pub fn compress_output(output: &str) -> Option<String> {
31    if disabled() {
32        return None;
33    }
34    let lines: Vec<&str> = output.split('\n').collect();
35    if lines.len() < MIN_LINES {
36        return None;
37    }
38
39    let mut out: Vec<String> = Vec::with_capacity(lines.len());
40    let mut saved = 0usize;
41    let mut i = 0;
42    while i < lines.len() {
43        let line = lines[i];
44        let mut j = i + 1;
45        while j < lines.len() && lines[j] == line {
46            j += 1;
47        }
48        let run = j - i;
49
50        if line.trim().is_empty() {
51            // Collapse a run of blank lines to a single blank.
52            out.push(String::new());
53            saved += run - 1;
54        } else if run >= RUN_MIN {
55            // Collapse identical non-blank lines, keeping the count (reversible).
56            out.push(format!("{line}  [winx: ×{run}]"));
57            saved += run - 1;
58        } else {
59            // Distinct content — keep verbatim.
60            for keep in &lines[i..j] {
61                out.push((*keep).to_string());
62            }
63        }
64        i = j;
65    }
66
67    if saved < MIN_SAVED_LINES {
68        return None;
69    }
70
71    let compressed_count = out.len();
72    out.push(format!(
73        "[winx: collapsed {saved} repeated lines ({} → {compressed_count}); \
74         set WINX_NO_COMPRESS=1 to see raw output]",
75        lines.len()
76    ));
77    Some(out.join("\n"))
78}
79
80fn disabled() -> bool {
81    std::env::var("WINX_NO_COMPRESS").is_ok_and(|value| {
82        let value = value.trim();
83        !value.is_empty() && value != "0" && !value.eq_ignore_ascii_case("false")
84    })
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn short_output_is_left_alone() {
93        let out = "line\n".repeat(5);
94        assert!(compress_output(&out).is_none());
95    }
96
97    #[test]
98    fn collapses_identical_run_but_keeps_count() -> Result<(), String> {
99        // 50 identical "retrying..." lines surrounded by distinct content.
100        let mut text = String::from("start\n");
101        for _ in 0..50 {
102            text.push_str("retrying connection...\n");
103        }
104        text.push_str("done\n");
105        let compressed = compress_output(&text).ok_or("should compress")?;
106        assert!(compressed.contains("retrying connection...  [winx: ×50]"));
107        assert!(compressed.contains("start"));
108        assert!(compressed.contains("done"));
109        // the 50 repeats became 1 line + footer
110        assert!(compressed.lines().count() < 10);
111        Ok(())
112    }
113
114    #[test]
115    fn keeps_distinct_lines_that_only_differ_by_number() {
116        use std::fmt::Write as _;
117        // Per-item lines carry real info and must NOT be collapsed.
118        let mut text = String::new();
119        for n in 0..40 {
120            let _ = writeln!(text, "Test {n} passed");
121        }
122        // Nothing identical repeats, so there's nothing to collapse.
123        assert!(compress_output(&text).is_none());
124    }
125
126    #[test]
127    fn disabled_via_env_returns_none() {
128        // Can't safely toggle env in parallel tests; just assert the helper logic
129        // by confirming a compressible payload compresses when env is unset.
130        let text = "spam\n".repeat(40);
131        // (env unset in CI) -> compresses
132        if !disabled() {
133            assert!(compress_output(&text).is_some());
134        }
135    }
136}