1#[derive(Clone, Copy, PartialEq, Eq)]
2pub enum ProviderProtocol {
3 Anthropic,
4 OpenAiCompat,
5}
6
7#[derive(Clone, Copy)]
8pub struct ProviderPreset {
9 pub id: &'static str,
10 pub label: &'static str,
11 pub protocol: ProviderProtocol,
12 pub default_base_url: &'static str,
13 pub models: &'static [&'static str],
14}
15
16pub const PROVIDER_PRESETS: &[ProviderPreset] = &[
20 ProviderPreset {
21 id: "alibaba",
22 label: "Alibaba Cloud (Qwen / DashScope)",
23 protocol: ProviderProtocol::OpenAiCompat,
24 default_base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1",
25 models: &["qwen3.8-max", "qwen3.7-max", "qwen3.7-plus"],
26 },
27 ProviderPreset {
28 id: "aliyun-bailian",
29 label: "Alibaba Cloud Bailian",
30 protocol: ProviderProtocol::OpenAiCompat,
31 default_base_url: "https://coding.dashscope.aliyuncs.com/v1",
32 models: &["qwen3.8-max", "qwen3.7-plus", "qwen3.6-plus"],
33 },
34 ProviderPreset {
35 id: "anthropic",
36 label: "Anthropic",
37 protocol: ProviderProtocol::Anthropic,
38 default_base_url: "",
39 models: &[
40 "claude-fable-5",
41 "claude-opus-5",
42 "claude-sonnet-5",
43 "claude-haiku-4-5-20251001",
44 ],
45 },
46 ProviderPreset {
47 id: "arcee",
48 label: "Arcee AI",
49 protocol: ProviderProtocol::OpenAiCompat,
50 default_base_url: "https://api.arcee.ai/api/v1",
51 models: &["trinity-large-thinking", "trinity-large", "blitz"],
52 },
53 ProviderPreset {
54 id: "azure",
55 label: "Microsoft Azure AI",
56 protocol: ProviderProtocol::OpenAiCompat,
57 default_base_url:
58 "https://YOUR-RESOURCE.openai.azure.com/openai/deployments/YOUR-DEPLOYMENT",
59 models: &["gpt-5.2", "gpt-5", "gpt-4.1"],
60 },
61 ProviderPreset {
62 id: "bedrock",
63 label: "Amazon AWS Bedrock",
64 protocol: ProviderProtocol::OpenAiCompat,
65 default_base_url: "https://bedrock-runtime.YOUR-REGION.amazonaws.com/openai/v1",
66 models: &[
67 "anthropic.claude-opus-4-6-v1",
68 "anthropic.claude-sonnet-4-5-v2",
69 "anthropic.claude-haiku-4-5-v1",
70 ],
71 },
72 ProviderPreset {
73 id: "cerebras",
74 label: "Cerebras",
75 protocol: ProviderProtocol::OpenAiCompat,
76 default_base_url: "https://api.cerebras.ai/v1",
77 models: &["zai-glm-4.7", "llama3.3-70b", "qwen-3-235b"],
78 },
79 ProviderPreset {
80 id: "chutes",
81 label: "Chutes",
82 protocol: ProviderProtocol::OpenAiCompat,
83 default_base_url: "https://llm.chutes.ai/v1",
84 models: &[
85 "deepseek-ai/DeepSeek-V3-0324",
86 "Qwen/Qwen3-Coder-480B-A35B-Instruct",
87 ],
88 },
89 ProviderPreset {
90 id: "cloudflare-ai-gateway",
91 label: "Cloudflare AI Gateway",
92 protocol: ProviderProtocol::OpenAiCompat,
93 default_base_url: "https://gateway.ai.cloudflare.com/v1/YOUR-ACCOUNT/YOUR-GATEWAY/openai",
94 models: &["claude-sonnet-4-6", "gpt-5.2", "openrouter/auto"],
95 },
96 ProviderPreset {
97 id: "cohere",
98 label: "Cohere",
99 protocol: ProviderProtocol::OpenAiCompat,
100 default_base_url: "https://api.cohere.ai/compatibility/v1",
101 models: &[
102 "command-a-03-2025",
103 "command-r-plus-08-2024",
104 "command-r-08-2024",
105 ],
106 },
107 ProviderPreset {
108 id: "custom",
109 label: "Custom (manual config)",
110 protocol: ProviderProtocol::OpenAiCompat,
111 default_base_url: "",
112 models: &["custom-model"],
113 },
114 ProviderPreset {
115 id: "deepinfra",
116 label: "DeepInfra",
117 protocol: ProviderProtocol::OpenAiCompat,
118 default_base_url: "https://api.deepinfra.com/v1/openai",
119 models: &[
120 "deepseek-ai/DeepSeek-V3.2",
121 "meta-llama/Meta-Llama-3.1-70B-Instruct",
122 "Qwen/Qwen2.5-72B-Instruct",
123 ],
124 },
125 ProviderPreset {
126 id: "deepseek",
127 label: "DeepSeek",
128 protocol: ProviderProtocol::OpenAiCompat,
129 default_base_url: "https://api.deepseek.com/v1",
130 models: &["deepseek-v4-pro", "deepseek-v4-flash"],
131 },
132 ProviderPreset {
133 id: "fireworks",
134 label: "Fireworks AI",
135 protocol: ProviderProtocol::OpenAiCompat,
136 default_base_url: "https://api.fireworks.ai/inference/v1",
137 models: &[
138 "accounts/fireworks/routers/kimi-k2p5-turbo",
139 "accounts/fireworks/models/llama-v3p3-70b-instruct",
140 "accounts/fireworks/models/qwen3-coder",
141 ],
142 },
143 ProviderPreset {
144 id: "google",
145 label: "Google DeepMind",
146 protocol: ProviderProtocol::OpenAiCompat,
147 default_base_url: "https://generativelanguage.googleapis.com/v1beta/openai",
148 models: &[
149 "gemini-3.7-flash",
150 "gemini-3.1-pro-preview",
151 "gemini-3.6-flash",
152 "gemini-3.5-flash-lite",
153 ],
154 },
155 ProviderPreset {
156 id: "groq",
157 label: "Groq",
158 protocol: ProviderProtocol::OpenAiCompat,
159 default_base_url: "https://api.groq.com/openai/v1",
160 models: &[
161 "llama-3.3-70b-versatile",
162 "llama-3.1-70b-versatile",
163 "mixtral-8x7b-32768",
164 ],
165 },
166 ProviderPreset {
167 id: "huggingface",
168 label: "Hugging Face",
169 protocol: ProviderProtocol::OpenAiCompat,
170 default_base_url: "https://router.huggingface.co/v1",
171 models: &[
172 "Qwen/Qwen3-Coder-Next",
173 "meta-llama/Llama-3.3-70B-Instruct",
174 "deepseek-ai/DeepSeek-V3",
175 ],
176 },
177 ProviderPreset {
178 id: "inferrs",
179 label: "Inferrs (local)",
180 protocol: ProviderProtocol::OpenAiCompat,
181 default_base_url: "http://127.0.0.1:8080/v1",
182 models: &["google/gemma-4-E2B-it", "custom-model"],
183 },
184 ProviderPreset {
185 id: "kilocode",
186 label: "KiloCode",
187 protocol: ProviderProtocol::OpenAiCompat,
188 default_base_url: "https://api.kilo.ai/api/gateway/",
189 models: &["kilo/auto", "kilo/anthropic/claude-sonnet-4.5"],
190 },
191 ProviderPreset {
192 id: "litellm",
193 label: "LiteLLM (proxy)",
194 protocol: ProviderProtocol::OpenAiCompat,
195 default_base_url: "http://localhost:4000",
196 models: &["claude-opus-4-6", "gpt-5", "custom-model"],
197 },
198 ProviderPreset {
199 id: "lmstudio",
200 label: "LM Studio (local)",
201 protocol: ProviderProtocol::OpenAiCompat,
202 default_base_url: "http://localhost:1234/v1",
203 models: &["custom-model"],
204 },
205 ProviderPreset {
206 id: "minimax",
207 label: "MiniMax",
208 protocol: ProviderProtocol::OpenAiCompat,
209 default_base_url: "https://api.minimax.io/v1",
210 models: &["MiniMax-M3", "MiniMax-M2.7", "MiniMax-M2.7-highspeed"],
211 },
212 ProviderPreset {
213 id: "mistral",
214 label: "Mistral AI",
215 protocol: ProviderProtocol::OpenAiCompat,
216 default_base_url: "https://api.mistral.ai/v1",
217 models: &[
218 "mistral-large-latest",
219 "mistral-medium-latest",
220 "ministral-8b-latest",
221 ],
222 },
223 ProviderPreset {
224 id: "moonshot",
225 label: "Moonshot AI (Kimi)",
226 protocol: ProviderProtocol::OpenAiCompat,
227 default_base_url: "https://api.moonshot.cn/v1",
228 models: &["kimi-k3", "kimi-k2.7-code-highspeed", "kimi-k2.6"],
229 },
230 ProviderPreset {
231 id: "nvidia",
232 label: "NVIDIA NIM",
233 protocol: ProviderProtocol::OpenAiCompat,
234 default_base_url: "https://integrate.api.nvidia.com/v1",
235 models: &[
236 "meta/llama-3.3-70b-instruct",
237 "meta/llama-3.1-70b-instruct",
238 "nvidia/llama-3.1-nemotron-ultra-253b-v1",
239 ],
240 },
241 ProviderPreset {
242 id: "ollama",
243 label: "Ollama (local)",
244 protocol: ProviderProtocol::OpenAiCompat,
245 default_base_url: "http://127.0.0.1:11434/v1",
246 models: &["qwen3.6", "qwen3-coder:30b", "gpt-oss:20b", "llama3.3"],
247 },
248 ProviderPreset {
249 id: "openai",
250 label: "OpenAI",
251 protocol: ProviderProtocol::OpenAiCompat,
252 default_base_url: "https://api.openai.com/v1",
253 models: &["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
254 },
255 ProviderPreset {
256 id: "openai-codex",
257 label: "OpenAI Codex",
258 protocol: ProviderProtocol::OpenAiCompat,
259 default_base_url: "",
260 models: &["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
261 },
262 ProviderPreset {
263 id: "openrouter",
264 label: "OpenRouter",
265 protocol: ProviderProtocol::OpenAiCompat,
266 default_base_url: "https://openrouter.ai/api/v1",
267 models: &[
268 "openrouter/auto",
269 "openai/gpt-5.6-sol",
270 "anthropic/claude-opus-5",
271 "google/gemini-3.7-flash",
272 ],
273 },
274 ProviderPreset {
275 id: "qianfan",
276 label: "Baidu Qianfan",
277 protocol: ProviderProtocol::OpenAiCompat,
278 default_base_url: "https://qianfan.baidubce.com/v2",
279 models: &["deepseek-v3.2", "ernie-5.0-thinking-preview"],
280 },
281 ProviderPreset {
282 id: "qwen-portal",
283 label: "Qwen Portal (OAuth)",
284 protocol: ProviderProtocol::OpenAiCompat,
285 default_base_url: "https://portal.qwen.ai/v1",
286 models: &["coder-model", "vision-model", "qwen3.5-plus"],
287 },
288 ProviderPreset {
289 id: "sglang",
290 label: "SGLang (local)",
291 protocol: ProviderProtocol::OpenAiCompat,
292 default_base_url: "http://127.0.0.1:30000/v1",
293 models: &["custom-model"],
294 },
295 ProviderPreset {
296 id: "stepfun",
297 label: "StepFun",
298 protocol: ProviderProtocol::OpenAiCompat,
299 default_base_url: "https://api.stepfun.ai/v1",
300 models: &["step-3.5-flash", "step-3.5-pro"],
301 },
302 ProviderPreset {
303 id: "synthetic",
304 label: "Synthetic",
305 protocol: ProviderProtocol::OpenAiCompat,
306 default_base_url: "https://api.synthetic.new/openai/v1",
307 models: &["hf:openai/gpt-oss-120b", "hf:deepseek-ai/DeepSeek-V3-0324"],
308 },
309 ProviderPreset {
310 id: "tencent",
311 label: "Tencent AI Lab",
312 protocol: ProviderProtocol::OpenAiCompat,
313 default_base_url: "https://api.hunyuan.cloud.tencent.com/v1",
314 models: &[
315 "hunyuan-t1-latest",
316 "hunyuan-turbos-latest",
317 "hunyuan-standard-latest",
318 ],
319 },
320 ProviderPreset {
321 id: "together",
322 label: "Together AI",
323 protocol: ProviderProtocol::OpenAiCompat,
324 default_base_url: "https://api.together.xyz/v1",
325 models: &[
326 "deepseek-ai/DeepSeek-V3",
327 "meta-llama/Llama-3.3-70B-Instruct-Turbo",
328 "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8",
329 ],
330 },
331 ProviderPreset {
332 id: "venice",
333 label: "Venice",
334 protocol: ProviderProtocol::OpenAiCompat,
335 default_base_url: "https://api.venice.ai/api/v1",
336 models: &["kimi-k2-5", "qwen-3-coder-480b"],
337 },
338 ProviderPreset {
339 id: "vercel-ai-gateway",
340 label: "Vercel AI Gateway",
341 protocol: ProviderProtocol::OpenAiCompat,
342 default_base_url: "https://ai-gateway.vercel.sh/v1",
343 models: &[
344 "anthropic/claude-opus-4.6",
345 "openai/gpt-5.2",
346 "google/gemini-2.5-pro",
347 ],
348 },
349 ProviderPreset {
350 id: "vllm",
351 label: "vLLM (local)",
352 protocol: ProviderProtocol::OpenAiCompat,
353 default_base_url: "http://127.0.0.1:8000/v1",
354 models: &["custom-model"],
355 },
356 ProviderPreset {
357 id: "volcengine",
358 label: "Volcano Engine (Doubao)",
359 protocol: ProviderProtocol::OpenAiCompat,
360 default_base_url: "https://ark.cn-beijing.volces.com/api/v3",
361 models: &["doubao-1.5-pro-256k", "doubao-pro-32k"],
362 },
363 ProviderPreset {
364 id: "xai",
365 label: "xAI",
366 protocol: ProviderProtocol::OpenAiCompat,
367 default_base_url: "https://api.x.ai/v1",
368 models: &["grok-4", "grok-4-fast", "grok-3"],
369 },
370 ProviderPreset {
371 id: "xiaomi",
372 label: "Xiaomi (MiMo)",
373 protocol: ProviderProtocol::OpenAiCompat,
374 default_base_url: "https://api.xiaomimimo.com/v1",
375 models: &[
376 "mimo-v2.5-pro",
377 "mimo-v2.5",
378 "mimo-v2-pro",
379 "mimo-v2-flash",
380 "mimo-v2-omni",
381 ],
382 },
383 ProviderPreset {
384 id: "zhipu",
385 label: "Zhipu AI (GLM / Z.AI)",
386 protocol: ProviderProtocol::OpenAiCompat,
387 default_base_url: "https://open.bigmodel.cn/api/paas/v4",
388 models: &["glm-4.7", "glm-4.7-flash", "glm-4.5-air"],
389 },
390];
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393pub struct ProviderCatalogEntry {
394 pub id: &'static str,
395 pub label: &'static str,
396 pub default_base_url: &'static str,
397 pub models: &'static [&'static str],
398}
399
400pub fn provider_catalog() -> impl Iterator<Item = ProviderCatalogEntry> {
401 PROVIDER_PRESETS.iter().map(|preset| ProviderCatalogEntry {
402 id: preset.id,
403 label: preset.label,
404 default_base_url: preset.default_base_url,
405 models: preset.models,
406 })
407}
408
409pub fn find_provider_preset(provider: &str) -> Option<&'static ProviderPreset> {
410 PROVIDER_PRESETS
411 .iter()
412 .find(|p| p.id.eq_ignore_ascii_case(provider))
413}
414
415pub fn default_base_url_for_provider(provider: &str) -> Option<&'static str> {
418 find_provider_preset(provider)
419 .map(|p| p.default_base_url)
420 .filter(|url| !url.is_empty())
421}
422
423pub fn provider_protocol(provider: &str) -> ProviderProtocol {
424 find_provider_preset(provider)
425 .map(|p| p.protocol)
426 .unwrap_or(ProviderProtocol::OpenAiCompat)
427}
428
429pub fn default_model_for_provider(provider: &str) -> &'static str {
430 find_provider_preset(provider)
431 .and_then(|p| p.models.first().copied())
432 .unwrap_or("gpt-5.2")
433}
434
435pub fn is_placeholder_provider_model_list(models: &[&str]) -> bool {
436 models.len() == 1 && models[0].eq_ignore_ascii_case("custom-model")
437}
438
439pub fn normalize_setup_provider_id(raw: &str) -> String {
440 let trimmed = raw.trim();
441 if trimmed.is_empty() {
442 return String::new();
443 }
444 if find_provider_preset(trimmed).is_some() {
445 return trimmed.to_ascii_lowercase();
446 }
447 let lower = trimmed.to_ascii_lowercase();
448 if let Some(suffix) = lower.strip_prefix("custom") {
449 if !suffix.is_empty() && suffix.chars().all(|c| c.is_ascii_digit()) {
450 return "custom".to_string();
451 }
452 }
453 trimmed.to_string()
454}
455
456pub fn provider_display(provider: &str) -> String {
457 if let Some(preset) = find_provider_preset(provider) {
458 format!("{} - {}", preset.id, preset.label)
459 } else {
460 format!("{provider} - custom")
461 }
462}