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