Skip to main content

aptu_core/ai/registry/
config.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Static provider configuration registry.
4
5use super::consts::{
6    PROVIDER_ANTHROPIC, PROVIDER_CEREBRAS, PROVIDER_GEMINI, PROVIDER_GROQ, PROVIDER_OPENROUTER,
7    PROVIDER_ZAI, PROVIDER_ZENMUX,
8};
9
10/// Configuration for an AI provider.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct ProviderConfig {
13    /// Provider identifier (lowercase, used in config files)
14    pub name: &'static str,
15
16    /// Human-readable provider name for UI display
17    pub display_name: &'static str,
18
19    /// API base URL for this provider
20    pub api_url: &'static str,
21
22    /// Environment variable name for API key
23    pub api_key_env: &'static str,
24
25    /// Default model name for this provider
26    pub model: &'static str,
27
28    /// Default maximum tokens for API responses
29    pub max_tokens: u32,
30
31    /// Default temperature for API requests
32    pub temperature: f32,
33}
34
35/// Static registry of all supported AI providers
36pub static PROVIDERS: &[ProviderConfig] = &[
37    ProviderConfig {
38        name: PROVIDER_GEMINI,
39        display_name: "Google Gemini",
40        api_url: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
41        api_key_env: "GEMINI_API_KEY",
42        model: "gemini-3.5-flash-lite",
43        max_tokens: 4096,
44        temperature: 0.3,
45    },
46    ProviderConfig {
47        name: PROVIDER_OPENROUTER,
48        display_name: "OpenRouter",
49        api_url: "https://openrouter.ai/api/v1/chat/completions",
50        api_key_env: "OPENROUTER_API_KEY",
51        model: "mistralai/mistral-small-2603",
52        max_tokens: 4096,
53        temperature: 0.3,
54    },
55    ProviderConfig {
56        name: PROVIDER_GROQ,
57        display_name: "Groq",
58        api_url: "https://api.groq.com/openai/v1/chat/completions",
59        api_key_env: "GROQ_API_KEY",
60        model: "openai/gpt-oss-20b",
61        max_tokens: 4096,
62        temperature: 0.3,
63    },
64    ProviderConfig {
65        name: PROVIDER_CEREBRAS,
66        display_name: "Cerebras",
67        api_url: "https://api.cerebras.ai/v1/chat/completions",
68        api_key_env: "CEREBRAS_API_KEY",
69        model: "gemma-4-31b",
70        max_tokens: 4096,
71        temperature: 0.3,
72    },
73    ProviderConfig {
74        name: PROVIDER_ZENMUX,
75        display_name: "Zenmux",
76        api_url: "https://zenmux.ai/api/v1/chat/completions",
77        api_key_env: "ZENMUX_API_KEY",
78        model: "openai/gpt-5.4-mini",
79        max_tokens: 4096,
80        temperature: 0.3,
81    },
82    ProviderConfig {
83        name: PROVIDER_ZAI,
84        display_name: "Z.AI (Zhipu)",
85        api_url: "https://api.z.ai/api/paas/v4/chat/completions",
86        api_key_env: "ZAI_API_KEY",
87        model: "glm-5.3",
88        max_tokens: 4096,
89        temperature: 0.3,
90    },
91    ProviderConfig {
92        name: PROVIDER_ANTHROPIC,
93        display_name: "Anthropic",
94        api_url: "https://api.anthropic.com/v1/chat/completions",
95        api_key_env: "ANTHROPIC_API_KEY",
96        model: "claude-sonnet-5",
97        max_tokens: 4096,
98        temperature: 0.3,
99    },
100];
101
102/// Retrieves a provider configuration by name.
103///
104/// # Arguments
105///
106/// * `name` - The provider name (case-sensitive, lowercase)
107///
108/// # Returns
109///
110/// Some(ProviderConfig) if found, None otherwise.
111///
112/// # Examples
113///
114/// ```
115/// use aptu_core::ai::registry::get_provider;
116///
117/// let provider = get_provider("openrouter");
118/// assert!(provider.is_some());
119/// assert_eq!(provider.unwrap().display_name, "OpenRouter");
120/// ```
121#[must_use]
122pub fn get_provider(name: &str) -> Option<&'static ProviderConfig> {
123    PROVIDERS.iter().find(|p| p.name == name)
124}
125
126/// Returns all available providers.
127///
128/// # Returns
129///
130/// A slice of all `ProviderConfig` entries in the registry.
131///
132/// # Examples
133///
134/// ```
135/// use aptu_core::ai::registry::all_providers;
136///
137/// let providers = all_providers();
138/// assert_eq!(providers.len(), 7);
139/// ```
140#[must_use]
141pub fn all_providers() -> &'static [ProviderConfig] {
142    PROVIDERS
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_get_provider_gemini() {
151        let provider = get_provider("gemini");
152        assert!(provider.is_some());
153        let provider = provider.unwrap();
154        assert_eq!(provider.display_name, "Google Gemini");
155        assert_eq!(provider.api_key_env, "GEMINI_API_KEY");
156    }
157
158    #[test]
159    fn test_get_provider_openrouter() {
160        let provider = get_provider("openrouter");
161        assert!(provider.is_some());
162        let provider = provider.unwrap();
163        assert_eq!(provider.display_name, "OpenRouter");
164        assert_eq!(provider.api_key_env, "OPENROUTER_API_KEY");
165    }
166
167    #[test]
168    fn test_get_provider_groq() {
169        let provider = get_provider("groq");
170        assert!(provider.is_some());
171        let provider = provider.unwrap();
172        assert_eq!(provider.display_name, "Groq");
173        assert_eq!(provider.api_key_env, "GROQ_API_KEY");
174    }
175
176    #[test]
177    fn test_get_provider_cerebras() {
178        let provider = get_provider("cerebras");
179        assert!(provider.is_some());
180        let provider = provider.unwrap();
181        assert_eq!(provider.display_name, "Cerebras");
182        assert_eq!(provider.api_key_env, "CEREBRAS_API_KEY");
183    }
184
185    #[test]
186    fn test_get_provider_not_found() {
187        let provider = get_provider("nonexistent");
188        assert!(provider.is_none());
189    }
190
191    #[test]
192    fn test_get_provider_case_sensitive() {
193        let provider = get_provider("OpenRouter");
194        assert!(
195            provider.is_none(),
196            "Provider lookup should be case-sensitive"
197        );
198    }
199
200    #[test]
201    fn test_all_providers_count() {
202        let providers = all_providers();
203        assert_eq!(providers.len(), 7, "Should have exactly 7 providers");
204    }
205
206    #[test]
207    fn test_all_providers_have_unique_names() {
208        let providers = all_providers();
209        let mut names = Vec::new();
210        for provider in providers {
211            assert!(
212                !names.contains(&provider.name),
213                "Duplicate provider name: {}",
214                provider.name
215            );
216            names.push(provider.name);
217        }
218    }
219
220    #[test]
221    fn test_get_provider_zenmux() {
222        let provider = get_provider("zenmux");
223        assert!(provider.is_some());
224        let provider = provider.unwrap();
225        assert_eq!(provider.display_name, "Zenmux");
226        assert_eq!(provider.api_key_env, "ZENMUX_API_KEY");
227    }
228
229    #[test]
230    fn test_get_provider_zai() {
231        let provider = get_provider("zai");
232        assert!(provider.is_some());
233        let provider = provider.unwrap();
234        assert_eq!(provider.display_name, "Z.AI (Zhipu)");
235        assert_eq!(provider.api_key_env, "ZAI_API_KEY");
236    }
237
238    #[test]
239    fn test_provider_api_urls_valid() {
240        let providers = all_providers();
241        for provider in providers {
242            assert!(
243                provider.api_url.starts_with("https://"),
244                "Provider {} API URL should use HTTPS",
245                provider.name
246            );
247        }
248    }
249
250    #[test]
251    fn test_provider_api_key_env_not_empty() {
252        let providers = all_providers();
253        for provider in providers {
254            assert!(
255                !provider.api_key_env.is_empty(),
256                "Provider {} should have API key env var",
257                provider.name
258            );
259        }
260    }
261}