agent_code_lib/tools/
glob.rs1use async_trait::async_trait;
7use serde_json::json;
8use std::path::PathBuf;
9
10use super::{Tool, ToolContext, ToolResult};
11use crate::error::ToolError;
12
13pub struct GlobTool;
14
15#[async_trait]
16impl Tool for GlobTool {
17 fn name(&self) -> &'static str {
18 "Glob"
19 }
20
21 fn description(&self) -> &'static str {
22 "Finds files matching a glob pattern. Returns paths sorted by modification time."
23 }
24
25 fn input_schema(&self) -> serde_json::Value {
26 json!({
27 "type": "object",
28 "required": ["pattern"],
29 "properties": {
30 "pattern": {
31 "type": "string",
32 "description": "Glob pattern (e.g., \"**/*.rs\", \"src/**/*.toml\")"
33 },
34 "path": {
35 "type": "string",
36 "description": "Directory to search in (defaults to cwd)"
37 }
38 }
39 })
40 }
41
42 fn is_read_only(&self) -> bool {
43 true
44 }
45
46 fn is_concurrency_safe(&self) -> bool {
47 true
48 }
49
50 async fn call(
51 &self,
52 input: serde_json::Value,
53 ctx: &ToolContext,
54 ) -> Result<ToolResult, ToolError> {
55 let pattern = input
56 .get("pattern")
57 .and_then(|v| v.as_str())
58 .ok_or_else(|| ToolError::InvalidInput("'pattern' is required".into()))?;
59
60 let base_path = input
61 .get("path")
62 .and_then(|v| v.as_str())
63 .map(PathBuf::from)
64 .unwrap_or_else(|| ctx.cwd.clone());
65
66 let full_pattern = if pattern.starts_with('/') {
68 pattern.to_string()
69 } else {
70 format!("{}/{pattern}", base_path.display())
71 };
72
73 let entries: Vec<PathBuf> = glob::glob(&full_pattern)
74 .map_err(|e| ToolError::InvalidInput(format!("Invalid glob pattern: {e}")))?
75 .filter_map(|entry| entry.ok())
76 .filter(|p| p.is_file())
77 .collect();
78
79 let mut entries_with_mtime: Vec<(PathBuf, std::time::SystemTime)> = entries
81 .into_iter()
82 .filter_map(|p| {
83 let mtime = std::fs::metadata(&p).ok()?.modified().ok()?;
84 Some((p, mtime))
85 })
86 .collect();
87
88 entries_with_mtime.sort_by_key(|e| std::cmp::Reverse(e.1));
89
90 let total = entries_with_mtime.len();
91 let max_results = 500;
92 let truncated = total > max_results;
93
94 let result: Vec<String> = entries_with_mtime
95 .iter()
96 .take(max_results)
97 .map(|(p, _)| p.display().to_string())
98 .collect();
99
100 let mut output = format!("Found {total} files:\n{}", result.join("\n"));
101 if truncated {
102 output.push_str(&format!("\n\n(Showing {max_results} of {total} files)"));
103 }
104
105 Ok(ToolResult::success(output))
106 }
107}