Skip to main content

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}