use crate::context::SharedContext;
use crate::error::RegistryError;
use crate::message::Message;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone)]
pub struct AgentRequest {
pub message: String,
pub history: Vec<Message>,
pub invoker: Option<String>,
}
impl AgentRequest {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
history: Vec::new(),
invoker: None,
}
}
pub fn with_history(mut self, history: Vec<Message>) -> Self {
self.history = history;
self
}
pub fn with_invoker(mut self, invoker: impl Into<String>) -> Self {
self.invoker = Some(invoker.into());
self
}
}
#[derive(Debug, Clone)]
pub struct AgentResponse {
pub content: String,
pub agent_name: String,
}
pub trait AgentHandle: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> &[String];
fn invoke<'a>(
&'a self,
request: AgentRequest,
) -> Pin<Box<dyn Future<Output = Result<AgentResponse, RegistryError>> + Send + 'a>>;
}
pub struct AgentRegistry {
agents: RwLock<HashMap<String, Arc<dyn AgentHandle>>>,
shared_context: Arc<RwLock<SharedContext>>,
invocation_depth: AtomicUsize,
max_depth: usize,
}
impl AgentRegistry {
pub fn new(max_depth: usize) -> Self {
Self {
agents: RwLock::new(HashMap::new()),
shared_context: Arc::new(RwLock::new(SharedContext::new())),
invocation_depth: AtomicUsize::new(0),
max_depth,
}
}
pub async fn register(&self, agent: Arc<dyn AgentHandle>) -> Result<(), RegistryError> {
let name = agent.name().to_string();
let mut agents = self.agents.write().await;
if agents.contains_key(&name) {
return Err(RegistryError::AlreadyRegistered(name));
}
tracing::info!(agent_name = %name, "agent registered");
agents.insert(name, agent);
Ok(())
}
pub async fn unregister(&self, name: &str) -> Result<(), RegistryError> {
let mut agents = self.agents.write().await;
agents
.remove(name)
.ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
tracing::info!(agent_name = %name, "agent unregistered");
Ok(())
}
pub async fn get(&self, name: &str) -> Option<Arc<dyn AgentHandle>> {
self.agents.read().await.get(name).cloned()
}
pub async fn discover(&self, capability: &str) -> Vec<Arc<dyn AgentHandle>> {
self.agents
.read()
.await
.values()
.filter(|a| a.capabilities().contains(&capability.to_string()))
.cloned()
.collect()
}
pub async fn invoke(
&self,
name: &str,
request: AgentRequest,
) -> Result<AgentResponse, RegistryError> {
let current_depth = self.invocation_depth.fetch_add(1, Ordering::SeqCst);
if current_depth >= self.max_depth {
self.invocation_depth.fetch_sub(1, Ordering::SeqCst);
tracing::warn!(
agent_name = %name,
max_depth = %self.max_depth,
"max invocation depth exceeded"
);
return Err(RegistryError::MaxDepthExceeded(self.max_depth));
}
let _span = tracing::info_span!(
"agent_invocation",
agent = %name,
depth = %current_depth,
invoker = ?request.invoker
)
.entered();
let agent = self
.get(name)
.await
.ok_or_else(|| RegistryError::NotFound(name.to_string()))?;
let result = agent.invoke(request).await;
self.invocation_depth.fetch_sub(1, Ordering::SeqCst);
result
}
pub fn shared_context(&self) -> Arc<RwLock<SharedContext>> {
self.shared_context.clone()
}
pub async fn list(&self) -> Vec<String> {
self.agents.read().await.keys().cloned().collect()
}
pub async fn has(&self, name: &str) -> bool {
self.agents.read().await.contains_key(name)
}
pub async fn len(&self) -> usize {
self.agents.read().await.len()
}
pub async fn is_empty(&self) -> bool {
self.agents.read().await.is_empty()
}
pub fn current_depth(&self) -> usize {
self.invocation_depth.load(Ordering::SeqCst)
}
pub fn max_depth(&self) -> usize {
self.max_depth
}
}
impl Default for AgentRegistry {
fn default() -> Self {
Self::new(10) }
}
#[cfg(test)]
mod tests {
use super::*;
struct MockAgent {
name: String,
capabilities: Vec<String>,
}
impl AgentHandle for MockAgent {
fn name(&self) -> &str {
&self.name
}
fn capabilities(&self) -> &[String] {
&self.capabilities
}
fn invoke<'a>(
&'a self,
request: AgentRequest,
) -> Pin<Box<dyn Future<Output = Result<AgentResponse, RegistryError>> + Send + 'a>>
{
Box::pin(async move {
Ok(AgentResponse {
content: format!("Response to: {}", request.message),
agent_name: self.name.clone(),
})
})
}
}
#[tokio::test]
async fn test_register_and_invoke() {
let registry = AgentRegistry::new(5);
let agent = Arc::new(MockAgent {
name: "test-agent".to_string(),
capabilities: vec!["testing".to_string()],
});
registry.register(agent).await.unwrap();
assert!(registry.has("test-agent").await);
let response = registry
.invoke("test-agent", AgentRequest::new("Hello"))
.await
.unwrap();
assert_eq!(response.agent_name, "test-agent");
assert!(response.content.contains("Hello"));
}
#[tokio::test]
async fn test_discover_by_capability() {
let registry = AgentRegistry::new(5);
let agent1 = Arc::new(MockAgent {
name: "analyst".to_string(),
capabilities: vec!["analysis".to_string(), "data".to_string()],
});
let agent2 = Arc::new(MockAgent {
name: "writer".to_string(),
capabilities: vec!["writing".to_string()],
});
registry.register(agent1).await.unwrap();
registry.register(agent2).await.unwrap();
let analysts = registry.discover("analysis").await;
assert_eq!(analysts.len(), 1);
assert_eq!(analysts[0].name(), "analyst");
let writers = registry.discover("writing").await;
assert_eq!(writers.len(), 1);
assert_eq!(writers[0].name(), "writer");
}
#[tokio::test]
async fn test_max_depth_exceeded() {
let registry = AgentRegistry::new(0);
let agent = Arc::new(MockAgent {
name: "test".to_string(),
capabilities: vec![],
});
registry.register(agent).await.unwrap();
let result = registry.invoke("test", AgentRequest::new("Hello")).await;
assert!(matches!(result, Err(RegistryError::MaxDepthExceeded(0))));
}
#[tokio::test]
async fn test_shared_context() {
let registry = AgentRegistry::new(5);
let shared_ctx = registry.shared_context();
{
let mut ctx = shared_ctx.write().await;
ctx.set("key", "value").unwrap();
}
{
let ctx = shared_ctx.read().await;
let value: String = ctx.get("key").unwrap().unwrap();
assert_eq!(value, "value");
}
}
#[tokio::test]
async fn test_agent_not_found() {
let registry = AgentRegistry::new(5);
let result = registry
.invoke("nonexistent", AgentRequest::new("Hello"))
.await;
assert!(matches!(result, Err(RegistryError::NotFound(_))));
}
}