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