1use serde::Deserialize;
31
32pub struct ProviderMeta {
34 pub name: &'static str,
36 pub url: &'static str,
38 pub model: &'static str,
40 pub env_key: &'static str,
42 pub api_key: bool,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
48#[serde(rename_all = "lowercase")]
49pub enum ProviderType {
50 OpenAI,
52 Anthropic,
54 LMStudio,
56 Gemini,
58 Groq,
60 Grok,
62 Ollama,
64 DeepSeek,
66 Mistral,
68 MiniMax,
70 OpenRouter,
72 Together,
74 Fireworks,
76 Vllm,
78 #[cfg(any(test, feature = "test-support"))]
80 Mock,
81}
82
83impl ProviderType {
84 pub fn meta(&self) -> ProviderMeta {
86 match self {
87 Self::OpenAI => ProviderMeta {
88 name: "openai",
89 url: "https://api.openai.com/v1",
90 model: "gpt-4o",
91 env_key: "OPENAI_API_KEY",
92 api_key: true,
93 },
94 Self::Anthropic => ProviderMeta {
95 name: "anthropic",
96 url: "https://api.anthropic.com",
97 model: "claude-sonnet-4-6",
98 env_key: "ANTHROPIC_API_KEY",
99 api_key: true,
100 },
101 Self::LMStudio => ProviderMeta {
102 name: "lm-studio",
103 url: "http://localhost:1234/v1",
104 model: "auto-detect",
105 env_key: "KODA_API_KEY",
106 api_key: false,
107 },
108 Self::Gemini => ProviderMeta {
109 name: "gemini",
110 url: "https://generativelanguage.googleapis.com",
111 model: "gemini-flash-latest",
112 env_key: "GEMINI_API_KEY",
113 api_key: true,
114 },
115 Self::Groq => ProviderMeta {
116 name: "groq",
117 url: "https://api.groq.com/openai/v1",
118 model: "llama-3.3-70b-versatile",
119 env_key: "GROQ_API_KEY",
120 api_key: true,
121 },
122 Self::Grok => ProviderMeta {
123 name: "grok",
124 url: "https://api.x.ai/v1",
125 model: "grok-3",
126 env_key: "XAI_API_KEY",
127 api_key: true,
128 },
129 Self::Ollama => ProviderMeta {
130 name: "ollama",
131 url: "http://localhost:11434/v1",
132 model: "auto-detect",
133 env_key: "KODA_API_KEY",
134 api_key: false,
135 },
136 Self::DeepSeek => ProviderMeta {
137 name: "deepseek",
138 url: "https://api.deepseek.com/v1",
139 model: "deepseek-chat",
140 env_key: "DEEPSEEK_API_KEY",
141 api_key: true,
142 },
143 Self::Mistral => ProviderMeta {
144 name: "mistral",
145 url: "https://api.mistral.ai/v1",
146 model: "mistral-large-latest",
147 env_key: "MISTRAL_API_KEY",
148 api_key: true,
149 },
150 Self::MiniMax => ProviderMeta {
151 name: "minimax",
152 url: "https://api.minimax.io/v1",
153 model: "minimax-text-01",
154 env_key: "MINIMAX_API_KEY",
155 api_key: true,
156 },
157 Self::OpenRouter => ProviderMeta {
158 name: "openrouter",
159 url: "https://openrouter.ai/api/v1",
160 model: "anthropic/claude-3.5-sonnet",
161 env_key: "OPENROUTER_API_KEY",
162 api_key: true,
163 },
164 Self::Together => ProviderMeta {
165 name: "together",
166 url: "https://api.together.xyz/v1",
167 model: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
168 env_key: "TOGETHER_API_KEY",
169 api_key: true,
170 },
171 Self::Fireworks => ProviderMeta {
172 name: "fireworks",
173 url: "https://api.fireworks.ai/inference/v1",
174 model: "accounts/fireworks/models/llama-v3p3-70b-instruct",
175 env_key: "FIREWORKS_API_KEY",
176 api_key: true,
177 },
178 Self::Vllm => ProviderMeta {
179 name: "vllm",
180 url: "http://localhost:8000/v1",
181 model: "auto-detect",
182 env_key: "KODA_API_KEY",
183 api_key: false,
184 },
185 #[cfg(any(test, feature = "test-support"))]
186 Self::Mock => ProviderMeta {
187 name: "mock",
188 url: "http://localhost:0",
189 model: "mock-model",
190 env_key: "KODA_API_KEY",
191 api_key: false,
192 },
193 }
194 }
195
196 pub fn requires_api_key(&self) -> bool {
198 self.meta().api_key
199 }
200 pub fn default_base_url(&self) -> &str {
202 self.meta().url
203 }
204 pub fn default_model(&self) -> &str {
206 self.meta().model
207 }
208 pub fn env_key_name(&self) -> &str {
210 self.meta().env_key
211 }
212
213 pub fn from_url_or_name(url: &str, name: Option<&str>) -> Self {
215 if let Some(n) = name {
216 return match n.to_lowercase().as_str() {
217 "anthropic" | "claude" => Self::Anthropic,
218 "gemini" | "google" => Self::Gemini,
219 "groq" => Self::Groq,
220 "grok" | "xai" => Self::Grok,
221 "lmstudio" | "lm-studio" => Self::LMStudio,
222 "ollama" => Self::Ollama,
223 "deepseek" => Self::DeepSeek,
224 "mistral" => Self::Mistral,
225 "minimax" => Self::MiniMax,
226 "openrouter" => Self::OpenRouter,
227 "together" => Self::Together,
228 "fireworks" => Self::Fireworks,
229 "vllm" => Self::Vllm,
230 #[cfg(any(test, feature = "test-support"))]
231 "mock" => Self::Mock,
232 _ => Self::OpenAI,
233 };
234 }
235 let url = url.to_lowercase();
237 if url.contains("anthropic.com") {
238 Self::Anthropic
239 } else if url.contains("localhost:11434") || url.contains("127.0.0.1:11434") {
240 Self::Ollama
241 } else if url.contains("localhost:8000") || url.contains("127.0.0.1:8000") {
242 Self::Vllm
243 } else if url.contains("localhost") || url.contains("127.0.0.1") {
244 Self::LMStudio
245 } else if url.contains("generativelanguage.googleapis.com") {
246 Self::Gemini
247 } else if url.contains("groq.com") {
248 Self::Groq
249 } else if url.contains("x.ai") {
250 Self::Grok
251 } else if url.contains("deepseek.com") {
252 Self::DeepSeek
253 } else if url.contains("mistral.ai") {
254 Self::Mistral
255 } else if url.contains("minimax.chat") || url.contains("minimaxi.com") {
256 Self::MiniMax
257 } else if url.contains("openrouter.ai") {
258 Self::OpenRouter
259 } else if url.contains("together.xyz") {
260 Self::Together
261 } else if url.contains("fireworks.ai") {
262 Self::Fireworks
263 } else {
264 Self::OpenAI
265 }
266 }
267}
268
269impl std::fmt::Display for ProviderType {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 write!(f, "{}", self.meta().name)
272 }
273}