1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum ProviderCategory {
10 Llm,
12 Mcp,
14 Local,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct Provider {
21 pub id: &'static str,
23 pub name: &'static str,
25 pub env_var: &'static str,
27 pub key_prefix: Option<&'static str>,
29 pub category: ProviderCategory,
31 pub requires_key: bool,
33 pub description: &'static str,
35}
36
37pub static KNOWN_PROVIDERS: &[Provider] = &[
42 Provider {
44 id: "anthropic",
45 name: "Anthropic Claude",
46 env_var: "ANTHROPIC_API_KEY",
47 key_prefix: Some("sk-ant-"),
48 category: ProviderCategory::Llm,
49 requires_key: true,
50 description: "Claude models (Opus, Sonnet, Haiku)",
51 },
52 Provider {
53 id: "openai",
54 name: "OpenAI GPT",
55 env_var: "OPENAI_API_KEY",
56 key_prefix: Some("sk-"),
57 category: ProviderCategory::Llm,
58 requires_key: true,
59 description: "GPT-4, GPT-3.5, and other OpenAI models",
60 },
61 Provider {
62 id: "mistral",
63 name: "Mistral AI",
64 env_var: "MISTRAL_API_KEY",
65 key_prefix: None,
66 category: ProviderCategory::Llm,
67 requires_key: true,
68 description: "Mistral and Mixtral models",
69 },
70 Provider {
71 id: "groq",
72 name: "Groq",
73 env_var: "GROQ_API_KEY",
74 key_prefix: Some("gsk_"),
75 category: ProviderCategory::Llm,
76 requires_key: true,
77 description: "Ultra-fast inference with Groq LPU",
78 },
79 Provider {
80 id: "deepseek",
81 name: "DeepSeek",
82 env_var: "DEEPSEEK_API_KEY",
83 key_prefix: Some("sk-"),
84 category: ProviderCategory::Llm,
85 requires_key: true,
86 description: "DeepSeek Coder and Chat models",
87 },
88 Provider {
89 id: "gemini",
90 name: "Google Gemini",
91 env_var: "GEMINI_API_KEY",
92 key_prefix: None,
93 category: ProviderCategory::Llm,
94 requires_key: true,
95 description: "Gemini Pro and Ultra models",
96 },
97 Provider {
98 id: "ollama",
99 name: "Ollama",
100 env_var: "OLLAMA_API_BASE_URL",
101 key_prefix: None,
102 category: ProviderCategory::Local,
103 requires_key: false,
104 description: "Local model runner (llama, mistral, etc.)",
105 },
106 Provider {
108 id: "neo4j",
109 name: "Neo4j Graph Database",
110 env_var: "NEO4J_PASSWORD",
111 key_prefix: None,
112 category: ProviderCategory::Mcp,
113 requires_key: true,
114 description: "Graph database for knowledge storage",
115 },
116 Provider {
117 id: "github",
118 name: "GitHub API",
119 env_var: "GITHUB_TOKEN",
120 key_prefix: Some("ghp_"),
121 category: ProviderCategory::Mcp,
122 requires_key: true,
123 description: "GitHub API access",
124 },
125 Provider {
126 id: "slack",
127 name: "Slack API",
128 env_var: "SLACK_BOT_TOKEN",
129 key_prefix: Some("xoxb-"),
130 category: ProviderCategory::Mcp,
131 requires_key: true,
132 description: "Slack workspace integration",
133 },
134 Provider {
135 id: "perplexity",
136 name: "Perplexity AI",
137 env_var: "PERPLEXITY_API_KEY",
138 key_prefix: Some("pplx-"),
139 category: ProviderCategory::Mcp,
140 requires_key: true,
141 description: "AI-powered web search",
142 },
143 Provider {
144 id: "firecrawl",
145 name: "Firecrawl",
146 env_var: "FIRECRAWL_API_KEY",
147 key_prefix: Some("fc-"),
148 category: ProviderCategory::Mcp,
149 requires_key: true,
150 description: "Web scraping and crawling",
151 },
152 Provider {
153 id: "supadata",
154 name: "Supadata API",
155 env_var: "SUPADATA_API_KEY",
156 key_prefix: None,
157 category: ProviderCategory::Mcp,
158 requires_key: true,
159 description: "Video transcription and web scraping",
160 },
161];
162
163#[must_use]
177pub fn find_provider(id: &str) -> Option<&'static Provider> {
178 KNOWN_PROVIDERS
179 .iter()
180 .find(|p| p.id.eq_ignore_ascii_case(id))
181}
182
183pub fn provider_to_env_var(id: &str) -> Option<&'static str> {
194 find_provider(id).map(|p| p.env_var)
195}
196
197pub fn providers_by_category(
208 category: ProviderCategory,
209) -> impl Iterator<Item = &'static Provider> {
210 KNOWN_PROVIDERS
211 .iter()
212 .filter(move |p| p.category == category)
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_find_provider() {
221 assert!(find_provider("anthropic").is_some());
222 assert!(find_provider("ANTHROPIC").is_some());
223 assert!(find_provider("unknown").is_none());
224 }
225
226 #[test]
227 fn test_provider_to_env_var() {
228 assert_eq!(provider_to_env_var("anthropic"), Some("ANTHROPIC_API_KEY"));
229 assert_eq!(provider_to_env_var("github"), Some("GITHUB_TOKEN"));
230 assert_eq!(provider_to_env_var("unknown"), None);
231 }
232
233 #[test]
234 fn test_providers_by_category() {
235 let llm: Vec<_> = providers_by_category(ProviderCategory::Llm).collect();
236 assert!(llm.len() >= 6);
237 assert!(llm.iter().all(|p| p.category == ProviderCategory::Llm));
238
239 let mcp: Vec<_> = providers_by_category(ProviderCategory::Mcp).collect();
240 assert!(mcp.len() >= 5);
241 assert!(mcp.iter().all(|p| p.category == ProviderCategory::Mcp));
242 }
243
244 #[test]
245 fn test_all_providers_have_env_var() {
246 for provider in KNOWN_PROVIDERS {
247 assert!(
248 !provider.env_var.is_empty(),
249 "Provider {} missing env_var",
250 provider.id
251 );
252 }
253 }
254
255 #[test]
256 fn test_provider_count() {
257 assert!(KNOWN_PROVIDERS.len() >= 13);
259 }
260}