deepseek/agent/builtin_tools/
grep.rs1use async_trait::async_trait;
2use serde_json::{json, Value};
3
4use crate::agent::tool::{Tool, ToolDefinition};
5
6const MAX_MATCHES: usize = 200;
7
8pub struct GrepTool;
9
10#[async_trait]
11impl Tool for GrepTool {
12 fn name(&self) -> &str {
13 "Grep"
14 }
15
16 fn read_only_hint(&self) -> bool {
17 true
18 }
19
20 fn definition(&self) -> ToolDefinition {
21 ToolDefinition {
22 name: self.name().to_string(),
23 description: "Search files for a regex pattern. Output is `path:line:match`, \
24 one per line, capped at 200 matches."
25 .into(),
26 parameters: json!({
27 "type": "object",
28 "properties": {
29 "pattern": { "type": "string", "description": "Rust regex." },
30 "path": { "type": "string", "description": "Root directory (default: cwd)." },
31 "include": { "type": "string", "description": "Optional glob filter, e.g. `*.rs`." }
32 },
33 "required": ["pattern"]
34 }),
35 }
36 }
37
38 async fn call_json(&self, args: Value) -> Result<String, String> {
39 let pattern = args
40 .get("pattern")
41 .and_then(Value::as_str)
42 .ok_or_else(|| "Grep: missing string `pattern`".to_string())?
43 .to_string();
44 let root = args
45 .get("path")
46 .and_then(Value::as_str)
47 .map(String::from)
48 .unwrap_or_else(|| ".".into());
49 let include = args
50 .get("include")
51 .and_then(Value::as_str)
52 .map(String::from);
53
54 let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
55 let re = regex::Regex::new(&pattern)
56 .map_err(|e| format!("Grep: bad regex `{pattern}`: {e}"))?;
57 let include_matcher = include
58 .map(|p| {
59 globset::Glob::new(&p)
60 .map(|g| g.compile_matcher())
61 .map_err(|e| format!("Grep: bad include `{p}`: {e}"))
62 })
63 .transpose()?;
64
65 let mut out = String::new();
66 let mut matches = 0usize;
67 for entry in walkdir::WalkDir::new(&root)
68 .into_iter()
69 .filter_map(Result::ok)
70 {
71 if !entry.file_type().is_file() {
72 continue;
73 }
74 let path = entry.path();
75 if let Some(m) = &include_matcher {
76 let name = path.file_name().unwrap_or_default();
77 if !m.is_match(name) {
78 continue;
79 }
80 }
81 let body = match std::fs::read_to_string(path) {
82 Ok(s) => s,
83 Err(_) => continue, };
85 for (lineno, line) in body.lines().enumerate() {
86 if re.is_match(line) {
87 out.push_str(&format!("{}:{}:{}\n", path.display(), lineno + 1, line));
88 matches += 1;
89 if matches >= MAX_MATCHES {
90 return Ok(out);
91 }
92 }
93 }
94 }
95 Ok(out)
96 })
97 .await
98 .map_err(|e| format!("Grep: task join error: {e}"))??;
99
100 Ok(result)
101 }
102}