use std::sync::OnceLock;
use tokio::sync::Mutex;
use car_external_agents::ExternalAgentSpec;
fn cache() -> &'static Mutex<Option<Vec<ExternalAgentSpec>>> {
static CACHE: OnceLock<Mutex<Option<Vec<ExternalAgentSpec>>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(None))
}
pub async fn list(include_health: bool) -> Result<String, String> {
let mut guard = cache().lock().await;
if guard.is_none() {
*guard = Some(car_external_agents::detect().await);
}
let mut specs = guard.as_ref().expect("populated above").clone();
drop(guard);
if include_health {
let healths = car_external_agents::health_all(&specs, false).await;
let by_id: std::collections::HashMap<&str, &car_external_agents::ExternalAgentHealth> =
healths.iter().map(|h| (h.id.as_str(), h)).collect();
for spec in specs.iter_mut() {
if let Some(h) = by_id.get(spec.id.as_str()) {
spec.health = Some((*h).clone());
}
}
}
serde_json::to_string(&specs).map_err(|e| e.to_string())
}
pub async fn detect(include_health: bool) -> Result<String, String> {
let specs = if include_health {
car_external_agents::detect_with_health(true).await
} else {
car_external_agents::detect().await
};
let mut guard = cache().lock().await;
*guard = Some(
specs
.iter()
.map(|s| {
let mut s = s.clone();
s.health = None;
s
})
.collect(),
);
serde_json::to_string(&specs).map_err(|e| e.to_string())
}
pub async fn health(force: bool) -> Result<String, String> {
let specs = car_external_agents::detect().await;
let healths = car_external_agents::health_all(&specs, force).await;
serde_json::to_string(&healths).map_err(|e| e.to_string())
}
pub async fn health_one(id: &str, force: bool) -> Result<String, String> {
let result = car_external_agents::health_one(id, force)
.await
.ok_or_else(|| format!("no detected external agent with id `{id}`"))?;
serde_json::to_string(&result).map_err(|e| e.to_string())
}
pub async fn invoke(id: &str, task: &str, options_json: &str) -> Result<String, String> {
let opts: car_external_agents::InvokeOptions = if options_json.trim().is_empty() {
car_external_agents::InvokeOptions::default()
} else {
serde_json::from_str(options_json).map_err(|e| format!("invalid options: {e}"))?
};
let result = car_external_agents::invoke(id, task, opts)
.await
.map_err(|e| e.to_string())?;
serde_json::to_string(&result).map_err(|e| e.to_string())
}