Skip to main content

pi_agent/tools/
glob_tool.rs

1use async_trait::async_trait;
2use serde_json::{json, Value};
3
4use crate::types::{AgentTool, AgentToolResult};
5
6pub struct GlobTool;
7
8#[async_trait]
9impl AgentTool for GlobTool {
10    fn name(&self) -> &str {
11        "glob"
12    }
13    fn description(&self) -> &str {
14        "Expand a glob pattern (e.g. 'src/**/*.rs') and return matching paths."
15    }
16    fn parameters(&self) -> Value {
17        json!({
18            "type": "object",
19            "properties": {"pattern": {"type": "string"}, "max": {"type": "integer", "default": 500}},
20            "required": ["pattern"]
21        })
22    }
23    async fn execute(&self, _id: &str, args: Value) -> Result<AgentToolResult, String> {
24        let pattern = args
25            .get("pattern")
26            .and_then(|v| v.as_str())
27            .ok_or("missing 'pattern'")?
28            .to_string();
29        let max = args.get("max").and_then(|v| v.as_u64()).unwrap_or(500) as usize;
30
31        let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
32            let mut buf = String::new();
33            let entries = glob::glob(&pattern).map_err(|e| e.to_string())?;
34            for (count, path) in entries.flatten().enumerate() {
35                if count >= max {
36                    buf.push_str(&format!("... (truncated at {max})\n"));
37                    break;
38                }
39                buf.push_str(&format!("{}\n", path.display()));
40            }
41            Ok(buf)
42        })
43        .await
44        .map_err(|e| e.to_string())??;
45        Ok(AgentToolResult::text(if result.is_empty() {
46            "(no matches)".to_string()
47        } else {
48            result
49        }))
50    }
51}