Skip to main content

sgr_agent_tools/
list.rs

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