1use anyhow::Result;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, Serialize, Deserialize)]
7pub struct FileEntry {
8 pub name: String,
9 pub path: String,
10 pub is_dir: bool,
11 pub size: Option<u64>,
12}
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
16pub struct GrepMatch {
17 pub path: String,
18 pub line_number: usize,
19 pub line_content: String,
20 pub match_start: usize,
21 pub match_end: usize,
22}
23
24#[derive(Clone, Debug, Serialize, Deserialize)]
26pub struct ExecResult {
27 pub stdout: String,
28 pub stderr: String,
29 pub exit_code: i32,
30}
31
32impl ExecResult {
33 #[must_use]
34 pub const fn success(&self) -> bool {
35 self.exit_code == 0
36 }
37}
38
39#[async_trait]
49pub trait Environment: Send + Sync {
50 async fn read_file(&self, path: &str) -> Result<String>;
55
56 async fn read_file_bytes(&self, path: &str) -> Result<Vec<u8>>;
61
62 async fn write_file(&self, path: &str, content: &str) -> Result<()>;
67
68 async fn write_file_bytes(&self, path: &str, content: &[u8]) -> Result<()>;
73
74 async fn list_dir(&self, path: &str) -> Result<Vec<FileEntry>>;
79
80 async fn exists(&self, path: &str) -> Result<bool>;
85
86 async fn is_dir(&self, path: &str) -> Result<bool>;
91
92 async fn is_file(&self, path: &str) -> Result<bool>;
97
98 async fn create_dir(&self, path: &str) -> Result<()>;
103
104 async fn delete_file(&self, path: &str) -> Result<()>;
109
110 async fn delete_dir(&self, path: &str, recursive: bool) -> Result<()>;
115
116 async fn grep(&self, pattern: &str, path: &str, recursive: bool) -> Result<Vec<GrepMatch>>;
121
122 async fn glob(&self, pattern: &str) -> Result<Vec<String>>;
127
128 async fn exec(&self, _command: &str, _timeout_ms: Option<u64>) -> Result<ExecResult> {
135 anyhow::bail!("Command execution not supported in this environment")
136 }
137
138 fn root(&self) -> &str;
140
141 fn resolve_path(&self, path: &str) -> String {
143 if path.starts_with('/') {
144 path.to_string()
145 } else {
146 format!("{}/{}", self.root().trim_end_matches('/'), path)
147 }
148 }
149}
150
151pub struct NullEnvironment;
154
155#[async_trait]
156impl Environment for NullEnvironment {
157 async fn read_file(&self, _path: &str) -> Result<String> {
158 anyhow::bail!("No environment configured")
159 }
160
161 async fn read_file_bytes(&self, _path: &str) -> Result<Vec<u8>> {
162 anyhow::bail!("No environment configured")
163 }
164
165 async fn write_file(&self, _path: &str, _content: &str) -> Result<()> {
166 anyhow::bail!("No environment configured")
167 }
168
169 async fn write_file_bytes(&self, _path: &str, _content: &[u8]) -> Result<()> {
170 anyhow::bail!("No environment configured")
171 }
172
173 async fn list_dir(&self, _path: &str) -> Result<Vec<FileEntry>> {
174 anyhow::bail!("No environment configured")
175 }
176
177 async fn exists(&self, _path: &str) -> Result<bool> {
178 anyhow::bail!("No environment configured")
179 }
180
181 async fn is_dir(&self, _path: &str) -> Result<bool> {
182 anyhow::bail!("No environment configured")
183 }
184
185 async fn is_file(&self, _path: &str) -> Result<bool> {
186 anyhow::bail!("No environment configured")
187 }
188
189 async fn create_dir(&self, _path: &str) -> Result<()> {
190 anyhow::bail!("No environment configured")
191 }
192
193 async fn delete_file(&self, _path: &str) -> Result<()> {
194 anyhow::bail!("No environment configured")
195 }
196
197 async fn delete_dir(&self, _path: &str, _recursive: bool) -> Result<()> {
198 anyhow::bail!("No environment configured")
199 }
200
201 async fn grep(&self, _pattern: &str, _path: &str, _recursive: bool) -> Result<Vec<GrepMatch>> {
202 anyhow::bail!("No environment configured")
203 }
204
205 async fn glob(&self, _pattern: &str) -> Result<Vec<String>> {
206 anyhow::bail!("No environment configured")
207 }
208
209 fn root(&self) -> &'static str {
210 "/"
211 }
212}