Skip to main content

adk_devtools/tools/
glob.rs

1//! `glob` — list workspace files matching a glob pattern.
2
3use std::sync::Arc;
4
5use adk_core::{Result, Tool, ToolContext};
6use async_trait::async_trait;
7use serde_json::{Value, json};
8
9use crate::error::DevToolError;
10use crate::tools::read::require_str;
11use crate::workspace::Workspace;
12
13const MAX_RESULTS: usize = 1000;
14
15/// Lists files matching a glob pattern (e.g. `src/**/*.rs`), relative to the
16/// workspace root or an optional sub-directory.
17pub struct GlobTool {
18    workspace: Workspace,
19}
20
21impl GlobTool {
22    /// Create a `glob` tool bound to `workspace`.
23    pub fn new(workspace: Workspace) -> Self {
24        Self { workspace }
25    }
26}
27
28#[async_trait]
29impl Tool for GlobTool {
30    fn name(&self) -> &str {
31        "glob"
32    }
33
34    fn description(&self) -> &str {
35        "List files matching a glob pattern (e.g. 'src/**/*.rs'). Returns paths \
36         relative to the workspace root."
37    }
38
39    fn is_read_only(&self) -> bool {
40        true
41    }
42
43    fn is_concurrency_safe(&self) -> bool {
44        true
45    }
46
47    fn parameters_schema(&self) -> Option<Value> {
48        Some(json!({
49            "type": "object",
50            "properties": {
51                "pattern": { "type": "string", "description": "Glob pattern, e.g. '**/*.toml'." },
52                "path": { "type": "string", "description": "Optional sub-directory to search within." }
53            },
54            "required": ["pattern"]
55        }))
56    }
57
58    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
59        let pattern = require_str(&args, "pattern")?;
60        let base = match args.get("path").and_then(Value::as_str) {
61            Some(sub) => self.workspace.resolve(sub)?,
62            None => self.workspace.root().to_path_buf(),
63        };
64
65        let full = format!("{}/{}", base.display(), pattern);
66        let entries = glob::glob(&full)
67            .map_err(|e| DevToolError::Other(format!("invalid glob pattern: {e}")))?;
68
69        let mut matches = Vec::new();
70        let mut truncated = false;
71        for entry in entries {
72            let path = match entry {
73                Ok(p) => p,
74                Err(_) => continue,
75            };
76            if matches.len() >= MAX_RESULTS {
77                truncated = true;
78                break;
79            }
80            matches.push(self.workspace.display(&path));
81        }
82
83        Ok(json!({
84            "pattern": pattern,
85            "matches": matches,
86            "count": matches.len(),
87            "truncated": truncated,
88        }))
89    }
90}