Skip to main content

bitrouter_core/routers/
dynamic_agent.rs

1//! An agent registry wrapper that adds admin inspection capabilities.
2//!
3//! [`DynamicAgentRegistry`] wraps any [`AgentRegistry`] and exposes
4//! upstream connection metadata through the [`AdminAgentRegistry`] trait.
5//!
6//! Parallel to [`DynamicRoutingTable`](super::dynamic::DynamicRoutingTable)
7//! for models and [`DynamicToolRegistry`](super::dynamic_tool::DynamicToolRegistry)
8//! for tools.
9
10use super::admin::{AdminAgentRegistry, AgentUpstreamEntry, AgentUpstreamSource};
11use super::registry::{AgentEntry, AgentRegistry};
12
13/// An agent registry wrapper that adds admin inspection capabilities.
14///
15/// Wraps any `T: AgentRegistry + AgentUpstreamSource` and delegates
16/// discovery through `AgentRegistry` while exposing operational metadata
17/// (connection status, URLs) through `AdminAgentRegistry`.
18pub struct DynamicAgentRegistry<T> {
19    inner: T,
20}
21
22impl<T> DynamicAgentRegistry<T> {
23    /// Create a new dynamic agent registry wrapping the given inner registry.
24    pub fn new(inner: T) -> Self {
25        Self { inner }
26    }
27
28    /// Access the inner registry for protocol-specific operations.
29    pub fn inner(&self) -> &T {
30        &self.inner
31    }
32}
33
34impl<T: AgentRegistry> AgentRegistry for DynamicAgentRegistry<T> {
35    async fn list_agents(&self) -> Vec<AgentEntry> {
36        self.inner.list_agents().await
37    }
38}
39
40impl<T: AgentRegistry + AgentUpstreamSource> AdminAgentRegistry for DynamicAgentRegistry<T> {
41    async fn list_upstreams(&self) -> Vec<AgentUpstreamEntry> {
42        self.inner.list_upstreams().await
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::routers::admin::AgentUpstreamEntry;
50    use crate::routers::registry::AgentEntry;
51
52    struct MockAgentSource {
53        entries: Vec<AgentEntry>,
54        upstreams: Vec<AgentUpstreamEntry>,
55    }
56
57    impl AgentRegistry for MockAgentSource {
58        async fn list_agents(&self) -> Vec<AgentEntry> {
59            self.entries.clone()
60        }
61    }
62
63    impl AgentUpstreamSource for MockAgentSource {
64        async fn list_upstreams(&self) -> Vec<AgentUpstreamEntry> {
65            self.upstreams.clone()
66        }
67    }
68
69    fn test_source() -> MockAgentSource {
70        MockAgentSource {
71            entries: vec![AgentEntry {
72                id: "test-agent".to_owned(),
73                name: Some("Test Agent".to_owned()),
74                provider: "a2a".to_owned(),
75                description: Some("A test agent".to_owned()),
76                version: Some("1.0".to_owned()),
77                skills: Vec::new(),
78                input_modes: vec!["text/plain".to_owned()],
79                output_modes: vec!["text/plain".to_owned()],
80                streaming: None,
81                icon_url: None,
82                documentation_url: None,
83            }],
84            upstreams: vec![AgentUpstreamEntry {
85                name: "test-agent".to_owned(),
86                url: "http://localhost:9000".to_owned(),
87                connected: true,
88            }],
89        }
90    }
91
92    #[tokio::test]
93    async fn delegates_list_agents() {
94        let reg = DynamicAgentRegistry::new(test_source());
95        let agents = reg.list_agents().await;
96        assert_eq!(agents.len(), 1);
97        assert_eq!(agents[0].id, "test-agent");
98    }
99
100    #[tokio::test]
101    async fn delegates_list_upstreams() {
102        let reg = DynamicAgentRegistry::new(test_source());
103        let upstreams = reg.list_upstreams().await;
104        assert_eq!(upstreams.len(), 1);
105        assert_eq!(upstreams[0].name, "test-agent");
106        assert!(upstreams[0].connected);
107    }
108
109    #[test]
110    fn inner_accessor() {
111        let reg = DynamicAgentRegistry::new(test_source());
112        assert_eq!(reg.inner().entries.len(), 1);
113    }
114}