1use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
7use serde::{Deserialize, Serialize};
8
9pub const PROTOCOL_VERSION: u32 = 1;
11
12#[derive(Debug, Clone, Serialize)]
14pub struct AgentRequest {
15 pub id: u64,
16 #[serde(rename = "m")]
17 pub method: String,
18 #[serde(rename = "p")]
19 pub params: serde_json::Value,
20}
21
22impl AgentRequest {
23 pub fn new(id: u64, method: impl Into<String>, params: serde_json::Value) -> Self {
24 Self {
25 id,
26 method: method.into(),
27 params,
28 }
29 }
30
31 pub fn to_json_line(&self) -> String {
32 serde_json::to_string(self).unwrap() + "\n"
33 }
34}
35
36#[derive(Debug, Clone, Deserialize)]
38pub struct AgentResponse {
39 pub id: u64,
40 #[serde(rename = "d")]
42 pub data: Option<serde_json::Value>,
43 #[serde(rename = "r")]
45 pub result: Option<serde_json::Value>,
46 #[serde(rename = "e")]
48 pub error: Option<String>,
49 pub ok: Option<bool>,
51 #[serde(rename = "v")]
52 pub version: Option<u32>,
53}
54
55impl AgentResponse {
56 pub fn is_ready(&self) -> bool {
58 self.ok == Some(true)
59 }
60
61 pub fn is_data(&self) -> bool {
63 self.data.is_some()
64 }
65
66 pub fn is_final(&self) -> bool {
68 self.result.is_some() || self.error.is_some()
69 }
70}
71
72#[derive(Debug, Clone, Deserialize)]
74#[allow(dead_code)]
75pub struct RemoteDirEntry {
76 pub name: String,
77 pub path: String,
78 #[serde(default)]
79 pub dir: bool,
80 #[serde(default)]
81 pub file: bool,
82 #[serde(default)]
83 pub link: bool,
84 #[serde(default)]
85 pub link_dir: bool,
86 #[serde(default)]
87 pub size: u64,
88 #[serde(default)]
89 pub mtime: i64,
90 #[serde(default)]
91 pub mode: u32,
92}
93
94#[derive(Debug, Clone, Deserialize)]
96#[allow(dead_code)]
97pub struct RemoteMetadata {
98 pub size: u64,
99 pub mtime: i64,
100 pub mode: u32,
101 #[serde(default)]
102 pub uid: u32,
103 #[serde(default)]
104 pub gid: u32,
105 #[serde(default)]
106 pub dir: bool,
107 #[serde(default)]
108 pub file: bool,
109 #[serde(default)]
110 pub link: bool,
111}
112
113#[derive(Debug, Clone, Deserialize)]
115#[allow(dead_code)]
116pub struct ExecResult {
117 pub code: i32,
118}
119
120#[derive(Debug, Clone, Deserialize)]
122#[allow(dead_code)]
123pub struct ExecOutput {
124 #[serde(default)]
125 pub out: Option<String>,
126 #[serde(default)]
127 pub err: Option<String>,
128}
129
130pub fn encode_base64(data: &[u8]) -> String {
132 BASE64.encode(data)
133}
134
135pub fn decode_base64(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
137 BASE64.decode(s)
138}
139
140pub fn read_params(path: &str, offset: Option<u64>, len: Option<usize>) -> serde_json::Value {
142 let mut params = serde_json::json!({"path": path});
143 if let Some(off) = offset {
144 params["off"] = serde_json::json!(off);
145 }
146 if let Some(l) = len {
147 params["len"] = serde_json::json!(l);
148 }
149 params
150}
151
152pub fn count_lf_params(path: &str, offset: u64, len: usize) -> serde_json::Value {
154 serde_json::json!({"path": path, "off": offset, "len": len})
155}
156
157pub fn write_params(path: &str, data: &[u8]) -> serde_json::Value {
159 serde_json::json!({
160 "path": path,
161 "data": encode_base64(data)
162 })
163}
164
165pub fn sudo_write_params(
167 path: &str,
168 data: &[u8],
169 mode: u32,
170 uid: u32,
171 gid: u32,
172) -> serde_json::Value {
173 serde_json::json!({
174 "path": path,
175 "data": encode_base64(data),
176 "mode": mode,
177 "uid": uid,
178 "gid": gid
179 })
180}
181
182pub fn stat_params(path: &str, follow_symlinks: bool) -> serde_json::Value {
184 serde_json::json!({
185 "path": path,
186 "link": follow_symlinks
187 })
188}
189
190pub fn ls_params(path: &str) -> serde_json::Value {
192 serde_json::json!({"path": path})
193}
194
195pub fn exec_params(cmd: &str, args: &[String], cwd: Option<&str>) -> serde_json::Value {
199 let mut params = serde_json::json!({
200 "cmd": cmd,
201 "args": args
202 });
203 if let Some(dir) = cwd {
204 params["cwd"] = serde_json::json!(dir);
205 }
206 params
207}
208
209pub fn cancel_params(request_id: u64) -> serde_json::Value {
211 serde_json::json!({"id": request_id})
212}
213
214pub fn append_params(path: &str, data: &[u8]) -> serde_json::Value {
216 serde_json::json!({
217 "path": path,
218 "data": encode_base64(data)
219 })
220}
221
222pub fn truncate_params(path: &str, len: u64) -> serde_json::Value {
224 serde_json::json!({
225 "path": path,
226 "len": len
227 })
228}
229
230#[derive(Debug, Clone, Serialize)]
232#[serde(untagged)]
233pub enum PatchOp {
234 Copy { copy: CopyRange },
236 Insert { insert: InsertData },
238}
239
240#[derive(Debug, Clone, Serialize)]
242pub struct CopyRange {
243 pub off: u64,
244 pub len: u64,
245}
246
247#[derive(Debug, Clone, Serialize)]
249pub struct InsertData {
250 pub data: String, }
252
253impl PatchOp {
254 pub fn copy(offset: u64, len: u64) -> Self {
256 PatchOp::Copy {
257 copy: CopyRange { off: offset, len },
258 }
259 }
260
261 pub fn insert(data: &[u8]) -> Self {
263 PatchOp::Insert {
264 insert: InsertData {
265 data: encode_base64(data),
266 },
267 }
268 }
269}
270
271pub fn patch_params(src: &str, dst: Option<&str>, ops: &[PatchOp]) -> serde_json::Value {
273 let mut params = serde_json::json!({
274 "src": src,
275 "ops": ops
276 });
277 if let Some(d) = dst {
278 params["dst"] = serde_json::json!(d);
279 }
280 params
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_request_serialization() {
289 let req = AgentRequest::new(1, "read", serde_json::json!({"path": "/test.txt"}));
290 let json = req.to_json_line();
291 assert!(json.contains("\"id\":1"));
292 assert!(json.contains("\"m\":\"read\""));
293 assert!(json.contains("\"p\":{\"path\":\"/test.txt\"}"));
294 }
295
296 #[test]
297 fn test_response_parsing() {
298 let ready = r#"{"id":0,"ok":true,"v":1}"#;
299 let resp: AgentResponse = serde_json::from_str(ready).unwrap();
300 assert!(resp.is_ready());
301 assert_eq!(resp.version, Some(1));
302
303 let data = r#"{"id":1,"d":{"data":"SGVsbG8="}}"#;
304 let resp: AgentResponse = serde_json::from_str(data).unwrap();
305 assert!(resp.is_data());
306 assert!(!resp.is_final());
307
308 let result = r#"{"id":1,"r":{"size":5}}"#;
309 let resp: AgentResponse = serde_json::from_str(result).unwrap();
310 assert!(resp.is_final());
311 assert!(resp.result.is_some());
312
313 let error = r#"{"id":1,"e":"not found"}"#;
314 let resp: AgentResponse = serde_json::from_str(error).unwrap();
315 assert!(resp.is_final());
316 assert_eq!(resp.error, Some("not found".to_string()));
317 }
318
319 #[test]
320 fn test_base64_roundtrip() {
321 let data = b"Hello, World!";
322 let encoded = encode_base64(data);
323 let decoded = decode_base64(&encoded).unwrap();
324 assert_eq!(data.as_slice(), decoded.as_slice());
325 }
326
327 #[test]
328 fn test_patch_op_copy_serialization() {
329 let op = PatchOp::copy(100, 500);
330 let json = serde_json::to_string(&op).unwrap();
331 assert!(json.contains("\"copy\""));
332 assert!(json.contains("\"off\":100"));
333 assert!(json.contains("\"len\":500"));
334 assert!(!json.contains("\"insert\""));
336 }
337
338 #[test]
339 fn test_patch_op_insert_serialization() {
340 let op = PatchOp::insert(b"hello");
341 let json = serde_json::to_string(&op).unwrap();
342 assert!(json.contains("\"insert\""));
343 assert!(json.contains("\"data\":\"aGVsbG8=\"")); assert!(!json.contains("\"copy\""));
346 }
347
348 #[test]
349 fn test_patch_params() {
350 let ops = vec![
351 PatchOp::copy(0, 100),
352 PatchOp::insert(b"new content"),
353 PatchOp::copy(200, 300),
354 ];
355
356 let params = patch_params("/path/to/file", None, &ops);
358 assert_eq!(params["src"], "/path/to/file");
359 assert!(params.get("dst").is_none() || params["dst"].is_null());
360 assert!(params["ops"].is_array());
361 assert_eq!(params["ops"].as_array().unwrap().len(), 3);
362
363 let params = patch_params("/src/file", Some("/dst/file"), &ops);
365 assert_eq!(params["src"], "/src/file");
366 assert_eq!(params["dst"], "/dst/file");
367 }
368}