use crate::types::*;
use glob::glob;
pub struct GlobTool;
impl GlobTool {
pub fn new() -> Self {
Self
}
pub fn name(&self) -> &str {
"Glob"
}
pub fn description(&self) -> &str {
"Find files by glob pattern"
}
pub fn input_schema(&self) -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"pattern": {
"type": "string",
"description": "The glob pattern to match"
}
}),
required: Some(vec!["pattern".to_string()]),
}
}
pub async fn execute(
&self,
input: serde_json::Value,
context: &ToolContext,
) -> Result<ToolResult, crate::error::AgentError> {
let pattern = input["pattern"]
.as_str()
.ok_or_else(|| crate::error::AgentError::Tool("pattern is required".to_string()))?;
let base_path = std::path::Path::new(&context.cwd);
let full_pattern = if std::path::Path::new(pattern).is_relative() {
base_path.join(pattern)
} else {
std::path::PathBuf::from(pattern)
};
let matches: Vec<String> = glob(full_pattern.to_string_lossy().as_ref())
.map_err(|e| crate::error::AgentError::Tool(e.to_string()))?
.filter_map(std::result::Result::ok)
.map(|path| path.to_string_lossy().to_string())
.collect();
let content = if matches.is_empty() {
"No files found".to_string()
} else {
matches.join("\n")
};
Ok(ToolResult {
result_type: "text".to_string(),
tool_use_id: "".to_string(),
content,
is_error: None,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glob_tool_name() {
let tool = GlobTool::new();
assert_eq!(tool.name(), "Glob");
}
#[test]
fn test_glob_tool_description_contains_glob() {
let tool = GlobTool::new();
assert!(tool.description().to_lowercase().contains("glob"));
}
#[test]
fn test_glob_tool_has_pattern_in_schema() {
let tool = GlobTool::new();
let schema = tool.input_schema();
assert!(schema.properties.get("pattern").is_some());
}
#[tokio::test]
async fn test_glob_tool_finds_matching_files() {
let temp_dir = std::env::temp_dir();
let test_dir = temp_dir.join("test_glob_dir");
std::fs::create_dir_all(&test_dir).ok();
std::fs::write(test_dir.join("file1.txt"), "content1").ok();
std::fs::write(test_dir.join("file2.txt"), "content2").ok();
std::fs::write(test_dir.join("file3.md"), "content3").ok();
let tool = GlobTool::new();
let input = serde_json::json!({
"pattern": format!("{}/**/*.txt", test_dir.to_str().unwrap())
});
let context = ToolContext::default();
let result = tool.execute(input, &context).await;
assert!(result.is_ok());
let tool_result = result.unwrap();
assert!(tool_result.content.contains("file1.txt"));
assert!(tool_result.content.contains("file2.txt"));
assert!(!tool_result.content.contains("file3.md"));
std::fs::remove_file(test_dir.join("file1.txt")).ok();
std::fs::remove_file(test_dir.join("file2.txt")).ok();
std::fs::remove_file(test_dir.join("file3.md")).ok();
std::fs::remove_dir(test_dir).ok();
}
#[tokio::test]
async fn test_glob_tool_returns_empty_for_no_matches() {
let temp_dir = std::env::temp_dir();
let test_dir = temp_dir.join("test_glob_empty");
std::fs::create_dir_all(&test_dir).ok();
let tool = GlobTool::new();
let input = serde_json::json!({
"pattern": format!("{}/**/*.nonexistent", test_dir.to_str().unwrap())
});
let context = ToolContext::default();
let result = tool.execute(input, &context).await;
assert!(result.is_ok());
let tool_result = result.unwrap();
assert!(tool_result.content.is_empty() || tool_result.content.contains("No files found") || tool_result.content.contains("[]"));
std::fs::remove_dir(test_dir).ok();
}
}