synwire_core/language_models/
registry.rs1use std::collections::HashMap;
4use std::sync::{Arc, RwLock};
5
6#[derive(Debug, Clone)]
26pub struct ModelProfile {
27 pub model_id: String,
29 pub provider: String,
31 pub supports_tools: bool,
33 pub supports_streaming: bool,
35 pub supports_structured_output: bool,
37 pub max_context_tokens: Option<u64>,
39 pub max_output_tokens: Option<u64>,
41}
42
43pub mod capabilities {
47 pub const TOOLS: &str = "tools";
49 pub const STREAMING: &str = "streaming";
51 pub const STRUCTURED_OUTPUT: &str = "structured_output";
53}
54
55pub trait ModelProfileRegistry: Send + Sync {
59 fn register(&self, profile: ModelProfile);
63
64 fn get(&self, model_id: &str) -> Option<ModelProfile>;
66
67 fn supports(&self, model_id: &str, capability: &str) -> bool;
72}
73
74#[derive(Debug, Clone, Default)]
101pub struct InMemoryModelProfileRegistry {
102 profiles: Arc<RwLock<HashMap<String, ModelProfile>>>,
103}
104
105impl InMemoryModelProfileRegistry {
106 pub fn new() -> Self {
108 Self::default()
109 }
110}
111
112impl ModelProfileRegistry for InMemoryModelProfileRegistry {
113 fn register(&self, profile: ModelProfile) {
114 let Ok(mut map) = self.profiles.write() else {
115 return;
116 };
117 let _ = map.insert(profile.model_id.clone(), profile);
118 }
119
120 fn get(&self, model_id: &str) -> Option<ModelProfile> {
121 let map = self.profiles.read().ok()?;
122 map.get(model_id).cloned()
123 }
124
125 fn supports(&self, model_id: &str, capability: &str) -> bool {
126 let Some(profile) = self.get(model_id) else {
127 return false;
128 };
129 match capability {
130 capabilities::TOOLS => profile.supports_tools,
131 capabilities::STREAMING => profile.supports_streaming,
132 capabilities::STRUCTURED_OUTPUT => profile.supports_structured_output,
133 _ => false,
134 }
135 }
136}
137
138#[cfg(test)]
139#[allow(clippy::unwrap_used)]
140mod tests {
141 use super::*;
142
143 fn sample_profile() -> ModelProfile {
144 ModelProfile {
145 model_id: "gpt-4o".into(),
146 provider: "openai".into(),
147 supports_tools: true,
148 supports_streaming: true,
149 supports_structured_output: false,
150 max_context_tokens: Some(128_000),
151 max_output_tokens: Some(16_384),
152 }
153 }
154
155 #[test]
156 fn register_and_retrieve() {
157 let registry = InMemoryModelProfileRegistry::new();
158 registry.register(sample_profile());
159
160 let profile = registry.get("gpt-4o").unwrap();
161 assert_eq!(profile.model_id, "gpt-4o");
162 assert_eq!(profile.provider, "openai");
163 assert!(profile.supports_tools);
164 assert_eq!(profile.max_context_tokens, Some(128_000));
165 }
166
167 #[test]
168 fn get_returns_none_for_unknown_model() {
169 let registry = InMemoryModelProfileRegistry::new();
170 assert!(registry.get("nonexistent-model").is_none());
171 }
172
173 #[test]
174 fn supports_tools() {
175 let registry = InMemoryModelProfileRegistry::new();
176 registry.register(sample_profile());
177
178 assert!(registry.supports("gpt-4o", "tools"));
179 }
180
181 #[test]
182 fn supports_streaming() {
183 let registry = InMemoryModelProfileRegistry::new();
184 registry.register(sample_profile());
185
186 assert!(registry.supports("gpt-4o", "streaming"));
187 }
188
189 #[test]
190 fn supports_structured_output_false() {
191 let registry = InMemoryModelProfileRegistry::new();
192 registry.register(sample_profile());
193
194 assert!(!registry.supports("gpt-4o", "structured_output"));
195 }
196
197 #[test]
198 fn supports_unknown_capability() {
199 let registry = InMemoryModelProfileRegistry::new();
200 registry.register(sample_profile());
201
202 assert!(!registry.supports("gpt-4o", "vision"));
203 }
204
205 #[test]
206 fn supports_unknown_model() {
207 let registry = InMemoryModelProfileRegistry::new();
208 assert!(!registry.supports("nonexistent", "tools"));
209 }
210
211 #[test]
212 fn register_replaces_existing() {
213 let registry = InMemoryModelProfileRegistry::new();
214 registry.register(sample_profile());
215
216 let mut updated = sample_profile();
217 updated.supports_structured_output = true;
218 registry.register(updated);
219
220 assert!(registry.supports("gpt-4o", "structured_output"));
221 }
222
223 #[test]
224 fn clone_shares_state() {
225 let registry = InMemoryModelProfileRegistry::new();
226 let cloned = registry.clone();
227
228 registry.register(sample_profile());
229 assert!(cloned.get("gpt-4o").is_some());
230 }
231}