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;
14
15pub struct ListTool<B: FileBackend>(pub Arc<B>);
16
17#[derive(Deserialize, JsonSchema)]
18struct ListArgs {
19 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}