Skip to main content

sgr_agent_tools/
tree.rs

1//! TreeTool — show directory tree structure.
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_level, def_root};
14
15pub struct TreeTool<B: FileBackend>(pub Arc<B>);
16
17#[derive(Deserialize, JsonSchema)]
18struct TreeArgs {
19    /// Directory path (default: workspace root)
20    #[serde(default = "def_root")]
21    root: String,
22    /// Max depth (default: 2)
23    #[serde(default = "def_level")]
24    level: i32,
25}
26
27#[async_trait::async_trait]
28impl<B: FileBackend> Tool for TreeTool<B> {
29    fn name(&self) -> &str {
30        "tree"
31    }
32    fn description(&self) -> &str {
33        "Show directory tree structure"
34    }
35    fn is_read_only(&self) -> bool {
36        true
37    }
38    fn parameters_schema(&self) -> Value {
39        json_schema_for::<TreeArgs>()
40    }
41    async fn execute(&self, args: Value, _ctx: &mut AgentContext) -> Result<ToolOutput, ToolError> {
42        let a: TreeArgs = parse_args(&args)?;
43        self.0
44            .tree(&a.root, a.level)
45            .await
46            .map(ToolOutput::text)
47            .map_err(backend_err)
48    }
49    async fn execute_readonly(
50        &self,
51        args: Value,
52        _ctx: &AgentContext,
53    ) -> Result<ToolOutput, ToolError> {
54        let a: TreeArgs = parse_args(&args)?;
55        self.0
56            .tree(&a.root, a.level)
57            .await
58            .map(ToolOutput::text)
59            .map_err(backend_err)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::mock_fs::MockFs;
67    use sgr_agent_core::agent_tool::Tool;
68
69    #[tokio::test]
70    async fn test_tree_output() {
71        let fs = Arc::new(MockFs::new());
72        fs.add_file("src/main.rs", "fn main() {}");
73        fs.add_file("readme.md", "hi");
74        let tool = TreeTool(fs.clone());
75        let ctx = AgentContext::new();
76        let result = tool
77            .execute_readonly(serde_json::json!({"root": "/"}), &ctx)
78            .await
79            .unwrap();
80        assert!(result.content.contains("src/main.rs"));
81        assert!(result.content.contains("readme.md"));
82    }
83}