use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::Mutex;
use super::{AgentRuntime, AgentSpec, Recovered, RuntimeError, RuntimeHealth, RuntimeStatus};
use crate::registry::AgentId;
#[derive(Clone, Default)]
pub struct InMemoryAgentRuntime {
agents: Arc<Mutex<HashMap<AgentId, (AgentSpec, RuntimeHealth)>>>,
logs: Arc<Mutex<HashMap<AgentId, Vec<String>>>>,
}
impl InMemoryAgentRuntime {
pub fn new() -> Self {
Self::default()
}
pub async fn push_log(&self, id: &AgentId, line: impl Into<String>) {
self.logs
.lock()
.await
.entry(id.clone())
.or_default()
.push(line.into());
}
}
#[async_trait]
impl AgentRuntime for InMemoryAgentRuntime {
async fn provision(&self, spec: AgentSpec) -> Result<AgentId, RuntimeError> {
let id = spec.id.clone();
self.agents
.lock()
.await
.insert(id.clone(), (spec, RuntimeHealth::Provisioned));
Ok(id)
}
async fn recover(&self) -> Result<Recovered<AgentId>, RuntimeError> {
let mut adopted: Vec<AgentId> = self.agents.lock().await.keys().cloned().collect();
adopted.sort_by(|a, b| a.as_str().cmp(b.as_str()));
Ok(Recovered::Adopted(adopted))
}
async fn start(&self, id: &AgentId) -> Result<(), RuntimeError> {
let mut guard = self.agents.lock().await;
let (_, health) = guard
.get_mut(id)
.ok_or_else(|| RuntimeError::NotFound(id.clone()))?;
if *health == RuntimeHealth::Healthy {
return Err(RuntimeError::AlreadyRunning(id.clone()));
}
*health = RuntimeHealth::Healthy;
Ok(())
}
async fn stop(&self, id: &AgentId) -> Result<(), RuntimeError> {
let mut guard = self.agents.lock().await;
let (_, health) = guard
.get_mut(id)
.ok_or_else(|| RuntimeError::NotFound(id.clone()))?;
*health = RuntimeHealth::Stopped;
Ok(())
}
async fn health(&self, id: &AgentId) -> Result<RuntimeHealth, RuntimeError> {
self.agents
.lock()
.await
.get(id)
.map(|(_, health)| *health)
.ok_or_else(|| RuntimeError::NotFound(id.clone()))
}
async fn list(&self) -> Result<Vec<RuntimeStatus>, RuntimeError> {
Ok(self
.agents
.lock()
.await
.values()
.map(|(spec, health)| RuntimeStatus {
id: spec.id.clone(),
health: *health,
endpoint: spec.endpoint.clone(),
})
.collect())
}
async fn logs(&self, id: &AgentId, tail: Option<usize>) -> Result<Vec<String>, RuntimeError> {
if !self.agents.lock().await.contains_key(id) {
return Err(RuntimeError::NotFound(id.clone()));
}
let lines = self.logs.lock().await.get(id).cloned().unwrap_or_default();
Ok(match tail {
Some(n) => lines[lines.len().saturating_sub(n)..].to_vec(),
None => lines,
})
}
}