refactor coordinator to use shared lib models and client

This commit is contained in:
Timur Gordon
2025-11-13 21:56:33 +01:00
parent 4b23e5eb7f
commit 84545f0d75
16 changed files with 729 additions and 1973 deletions

View File

@@ -2,6 +2,13 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use serde_json;
#[cfg(not(target_arch = "wasm32"))]
use async_trait::async_trait;
// Transport implementations
#[cfg(not(target_arch = "wasm32"))]
pub mod transports;
// Import types from the main supervisor crate
@@ -32,19 +39,69 @@ use jsonrpsee::{
#[cfg(not(target_arch = "wasm32"))]
use http::{HeaderMap, HeaderName, HeaderValue};
#[cfg(not(target_arch = "wasm32"))]
use std::path::PathBuf;
/// Client for communicating with Hero Supervisor OpenRPC server
/// Requires authentication secret for all operations
/// Transport abstraction for supervisor communication
/// Allows different transport layers (HTTP, Mycelium, etc.)
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
pub trait SupervisorTransport: Send + Sync {
/// Send a JSON-RPC request and await the response
async fn call(
&self,
method: &str,
params: serde_json::Value,
) -> Result<serde_json::Value, ClientError>;
}
/// HTTP transport implementation using jsonrpsee
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone)]
pub struct SupervisorClient {
pub struct HttpTransport {
client: HttpClient,
server_url: String,
}
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl SupervisorTransport for HttpTransport {
async fn call(
&self,
method: &str,
params: serde_json::Value,
) -> Result<serde_json::Value, ClientError> {
// params is already an array from the caller
// jsonrpsee expects params as an array, so pass it directly
let result: serde_json::Value = if params.is_array() {
// Use the array directly with rpc_params
let arr = params.as_array().unwrap();
match arr.len() {
0 => self.client.request(method, rpc_params![]).await?,
1 => self.client.request(method, rpc_params![&arr[0]]).await?,
_ => {
// For multiple params, we need to pass them as a slice
self.client.request(method, rpc_params![arr]).await?
}
}
} else {
// Single param not in array
self.client.request(method, rpc_params![&params]).await?
};
Ok(result)
}
}
/// Client for communicating with Hero Supervisor OpenRPC server
/// Generic over transport layer (HTTP, Mycelium, etc.)
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone)]
pub struct SupervisorClient<T: SupervisorTransport = HttpTransport> {
transport: T,
secret: String,
}
/// Legacy type alias for backward compatibility
#[cfg(not(target_arch = "wasm32"))]
pub type HttpSupervisorClient = SupervisorClient<HttpTransport>;
/// Error types for client operations
#[cfg(not(target_arch = "wasm32"))]
#[derive(Error, Debug)]
@@ -258,8 +315,8 @@ impl SupervisorClientBuilder {
self
}
/// Build the SupervisorClient
pub fn build(self) -> ClientResult<SupervisorClient> {
/// Build the SupervisorClient with HTTP transport
pub fn build(self) -> ClientResult<SupervisorClient<HttpTransport>> {
let server_url = self.url
.ok_or_else(|| ClientError::Http("URL is required".to_string()))?;
let secret = self.secret
@@ -280,9 +337,10 @@ impl SupervisorClientBuilder {
.build(&server_url)
.map_err(|e| ClientError::Http(e.to_string()))?;
let transport = HttpTransport { client };
Ok(SupervisorClient {
client,
server_url,
transport,
secret,
})
}
@@ -296,25 +354,24 @@ impl Default for SupervisorClientBuilder {
}
#[cfg(not(target_arch = "wasm32"))]
impl SupervisorClient {
/// Create a builder for SupervisorClient
impl SupervisorClient<HttpTransport> {
/// Create a builder for HTTP-based SupervisorClient
pub fn builder() -> SupervisorClientBuilder {
SupervisorClientBuilder::new()
}
/// Get the server URL
pub fn server_url(&self) -> &str {
&self.server_url
}
#[cfg(not(target_arch = "wasm32"))]
impl<T: SupervisorTransport> SupervisorClient<T> {
/// Create a new client with a custom transport
pub fn new(transport: T, secret: String) -> Self {
Self { transport, secret }
}
/// Test connection using OpenRPC discovery method
/// This calls the standard `rpc.discover` method that should be available on any OpenRPC server
pub async fn discover(&self) -> ClientResult<serde_json::Value> {
let result: serde_json::Value = self
.client
.request("rpc.discover", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
self.transport.call("rpc.discover", serde_json::json!([])).await
}
/// Register a new runner to the supervisor
@@ -324,11 +381,8 @@ impl SupervisorClient {
&self,
name: &str,
) -> ClientResult<()> {
let _: () = self
.client
.request("runner.create", rpc_params![name])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(())
let result = self.transport.call("runner.create", serde_json::json!([name])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Create a new job without queuing it to a runner
@@ -337,20 +391,14 @@ impl SupervisorClient {
&self,
job: Job,
) -> ClientResult<String> {
let job_id: String = self
.client
.request("job.create", rpc_params![job])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(job_id)
let result = self.transport.call("job.create", serde_json::json!([job])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// List all jobs
pub async fn job_list(&self) -> ClientResult<Vec<Job>> {
let jobs: Vec<Job> = self
.client
.request("job.list", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(jobs)
let result = self.transport.call("job.list", serde_json::json!([])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Run a job on the appropriate runner and wait for the result (blocking)
@@ -369,11 +417,8 @@ impl SupervisorClient {
params["timeout"] = serde_json::json!(t);
}
let result: JobRunResponse = self
.client
.request("job.run", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
let result = self.transport.call("job.run", serde_json::json!([params])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Start a job without waiting for the result (non-blocking)
@@ -387,58 +432,40 @@ impl SupervisorClient {
"job": job
});
let result: JobStartResponse = self
.client
.request("job.start", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
let result = self.transport.call("job.start", serde_json::json!([params])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get the current status of a job
pub async fn job_status(&self, job_id: &str) -> ClientResult<JobStatus> {
let status: JobStatus = self
.client
.request("job.status", rpc_params![job_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(status)
let result = self.transport.call("job.status", serde_json::json!([job_id])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get the result of a completed job (blocks until result is available)
pub async fn job_result(&self, job_id: &str) -> ClientResult<JobResult> {
let result: JobResult = self
.client
.request("job.result", rpc_params![job_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
let result = self.transport.call("job.result", serde_json::json!([job_id])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Remove a runner from the supervisor
/// Authentication via Authorization header (set during client creation)
pub async fn runner_remove(&self, runner_id: &str) -> ClientResult<()> {
let _: () = self
.client
.request("runner.remove", rpc_params![runner_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(())
let result = self.transport.call("runner.remove", serde_json::json!([runner_id])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// List all runner IDs
pub async fn runner_list(&self) -> ClientResult<Vec<String>> {
let runners: Vec<String> = self
.client
.request("runner.list", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(runners)
let result = self.transport.call("runner.list", serde_json::json!([])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Start a specific runner
/// Authentication via Authorization header (set during client creation)
pub async fn start_runner(&self, actor_id: &str) -> ClientResult<()> {
let _: () = self
.client
.request("runner.start", rpc_params![actor_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(())
let result = self.transport.call("runner.start", serde_json::json!([actor_id])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Add a runner to the supervisor
@@ -447,21 +474,21 @@ impl SupervisorClient {
let params = serde_json::json!({
"config": config
});
let _: () = self
.client
.request("runner.add", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
let result = self
.transport
.call("runner.add", serde_json::json!([params]))
.await?;
Ok(())
}
/// Get status of a specific runner
/// Authentication via Authorization header (set during client creation)
pub async fn get_runner_status(&self, actor_id: &str) -> ClientResult<RunnerStatus> {
let status: RunnerStatus = self
.client
.request("runner.status", rpc_params![actor_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(status)
let result = self
.transport
.call("runner.status", serde_json::json!([actor_id]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get logs for a specific runner
@@ -471,11 +498,11 @@ impl SupervisorClient {
lines: Option<usize>,
follow: bool,
) -> ClientResult<Vec<LogInfo>> {
let logs: Vec<LogInfo> = self
.client
.request("get_runner_logs", rpc_params![actor_id, lines, follow])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(logs)
let result = self
.transport
.call("get_runner_logs", serde_json::json!([actor_id, lines, follow]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Queue a job to a specific runner
@@ -485,10 +512,10 @@ impl SupervisorClient {
"job": job
});
let _: () = self
.client
.request("queue_job_to_runner", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
let result = self
.transport
.call("queue_job_to_runner", serde_json::json!([params]))
.await?;
Ok(())
}
@@ -500,11 +527,11 @@ impl SupervisorClient {
"timeout_secs": timeout_secs
});
let result: Option<String> = self
.client
.request("queue_and_wait", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
let result = self
.transport
.call("queue_and_wait", serde_json::json!([params]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Run a job on a specific runner
@@ -513,56 +540,56 @@ impl SupervisorClient {
"job": job
});
let result: JobResult = self
.client
.request("job.run", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
let result = self
.transport
.call("job.run", serde_json::json!([params]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get job result by job ID
pub async fn get_job_result(&self, job_id: &str) -> ClientResult<Option<String>> {
let result: Option<String> = self
.client
.request("get_job_result", rpc_params![job_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(result)
let result = self
.transport
.call("get_job_result", serde_json::json!([job_id]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get status of all runners
pub async fn get_all_runner_status(&self) -> ClientResult<Vec<(String, RunnerStatus)>> {
let statuses: Vec<(String, RunnerStatus)> = self
.client
.request("get_all_runner_status", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(statuses)
let result = self
.transport
.call("get_all_runner_status", serde_json::json!([]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Start all runners
pub async fn start_all(&self) -> ClientResult<Vec<(String, bool)>> {
let results: Vec<(String, bool)> = self
.client
.request("start_all", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(results)
let result = self
.transport
.call("start_all", serde_json::json!([]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Stop all runners
pub async fn stop_all(&self, force: bool) -> ClientResult<Vec<(String, bool)>> {
let results: Vec<(String, bool)> = self
.client
.request("stop_all", rpc_params![force])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(results)
let result = self
.transport
.call("stop_all", serde_json::json!([force]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get status of all runners (alternative method)
pub async fn get_all_status(&self) -> ClientResult<Vec<(String, RunnerStatus)>> {
let statuses: Vec<(String, RunnerStatus)> = self
.client
.request("get_all_status", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(statuses)
let result = self
.transport
.call("get_all_status", serde_json::json!([]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Add a secret to the supervisor
@@ -576,10 +603,10 @@ impl SupervisorClient {
"secret_value": secret_value
});
let _: () = self
.client
.request("add_secret", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
let result = self
.transport
.call("add_secret", serde_json::json!([params]))
.await?;
Ok(())
}
@@ -594,10 +621,10 @@ impl SupervisorClient {
"secret_value": secret_value
});
let _: () = self
.client
.request("remove_secret", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
let result = self
.transport
.call("remove_secret", serde_json::json!([params]))
.await?;
Ok(())
}
@@ -605,91 +632,87 @@ impl SupervisorClient {
pub async fn list_secrets(&self) -> ClientResult<SupervisorInfo> {
let params = serde_json::json!({});
let info: SupervisorInfo = self
.client
.request("list_secrets", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(info)
let result = self
.transport
.call("list_secrets", serde_json::json!([params]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Stop a running job
pub async fn job_stop(&self, job_id: &str) -> ClientResult<()> {
let _: () = self.client
.request("job.stop", rpc_params![job_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(())
let result = self.transport.call("job.stop", serde_json::json!([job_id])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Delete a job from the system
pub async fn job_delete(&self, job_id: &str) -> ClientResult<()> {
let _: () = self.client
.request("job.delete", rpc_params![job_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(())
let result = self.transport.call("job.delete", serde_json::json!([job_id])).await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get supervisor information including secret counts
pub async fn get_supervisor_info(&self) -> ClientResult<SupervisorInfo> {
let info: SupervisorInfo = self
.client
.request("supervisor.info", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(info)
let result = self
.transport
.call("supervisor.info", serde_json::json!([]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Get a job by ID
pub async fn job_get(&self, job_id: &str) -> ClientResult<Job> {
let job: Job = self
.client
.request("job.get", rpc_params![job_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(job)
let result = self
.transport
.call("job.get", serde_json::json!([job_id]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
// ========== Auth/API Key Methods ==========
/// Verify the current API key
pub async fn auth_verify(&self) -> ClientResult<AuthVerifyResponse> {
let response: AuthVerifyResponse = self
.client
.request("auth.verify", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(response)
let result = self
.transport
.call("auth.verify", serde_json::json!([]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Create a new API key (admin only)
pub async fn key_create(&self, key: ApiKey) -> ClientResult<()> {
let _: () = self
.client
.request("key.create", rpc_params![key])
.await.map_err(|e| ClientError::JsonRpc(e))?;
let result = self
.transport
.call("key.create", serde_json::json!([key]))
.await?;
Ok(())
}
/// Generate a new API key with auto-generated key value (admin only)
pub async fn key_generate(&self, params: GenerateApiKeyParams) -> ClientResult<ApiKey> {
let api_key: ApiKey = self
.client
.request("key.generate", rpc_params![params])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(api_key)
let result = self
.transport
.call("key.generate", serde_json::json!([params]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
/// Remove an API key (admin only)
pub async fn key_delete(&self, key_id: String) -> ClientResult<()> {
let _: () = self
.client
.request("key.delete", rpc_params![key_id])
.await.map_err(|e| ClientError::JsonRpc(e))?;
let result = self
.transport
.call("key.delete", serde_json::json!([key_id]))
.await?;
Ok(())
}
/// List all API keys (admin only)
pub async fn key_list(&self) -> ClientResult<Vec<ApiKey>> {
let keys: Vec<ApiKey> = self
.client
.request("key.list", rpc_params![])
.await.map_err(|e| ClientError::JsonRpc(e))?;
Ok(keys)
let result = self
.transport
.call("key.list", serde_json::json!([]))
.await?;
serde_json::from_value(result).map_err(ClientError::Serialization)
}
}