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 cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
201 let mut params = Map::new();
202 params.insert("text".into(), json!(text));
203 if let Some(p) = priority {
204 params.insert("priority".into(), json!(p));
205 }
206 let result = self.rpc("cron_add", params).await?;
207 Ok(normalize_cron_add(result))
208 }
209
210 pub async fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
212 let mut params = Map::new();
213 if let Some(s) = status {
214 params.insert("status".into(), json!(s));
215 }
216 self.rpc("cron_list", params).await
217 }
218
219 pub async fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
221 let mut params = Map::new();
222 params.insert("job_id".into(), json!(job_id));
223 let result = self.rpc("cron_show", params).await?;
224 Ok(normalize_cron_show(result))
225 }
226
227 pub async fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
229 let mut params = Map::new();
230 params.insert("job_id".into(), json!(job_id));
231 self.rpc("cron_cancel", params).await
232 }
233
234 pub async fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
236 let mut params = Map::new();
237 params.insert("mode".into(), json!(mode));
238 self.rpc("memory_stats", params).await
239 }
240}
241
242#[derive(Debug, Clone)]
244pub struct CommandClient {
245 inner: AsyncCommandClient,
246}
247
248impl CommandClient {
249 pub fn new(ws_url: impl Into<String>) -> Self {
251 Self {
252 inner: AsyncCommandClient::new(ws_url),
253 }
254 }
255
256 pub fn with_timeout(mut self, timeout: Duration) -> Self {
258 self.inner = self.inner.with_timeout(timeout);
259 self
260 }
261
262 fn block<F, T>(&self, f: F) -> Result<T>
263 where
264 F: std::future::Future<Output = Result<T>>,
265 {
266 match tokio::runtime::Handle::try_current() {
267 Ok(handle) => tokio::task::block_in_place(|| handle.block_on(f)),
268 Err(_) => {
269 let rt = tokio::runtime::Builder::new_current_thread()
270 .enable_all()
271 .build()
272 .map_err(|e| crate::errors::Error::msg(e.to_string()))?;
273 rt.block_on(f)
274 }
275 }
276 }
277
278 pub fn job_create(&self, goal: &str, workspace: Option<&str>) -> Result<Map<String, Value>> {
280 self.block(self.inner.job_create(goal, workspace))
281 }
282
283 pub fn job_status(&self, job_id: &str) -> Result<Map<String, Value>> {
285 self.block(self.inner.job_status(job_id))
286 }
287
288 pub fn job_pause(&self, job_id: &str) -> Result<Map<String, Value>> {
290 self.block(self.inner.job_pause(job_id))
291 }
292
293 pub fn job_resume(&self, job_id: &str) -> Result<Map<String, Value>> {
295 self.block(self.inner.job_resume(job_id))
296 }
297
298 pub fn job_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
300 self.block(self.inner.job_cancel(job_id))
301 }
302
303 pub fn job_dag(&self, job_id: &str) -> Result<Map<String, Value>> {
305 self.block(self.inner.job_dag(job_id))
306 }
307
308 pub fn job_guidance(
310 &self,
311 job_id: &str,
312 content: &str,
313 goal_id: Option<&str>,
314 ) -> Result<Map<String, Value>> {
315 self.block(self.inner.job_guidance(job_id, content, goal_id))
316 }
317
318 pub fn autopilot_status(&self) -> Result<Map<String, Value>> {
320 self.block(self.inner.autopilot_status())
321 }
322
323 pub fn autopilot_submit(
325 &self,
326 description: &str,
327 priority: i32,
328 workspace: Option<&str>,
329 ) -> Result<Map<String, Value>> {
330 self.block(
331 self.inner
332 .autopilot_submit(description, priority, workspace),
333 )
334 }
335
336 pub fn autopilot_list_goals(&self) -> Result<Map<String, Value>> {
338 self.block(self.inner.autopilot_list_goals())
339 }
340
341 pub fn autopilot_get_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
343 self.block(self.inner.autopilot_get_goal(goal_id))
344 }
345
346 pub fn autopilot_cancel_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
348 self.block(self.inner.autopilot_cancel_goal(goal_id))
349 }
350
351 pub fn autopilot_cancel_all(&self) -> Result<Map<String, Value>> {
353 self.block(self.inner.autopilot_cancel_all())
354 }
355
356 pub fn autopilot_wake(&self) -> Result<Map<String, Value>> {
358 self.block(self.inner.autopilot_wake())
359 }
360
361 pub fn autopilot_dream(&self) -> Result<Map<String, Value>> {
363 self.block(self.inner.autopilot_dream())
364 }
365
366 pub fn autopilot_resume(&self, goal_id: &str) -> Result<Map<String, Value>> {
368 self.block(self.inner.autopilot_resume(goal_id))
369 }
370
371 pub fn autopilot_list_jobs(&self) -> Result<Map<String, Value>> {
373 self.block(self.inner.autopilot_list_jobs())
374 }
375
376 pub fn autopilot_get_job(&self, job_id: &str) -> Result<Map<String, Value>> {
378 self.block(self.inner.autopilot_get_job(job_id))
379 }
380
381 pub fn cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
383 self.block(self.inner.cron_add(text, priority))
384 }
385
386 pub fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
388 self.block(self.inner.cron_list(status))
389 }
390
391 pub fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
393 self.block(self.inner.cron_show(job_id))
394 }
395
396 pub fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
398 self.block(self.inner.cron_cancel(job_id))
399 }
400
401 pub fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
403 self.block(self.inner.memory_stats(mode))
404 }
405
406 pub fn request(&self, method: &str, params: Map<String, Value>) -> Result<Map<String, Value>> {
408 self.block(self.inner.request(method, params))
409 }
410}
411
412fn normalize_cron_add(result: Map<String, Value>) -> Map<String, Value> {
413 if result.contains_key("job") {
414 return result;
415 }
416 let job_id = result.get("job_id").or_else(|| result.get("id")).cloned();
417 if let Some(id) = job_id {
418 let mut job = result.clone();
419 job.insert("id".into(), id);
420 job.remove("job_id");
421 let mut out = Map::new();
422 out.insert("job".into(), Value::Object(job));
423 if result.get("duplicate").and_then(|v| v.as_bool()) == Some(true) {
424 out.insert("duplicate".into(), json!(true));
425 }
426 return out;
427 }
428 result
429}
430
431fn normalize_cron_show(result: Map<String, Value>) -> Map<String, Value> {
432 if result.contains_key("job") {
433 return result;
434 }
435 let job_id = result.get("job_id").or_else(|| result.get("id")).cloned();
436 let Some(id) = job_id else {
437 let mut out = Map::new();
438 out.insert("job".into(), Value::Null);
439 return out;
440 };
441 let mut job = result.clone();
442 job.insert("id".into(), id);
443 job.remove("job_id");
444 let mut out = Map::new();
445 out.insert("job".into(), Value::Object(job));
446 out
447}