Skip to main content

deepseek/agent/builtin_tools/
glob.rs

1use async_trait::async_trait;
2use serde_json::{json, Value};
3
4use crate::agent::tool::{Tool, ToolDefinition};
5
6const MAX_RESULTS: usize = 1000;
7
8pub struct GlobTool;
9
10#[async_trait]
11impl Tool for GlobTool {
12    fn name(&self) -> &str {
13        "Glob"
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: "Find files by glob pattern under a directory. Returns paths \
24                          relative to that directory, one per line, sorted, capped at \
25                          1000 results."
26                .into(),
27            parameters: json!({
28                "type": "object",
29                "properties": {
30                    "pattern": { "type": "string", "description": "e.g. `**/*.rs`" },
31                    "path":    { "type": "string", "description": "Root directory (default: cwd)." }
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(|| "Glob: 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
50        let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
51            let glob = globset::Glob::new(&pattern)
52                .map_err(|e| format!("Glob: bad pattern `{pattern}`: {e}"))?
53                .compile_matcher();
54            let root_path = std::path::PathBuf::from(&root);
55            let mut hits: Vec<String> = Vec::new();
56            for entry in walkdir::WalkDir::new(&root_path)
57                .into_iter()
58                .filter_map(Result::ok)
59            {
60                if !entry.file_type().is_file() {
61                    continue;
62                }
63                let rel = entry
64                    .path()
65                    .strip_prefix(&root_path)
66                    .unwrap_or(entry.path());
67                if glob.is_match(rel) {
68                    hits.push(rel.display().to_string());
69                    if hits.len() >= MAX_RESULTS {
70                        break;
71                    }
72                }
73            }
74            hits.sort();
75            Ok(hits.join("\n"))
76        })
77        .await
78        .map_err(|e| format!("Glob: task join error: {e}"))??;
79
80        Ok(result)
81    }
82}