ai_agent/utils/
debug_filter.rs1use once_cell::sync::Lazy;
6use regex::Regex;
7
8#[derive(Debug, Clone)]
10pub struct DebugFilter {
11 pub include: Vec<String>,
12 pub exclude: Vec<String>,
13 pub is_exclusive: bool,
14}
15
16pub fn parse_debug_filter(filter_string: Option<&str>) -> Option<DebugFilter> {
22 let filter_string = filter_string?.trim();
23 if filter_string.is_empty() {
24 return None;
25 }
26
27 let filters: Vec<&str> = filter_string
28 .split(',')
29 .map(|f| f.trim())
30 .filter(|f| !f.is_empty())
31 .collect();
32
33 if filters.is_empty() {
35 return None;
36 }
37
38 let has_exclusive: bool = filters.iter().any(|f| f.starts_with('!'));
40 let has_inclusive: bool = filters.iter().any(|f| !f.starts_with('!'));
41
42 if has_exclusive && has_inclusive {
43 return None;
45 }
46
47 let clean_filters: Vec<String> = filters
49 .iter()
50 .map(|f| f.trim_start_matches('!').to_lowercase())
51 .collect();
52
53 Some(DebugFilter {
54 include: if has_exclusive {
55 vec![]
56 } else {
57 clean_filters.clone()
58 },
59 exclude: if has_exclusive { clean_filters } else { vec![] },
60 is_exclusive: has_exclusive,
61 })
62}
63
64pub fn extract_debug_categories(message: &str) -> Vec<String> {
73 let mut categories: Vec<String> = Vec::new();
74
75 static MCP_REGEX: Lazy<Regex> =
77 Lazy::new(|| Regex::new(r#"^MCP server ["']([^"']+)["']"#).unwrap());
78 if let Some(mcp_match) = MCP_REGEX.captures(message) {
79 if let Some(mcp_name) = mcp_match.get(1) {
80 categories.push("mcp".to_string());
81 categories.push(mcp_name.as_str().to_lowercase());
82 }
83 } else {
84 static PREFIX_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([^:[]+):").unwrap());
86 if let Some(prefix_match) = PREFIX_REGEX.captures(message) {
87 if let Some(prefix) = prefix_match.get(1) {
88 categories.push(prefix.as_str().trim().to_lowercase());
89 }
90 }
91 }
92
93 static BRACKET_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\[([^\]]+)]").unwrap());
95 if let Some(bracket_match) = BRACKET_REGEX.captures(message) {
96 if let Some(bracket) = bracket_match.get(1) {
97 categories.push(bracket.as_str().trim().to_lowercase());
98 }
99 }
100
101 if message.to_lowercase().contains("1p event:") {
103 categories.push("1p".to_string());
104 }
105
106 static SECONDARY_REGEX: Lazy<Regex> =
108 Lazy::new(|| Regex::new(r":\s*([^:]+?)(?:\s+(?:type|mode|status|event))?:").unwrap());
109 if let Some(secondary_match) = SECONDARY_REGEX.captures(message) {
110 if let Some(secondary) = secondary_match.get(1) {
111 let secondary = secondary.as_str().trim().to_lowercase();
112 if secondary.len() < 30 && !secondary.contains(' ') {
114 categories.push(secondary);
115 }
116 }
117 }
118
119 categories.sort();
121 categories.dedup();
122 categories
123}
124
125pub fn should_show_debug_categories(categories: &[String], filter: &Option<DebugFilter>) -> bool {
127 let filter = match filter {
129 Some(f) => f,
130 None => return true,
131 };
132
133 if categories.is_empty() {
135 return false;
138 }
139
140 if filter.is_exclusive {
141 !categories.iter().any(|cat| filter.exclude.contains(cat))
143 } else {
144 categories.iter().any(|cat| filter.include.contains(cat))
146 }
147}
148
149pub fn should_show_debug_message(message: &str, filter: &Option<DebugFilter>) -> bool {
152 if filter.is_none() {
154 return true;
155 }
156
157 let categories = extract_debug_categories(message);
159 should_show_debug_categories(&categories, filter)
160}