Skip to main content

microclaw_engine/setup/
presets.rs

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
16// Sorted A→Z by `id`. Keep this invariant when adding new providers — the
17// setup wizard's preset picker and the generated provider matrix mirror
18// this order verbatim.
19pub 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
415/// Returns the default base URL for a known provider, or `None` if the
416/// provider is unknown or has no preset base URL.
417pub 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}