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}