Skip to main content

bitrouter_core/routers/
registry.rs

1//! Discovery registry types and traits for models, tools, and agents.
2//!
3//! These are the core abstractions powering public discovery endpoints
4//! (`GET /v1/models`, `GET /v1/tools`, `GET /v1/agents`). Each entry
5//! type is protocol-agnostic — conversion from protocol-specific types
6//! (MCP tools, A2A agent cards) happens in the respective crates.
7
8use std::future::Future;
9
10use serde_json::Value;
11
12use super::routing_table::ModelPricing;
13
14// ── Model ──────────────────────────────────────────────────────────
15
16/// A single model available through a provider, with its metadata.
17#[derive(Debug, Clone)]
18pub struct ModelEntry {
19    /// The upstream model ID (e.g. "gpt-4o", "claude-sonnet-4-20250514").
20    pub id: String,
21    /// The providers that offer this model.
22    pub providers: Vec<String>,
23    /// Human-readable display name.
24    pub name: Option<String>,
25    /// Brief description of the model's capabilities.
26    pub description: Option<String>,
27    /// Maximum input context window in tokens.
28    pub max_input_tokens: Option<u64>,
29    /// Maximum number of output tokens the model can produce.
30    pub max_output_tokens: Option<u64>,
31    /// Input modalities the model accepts (e.g. "text", "image").
32    pub input_modalities: Vec<String>,
33    /// Output modalities the model can produce.
34    pub output_modalities: Vec<String>,
35    /// Token pricing per million tokens.
36    pub pricing: Option<ModelPricing>,
37}
38
39/// Read-only registry for discovering models available across all configured providers.
40///
41/// Parallel to [`RoutingTable`](super::routing_table::RoutingTable) which handles
42/// request routing, this trait handles model discovery — listing what external
43/// capabilities BitRouter knows about.
44pub trait ModelRegistry {
45    /// Lists all models available across all configured providers.
46    fn list_models(&self) -> Vec<ModelEntry> {
47        Vec::new()
48    }
49}
50
51// ── Tool ───────────────────────────────────────────────────────────
52
53/// A single tool available through the router, regardless of origin protocol.
54///
55/// Unifies MCP tools (structured, schema-driven) and A2A skills
56/// (unstructured, tag-driven) into a common discovery type.
57#[derive(Debug, Clone)]
58pub struct ToolEntry {
59    /// Machine-readable tool identifier (e.g. `"github/search"`).
60    pub id: String,
61    /// Human-readable display name.
62    pub name: Option<String>,
63    /// The server or agent that provides this tool.
64    pub provider: String,
65    /// Description of what the tool does.
66    pub description: Option<String>,
67    /// JSON Schema describing input parameters.
68    pub input_schema: Option<Value>,
69}
70
71/// Read-only registry for discovering tools available across all sources.
72pub trait ToolRegistry: Send + Sync {
73    /// Lists all tools available through the router.
74    fn list_tools(&self) -> impl Future<Output = Vec<ToolEntry>> + Send;
75}
76
77impl<T: ToolRegistry> ToolRegistry for std::sync::Arc<T> {
78    async fn list_tools(&self) -> Vec<ToolEntry> {
79        (**self).list_tools().await
80    }
81}
82
83/// Combines two [`ToolRegistry`] implementations into one.
84///
85/// `list_tools()` returns entries from both registries (primary first).
86pub struct CompositeToolRegistry<A, B> {
87    primary: A,
88    secondary: B,
89}
90
91impl<A, B> CompositeToolRegistry<A, B> {
92    pub fn new(primary: A, secondary: B) -> Self {
93        Self { primary, secondary }
94    }
95}
96
97impl<A: ToolRegistry, B: ToolRegistry> ToolRegistry for CompositeToolRegistry<A, B> {
98    async fn list_tools(&self) -> Vec<ToolEntry> {
99        let mut tools = self.primary.list_tools().await;
100        tools.extend(self.secondary.list_tools().await);
101        tools
102    }
103}
104
105// ── Skill ─────────────────────────────────────────────────────────
106
107/// A skill tracked in the bitrouter skills registry.
108///
109/// Mirrors the Anthropic Skills API object shape while adding
110/// bitrouter-specific fields (source, required_apis).
111#[derive(Debug, Clone)]
112pub struct SkillEntry {
113    /// Unique skill identifier (UUID string).
114    pub id: String,
115    /// Skill name (agentskills.io format: 1–64 chars, lowercase + hyphens).
116    pub name: String,
117    /// What the skill does and when to use it.
118    pub description: String,
119    /// "config" or "manual".
120    pub source: String,
121    /// Provider names this skill depends on for paid API access.
122    pub required_apis: Vec<String>,
123    /// ISO 8601 timestamp.
124    pub created_at: String,
125    /// ISO 8601 timestamp.
126    pub updated_at: String,
127}
128
129/// CRUD service for the skills registry.
130///
131/// Implemented by `bitrouter-skills`, consumed by `bitrouter-api` filters.
132pub trait SkillService: Send + Sync {
133    /// Register a new skill. Returns the assigned ID.
134    fn create(
135        &self,
136        name: String,
137        description: String,
138        source: Option<String>,
139        required_apis: Vec<String>,
140    ) -> impl Future<Output = Result<SkillEntry, String>> + Send;
141
142    /// List all registered skills.
143    fn list(&self) -> impl Future<Output = Result<Vec<SkillEntry>, String>> + Send;
144
145    /// Retrieve a single skill by name.
146    fn get(&self, name: &str) -> impl Future<Output = Result<Option<SkillEntry>, String>> + Send;
147
148    /// Delete a skill by name. Returns true if it existed.
149    fn delete(&self, name: &str) -> impl Future<Output = Result<bool, String>> + Send;
150}
151
152impl<T: SkillService> SkillService for std::sync::Arc<T> {
153    async fn create(
154        &self,
155        name: String,
156        description: String,
157        source: Option<String>,
158        required_apis: Vec<String>,
159    ) -> Result<SkillEntry, String> {
160        (**self)
161            .create(name, description, source, required_apis)
162            .await
163    }
164
165    async fn list(&self) -> Result<Vec<SkillEntry>, String> {
166        (**self).list().await
167    }
168
169    async fn get(&self, name: &str) -> Result<Option<SkillEntry>, String> {
170        (**self).get(name).await
171    }
172
173    async fn delete(&self, name: &str) -> Result<bool, String> {
174        (**self).delete(name).await
175    }
176}
177
178// ── Agent ──────────────────────────────────────────────────────────
179
180/// A single agent available through the router, with its metadata.
181///
182/// Protocol-agnostic summary of an agent's identity and capabilities.
183/// Conversion from A2A `AgentCard` happens in `bitrouter-a2a`.
184#[derive(Debug, Clone)]
185pub struct AgentEntry {
186    /// Machine-readable agent identifier.
187    pub id: String,
188    /// Human-readable display name.
189    pub name: Option<String>,
190    /// The source that provides this agent.
191    pub provider: String,
192    /// Description of the agent's capabilities.
193    pub description: Option<String>,
194    /// Agent version string.
195    pub version: Option<String>,
196    /// Skills or capabilities the agent advertises.
197    pub skills: Vec<AgentSkillEntry>,
198    /// Input content types the agent accepts (MIME types).
199    pub input_modes: Vec<String>,
200    /// Output content types the agent produces (MIME types).
201    pub output_modes: Vec<String>,
202    /// Whether the agent supports streaming responses.
203    pub streaming: Option<bool>,
204    /// Icon URL for display.
205    pub icon_url: Option<String>,
206    /// Documentation URL.
207    pub documentation_url: Option<String>,
208}
209
210/// A skill advertised by an agent.
211#[derive(Debug, Clone)]
212pub struct AgentSkillEntry {
213    /// Unique skill identifier.
214    pub id: String,
215    /// Human-readable skill name.
216    pub name: String,
217    /// Description of the skill.
218    pub description: Option<String>,
219    /// Keywords for discovery.
220    pub tags: Vec<String>,
221    /// Example prompts or scenarios.
222    pub examples: Vec<String>,
223}
224
225/// Read-only registry for discovering agents available across all sources.
226pub trait AgentRegistry: Send + Sync {
227    /// Lists all agents available through the router.
228    fn list_agents(&self) -> impl Future<Output = Vec<AgentEntry>> + Send;
229}
230
231impl<T: AgentRegistry> AgentRegistry for std::sync::Arc<T> {
232    async fn list_agents(&self) -> Vec<AgentEntry> {
233        (**self).list_agents().await
234    }
235}