Skip to main content

lean_ctx/tools/
ctx_response.rs

1use crate::core::tokens::count_tokens;
2use crate::tools::CrpMode;
3
4pub fn handle(response: &str, crp_mode: CrpMode) -> String {
5    handle_with_context(response, crp_mode, None)
6}
7
8pub fn handle_with_context(
9    response: &str,
10    crp_mode: CrpMode,
11    input_context: Option<&str>,
12) -> String {
13    let original_tokens = count_tokens(response);
14
15    if original_tokens <= 100 {
16        return response.to_string();
17    }
18
19    let compressed = if crp_mode.is_tdd() {
20        compress_tdd(response, input_context)
21    } else {
22        compress_standard(response, input_context)
23    };
24
25    let compressed_tokens = count_tokens(&compressed);
26    let savings = original_tokens.saturating_sub(compressed_tokens);
27    let pct = if original_tokens > 0 {
28        (savings as f64 / original_tokens as f64 * 100.0) as u32
29    } else {
30        0
31    };
32
33    if savings < 20 {
34        return response.to_string();
35    }
36
37    if crate::core::protocol::savings_footer_visible() {
38        format!(
39            "{compressed}\n[response compressed: {original_tokens}→{compressed_tokens} tok, -{pct}%]"
40        )
41    } else {
42        compressed
43    }
44}
45
46fn compress_standard(text: &str, input_context: Option<&str>) -> String {
47    let echo_lines = input_context.map(build_echo_set);
48
49    let mut result = Vec::new();
50    let mut prev_empty = false;
51
52    for line in text.lines() {
53        let trimmed = line.trim();
54
55        if trimmed.is_empty() {
56            if !prev_empty {
57                result.push(String::new());
58                prev_empty = true;
59            }
60            continue;
61        }
62        prev_empty = false;
63
64        if is_filler_line(trimmed) {
65            continue;
66        }
67        if is_boilerplate_code(trimmed) {
68            continue;
69        }
70        if let Some(ref echoes) = echo_lines {
71            if is_context_echo(trimmed, echoes) {
72                continue;
73            }
74        }
75
76        result.push(line.to_string());
77    }
78
79    result.join("\n")
80}
81
82fn compress_tdd(text: &str, input_context: Option<&str>) -> String {
83    let echo_lines = input_context.map(build_echo_set);
84
85    let mut result = Vec::new();
86    let mut prev_empty = false;
87
88    for line in text.lines() {
89        let trimmed = line.trim();
90
91        if trimmed.is_empty() {
92            if !prev_empty {
93                prev_empty = true;
94            }
95            continue;
96        }
97        prev_empty = false;
98
99        if is_filler_line(trimmed) {
100            continue;
101        }
102        if is_boilerplate_code(trimmed) {
103            continue;
104        }
105        if let Some(ref echoes) = echo_lines {
106            if is_context_echo(trimmed, echoes) {
107                continue;
108            }
109        }
110
111        let compressed = apply_tdd_shortcuts(trimmed);
112        result.push(compressed);
113    }
114
115    result.join("\n")
116}
117
118fn build_echo_set(context: &str) -> std::collections::HashSet<String> {
119    context
120        .lines()
121        .map(normalize_for_echo)
122        .filter(|l| l.len() > 10)
123        .collect()
124}
125
126fn normalize_for_echo(line: &str) -> String {
127    line.trim().to_lowercase().replace(char::is_whitespace, " ")
128}
129
130fn is_context_echo(line: &str, echo_set: &std::collections::HashSet<String>) -> bool {
131    let normalized = normalize_for_echo(line);
132    if normalized.len() <= 10 {
133        return false;
134    }
135    echo_set.contains(&normalized)
136}
137
138fn is_boilerplate_code(line: &str) -> bool {
139    let trimmed = line.trim();
140
141    if trimmed.starts_with("//")
142        && !trimmed.starts_with("// TODO")
143        && !trimmed.starts_with("// FIXME")
144        && !trimmed.starts_with("// SAFETY")
145        && !trimmed.starts_with("// NOTE")
146    {
147        let comment_body = trimmed.trim_start_matches("//").trim();
148        if is_narration_comment(comment_body) {
149            return true;
150        }
151    }
152
153    if trimmed.starts_with('#') && !trimmed.starts_with("#[") && !trimmed.starts_with("#!") {
154        let comment_body = trimmed.trim_start_matches('#').trim();
155        if is_narration_comment(comment_body) {
156            return true;
157        }
158    }
159
160    false
161}
162
163fn is_narration_comment(body: &str) -> bool {
164    let b = body.to_lowercase();
165
166    let what_prefixes = [
167        "import ",
168        "define ",
169        "create ",
170        "set up ",
171        "initialize ",
172        "declare ",
173        "add ",
174        "get ",
175        "return ",
176        "check ",
177        "handle ",
178        "call ",
179        "update ",
180        "increment ",
181        "decrement ",
182        "loop ",
183        "iterate ",
184        "print ",
185        "log ",
186        "convert ",
187        "parse ",
188        "read ",
189        "write ",
190        "send ",
191        "receive ",
192        "validate ",
193        "set ",
194        "start ",
195        "stop ",
196        "open ",
197        "close ",
198        "fetch ",
199        "load ",
200        "save ",
201        "store ",
202        "delete ",
203        "remove ",
204        "calculate ",
205        "compute ",
206        "render ",
207        "display ",
208        "show ",
209        "this function ",
210        "this method ",
211        "this class ",
212        "the following ",
213        "here we ",
214        "now we ",
215    ];
216    if what_prefixes.iter().any(|p| b.starts_with(p)) {
217        return true;
218    }
219
220    let what_patterns = [" the ", " a ", " an "];
221    if b.len() < 60 && what_patterns.iter().all(|p| !b.contains(p)) {
222        return false;
223    }
224    if b.len() < 40
225        && b.split_whitespace().count() <= 5
226        && b.chars().filter(|c| c.is_uppercase()).count() == 0
227    {
228        return false;
229    }
230
231    false
232}
233
234fn is_filler_line(line: &str) -> bool {
235    let l = line.to_lowercase();
236
237    // Preserve lines with genuine information signals
238    if l.starts_with("note:")
239        || l.starts_with("hint:")
240        || l.starts_with("warning:")
241        || l.starts_with("error:")
242        || l.starts_with("however,")
243        || l.starts_with("but ")
244        || l.starts_with("caution:")
245        || l.starts_with("important:")
246    {
247        return false;
248    }
249
250    // H=0 patterns: carry zero task-relevant information
251    let prefix_fillers = [
252        // Narration / preamble
253        "here's what i",
254        "here is what i",
255        "let me explain",
256        "let me walk you",
257        "let me break",
258        "i'll now",
259        "i will now",
260        "i'm going to",
261        "first, let me",
262        "allow me to",
263        // Hedging
264        "i think",
265        "i believe",
266        "i would say",
267        "it seems like",
268        "it looks like",
269        "it appears that",
270        // Meta-commentary
271        "that's a great question",
272        "that's an interesting",
273        "good question",
274        "great question",
275        "sure thing",
276        "sure,",
277        "of course,",
278        "absolutely,",
279        // Transitions (zero-info)
280        "now, let's",
281        "now let's",
282        "next, i'll",
283        "moving on",
284        "going forward",
285        "with that said",
286        "with that in mind",
287        "having said that",
288        "that being said",
289        // Closings
290        "hope this helps",
291        "i hope this",
292        "let me know if",
293        "feel free to",
294        "don't hesitate",
295        "happy to help",
296        // Filler connectives
297        "as you can see",
298        "as we can see",
299        "this is because",
300        "the reason is",
301        "in this case",
302        "in other words",
303        "to summarize",
304        "to sum up",
305        "basically,",
306        "essentially,",
307        "it's worth noting",
308        "it should be noted",
309        "as mentioned",
310        "as i mentioned",
311        // Acknowledgments
312        "understood.",
313        "got it.",
314        "i understand.",
315        "i see.",
316        "right,",
317        "okay,",
318        "ok,",
319    ];
320
321    prefix_fillers.iter().any(|f| l.starts_with(f))
322}
323
324fn apply_tdd_shortcuts(line: &str) -> String {
325    let mut result = line.to_string();
326
327    let replacements = [
328        // Structural
329        ("function", "fn"),
330        ("configuration", "cfg"),
331        ("implementation", "impl"),
332        ("dependencies", "deps"),
333        ("dependency", "dep"),
334        ("request", "req"),
335        ("response", "res"),
336        ("context", "ctx"),
337        ("parameter", "param"),
338        ("argument", "arg"),
339        ("variable", "val"),
340        ("directory", "dir"),
341        ("repository", "repo"),
342        ("application", "app"),
343        ("environment", "env"),
344        ("description", "desc"),
345        ("information", "info"),
346        // Symbols (1 token each, replaces 5-10 tokens of prose)
347        ("returns ", "→ "),
348        ("therefore", "∴"),
349        ("approximately", "≈"),
350        ("successfully", "✓"),
351        ("completed", "✓"),
352        ("failed", "✗"),
353        ("warning", "⚠"),
354        // Operators
355        (" is not ", " ≠ "),
356        (" does not ", " ≠ "),
357        (" equals ", " = "),
358        (" and ", " & "),
359        ("error", "err"),
360        ("module", "mod"),
361        ("package", "pkg"),
362        ("initialize", "init"),
363    ];
364
365    for (from, to) in &replacements {
366        result = result.replace(from, to);
367    }
368
369    result
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn test_filler_detection_original() {
378        assert!(is_filler_line("Here's what I found"));
379        assert!(is_filler_line("Let me explain how this works"));
380        assert!(!is_filler_line("fn main() {}"));
381        assert!(!is_filler_line("Note: important detail"));
382    }
383
384    #[test]
385    fn test_filler_hedging_patterns() {
386        assert!(is_filler_line("I think the issue is here"));
387        assert!(is_filler_line("I believe this is correct"));
388        assert!(is_filler_line("It seems like the problem is"));
389        assert!(is_filler_line("It looks like we need to"));
390        assert!(is_filler_line("It appears that something broke"));
391    }
392
393    #[test]
394    fn test_filler_meta_commentary() {
395        assert!(is_filler_line("That's a great question!"));
396        assert!(is_filler_line("Good question, let me check"));
397        assert!(is_filler_line("Sure thing, I'll do that"));
398        assert!(is_filler_line("Of course, here's the code"));
399        assert!(is_filler_line("Absolutely, that makes sense"));
400    }
401
402    #[test]
403    fn test_filler_closings() {
404        assert!(is_filler_line("Hope this helps!"));
405        assert!(is_filler_line("Let me know if you need more"));
406        assert!(is_filler_line("Feel free to ask questions"));
407        assert!(is_filler_line("Don't hesitate to reach out"));
408        assert!(is_filler_line("Happy to help with anything"));
409    }
410
411    #[test]
412    fn test_filler_transitions() {
413        assert!(is_filler_line("Now, let's move on"));
414        assert!(is_filler_line("Moving on to the next part"));
415        assert!(is_filler_line("Going forward, we should"));
416        assert!(is_filler_line("With that said, here's what"));
417        assert!(is_filler_line("Having said that, let's"));
418    }
419
420    #[test]
421    fn test_filler_acknowledgments() {
422        assert!(is_filler_line("Understood."));
423        assert!(is_filler_line("Got it."));
424        assert!(is_filler_line("I understand."));
425        assert!(is_filler_line("I see."));
426    }
427
428    #[test]
429    fn test_filler_false_positive_protection() {
430        assert!(!is_filler_line("Note: this is critical"));
431        assert!(!is_filler_line("Warning: deprecated API"));
432        assert!(!is_filler_line("Error: connection refused"));
433        assert!(!is_filler_line("However, the edge case fails"));
434        assert!(!is_filler_line("But the second argument is wrong"));
435        assert!(!is_filler_line("Important: do not skip this step"));
436        assert!(!is_filler_line("Caution: this deletes all data"));
437        assert!(!is_filler_line("Hint: use --force flag"));
438        assert!(!is_filler_line("fn validate_token()"));
439        assert!(!is_filler_line("  let result = parse(input);"));
440        assert!(!is_filler_line("The token is expired after 24h"));
441    }
442
443    #[test]
444    fn test_tdd_shortcuts() {
445        let result = apply_tdd_shortcuts("the function returns successfully");
446        assert!(result.contains("fn"));
447        assert!(result.contains("→"));
448        assert!(result.contains("✓"));
449    }
450
451    #[test]
452    fn test_tdd_shortcuts_extended() {
453        let result = apply_tdd_shortcuts("the application environment failed");
454        assert!(result.contains("app"));
455        assert!(result.contains("env"));
456        assert!(result.contains("✗"));
457    }
458
459    #[test]
460    fn test_compress_integration() {
461        let response = "Let me explain how this works.\n\
462            I think this is correct.\n\
463            Hope this helps!\n\
464            \n\
465            The function returns an error when the token is expired.\n\
466            Note: always check the expiry first.";
467
468        let compressed = compress_standard(response, None);
469        assert!(!compressed.contains("Let me explain"));
470        assert!(!compressed.contains("I think"));
471        assert!(!compressed.contains("Hope this helps"));
472        assert!(compressed.contains("error when the token"));
473        assert!(compressed.contains("Note:"));
474    }
475
476    #[test]
477    fn test_echo_detection() {
478        let context = "fn shannon_entropy(text: &str) -> f64 {\n    let freq = HashMap::new();\n}";
479        let response = "Here's the code:\nfn shannon_entropy(text: &str) -> f64 {\n    let freq = HashMap::new();\n}\nI added the new function below.";
480
481        let compressed = compress_standard(response, Some(context));
482        assert!(!compressed.contains("fn shannon_entropy"));
483        assert!(compressed.contains("added the new function"));
484    }
485
486    #[test]
487    fn test_boilerplate_comment_removal() {
488        let response = "// Import the module\nuse std::io;\n// Define the function\nfn main() {}\n// NOTE: important edge case\nlet x = 1;";
489        let compressed = compress_standard(response, None);
490        assert!(!compressed.contains("Import the module"));
491        assert!(!compressed.contains("Define the function"));
492        assert!(compressed.contains("NOTE: important edge case"));
493        assert!(compressed.contains("use std::io"));
494        assert!(compressed.contains("fn main()"));
495    }
496}