Skip to main content

deepseek_rust_cli/agent/
mentions.rs

1use std::{fs, sync::OnceLock};
2
3use regex::Regex;
4
5static MENTION_RE: OnceLock<Regex> = OnceLock::new();
6
7pub fn process_mentions(text: &str) -> String {
8    let mention_re = MENTION_RE.get_or_init(|| Regex::new(r"@([^\n@\s]+)").unwrap());
9
10    let mut result = String::new();
11    let mut last_end = 0;
12
13    for cap in mention_re.captures_iter(text) {
14        let full_match = cap.get(0).unwrap();
15        let path_str = cap[1].trim();
16
17        // Push everything between last match and current match
18        result.push_str(&text[last_end..full_match.start()]);
19
20        // Validate path for security before reading
21        let validated_path = match crate::tools::base::validate_path(path_str) {
22            Ok(p) => p,
23            Err(_) => {
24                result.push_str(full_match.as_str());
25                last_end = full_match.end();
26                continue;
27            }
28        };
29
30        if validated_path.exists() && validated_path.is_file() {
31            if let Ok(content) = fs::read_to_string(&validated_path) {
32                let trimmed = if content.len() > 50000 {
33                    let truncate_at = content
34                        .char_indices()
35                        .nth(50000)
36                        .map(|(i, _)| i)
37                        .unwrap_or(content.len());
38                    format!("{}... (truncated)", &content[..truncate_at])
39                } else {
40                    content
41                };
42                let replacement = format!("@{} (File Content):\n```\n{}\n```", path_str, trimmed);
43                result.push_str(&replacement);
44            } else {
45                result.push_str(full_match.as_str());
46            }
47        } else {
48            result.push_str(full_match.as_str());
49        }
50
51        last_end = full_match.end();
52    }
53
54    // Push remaining text
55    result.push_str(&text[last_end..]);
56    result
57}
58
59#[cfg(test)]
60mod tests {
61    use std::fs;
62
63    use tempfile::TempDir;
64
65    use super::*;
66
67    #[test]
68    fn test_process_mentions_basic() {
69        let dir = TempDir::new_in(".").unwrap();
70        let file_path = dir.path().join("mention.txt");
71        fs::write(&file_path, "file content").unwrap();
72
73        let path_str = file_path.to_str().unwrap();
74        let input = format!("check this file @{}", path_str);
75        let processed = process_mentions(&input);
76
77        assert!(processed.contains("file content"));
78        assert!(processed.contains("check this file"));
79    }
80}