Skip to main content

sgr_agent_tools/
find.rs

1//! FindTool — find files/directories by name pattern.
2
3use std::sync::Arc;
4
5use schemars::JsonSchema;
6use serde::Deserialize;
7use serde_json::Value;
8use sgr_agent_core::agent_tool::{Tool, ToolError, ToolOutput, parse_args};
9use sgr_agent_core::context::AgentContext;
10use sgr_agent_core::schema::json_schema_for;
11
12use crate::backend::FileBackend;
13use crate::helpers::{backend_err, def_root};
14
15pub struct FindTool<B: FileBackend>(pub Arc<B>);
16
17#[derive(Deserialize, JsonSchema)]
18struct FindArgs {
19    /// Search root directory
20    #[serde(default = "def_root")]
21    root: String,
22    /// File/directory name pattern
23    name: String,
24    /// Filter: "files", "dirs", or empty for all
25    #[serde(default, rename = "type")]
26    file_type: String,
27    /// Max results (0 = no limit)
28    #[serde(default)]
29    limit: i32,
30}
31
32#[async_trait::async_trait]
33impl<B: FileBackend> Tool for FindTool<B> {
34    fn name(&self) -> &str {
35        "find"
36    }
37    fn description(&self) -> &str {
38        "Find files/directories by name pattern"
39    }
40    fn is_read_only(&self) -> bool {
41        true
42    }
43    fn parameters_schema(&self) -> Value {
44        json_schema_for::<FindArgs>()
45    }
46    async fn execute(&self, args: Value, _ctx: &mut AgentContext) -> Result<ToolOutput, ToolError> {
47        let a: FindArgs = parse_args(&args)?;
48        self.0
49            .find(&a.root, &a.name, &a.file_type, a.limit)
50            .await
51            .map(ToolOutput::text)
52            .map_err(backend_err)
53    }
54    async fn execute_readonly(
55        &self,
56        args: Value,
57        _ctx: &AgentContext,
58    ) -> Result<ToolOutput, ToolError> {
59        let a: FindArgs = parse_args(&args)?;
60        self.0
61            .find(&a.root, &a.name, &a.file_type, a.limit)
62            .await
63            .map(ToolOutput::text)
64            .map_err(backend_err)
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::mock_fs::MockFs;
72    use sgr_agent_core::agent_tool::Tool;
73
74    #[tokio::test]
75    async fn test_find_by_name() {
76        let fs = Arc::new(MockFs::new());
77        fs.add_file("src/main.rs", "fn main() {}");
78        fs.add_file("src/lib.rs", "pub mod foo;");
79        fs.add_file("readme.md", "hi");
80        let tool = FindTool(fs.clone());
81        let ctx = AgentContext::new();
82        let result = tool
83            .execute_readonly(serde_json::json!({"root": "/", "name": "main"}), &ctx)
84            .await
85            .unwrap();
86        assert!(result.content.contains("main.rs"));
87        assert!(!result.content.contains("lib.rs"));
88    }
89}