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