use super::{AgentBus, BusMessage};
use crate::a2a::types::Part;
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct RelayAgentProfile {
pub name: String,
pub capabilities: Vec<String>,
}
#[derive(Clone)]
pub struct ProtocolRelayRuntime {
bus: Arc<AgentBus>,
relay_id: String,
}
impl ProtocolRelayRuntime {
pub fn new(bus: Arc<AgentBus>) -> Self {
Self {
bus,
relay_id: format!("relay-{}", &Uuid::new_v4().to_string()[..8]),
}
}
pub fn with_relay_id(bus: Arc<AgentBus>, relay_id: impl Into<String>) -> Self {
Self {
bus,
relay_id: relay_id.into(),
}
}
pub fn relay_id(&self) -> &str {
&self.relay_id
}
pub fn register_agents(&self, agents: &[RelayAgentProfile]) {
for agent in agents {
let mut capabilities = agent.capabilities.clone();
if !capabilities.iter().any(|c| c == "relay") {
capabilities.push("relay".to_string());
}
capabilities.push(format!("relay:{}", self.relay_id));
let handle = self.bus.handle(agent.name.clone());
handle.announce_ready(capabilities);
}
}
pub fn shutdown_agents(&self, agent_ids: &[String]) {
for agent_id in agent_ids {
let handle = self.bus.handle(agent_id.clone());
handle.announce_shutdown();
}
}
pub fn send_handoff(&self, from: &str, to: &str, text: &str) {
let payload = BusMessage::AgentMessage {
from: from.to_string(),
to: to.to_string(),
parts: vec![Part::Text {
text: text.to_string(),
}],
};
let correlation = Some(format!("{}:{}", self.relay_id, Uuid::new_v4()));
let handle = self.bus.handle(from.to_string());
handle.send_with_correlation(format!("agent.{to}"), payload.clone(), correlation.clone());
handle.send_with_correlation(format!("relay.{}", self.relay_id), payload, correlation);
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::time::{Duration, timeout};
#[tokio::test]
async fn test_register_and_shutdown_agents() {
let bus = AgentBus::new().into_arc();
let relay = ProtocolRelayRuntime::with_relay_id(bus.clone(), "relay-test");
let agents = vec![
RelayAgentProfile {
name: "auto-planner".to_string(),
capabilities: vec!["planning".to_string()],
},
RelayAgentProfile {
name: "auto-coder".to_string(),
capabilities: vec!["coding".to_string()],
},
];
relay.register_agents(&agents);
assert!(bus.registry.get("auto-planner").is_some());
assert!(bus.registry.get("auto-coder").is_some());
relay.shutdown_agents(&["auto-planner".to_string(), "auto-coder".to_string()]);
assert!(bus.registry.get("auto-planner").is_none());
assert!(bus.registry.get("auto-coder").is_none());
}
#[tokio::test]
async fn test_send_handoff_emits_agent_and_relay_topics() {
let bus = AgentBus::new().into_arc();
let relay = ProtocolRelayRuntime::with_relay_id(bus.clone(), "relay-test");
let mut observer = bus.handle("observer");
relay.send_handoff("auto-planner", "auto-coder", "handoff payload");
let first = timeout(Duration::from_millis(200), observer.recv())
.await
.expect("first envelope timeout")
.expect("first envelope missing");
let second = timeout(Duration::from_millis(200), observer.recv())
.await
.expect("second envelope timeout")
.expect("second envelope missing");
let topics = [first.topic, second.topic];
assert!(topics.iter().any(|t| t == "agent.auto-coder"));
assert!(topics.iter().any(|t| t == "relay.relay-test"));
}
}