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