1use async_trait::async_trait;
2use serde_json::{json, Value};
3use tokio::fs;
4
5use crate::types::{AgentTool, AgentToolResult};
6
7pub struct LsTool;
8
9#[async_trait]
10impl AgentTool for LsTool {
11 fn name(&self) -> &str {
12 "ls"
13 }
14 fn description(&self) -> &str {
15 "List entries in a directory. Returns name and kind (file/dir/symlink)."
16 }
17 fn parameters(&self) -> Value {
18 json!({
19 "type": "object",
20 "properties": {"path": {"type": "string"}},
21 "required": ["path"]
22 })
23 }
24 async fn execute(&self, _id: &str, args: Value) -> Result<AgentToolResult, String> {
25 let path = args
26 .get("path")
27 .and_then(|v| v.as_str())
28 .ok_or("missing 'path'")?;
29 let mut read = fs::read_dir(path)
30 .await
31 .map_err(|e| format!("ls {path}: {e}"))?;
32 let mut buf = String::new();
33 while let Some(entry) = read.next_entry().await.map_err(|e| e.to_string())? {
34 let ft = entry.file_type().await.map_err(|e| e.to_string())?;
35 let kind = if ft.is_dir() {
36 "dir"
37 } else if ft.is_symlink() {
38 "symlink"
39 } else {
40 "file"
41 };
42 buf.push_str(&format!(
43 "{}\t{}\n",
44 kind,
45 entry.file_name().to_string_lossy()
46 ));
47 }
48 Ok(AgentToolResult::text(buf))
49 }
50}