use std::time::Duration;
use serde_json::{json, Map, Value};
use crate::client::Client;
use crate::errors::Result;
use crate::session::connect_with_retries;
#[derive(Debug, Clone)]
pub struct AsyncCommandClient {
ws_url: String,
timeout: Duration,
}
impl AsyncCommandClient {
pub fn new(ws_url: impl Into<String>) -> Self {
Self {
ws_url: ws_url.into(),
timeout: Duration::from_secs(30),
}
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
async fn with_client<F, Fut>(&self, f: F) -> Result<Map<String, Value>>
where
F: FnOnce(Client) -> Fut,
Fut: std::future::Future<Output = Result<Map<String, Value>>>,
{
let client = Client::new(&self.ws_url);
connect_with_retries(&client, 5, Duration::from_millis(250)).await?;
let result = f(client.clone()).await;
let _ = client.close().await;
result
}
async fn rpc(&self, method: &str, params: Map<String, Value>) -> Result<Map<String, Value>> {
let timeout = self.timeout;
self.with_client(|c| async move { c.request(method, params, timeout).await })
.await
}
pub async fn request(
&self,
method: &str,
params: Map<String, Value>,
) -> Result<Map<String, Value>> {
self.rpc(method, params).await
}
pub async fn job_create(
&self,
goal: &str,
workspace: Option<&str>,
) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("goal".into(), json!(goal));
if let Some(ws) = workspace {
params.insert("workspace".into(), json!(ws));
}
self.rpc("job_create", params).await
}
pub async fn job_status(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("job_status", params).await
}
pub async fn job_pause(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("job_pause", params).await
}
pub async fn job_resume(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("job_resume", params).await
}
pub async fn job_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("job_cancel", params).await
}
pub async fn job_dag(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("job_dag", params).await
}
pub async fn job_guidance(
&self,
job_id: &str,
content: &str,
goal_id: Option<&str>,
) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
params.insert("content".into(), json!(content));
if let Some(g) = goal_id {
params.insert("goal_id".into(), json!(g));
}
self.rpc("job_guidance", params).await
}
pub async fn autopilot_status(&self) -> Result<Map<String, Value>> {
self.rpc("autopilot_status", Map::new()).await
}
pub async fn autopilot_submit(
&self,
description: &str,
priority: i32,
workspace: Option<&str>,
) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("description".into(), json!(description));
params.insert("priority".into(), json!(priority));
if let Some(ws) = workspace {
params.insert("workspace".into(), json!(ws));
}
self.rpc("autopilot_submit", params).await
}
pub async fn autopilot_list_goals(&self) -> Result<Map<String, Value>> {
self.rpc("autopilot_list_goals", Map::new()).await
}
pub async fn autopilot_get_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("goal_id".into(), json!(goal_id));
self.rpc("autopilot_get_goal", params).await
}
pub async fn autopilot_cancel_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("goal_id".into(), json!(goal_id));
self.rpc("autopilot_cancel_goal", params).await
}
pub async fn autopilot_cancel_all(&self) -> Result<Map<String, Value>> {
self.rpc("autopilot_cancel_all", Map::new()).await
}
pub async fn autopilot_wake(&self) -> Result<Map<String, Value>> {
self.rpc("autopilot_wake", Map::new()).await
}
pub async fn autopilot_dream(&self) -> Result<Map<String, Value>> {
self.rpc("autopilot_dream", Map::new()).await
}
pub async fn autopilot_resume(&self, goal_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("goal_id".into(), json!(goal_id));
self.rpc("autopilot_resume", params).await
}
pub async fn autopilot_list_jobs(&self) -> Result<Map<String, Value>> {
self.rpc("autopilot_list_jobs", Map::new()).await
}
pub async fn autopilot_get_job(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("autopilot_get_job", params).await
}
pub async fn cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("text".into(), json!(text));
if let Some(p) = priority {
params.insert("priority".into(), json!(p));
}
let result = self.rpc("cron_add", params).await?;
Ok(normalize_cron_add(result))
}
pub async fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
let mut params = Map::new();
if let Some(s) = status {
params.insert("status".into(), json!(s));
}
self.rpc("cron_list", params).await
}
pub async fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
let result = self.rpc("cron_show", params).await?;
Ok(normalize_cron_show(result))
}
pub async fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("job_id".into(), json!(job_id));
self.rpc("cron_cancel", params).await
}
pub async fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
let mut params = Map::new();
params.insert("mode".into(), json!(mode));
self.rpc("memory_stats", params).await
}
}
#[derive(Debug, Clone)]
pub struct CommandClient {
inner: AsyncCommandClient,
}
impl CommandClient {
pub fn new(ws_url: impl Into<String>) -> Self {
Self {
inner: AsyncCommandClient::new(ws_url),
}
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.inner = self.inner.with_timeout(timeout);
self
}
fn block<F, T>(&self, f: F) -> Result<T>
where
F: std::future::Future<Output = Result<T>>,
{
match tokio::runtime::Handle::try_current() {
Ok(handle) => tokio::task::block_in_place(|| handle.block_on(f)),
Err(_) => {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| crate::errors::Error::msg(e.to_string()))?;
rt.block_on(f)
}
}
}
pub fn job_create(&self, goal: &str, workspace: Option<&str>) -> Result<Map<String, Value>> {
self.block(self.inner.job_create(goal, workspace))
}
pub fn job_status(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.job_status(job_id))
}
pub fn job_pause(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.job_pause(job_id))
}
pub fn job_resume(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.job_resume(job_id))
}
pub fn job_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.job_cancel(job_id))
}
pub fn job_dag(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.job_dag(job_id))
}
pub fn job_guidance(
&self,
job_id: &str,
content: &str,
goal_id: Option<&str>,
) -> Result<Map<String, Value>> {
self.block(self.inner.job_guidance(job_id, content, goal_id))
}
pub fn autopilot_status(&self) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_status())
}
pub fn autopilot_submit(
&self,
description: &str,
priority: i32,
workspace: Option<&str>,
) -> Result<Map<String, Value>> {
self.block(
self.inner
.autopilot_submit(description, priority, workspace),
)
}
pub fn autopilot_list_goals(&self) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_list_goals())
}
pub fn autopilot_get_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_get_goal(goal_id))
}
pub fn autopilot_cancel_goal(&self, goal_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_cancel_goal(goal_id))
}
pub fn autopilot_cancel_all(&self) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_cancel_all())
}
pub fn autopilot_wake(&self) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_wake())
}
pub fn autopilot_dream(&self) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_dream())
}
pub fn autopilot_resume(&self, goal_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_resume(goal_id))
}
pub fn autopilot_list_jobs(&self) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_list_jobs())
}
pub fn autopilot_get_job(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.autopilot_get_job(job_id))
}
pub fn cron_add(&self, text: &str, priority: Option<i32>) -> Result<Map<String, Value>> {
self.block(self.inner.cron_add(text, priority))
}
pub fn cron_list(&self, status: Option<&str>) -> Result<Map<String, Value>> {
self.block(self.inner.cron_list(status))
}
pub fn cron_show(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.cron_show(job_id))
}
pub fn cron_cancel(&self, job_id: &str) -> Result<Map<String, Value>> {
self.block(self.inner.cron_cancel(job_id))
}
pub fn memory_stats(&self, mode: &str) -> Result<Map<String, Value>> {
self.block(self.inner.memory_stats(mode))
}
pub fn request(&self, method: &str, params: Map<String, Value>) -> Result<Map<String, Value>> {
self.block(self.inner.request(method, params))
}
}
fn normalize_cron_add(result: Map<String, Value>) -> Map<String, Value> {
if result.contains_key("job") {
return result;
}
let job_id = result.get("job_id").or_else(|| result.get("id")).cloned();
if let Some(id) = job_id {
let mut job = result.clone();
job.insert("id".into(), id);
job.remove("job_id");
let mut out = Map::new();
out.insert("job".into(), Value::Object(job));
if result.get("duplicate").and_then(|v| v.as_bool()) == Some(true) {
out.insert("duplicate".into(), json!(true));
}
return out;
}
result
}
fn normalize_cron_show(result: Map<String, Value>) -> Map<String, Value> {
if result.contains_key("job") {
return result;
}
let job_id = result.get("job_id").or_else(|| result.get("id")).cloned();
let Some(id) = job_id else {
let mut out = Map::new();
out.insert("job".into(), Value::Null);
return out;
};
let mut job = result.clone();
job.insert("id".into(), id);
job.remove("job_id");
let mut out = Map::new();
out.insert("job".into(), Value::Object(job));
out
}