sgr_agent_tools/
delete.rs1use 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;
13
14pub struct DeleteTool<B: FileBackend>(pub Arc<B>);
15
16#[derive(Deserialize, JsonSchema)]
17struct DeleteArgs {
18 #[serde(default)]
20 path: Option<String>,
21 #[serde(default)]
23 paths: Option<Vec<String>>,
24}
25
26#[async_trait::async_trait]
27impl<B: FileBackend> Tool for DeleteTool<B> {
28 fn name(&self) -> &str {
29 "delete"
30 }
31 fn description(&self) -> &str {
32 "Delete one or more files. Pass `path` for a single file, or `paths` (array) to delete many files at once."
33 }
34 fn parameters_schema(&self) -> Value {
35 json_schema_for::<DeleteArgs>()
36 }
37 async fn execute(&self, args: Value, _ctx: &mut AgentContext) -> Result<ToolOutput, ToolError> {
38 let a: DeleteArgs = parse_args(&args)?;
39
40 let targets: Vec<String> = match (a.path, a.paths) {
41 (_, Some(ps)) if !ps.is_empty() => ps,
42 (Some(p), _) => vec![p],
43 _ => {
44 return Err(ToolError::InvalidArgs(
45 "provide `path` (string) or `paths` (array)".into(),
46 ));
47 }
48 };
49
50 let mut results: Vec<String> = Vec::with_capacity(targets.len());
51 let mut errors: Vec<String> = Vec::new();
52
53 for path in &targets {
54 match self.0.delete(path).await {
55 Ok(()) => results.push(format!("Deleted {}", path)),
56 Err(e) => errors.push(format!("FAILED {}: {}", path, e)),
57 }
58 }
59
60 let mut out = results.join("\n");
61 if !errors.is_empty() {
62 if !out.is_empty() {
63 out.push('\n');
64 }
65 out.push_str(&errors.join("\n"));
66 }
67 if out.is_empty() {
68 out = "No files deleted".to_string();
69 }
70 Ok(ToolOutput::text(out))
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::mock_fs::MockFs;
78 use sgr_agent_core::agent_tool::Tool;
79
80 #[tokio::test]
81 async fn test_single_delete() {
82 let fs = Arc::new(MockFs::new());
83 fs.add_file("tmp.txt", "bye");
84 let tool = DeleteTool(fs.clone());
85 let mut ctx = AgentContext::new();
86 let result = tool
87 .execute(serde_json::json!({"path": "tmp.txt"}), &mut ctx)
88 .await
89 .unwrap();
90 assert!(result.content.contains("Deleted tmp.txt"));
91 assert!(!fs.exists("tmp.txt"));
92 }
93
94 #[tokio::test]
95 async fn test_batch_delete() {
96 let fs = Arc::new(MockFs::new());
97 fs.add_file("a.txt", "1");
98 fs.add_file("b.txt", "2");
99 let tool = DeleteTool(fs.clone());
100 let mut ctx = AgentContext::new();
101 let result = tool
102 .execute(serde_json::json!({"paths": ["a.txt", "b.txt"]}), &mut ctx)
103 .await
104 .unwrap();
105 assert!(result.content.contains("Deleted a.txt"));
106 assert!(result.content.contains("Deleted b.txt"));
107 assert!(!fs.exists("a.txt"));
108 assert!(!fs.exists("b.txt"));
109 }
110
111 #[tokio::test]
112 async fn test_delete_nonexistent() {
113 let fs = Arc::new(MockFs::new());
114 let tool = DeleteTool(fs.clone());
115 let mut ctx = AgentContext::new();
116 let result = tool
117 .execute(serde_json::json!({"path": "ghost.txt"}), &mut ctx)
118 .await
119 .unwrap();
120 assert!(result.content.contains("FAILED ghost.txt"));
121 }
122}