adk_devtools/tools/
glob.rs1use 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
15pub struct GlobTool {
18 workspace: Workspace,
19}
20
21impl GlobTool {
22 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}