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_level, def_root};
14
15pub struct TreeTool<B: FileBackend>(pub Arc<B>);
16
17#[derive(Deserialize, JsonSchema)]
18struct TreeArgs {
19 #[serde(default = "def_root")]
21 root: String,
22 #[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}