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