batty_cli/shim/
meta_detector.rs1use std::collections::VecDeque;
2
3const WINDOW_CAPACITY: usize = 10;
4const REDIRECT_FAILURE_THRESHOLD: u8 = 2;
5const META_PATTERNS: [&str; 5] = [
6 "I apologize",
7 "I cannot",
8 "As an AI",
9 "Let me explain why",
10 "I understand your frustration",
11];
12
13pub struct MetaDetector {
14 window: VecDeque<bool>,
15 redirect_failures: u8,
16}
17
18impl MetaDetector {
19 pub fn new() -> Self {
20 Self {
21 window: VecDeque::with_capacity(WINDOW_CAPACITY),
22 redirect_failures: 0,
23 }
24 }
25
26 pub fn push_line(&mut self, line: &str) -> bool {
27 let is_meta = META_PATTERNS.iter().any(|pattern| line.contains(pattern));
28
29 if self.window.len() == WINDOW_CAPACITY {
30 self.window.pop_front();
31 }
32 self.window.push_back(is_meta);
33
34 if is_meta {
35 self.redirect_failures = self.redirect_failures.saturating_add(1);
36 } else {
37 self.redirect_failures = 0;
38 }
39
40 self.window.iter().filter(|&&flag| flag).count() >= 5
41 }
42
43 pub fn should_restart(&self) -> bool {
44 self.redirect_failures >= REDIRECT_FAILURE_THRESHOLD
45 }
46
47 pub fn reset(&mut self) {
48 self.window.clear();
49 self.redirect_failures = 0;
50 }
51}
52
53impl Default for MetaDetector {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::MetaDetector;
62
63 #[test]
64 fn detects_meta_patterns() {
65 let mut detector = MetaDetector::new();
66
67 assert!(!detector.push_line("I apologize, but I cannot help with that."));
68 assert!(!detector.should_restart());
69 detector.reset();
70 assert!(!detector.push_line("As an AI, I have to refuse."));
71 assert!(!detector.should_restart());
72 detector.push_line("normal output");
73 assert!(!detector.should_restart());
74 }
75
76 #[test]
77 fn triggers_after_five_meta_lines_in_window() {
78 let mut detector = MetaDetector::new();
79
80 for _ in 0..4 {
81 assert!(!detector.push_line("I apologize for the confusion."));
82 }
83 assert!(detector.push_line("Let me explain why this is blocked."));
84
85 detector.reset();
86
87 for _ in 0..5 {
88 detector.push_line("normal output");
89 }
90 for _ in 0..4 {
91 assert!(!detector.push_line("I understand your frustration."));
92 }
93 assert!(detector.push_line("As an AI, I need to stop here."));
94 }
95
96 #[test]
97 fn restart_triggers_after_two_failed_redirects() {
98 let mut detector = MetaDetector::new();
99
100 assert!(!detector.should_restart());
101 detector.push_line("I cannot continue with that request.");
102 assert!(!detector.should_restart());
103 detector.push_line("I apologize, but I cannot do that.");
104 assert!(detector.should_restart());
105
106 detector.reset();
107 assert!(!detector.should_restart());
108 }
109}