1use 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 #[serde(default = "def_root")]
21 root: String,
22 name: String,
24 #[serde(default, rename = "type")]
26 file_type: String,
27 #[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}