codetether_agent/provider/registry.rs
1//! [`ProviderRegistry`] — name → provider map with resolution.
2//!
3//! Holds all initialised providers and resolves `"provider/model"` strings
4//! to the correct [`Provider`] instance.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use codetether_agent::provider::ProviderRegistry;
10//!
11//! let registry = ProviderRegistry::new();
12//! assert!(registry.list().is_empty());
13//! ```
14
15use super::parse::parse_model_string;
16use super::traits::Provider;
17use crate::secrets::ProviderSecrets;
18use anyhow::Result;
19use std::collections::HashMap;
20use std::sync::Arc;
21
22/// Registry of available providers.
23#[derive(Clone)]
24pub struct ProviderRegistry {
25 pub(crate) providers: HashMap<String, Arc<dyn Provider>>,
26}
27
28impl std::fmt::Debug for ProviderRegistry {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("ProviderRegistry")
31 .field("provider_count", &self.providers.len())
32 .field("providers", &self.providers.keys().collect::<Vec<_>>())
33 .finish()
34 }
35}
36
37impl ProviderRegistry {
38 /// Create an empty registry.
39 ///
40 /// # Examples
41 ///
42 /// ```rust
43 /// use codetether_agent::provider::ProviderRegistry;
44 /// let registry = ProviderRegistry::new();
45 /// assert!(registry.list().is_empty());
46 /// ```
47 pub fn new() -> Self {
48 Self {
49 providers: HashMap::new(),
50 }
51 }
52
53 /// Register a provider (automatically wrapped with metrics instrumentation).
54 ///
55 /// # Examples
56 ///
57 /// ```rust,no_run
58 /// use codetether_agent::provider::ProviderRegistry;
59 /// use std::sync::Arc;
60 /// # fn demo(registry: &mut ProviderRegistry, p: Arc<dyn codetether_agent::provider::Provider>) {
61 /// registry.register(p);
62 /// # }
63 /// ```
64 pub fn register(&mut self, provider: Arc<dyn Provider>) {
65 let name = provider.name().to_string();
66 let wrapped = super::metrics::MetricsProvider::wrap(provider);
67 self.providers.insert(name, wrapped);
68 }
69
70 /// Build a registry directly from provider secret records.
71 pub fn from_provider_secrets_map(secrets: &HashMap<String, ProviderSecrets>) -> Self {
72 let mut registry = Self::new();
73 for (provider_id, provider_secrets) in secrets {
74 if let Some(provider) = super::init_dispatch::dispatch(provider_id, provider_secrets) {
75 registry.register(provider);
76 }
77 }
78 registry
79 }
80
81 /// Get a provider by name.
82 ///
83 /// # Examples
84 ///
85 /// ```rust
86 /// use codetether_agent::provider::ProviderRegistry;
87 /// let registry = ProviderRegistry::new();
88 /// assert!(registry.get("openai").is_none());
89 /// ```
90 pub fn get(&self, name: &str) -> Option<Arc<dyn Provider>> {
91 self.providers.get(name).cloned()
92 }
93
94 /// List all registered provider names.
95 ///
96 /// # Examples
97 ///
98 /// ```rust
99 /// use codetether_agent::provider::ProviderRegistry;
100 /// let registry = ProviderRegistry::new();
101 /// assert!(registry.list().is_empty());
102 /// ```
103 pub fn list(&self) -> Vec<&str> {
104 self.providers.keys().map(|s| s.as_str()).collect()
105 }
106
107 /// Resolve a model string to a provider and model name.
108 ///
109 /// Accepts:
110 /// - `"provider/model"` (e.g. `"openai/gpt-4o"`)
111 /// - `"model"` alone (uses first available provider)
112 ///
113 /// # Examples
114 ///
115 /// ```rust
116 /// use codetether_agent::provider::ProviderRegistry;
117 ///
118 /// let registry = ProviderRegistry::new();
119 /// // No providers ⇒ error
120 /// assert!(registry.resolve_model("gpt-4o").is_err());
121 /// ```
122 pub fn resolve_model(&self, model_str: &str) -> Result<(Arc<dyn Provider>, String)> {
123 let (provider_name, model) = parse_model_string(model_str);
124
125 if let Some(provider_name) = provider_name {
126 let normalized = match provider_name {
127 "local-cuda" | "localcuda" => "local_cuda",
128 "zhipuai" => "zai",
129 other => other,
130 };
131
132 let provider = self.providers.get(normalized).cloned().ok_or_else(|| {
133 anyhow::anyhow!(
134 "Provider '{}' not found. Available: {:?}",
135 normalized,
136 self.list()
137 )
138 })?;
139 Ok((provider, model.to_string()))
140 } else {
141 let first_provider = self
142 .providers
143 .values()
144 .next()
145 .ok_or_else(|| anyhow::anyhow!("No providers available in registry"))?;
146 Ok((first_provider.clone(), model_str.to_string()))
147 }
148 }
149}
150
151impl Default for ProviderRegistry {
152 fn default() -> Self {
153 Self::new()
154 }
155}