1use std::time::Duration;
4
5use serde_json::{json, Map, Value};
6
7use crate::client::Client;
8use crate::errors::Result;
9use crate::session::connect_with_retries;
10
11#[derive(Debug, Clone)]
13pub struct AsyncCommandClient {
14 ws_url: String,
15 timeout: Duration,
16}
17
18impl AsyncCommandClient {
19 pub fn new(ws_url: impl Into<String>) -> Self {
21 Self {
22 ws_url: ws_url.into(),
23 timeout: Duration::from_secs(30),
24 }
25 }
26
27 pub fn with_timeout(mut self, timeout: Duration) -> Self {
29 self.timeout = timeout;
30 self
31 }
32
33 async fn with_client<F, Fut>(&self, f: F) -> Result<Map<String, Value>>
34 where
35 F: FnOnce(Client) -> Fut,
36 Fut: std::future::Future<Output = Result<Map<String, Value>>>,
37 {
38 let client = Client::new(&self.ws_url);
39 connect_with_retries(&client, 5, Duration::from_millis(250)).await?;
40 let result = f(client.clone()).await;
41 let _ = client.close().await;
42 result
43 }
44
45 async fn rpc(&self, method: &str, params: Map<String, Value>) -> Result<Map<String, Value>> {
46 let timeout = self.timeout;
47 self.with_client(|c| async move { c.request(method, params, timeout).await })
48 .await
49 }
50
51 pub async fn request(
53 &self,
54 method: &str,
55 params: Map<String, Value>,
56 ) -> Result<Map<String, Value>> {
57 self.rpc(method, params).await
58 }
59
60 pub async fn job_create(
62 &self,
63 goal: &str,
64 workspace: Option<&str>,
65 ) -> Result<Map<String, Value>> {
66 let mut params = Map::new();
67 params.insert("goal".into(), json!(goal));
68 if let Some(ws) = workspace {
69 params.insert("workspace".into(), json!(ws));
70 }
71 self.rpc("job_create", params).await
72 }
73
74 pub async fn job_status(&self, job_id: &str) -> Result<Map<String, Value>> {
76 let mut params = Map::new();
77 params.insert("job_id".into(), json!(job_id));
78 self.rpc("job_status", params).await
79 }
80
81 pub async fn job_pause(&self, job_id: &str) -> Result<Map<String, Value>> {
83 let mut params = Map::new();
84 params.insert("job_id".into(), json!(job_id));
85 self.rpc("job_pause", params).await
86 }
87
88 pub async fn job_resume(&self, job_id: &str) -> Result<Map<String, Value>> {
90 let mut params = Map::new();
91 params.insert("job_id".into(), json!(job_id));
92 self.rpc("job_resume", params).await
93 }
94
95 pub async fn job_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
97 let mut params = Map::new();
98 params.insert("job_id".into(), json!(job_id));
99 self.rpc("job_cancel", params).await
100 }
101
102 pub async fn job_dag(&self, job_id: &str) -> Result<Map<String, Value>> {
104 let mut params = Map::new();
105 params.insert("job_id".into(), json!(job_id));
106 self.rpc("job_dag", params).await
107 }
108
109 pub async fn job_guidance(
111 &self,
112 job_id: &str,
113 content: &str,
114 goal_id: Option<&str>,
115 ) -> Result<Map<String, Value>> {
116 let mut params = Map::new();
117 params.insert("job_id".into(), json!(job_id));
118 params.insert("content".into(), json!(content));
119 if let Some(g) = goal_id {
120 params.insert("goal_id".into(), json!(g));
121 }
122 self.rpc("job_guidance", params).await
123 }
124
125 pub async fn autopilot_status(&self) -> Result<Map<String, Value>> {
127 self.rpc("autopilot_status", Map::new()).await
128 }
129
130 pub async fn autopilot_submit(
132 &self,
133 description: &str,
134 priority: i32,
135 workspace: Option<&str>,
136 ) -> Result<Map<String, Value>> {
137 let mut params = Map::new();
138 params.insert("description".into(), json!(description));
139 params.insert("priority".into(), json!(priority));
140 if let Some(ws) = workspace {
141 params.insert("workspace".into(), json!(ws));
142 }
143 self.rpc("autopilot_submit", params).await
144 }
145
146 pub async fn autopilot_list_goals(&self) -> Result<Map<String, Value>> {
148 self.rpc("autopilot_list_goals", Map::new()).await
149 }
150
151 pub async fn autopilot_get_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
153 let mut params = Map::new();
154 params.insert("goal_id".into(), json!(goal_id));
155 self.rpc("autopilot_get_goal", params).await
156 }
157
158 pub async fn autopilot_cancel_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
160 let mut params = Map::new();
161 params.insert("goal_id".into(), json!(goal_id));
162 self.rpc("autopilot_cancel_goal", params).await
163 }
164
165 pub async fn autopilot_cancel_all(&self) -> Result<Map<String, Value>> {
167 self.rpc("autopilot_cancel_all", Map::new()).await
168 }
169
170 pub async fn autopilot_wake(&self) -> Result<Map<String, Value>> {
172 self.rpc("autopilot_wake", Map::new()).await
173 }
174
175 pub async fn autopilot_dream(&self) -> Result<Map<String, Value>> {
177 self.rpc("autopilot_dream", Map::new()).await
178 }
179
180 pub async fn autopilot_resume(&self, goal_id: &str) -> Result<Map<String, Value>> {
182 let mut params = Map::new();
183 params.insert("goal_id".into(), json!(goal_id));
184 self.rpc("autopilot_resume", params).await
185 }
186
187 pub async fn autopilot_list_jobs(&self) -> Result<Map<String, Value>> {
189 self.rpc("autopilot_list_jobs", Map::new()).await
190 }
191
192 pub async fn autopilot_get_job(&self, job_id: &str) -> Result<Map<String, Value>> {
194 let mut params = Map::new();
195 params.insert("job_id".into(), json!(job_id));
196 self.rpc("autopilot_get_job", params).await
197 }
198
199 pub async fn autopilot_top(&self, include_terminal: bool) -> Result<Map<String, Value>> {
201 let mut params = Map::new();
202 params.insert("include_terminal".into(), json!(include_terminal));
203 self.rpc("autopilot_top", params).await
204 }
205
206 pub async fn cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
208 let mut params = Map::new();
209 params.insert("text".into(), json!(text));
210 if let Some(p) = priority {
211 params.insert("priority".into(), json!(p));
212 }
213 let result = self.rpc("cron_add", params).await?;
214 Ok(normalize_cron_add(result))
215 }
216
217 pub async fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
219 let mut params = Map::new();
220 if let Some(s) = status {
221 params.insert("status".into(), json!(s));
222 }
223 self.rpc("cron_list", params).await
224 }
225
226 pub async fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
228 let mut params = Map::new();
229 params.insert("job_id".into(), json!(job_id));
230 let result = self.rpc("cron_show", params).await?;
231 Ok(normalize_cron_show(result))
232 }
233
234 pub async fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
236 let mut params = Map::new();
237 params.insert("job_id".into(), json!(job_id));
238 self.rpc("cron_cancel", params).await
239 }
240
241 pub async fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
243 let mut params = Map::new();
244 params.insert("mode".into(), json!(mode));
245 self.rpc("memory_stats", params).await
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct CommandClient {
252 inner: AsyncCommandClient,
253}
254
255impl CommandClient {
256 pub fn new(ws_url: impl Into<String>) -> Self {
258 Self {
259 inner: AsyncCommandClient::new(ws_url),
260 }
261 }
262
263 pub fn with_timeout(mut self, timeout: Duration) -> Self {
265 self.inner = self.inner.with_timeout(timeout);
266 self
267 }
268
269 fn block<F, T>(&self, f: F) -> Result<T>
270 where
271 F: std::future::Future<Output = Result<T>>,
272 {
273 match tokio::runtime::Handle::try_current() {
274 Ok(handle) => tokio::task::block_in_place(|| handle.block_on(f)),
275 Err(_) => {
276 let rt = tokio::runtime::Builder::new_current_thread()
277 .enable_all()
278 .build()
279 .map_err(|e| crate::errors::Error::msg(e.to_string()))?;
280 rt.block_on(f)
281 }
282 }
283 }
284
285 pub fn job_create(&self, goal: &str, workspace: Option<&str>) -> Result<Map<String, Value>> {
287 self.block(self.inner.job_create(goal, workspace))
288 }
289
290 pub fn job_status(&self, job_id: &str) -> Result<Map<String, Value>> {
292 self.block(self.inner.job_status(job_id))
293 }
294
295 pub fn job_pause(&self, job_id: &str) -> Result<Map<String, Value>> {
297 self.block(self.inner.job_pause(job_id))
298 }
299
300 pub fn job_resume(&self, job_id: &str) -> Result<Map<String, Value>> {
302 self.block(self.inner.job_resume(job_id))
303 }
304
305 pub fn job_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
307 self.block(self.inner.job_cancel(job_id))
308 }
309
310 pub fn job_dag(&self, job_id: &str) -> Result<Map<String, Value>> {
312 self.block(self.inner.job_dag(job_id))
313 }
314
315 pub fn job_guidance(
317 &self,
318 job_id: &str,
319 content: &str,
320 goal_id: Option<&str>,
321 ) -> Result<Map<String, Value>> {
322 self.block(self.inner.job_guidance(job_id, content, goal_id))
323 }
324
325 pub fn autopilot_status(&self) -> Result<Map<String, Value>> {
327 self.block(self.inner.autopilot_status())
328 }
329
330 pub fn autopilot_submit(
332 &self,
333 description: &str,
334 priority: i32,
335 workspace: Option<&str>,
336 ) -> Result<Map<String, Value>> {
337 self.block(
338 self.inner
339 .autopilot_submit(description, priority, workspace),
340 )
341 }
342
343 pub fn autopilot_list_goals(&self) -> Result<Map<String, Value>> {
345 self.block(self.inner.autopilot_list_goals())
346 }
347
348 pub fn autopilot_get_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
350 self.block(self.inner.autopilot_get_goal(goal_id))
351 }
352
353 pub fn autopilot_cancel_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
355 self.block(self.inner.autopilot_cancel_goal(goal_id))
356 }
357
358 pub fn autopilot_cancel_all(&self) -> Result<Map<String, Value>> {
360 self.block(self.inner.autopilot_cancel_all())
361 }
362
363 pub fn autopilot_wake(&self) -> Result<Map<String, Value>> {
365 self.block(self.inner.autopilot_wake())
366 }
367
368 pub fn autopilot_dream(&self) -> Result<Map<String, Value>> {
370 self.block(self.inner.autopilot_dream())
371 }
372
373 pub fn autopilot_resume(&self, goal_id: &str) -> Result<Map<String, Value>> {
375 self.block(self.inner.autopilot_resume(goal_id))
376 }
377
378 pub fn autopilot_list_jobs(&self) -> Result<Map<String, Value>> {
380 self.block(self.inner.autopilot_list_jobs())
381 }
382
383 pub fn autopilot_get_job(&self, job_id: &str) -> Result<Map<String, Value>> {
385 self.block(self.inner.autopilot_get_job(job_id))
386 }
387
388 pub fn autopilot_top(&self, include_terminal: bool) -> Result<Map<String, Value>> {
390 self.block(self.inner.autopilot_top(include_terminal))
391 }
392
393 pub fn cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
395 self.block(self.inner.cron_add(text, priority))
396 }
397
398 pub fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
400 self.block(self.inner.cron_list(status))
401 }
402
403 pub fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
405 self.block(self.inner.cron_show(job_id))
406 }
407
408 pub fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
410 self.block(self.inner.cron_cancel(job_id))
411 }
412
413 pub fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
415 self.block(self.inner.memory_stats(mode))
416 }
417
418 pub fn request(&self, method: &str, params: Map<String, Value>) -> Result<Map<String, Value>> {
420 self.block(self.inner.request(method, params))
421 }
422}
423
424fn normalize_cron_add(result: Map<String, Value>) -> Map<String, Value> {
425 if result.contains_key("job") {
426 return result;
427 }
428 let job_id = result.get("job_id").or_else(|| result.get("id")).cloned();
429 if let Some(id) = job_id {
430 let mut job = result.clone();
431 job.insert("id".into(), id);
432 job.remove("job_id");
433 let mut out = Map::new();
434 out.insert("job".into(), Value::Object(job));
435 if result.get("duplicate").and_then(|v| v.as_bool()) == Some(true) {
436 out.insert("duplicate".into(), json!(true));
437 }
438 return out;
439 }
440 result
441}
442
443fn normalize_cron_show(result: Map<String, Value>) -> Map<String, Value> {
444 if result.contains_key("job") {
445 return result;
446 }
447 let job_id = result.get("job_id").or_else(|| result.get("id")).cloned();
448 let Some(id) = job_id else {
449 let mut out = Map::new();
450 out.insert("job".into(), Value::Null);
451 return out;
452 };
453 let mut job = result.clone();
454 job.insert("id".into(), id);
455 job.remove("job_id");
456 let mut out = Map::new();
457 out.insert("job".into(), Value::Object(job));
458 out
459}