1use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::str::FromStr;
10
11#[derive(Clone, Copy)]
12pub struct OpenRouterMetadata {
13 id: &'static str,
14 vendor: &'static str,
15 display: &'static str,
16 description: &'static str,
17 efficient: bool,
18 top_tier: bool,
19 generation: &'static str,
20 reasoning: bool,
21 tool_call: bool,
22}
23
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
27pub enum Provider {
28 #[default]
30 Gemini,
31 OpenAI,
33 Anthropic,
35 DeepSeek,
37 OpenRouter,
39 Ollama,
41 LmStudio,
43 Moonshot,
45 XAI,
47 ZAI,
49}
50
51impl Provider {
52 pub fn default_api_key_env(&self) -> &'static str {
54 match self {
55 Provider::Gemini => "GEMINI_API_KEY",
56 Provider::OpenAI => "OPENAI_API_KEY",
57 Provider::Anthropic => "ANTHROPIC_API_KEY",
58 Provider::DeepSeek => "DEEPSEEK_API_KEY",
59 Provider::OpenRouter => "OPENROUTER_API_KEY",
60 Provider::Ollama => "OLLAMA_API_KEY",
61 Provider::LmStudio => "LMSTUDIO_API_KEY",
62 Provider::Moonshot => "MOONSHOT_API_KEY",
63 Provider::XAI => "XAI_API_KEY",
64 Provider::ZAI => "ZAI_API_KEY",
65 }
66 }
67
68 pub fn all_providers() -> Vec<Provider> {
70 vec![
71 Provider::OpenAI,
72 Provider::Anthropic,
73 Provider::Gemini,
74 Provider::DeepSeek,
75 Provider::OpenRouter,
76 Provider::Ollama,
77 Provider::LmStudio,
78 Provider::Moonshot,
79 Provider::XAI,
80 Provider::ZAI,
81 ]
82 }
83
84 pub fn label(&self) -> &'static str {
86 match self {
87 Provider::Gemini => "Gemini",
88 Provider::OpenAI => "OpenAI",
89 Provider::Anthropic => "Anthropic",
90 Provider::DeepSeek => "DeepSeek",
91 Provider::OpenRouter => "OpenRouter",
92 Provider::Ollama => "Ollama",
93 Provider::LmStudio => "LM Studio",
94 Provider::Moonshot => "Moonshot",
95 Provider::XAI => "xAI",
96 Provider::ZAI => "Z.AI",
97 }
98 }
99
100 pub fn supports_reasoning_effort(&self, model: &str) -> bool {
102 use crate::constants::models;
103
104 match self {
105 Provider::Gemini => model == models::google::GEMINI_2_5_PRO,
106 Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
107 Provider::Anthropic => models::anthropic::REASONING_MODELS.contains(&model),
108 Provider::DeepSeek => model == models::deepseek::DEEPSEEK_REASONER,
109 Provider::OpenRouter => {
110 if let Ok(model_id) = ModelId::from_str(model) {
111 return model_id.is_reasoning_variant();
112 }
113 models::openrouter::REASONING_MODELS.contains(&model)
114 }
115 Provider::Ollama => false,
116 Provider::LmStudio => false,
117 Provider::Moonshot => false,
118 Provider::XAI => model == models::xai::GROK_4 || model == models::xai::GROK_4_CODE,
119 Provider::ZAI => model == models::zai::GLM_4_6,
120 }
121 }
122}
123
124impl fmt::Display for Provider {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 match self {
127 Provider::Gemini => write!(f, "gemini"),
128 Provider::OpenAI => write!(f, "openai"),
129 Provider::Anthropic => write!(f, "anthropic"),
130 Provider::DeepSeek => write!(f, "deepseek"),
131 Provider::OpenRouter => write!(f, "openrouter"),
132 Provider::Ollama => write!(f, "ollama"),
133 Provider::LmStudio => write!(f, "lmstudio"),
134 Provider::Moonshot => write!(f, "moonshot"),
135 Provider::XAI => write!(f, "xai"),
136 Provider::ZAI => write!(f, "zai"),
137 }
138 }
139}
140
141impl FromStr for Provider {
142 type Err = ModelParseError;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 match s.to_lowercase().as_str() {
146 "gemini" => Ok(Provider::Gemini),
147 "openai" => Ok(Provider::OpenAI),
148 "anthropic" => Ok(Provider::Anthropic),
149 "deepseek" => Ok(Provider::DeepSeek),
150 "openrouter" => Ok(Provider::OpenRouter),
151 "ollama" => Ok(Provider::Ollama),
152 "lmstudio" => Ok(Provider::LmStudio),
153 "moonshot" => Ok(Provider::Moonshot),
154 "xai" => Ok(Provider::XAI),
155 "zai" => Ok(Provider::ZAI),
156 _ => Err(ModelParseError::InvalidProvider(s.to_string())),
157 }
158 }
159}
160
161#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
163#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
164pub enum ModelId {
165 #[default]
168 Gemini25FlashPreview,
169 Gemini25Flash,
171 Gemini25FlashLite,
173 Gemini25Pro,
175
176 GPT5,
179 GPT5Codex,
181 GPT5Mini,
183 GPT5Nano,
185 CodexMiniLatest,
187 OpenAIGptOss20b,
189 OpenAIGptOss120b,
191
192 ClaudeOpus41,
195 ClaudeSonnet45,
197 ClaudeHaiku45,
199 ClaudeSonnet4,
201
202 DeepSeekChat,
205 DeepSeekReasoner,
207
208 XaiGrok4,
211 XaiGrok4Mini,
213 XaiGrok4Code,
215 XaiGrok4CodeLatest,
217 XaiGrok4Vision,
219
220 ZaiGlm46,
223 ZaiGlm45,
225 ZaiGlm45Air,
227 ZaiGlm45X,
229 ZaiGlm45Airx,
231 ZaiGlm45Flash,
233 ZaiGlm432b0414128k,
235
236 MoonshotKimiK2TurboPreview,
239 MoonshotKimiK20905Preview,
241 MoonshotKimiK20711Preview,
243 MoonshotKimiLatest,
245 MoonshotKimiLatest8k,
247 MoonshotKimiLatest32k,
249 MoonshotKimiLatest128k,
251
252 OllamaGptOss20b,
255 OllamaGptOss120bCloud,
257 OllamaQwen317b,
259
260 LmStudioMetaLlama38BInstruct,
263 LmStudioMetaLlama318BInstruct,
265 LmStudioQwen257BInstruct,
267 LmStudioGemma22BIt,
269 LmStudioGemma29BIt,
271 LmStudioPhi31Mini4kInstruct,
273
274 OpenRouterGrokCodeFast1,
277 OpenRouterGrok4Fast,
279 OpenRouterGrok4,
281 OpenRouterZaiGlm46,
283 OpenRouterMoonshotaiKimiK20905,
285 OpenRouterMoonshotaiKimiK2Free,
287 OpenRouterQwen3Max,
289 OpenRouterQwen3235bA22b,
291 OpenRouterQwen3235bA22bFree,
293 OpenRouterQwen3235bA22b2507,
295 OpenRouterQwen3235bA22bThinking2507,
297 OpenRouterQwen332b,
299 OpenRouterQwen330bA3b,
301 OpenRouterQwen330bA3bFree,
303 OpenRouterQwen330bA3bInstruct2507,
305 OpenRouterQwen330bA3bThinking2507,
307 OpenRouterQwen314b,
309 OpenRouterQwen314bFree,
311 OpenRouterQwen38b,
313 OpenRouterQwen38bFree,
315 OpenRouterQwen34bFree,
317 OpenRouterQwen3Next80bA3bInstruct,
319 OpenRouterQwen3Next80bA3bThinking,
321 OpenRouterQwen3Coder,
323 OpenRouterQwen3CoderFree,
325 OpenRouterQwen3CoderPlus,
327 OpenRouterQwen3CoderFlash,
329 OpenRouterQwen3Coder30bA3bInstruct,
331 OpenRouterDeepSeekV32Exp,
333 OpenRouterDeepSeekChatV31,
335 OpenRouterDeepSeekR1,
337 OpenRouterDeepSeekChatV31Free,
339 OpenRouterNvidiaNemotronNano9bV2Free,
341 OpenRouterOpenAIGptOss120b,
343 OpenRouterOpenAIGptOss20b,
345 OpenRouterOpenAIGptOss20bFree,
347 OpenRouterOpenAIGpt5,
349 OpenRouterOpenAIGpt5Codex,
351 OpenRouterOpenAIGpt5Chat,
353 OpenRouterOpenAIGpt4oSearchPreview,
355 OpenRouterOpenAIGpt4oMiniSearchPreview,
357 OpenRouterOpenAIChatgpt4oLatest,
359 OpenRouterAnthropicClaudeSonnet45,
361 OpenRouterAnthropicClaudeHaiku45,
363 OpenRouterAnthropicClaudeOpus41,
365 OpenRouterMinimaxM2Free,
367}
368
369pub mod openrouter_generated {
370 include!(concat!(env!("OUT_DIR"), "/openrouter_metadata.rs"));
371}
372
373impl ModelId {
374 fn openrouter_metadata(&self) -> Option<OpenRouterMetadata> {
375 openrouter_generated::metadata_for(*self)
376 }
377
378 fn parse_openrouter_model(value: &str) -> Option<Self> {
379 openrouter_generated::parse_model(value)
380 }
381
382 fn openrouter_vendor_groups() -> Vec<(&'static str, &'static [Self])> {
383 openrouter_generated::vendor_groups()
384 .iter()
385 .map(|group| (group.vendor, group.models))
386 .collect()
387 }
388
389 fn openrouter_models() -> Vec<Self> {
390 Self::openrouter_vendor_groups()
391 .into_iter()
392 .flat_map(|(_, models)| models.iter().copied())
393 .collect()
394 }
395
396 pub fn as_str(&self) -> &'static str {
399 use crate::constants::models;
400 if let Some(meta) = self.openrouter_metadata() {
401 return meta.id;
402 }
403 match self {
404 ModelId::Gemini25FlashPreview => models::GEMINI_2_5_FLASH_PREVIEW,
406 ModelId::Gemini25Flash => models::GEMINI_2_5_FLASH,
407 ModelId::Gemini25FlashLite => models::GEMINI_2_5_FLASH_LITE,
408 ModelId::Gemini25Pro => models::GEMINI_2_5_PRO,
409 ModelId::GPT5 => models::GPT_5,
411 ModelId::GPT5Codex => models::GPT_5_CODEX,
412 ModelId::GPT5Mini => models::GPT_5_MINI,
413 ModelId::GPT5Nano => models::GPT_5_NANO,
414 ModelId::CodexMiniLatest => models::CODEX_MINI_LATEST,
415 ModelId::ClaudeOpus41 => models::CLAUDE_OPUS_4_1_20250805,
417 ModelId::ClaudeSonnet45 => models::CLAUDE_SONNET_4_5,
418 ModelId::ClaudeHaiku45 => models::CLAUDE_HAIKU_4_5,
419 ModelId::ClaudeSonnet4 => models::CLAUDE_SONNET_4_20250514,
420 ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
422 ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
423 ModelId::XaiGrok4 => models::xai::GROK_4,
425 ModelId::XaiGrok4Mini => models::xai::GROK_4_MINI,
426 ModelId::XaiGrok4Code => models::xai::GROK_4_CODE,
427 ModelId::XaiGrok4CodeLatest => models::xai::GROK_4_CODE_LATEST,
428 ModelId::XaiGrok4Vision => models::xai::GROK_4_VISION,
429 ModelId::ZaiGlm46 => models::zai::GLM_4_6,
431 ModelId::ZaiGlm45 => models::zai::GLM_4_5,
432 ModelId::ZaiGlm45Air => models::zai::GLM_4_5_AIR,
433 ModelId::ZaiGlm45X => models::zai::GLM_4_5_X,
434 ModelId::ZaiGlm45Airx => models::zai::GLM_4_5_AIRX,
435 ModelId::ZaiGlm45Flash => models::zai::GLM_4_5_FLASH,
436 ModelId::ZaiGlm432b0414128k => models::zai::GLM_4_32B_0414_128K,
437 ModelId::MoonshotKimiK2TurboPreview => models::MOONSHOT_KIMI_K2_TURBO_PREVIEW,
439 ModelId::MoonshotKimiK20905Preview => models::MOONSHOT_KIMI_K2_0905_PREVIEW,
440 ModelId::MoonshotKimiK20711Preview => models::MOONSHOT_KIMI_K2_0711_PREVIEW,
441 ModelId::MoonshotKimiLatest => models::MOONSHOT_KIMI_LATEST,
442 ModelId::MoonshotKimiLatest8k => models::MOONSHOT_KIMI_LATEST_8K,
443 ModelId::MoonshotKimiLatest32k => models::MOONSHOT_KIMI_LATEST_32K,
444 ModelId::MoonshotKimiLatest128k => models::MOONSHOT_KIMI_LATEST_128K,
445 ModelId::OllamaGptOss20b => models::ollama::GPT_OSS_20B,
447 ModelId::OllamaGptOss120bCloud => models::ollama::GPT_OSS_120B_CLOUD,
448 ModelId::OllamaQwen317b => models::ollama::QWEN3_1_7B,
449 ModelId::LmStudioMetaLlama38BInstruct => models::lmstudio::META_LLAMA_3_8B_INSTRUCT,
451 ModelId::LmStudioMetaLlama318BInstruct => models::lmstudio::META_LLAMA_31_8B_INSTRUCT,
452 ModelId::LmStudioQwen257BInstruct => models::lmstudio::QWEN25_7B_INSTRUCT,
453 ModelId::LmStudioGemma22BIt => models::lmstudio::GEMMA_2_2B_IT,
454 ModelId::LmStudioGemma29BIt => models::lmstudio::GEMMA_2_9B_IT,
455 ModelId::LmStudioPhi31Mini4kInstruct => models::lmstudio::PHI_31_MINI_4K_INSTRUCT,
456 _ => unreachable!(),
458 }
459 }
460
461 pub fn provider(&self) -> Provider {
463 if self.openrouter_metadata().is_some() {
464 return Provider::OpenRouter;
465 }
466 match self {
467 ModelId::Gemini25FlashPreview
468 | ModelId::Gemini25Flash
469 | ModelId::Gemini25FlashLite
470 | ModelId::Gemini25Pro => Provider::Gemini,
471 ModelId::GPT5
472 | ModelId::GPT5Codex
473 | ModelId::GPT5Mini
474 | ModelId::GPT5Nano
475 | ModelId::CodexMiniLatest
476 | ModelId::OpenAIGptOss20b
477 | ModelId::OpenAIGptOss120b => Provider::OpenAI,
478 ModelId::ClaudeOpus41
479 | ModelId::ClaudeSonnet45
480 | ModelId::ClaudeHaiku45
481 | ModelId::ClaudeSonnet4 => Provider::Anthropic,
482 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => Provider::DeepSeek,
483 ModelId::XaiGrok4
484 | ModelId::XaiGrok4Mini
485 | ModelId::XaiGrok4Code
486 | ModelId::XaiGrok4CodeLatest
487 | ModelId::XaiGrok4Vision => Provider::XAI,
488 ModelId::ZaiGlm46
489 | ModelId::ZaiGlm45
490 | ModelId::ZaiGlm45Air
491 | ModelId::ZaiGlm45X
492 | ModelId::ZaiGlm45Airx
493 | ModelId::ZaiGlm45Flash
494 | ModelId::ZaiGlm432b0414128k => Provider::ZAI,
495 ModelId::MoonshotKimiK2TurboPreview
496 | ModelId::MoonshotKimiK20905Preview
497 | ModelId::MoonshotKimiK20711Preview
498 | ModelId::MoonshotKimiLatest
499 | ModelId::MoonshotKimiLatest8k
500 | ModelId::MoonshotKimiLatest32k
501 | ModelId::MoonshotKimiLatest128k => Provider::Moonshot,
502 ModelId::OllamaGptOss20b | ModelId::OllamaGptOss120bCloud | ModelId::OllamaQwen317b => {
503 Provider::Ollama
504 }
505 ModelId::LmStudioMetaLlama38BInstruct
506 | ModelId::LmStudioMetaLlama318BInstruct
507 | ModelId::LmStudioQwen257BInstruct
508 | ModelId::LmStudioGemma22BIt
509 | ModelId::LmStudioGemma29BIt
510 | ModelId::LmStudioPhi31Mini4kInstruct => Provider::LmStudio,
511 _ => unreachable!(),
512 }
513 }
514
515 pub fn supports_reasoning_effort(&self) -> bool {
517 self.provider().supports_reasoning_effort(self.as_str())
518 }
519
520 pub fn display_name(&self) -> &'static str {
522 if let Some(meta) = self.openrouter_metadata() {
523 return meta.display;
524 }
525 match self {
526 ModelId::Gemini25FlashPreview => "Gemini 2.5 Flash Preview",
528 ModelId::Gemini25Flash => "Gemini 2.5 Flash",
529 ModelId::Gemini25FlashLite => "Gemini 2.5 Flash Lite",
530 ModelId::Gemini25Pro => "Gemini 2.5 Pro",
531 ModelId::GPT5 => "GPT-5",
533 ModelId::GPT5Codex => "GPT-5 Codex",
534 ModelId::GPT5Mini => "GPT-5 Mini",
535 ModelId::GPT5Nano => "GPT-5 Nano",
536 ModelId::CodexMiniLatest => "Codex Mini Latest",
537 ModelId::ClaudeOpus41 => "Claude Opus 4.1",
539 ModelId::ClaudeSonnet45 => "Claude Sonnet 4.5",
540 ModelId::ClaudeHaiku45 => "Claude Haiku 4.5",
541 ModelId::ClaudeSonnet4 => "Claude Sonnet 4",
542 ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
544 ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
545 ModelId::XaiGrok4 => "Grok-4",
547 ModelId::XaiGrok4Mini => "Grok-4 Mini",
548 ModelId::XaiGrok4Code => "Grok-4 Code",
549 ModelId::XaiGrok4CodeLatest => "Grok-4 Code Latest",
550 ModelId::XaiGrok4Vision => "Grok-4 Vision",
551 ModelId::ZaiGlm46 => "GLM 4.6",
553 ModelId::ZaiGlm45 => "GLM 4.5",
554 ModelId::ZaiGlm45Air => "GLM 4.5 Air",
555 ModelId::ZaiGlm45X => "GLM 4.5 X",
556 ModelId::ZaiGlm45Airx => "GLM 4.5 AirX",
557 ModelId::ZaiGlm45Flash => "GLM 4.5 Flash",
558 ModelId::ZaiGlm432b0414128k => "GLM 4 32B 0414 128K",
559 ModelId::MoonshotKimiK2TurboPreview => "Kimi K2 Turbo Preview",
561 ModelId::MoonshotKimiK20905Preview => "Kimi K2 0905 Preview",
562 ModelId::MoonshotKimiK20711Preview => "Kimi K2 0711 Preview",
563 ModelId::MoonshotKimiLatest => "Kimi Latest (auto-tier)",
564 ModelId::MoonshotKimiLatest8k => "Kimi Latest 8K",
565 ModelId::MoonshotKimiLatest32k => "Kimi Latest 32K",
566 ModelId::MoonshotKimiLatest128k => "Kimi Latest 128K",
567 ModelId::OllamaGptOss20b => "GPT-OSS 20B (local)",
569 ModelId::OllamaGptOss120bCloud => "GPT-OSS 120B (cloud)",
570 ModelId::OllamaQwen317b => "Qwen3 1.7B (local)",
571 ModelId::LmStudioMetaLlama38BInstruct => "Meta Llama 3 8B (LM Studio)",
572 ModelId::LmStudioMetaLlama318BInstruct => "Meta Llama 3.1 8B (LM Studio)",
573 ModelId::LmStudioQwen257BInstruct => "Qwen2.5 7B (LM Studio)",
574 ModelId::LmStudioGemma22BIt => "Gemma 2 2B (LM Studio)",
575 ModelId::LmStudioGemma29BIt => "Gemma 2 9B (LM Studio)",
576 ModelId::LmStudioPhi31Mini4kInstruct => "Phi-3.1 Mini 4K (LM Studio)",
577 _ => unreachable!(),
579 }
580 }
581
582 pub fn description(&self) -> &'static str {
584 if let Some(meta) = self.openrouter_metadata() {
585 return meta.description;
586 }
587 match self {
588 ModelId::Gemini25FlashPreview => {
590 "Latest fast Gemini model with advanced multimodal capabilities"
591 }
592 ModelId::Gemini25Flash => {
593 "Legacy alias for Gemini 2.5 Flash Preview (same capabilities)"
594 }
595 ModelId::Gemini25FlashLite => {
596 "Legacy alias for Gemini 2.5 Flash Preview optimized for efficiency"
597 }
598 ModelId::Gemini25Pro => "Latest most capable Gemini model with reasoning",
599 ModelId::GPT5 => "Latest most capable OpenAI model with advanced reasoning",
601 ModelId::GPT5Codex => {
602 "Code-focused GPT-5 variant optimized for tool calling and structured outputs"
603 }
604 ModelId::GPT5Mini => "Latest efficient OpenAI model, great for most tasks",
605 ModelId::GPT5Nano => "Latest most cost-effective OpenAI model",
606 ModelId::CodexMiniLatest => "Latest Codex model optimized for code generation",
607 ModelId::OpenAIGptOss20b => {
608 "OpenAI's open-source 20B parameter GPT-OSS model using harmony tokenization"
609 }
610 ModelId::OpenAIGptOss120b => {
611 "OpenAI's open-source 120B parameter GPT-OSS model using harmony tokenization"
612 }
613 ModelId::ClaudeOpus41 => "Latest most capable Anthropic model with advanced reasoning",
615 ModelId::ClaudeSonnet45 => "Latest balanced Anthropic model for general tasks",
616 ModelId::ClaudeHaiku45 => {
617 "Latest efficient Anthropic model optimized for low-latency agent workflows"
618 }
619 ModelId::ClaudeSonnet4 => {
620 "Previous balanced Anthropic model maintained for compatibility"
621 }
622 ModelId::DeepSeekChat => {
624 "DeepSeek V3.2-Exp non-thinking mode optimized for fast coding responses"
625 }
626 ModelId::DeepSeekReasoner => {
627 "DeepSeek V3.2-Exp thinking mode with structured reasoning output"
628 }
629 ModelId::XaiGrok4 => "Flagship Grok 4 model with long context and tool use",
631 ModelId::XaiGrok4Mini => "Efficient Grok 4 Mini tuned for low latency",
632 ModelId::XaiGrok4Code => "Code-specialized Grok 4 deployment with tool support",
633 ModelId::XaiGrok4CodeLatest => {
634 "Latest Grok 4 code model offering enhanced reasoning traces"
635 }
636 ModelId::XaiGrok4Vision => "Multimodal Grok 4 model with image understanding",
637 ModelId::ZaiGlm46 => {
639 "Latest Z.AI GLM flagship with long-context reasoning and coding strengths"
640 }
641 ModelId::ZaiGlm45 => "Balanced GLM 4.5 release for general assistant tasks",
642 ModelId::ZaiGlm45Air => "Efficient GLM 4.5 Air variant tuned for lower latency",
643 ModelId::ZaiGlm45X => "Enhanced GLM 4.5 X variant with improved reasoning",
644 ModelId::ZaiGlm45Airx => "Hybrid GLM 4.5 AirX variant blending efficiency with quality",
645 ModelId::ZaiGlm45Flash => "Low-latency GLM 4.5 Flash optimized for responsiveness",
646 ModelId::ZaiGlm432b0414128k => {
647 "Legacy GLM 4 32B deployment offering extended 128K context window"
648 }
649 ModelId::MoonshotKimiK2TurboPreview => {
651 "Recommended high-speed Kimi K2 turbo variant with 256K context and 60+ tok/s output"
652 }
653 ModelId::MoonshotKimiK20905Preview => {
654 "Latest Kimi K2 0905 flagship with enhanced agentic coding, 256K context, and richer tool support"
655 }
656 ModelId::MoonshotKimiK20711Preview => {
657 "Kimi K2 0711 preview tuned for balanced cost and capability with 131K context"
658 }
659 ModelId::MoonshotKimiLatest => {
660 "Auto-tier alias that selects the right Kimi Latest vision tier (8K/32K/128K) with context caching"
661 }
662 ModelId::MoonshotKimiLatest8k => {
663 "Kimi Latest 8K vision tier for short tasks with automatic context caching"
664 }
665 ModelId::MoonshotKimiLatest32k => {
666 "Kimi Latest 32K vision tier blending longer context with latest assistant features"
667 }
668 ModelId::MoonshotKimiLatest128k => {
669 "Kimi Latest 128K flagship vision tier delivering maximum context and newest capabilities"
670 }
671 ModelId::OllamaGptOss20b => {
672 "Local GPT-OSS 20B deployment served via Ollama with no external API dependency"
673 }
674 ModelId::OllamaGptOss120bCloud => {
675 "Cloud-hosted GPT-OSS 120B accessed through Ollama Cloud for larger reasoning tasks"
676 }
677 ModelId::OllamaQwen317b => {
678 "Qwen3 1.7B served locally through Ollama without external API requirements"
679 }
680 ModelId::LmStudioMetaLlama38BInstruct => {
681 "Meta Llama 3 8B running through LM Studio's local OpenAI-compatible server"
682 }
683 ModelId::LmStudioMetaLlama318BInstruct => {
684 "Meta Llama 3.1 8B running through LM Studio's local OpenAI-compatible server"
685 }
686 ModelId::LmStudioQwen257BInstruct => {
687 "Qwen2.5 7B hosted in LM Studio for local experimentation and coding tasks"
688 }
689 ModelId::LmStudioGemma22BIt => {
690 "Gemma 2 2B IT deployed via LM Studio for lightweight on-device assistance"
691 }
692 ModelId::LmStudioGemma29BIt => {
693 "Gemma 2 9B IT served locally via LM Studio when you need additional capacity"
694 }
695 ModelId::LmStudioPhi31Mini4kInstruct => {
696 "Phi-3.1 Mini 4K hosted in LM Studio for compact reasoning and experimentation"
697 }
698 _ => unreachable!(),
699 }
700 }
701
702 pub fn openrouter_vendor(&self) -> Option<&'static str> {
704 self.openrouter_metadata().map(|meta| meta.vendor)
705 }
706
707 pub fn all_models() -> Vec<ModelId> {
709 let mut models = vec![
710 ModelId::Gemini25FlashPreview,
712 ModelId::Gemini25Flash,
713 ModelId::Gemini25FlashLite,
714 ModelId::Gemini25Pro,
715 ModelId::GPT5,
717 ModelId::GPT5Codex,
718 ModelId::GPT5Mini,
719 ModelId::GPT5Nano,
720 ModelId::CodexMiniLatest,
721 ModelId::ClaudeOpus41,
723 ModelId::ClaudeSonnet45,
724 ModelId::ClaudeHaiku45,
725 ModelId::ClaudeSonnet4,
726 ModelId::DeepSeekChat,
728 ModelId::DeepSeekReasoner,
729 ModelId::XaiGrok4,
731 ModelId::XaiGrok4Mini,
732 ModelId::XaiGrok4Code,
733 ModelId::XaiGrok4CodeLatest,
734 ModelId::XaiGrok4Vision,
735 ModelId::ZaiGlm46,
737 ModelId::ZaiGlm45,
738 ModelId::ZaiGlm45Air,
739 ModelId::ZaiGlm45X,
740 ModelId::ZaiGlm45Airx,
741 ModelId::ZaiGlm45Flash,
742 ModelId::ZaiGlm432b0414128k,
743 ModelId::MoonshotKimiK2TurboPreview,
745 ModelId::MoonshotKimiK20905Preview,
746 ModelId::MoonshotKimiK20711Preview,
747 ModelId::MoonshotKimiLatest,
748 ModelId::MoonshotKimiLatest8k,
749 ModelId::MoonshotKimiLatest32k,
750 ModelId::MoonshotKimiLatest128k,
751 ModelId::OllamaGptOss20b,
753 ModelId::OllamaGptOss120bCloud,
754 ModelId::OllamaQwen317b,
755 ModelId::LmStudioMetaLlama38BInstruct,
757 ModelId::LmStudioMetaLlama318BInstruct,
758 ModelId::LmStudioQwen257BInstruct,
759 ModelId::LmStudioGemma22BIt,
760 ModelId::LmStudioGemma29BIt,
761 ModelId::LmStudioPhi31Mini4kInstruct,
762 ];
763 models.extend(Self::openrouter_models());
764 models
765 }
766
767 pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
769 Self::all_models()
770 .into_iter()
771 .filter(|model| model.provider() == provider)
772 .collect()
773 }
774
775 pub fn fallback_models() -> Vec<ModelId> {
777 vec![
778 ModelId::Gemini25FlashPreview,
779 ModelId::Gemini25Pro,
780 ModelId::GPT5,
781 ModelId::OpenAIGptOss20b,
782 ModelId::ClaudeOpus41,
783 ModelId::ClaudeSonnet45,
784 ModelId::DeepSeekReasoner,
785 ModelId::MoonshotKimiK20905Preview,
786 ModelId::XaiGrok4,
787 ModelId::ZaiGlm46,
788 ModelId::OpenRouterGrokCodeFast1,
789 ]
790 }
791
792 pub fn default_orchestrator() -> Self {
794 ModelId::Gemini25Pro
795 }
796
797 pub fn default_subagent() -> Self {
799 ModelId::Gemini25FlashPreview
800 }
801
802 pub fn default_orchestrator_for_provider(provider: Provider) -> Self {
804 match provider {
805 Provider::Gemini => ModelId::Gemini25Pro,
806 Provider::OpenAI => ModelId::GPT5,
807 Provider::Anthropic => ModelId::ClaudeOpus41,
808 Provider::DeepSeek => ModelId::DeepSeekReasoner,
809 Provider::Moonshot => ModelId::MoonshotKimiK20905Preview,
810 Provider::XAI => ModelId::XaiGrok4,
811 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
812 Provider::Ollama => ModelId::OllamaGptOss20b,
813 Provider::LmStudio => ModelId::LmStudioMetaLlama318BInstruct,
814 Provider::ZAI => ModelId::ZaiGlm46,
815 }
816 }
817
818 pub fn default_subagent_for_provider(provider: Provider) -> Self {
820 match provider {
821 Provider::Gemini => ModelId::Gemini25FlashPreview,
822 Provider::OpenAI => ModelId::GPT5Mini,
823 Provider::Anthropic => ModelId::ClaudeSonnet45,
824 Provider::DeepSeek => ModelId::DeepSeekChat,
825 Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
826 Provider::XAI => ModelId::XaiGrok4Code,
827 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
828 Provider::Ollama => ModelId::OllamaQwen317b,
829 Provider::LmStudio => ModelId::LmStudioQwen257BInstruct,
830 Provider::ZAI => ModelId::ZaiGlm45Flash,
831 }
832 }
833
834 pub fn default_single_for_provider(provider: Provider) -> Self {
836 match provider {
837 Provider::Gemini => ModelId::Gemini25FlashPreview,
838 Provider::OpenAI => ModelId::GPT5,
839 Provider::Anthropic => ModelId::ClaudeOpus41,
840 Provider::DeepSeek => ModelId::DeepSeekReasoner,
841 Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
842 Provider::XAI => ModelId::XaiGrok4,
843 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
844 Provider::Ollama => ModelId::OllamaGptOss20b,
845 Provider::LmStudio => ModelId::LmStudioMetaLlama318BInstruct,
846 Provider::ZAI => ModelId::ZaiGlm46,
847 }
848 }
849
850 pub fn is_flash_variant(&self) -> bool {
852 matches!(
853 self,
854 ModelId::Gemini25FlashPreview
855 | ModelId::Gemini25Flash
856 | ModelId::Gemini25FlashLite
857 | ModelId::ZaiGlm45Flash
858 | ModelId::MoonshotKimiK2TurboPreview
859 | ModelId::MoonshotKimiLatest8k
860 )
861 }
862
863 pub fn is_pro_variant(&self) -> bool {
865 matches!(
866 self,
867 ModelId::Gemini25Pro
868 | ModelId::GPT5
869 | ModelId::GPT5Codex
870 | ModelId::ClaudeOpus41
871 | ModelId::DeepSeekReasoner
872 | ModelId::XaiGrok4
873 | ModelId::ZaiGlm46
874 | ModelId::MoonshotKimiK20905Preview
875 | ModelId::MoonshotKimiLatest128k
876 )
877 }
878
879 pub fn is_efficient_variant(&self) -> bool {
881 if let Some(meta) = self.openrouter_metadata() {
882 return meta.efficient;
883 }
884 matches!(
885 self,
886 ModelId::Gemini25FlashPreview
887 | ModelId::Gemini25Flash
888 | ModelId::Gemini25FlashLite
889 | ModelId::GPT5Mini
890 | ModelId::GPT5Nano
891 | ModelId::ClaudeHaiku45
892 | ModelId::DeepSeekChat
893 | ModelId::XaiGrok4Code
894 | ModelId::ZaiGlm45Air
895 | ModelId::ZaiGlm45Airx
896 | ModelId::ZaiGlm45Flash
897 | ModelId::MoonshotKimiK2TurboPreview
898 | ModelId::MoonshotKimiLatest8k
899 )
900 }
901
902 pub fn is_top_tier(&self) -> bool {
904 if let Some(meta) = self.openrouter_metadata() {
905 return meta.top_tier;
906 }
907 matches!(
908 self,
909 ModelId::Gemini25Pro
910 | ModelId::GPT5
911 | ModelId::GPT5Codex
912 | ModelId::ClaudeOpus41
913 | ModelId::ClaudeSonnet45
914 | ModelId::ClaudeSonnet4
915 | ModelId::DeepSeekReasoner
916 | ModelId::XaiGrok4
917 | ModelId::XaiGrok4CodeLatest
918 | ModelId::ZaiGlm46
919 | ModelId::MoonshotKimiK20905Preview
920 | ModelId::MoonshotKimiLatest128k
921 )
922 }
923
924 pub fn is_reasoning_variant(&self) -> bool {
926 if let Some(meta) = self.openrouter_metadata() {
927 return meta.reasoning;
928 }
929 self.provider().supports_reasoning_effort(self.as_str())
930 }
931
932 pub fn supports_tool_calls(&self) -> bool {
934 if let Some(meta) = self.openrouter_metadata() {
935 return meta.tool_call;
936 }
937 true
938 }
939
940 pub fn generation(&self) -> &'static str {
942 if let Some(meta) = self.openrouter_metadata() {
943 return meta.generation;
944 }
945 match self {
946 ModelId::Gemini25FlashPreview
948 | ModelId::Gemini25Flash
949 | ModelId::Gemini25FlashLite
950 | ModelId::Gemini25Pro => "2.5",
951 ModelId::GPT5
953 | ModelId::GPT5Codex
954 | ModelId::GPT5Mini
955 | ModelId::GPT5Nano
956 | ModelId::CodexMiniLatest => "5",
957 ModelId::ClaudeSonnet45 | ModelId::ClaudeHaiku45 => "4.5",
959 ModelId::ClaudeSonnet4 => "4",
960 ModelId::ClaudeOpus41 => "4.1",
961 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
963 ModelId::XaiGrok4
965 | ModelId::XaiGrok4Mini
966 | ModelId::XaiGrok4Code
967 | ModelId::XaiGrok4CodeLatest
968 | ModelId::XaiGrok4Vision => "4",
969 ModelId::ZaiGlm46 => "4.6",
971 ModelId::ZaiGlm45
972 | ModelId::ZaiGlm45Air
973 | ModelId::ZaiGlm45X
974 | ModelId::ZaiGlm45Airx
975 | ModelId::ZaiGlm45Flash => "4.5",
976 ModelId::ZaiGlm432b0414128k => "4-32B",
977 ModelId::MoonshotKimiK2TurboPreview
979 | ModelId::MoonshotKimiK20905Preview
980 | ModelId::MoonshotKimiK20711Preview => "k2",
981 ModelId::MoonshotKimiLatest
982 | ModelId::MoonshotKimiLatest8k
983 | ModelId::MoonshotKimiLatest32k
984 | ModelId::MoonshotKimiLatest128k => "latest",
985 ModelId::OllamaGptOss20b => "oss",
986 ModelId::OllamaGptOss120bCloud => "oss-cloud",
987 ModelId::OllamaQwen317b => "oss",
988 ModelId::LmStudioMetaLlama38BInstruct => "meta-llama-3",
989 ModelId::LmStudioMetaLlama318BInstruct => "meta-llama-3.1",
990 ModelId::LmStudioQwen257BInstruct => "qwen2.5",
991 ModelId::LmStudioGemma22BIt => "gemma-2",
992 ModelId::LmStudioGemma29BIt => "gemma-2",
993 ModelId::LmStudioPhi31Mini4kInstruct => "phi-3.1",
994 _ => unreachable!(),
995 }
996 }
997}
998
999impl fmt::Display for ModelId {
1000 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1001 write!(f, "{}", self.as_str())
1002 }
1003}
1004
1005impl FromStr for ModelId {
1006 type Err = ModelParseError;
1007
1008 fn from_str(s: &str) -> Result<Self, Self::Err> {
1009 use crate::constants::models;
1010 match s {
1011 s if s == models::GEMINI_2_5_FLASH_PREVIEW => Ok(ModelId::Gemini25FlashPreview),
1013 s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
1014 s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
1015 s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
1016 s if s == models::GPT_5 => Ok(ModelId::GPT5),
1018 s if s == models::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
1019 s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
1020 s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
1021 s if s == models::CODEX_MINI_LATEST => Ok(ModelId::CodexMiniLatest),
1022 s if s == models::openai::GPT_OSS_20B => Ok(ModelId::OpenAIGptOss20b),
1023 s if s == models::openai::GPT_OSS_120B => Ok(ModelId::OpenAIGptOss120b),
1024 s if s == models::CLAUDE_OPUS_4_1_20250805 => Ok(ModelId::ClaudeOpus41),
1026 s if s == models::CLAUDE_SONNET_4_5 => Ok(ModelId::ClaudeSonnet45),
1027 s if s == models::CLAUDE_HAIKU_4_5 => Ok(ModelId::ClaudeHaiku45),
1028 s if s == models::CLAUDE_SONNET_4_20250514 => Ok(ModelId::ClaudeSonnet4),
1029 s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
1031 s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
1032 s if s == models::xai::GROK_4 => Ok(ModelId::XaiGrok4),
1034 s if s == models::xai::GROK_4_MINI => Ok(ModelId::XaiGrok4Mini),
1035 s if s == models::xai::GROK_4_CODE => Ok(ModelId::XaiGrok4Code),
1036 s if s == models::xai::GROK_4_CODE_LATEST => Ok(ModelId::XaiGrok4CodeLatest),
1037 s if s == models::xai::GROK_4_VISION => Ok(ModelId::XaiGrok4Vision),
1038 s if s == models::zai::GLM_4_6 => Ok(ModelId::ZaiGlm46),
1040 s if s == models::zai::GLM_4_5 => Ok(ModelId::ZaiGlm45),
1041 s if s == models::zai::GLM_4_5_AIR => Ok(ModelId::ZaiGlm45Air),
1042 s if s == models::zai::GLM_4_5_X => Ok(ModelId::ZaiGlm45X),
1043 s if s == models::zai::GLM_4_5_AIRX => Ok(ModelId::ZaiGlm45Airx),
1044 s if s == models::zai::GLM_4_5_FLASH => Ok(ModelId::ZaiGlm45Flash),
1045 s if s == models::zai::GLM_4_32B_0414_128K => Ok(ModelId::ZaiGlm432b0414128k),
1046 s if s == models::MOONSHOT_KIMI_K2_TURBO_PREVIEW => {
1048 Ok(ModelId::MoonshotKimiK2TurboPreview)
1049 }
1050 s if s == models::MOONSHOT_KIMI_K2_0905_PREVIEW => {
1051 Ok(ModelId::MoonshotKimiK20905Preview)
1052 }
1053 s if s == models::MOONSHOT_KIMI_K2_0711_PREVIEW => {
1054 Ok(ModelId::MoonshotKimiK20711Preview)
1055 }
1056 s if s == models::MOONSHOT_KIMI_LATEST => Ok(ModelId::MoonshotKimiLatest),
1057 s if s == models::MOONSHOT_KIMI_LATEST_8K => Ok(ModelId::MoonshotKimiLatest8k),
1058 s if s == models::MOONSHOT_KIMI_LATEST_32K => Ok(ModelId::MoonshotKimiLatest32k),
1059 s if s == models::MOONSHOT_KIMI_LATEST_128K => Ok(ModelId::MoonshotKimiLatest128k),
1060 s if s == models::ollama::GPT_OSS_20B => Ok(ModelId::OllamaGptOss20b),
1061 s if s == models::ollama::GPT_OSS_120B_CLOUD => Ok(ModelId::OllamaGptOss120bCloud),
1062 s if s == models::ollama::QWEN3_1_7B => Ok(ModelId::OllamaQwen317b),
1063 s if s == models::lmstudio::META_LLAMA_3_8B_INSTRUCT => {
1064 Ok(ModelId::LmStudioMetaLlama38BInstruct)
1065 }
1066 s if s == models::lmstudio::META_LLAMA_31_8B_INSTRUCT => {
1067 Ok(ModelId::LmStudioMetaLlama318BInstruct)
1068 }
1069 s if s == models::lmstudio::QWEN25_7B_INSTRUCT => Ok(ModelId::LmStudioQwen257BInstruct),
1070 s if s == models::lmstudio::GEMMA_2_2B_IT => Ok(ModelId::LmStudioGemma22BIt),
1071 s if s == models::lmstudio::GEMMA_2_9B_IT => Ok(ModelId::LmStudioGemma29BIt),
1072 s if s == models::lmstudio::PHI_31_MINI_4K_INSTRUCT => {
1073 Ok(ModelId::LmStudioPhi31Mini4kInstruct)
1074 }
1075 _ => {
1076 if let Some(model) = Self::parse_openrouter_model(s) {
1077 Ok(model)
1078 } else {
1079 Err(ModelParseError::InvalidModel(s.to_string()))
1080 }
1081 }
1082 }
1083 }
1084}
1085
1086#[derive(Debug, Clone, PartialEq)]
1088pub enum ModelParseError {
1089 InvalidModel(String),
1090 InvalidProvider(String),
1091}
1092
1093impl fmt::Display for ModelParseError {
1094 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1095 match self {
1096 ModelParseError::InvalidModel(model) => {
1097 write!(
1098 f,
1099 "Invalid model identifier: '{}'. Supported models: {}",
1100 model,
1101 ModelId::all_models()
1102 .iter()
1103 .map(|m| m.as_str())
1104 .collect::<Vec<_>>()
1105 .join(", ")
1106 )
1107 }
1108 ModelParseError::InvalidProvider(provider) => {
1109 write!(
1110 f,
1111 "Invalid provider: '{}'. Supported providers: {}",
1112 provider,
1113 Provider::all_providers()
1114 .iter()
1115 .map(|p| p.to_string())
1116 .collect::<Vec<_>>()
1117 .join(", ")
1118 )
1119 }
1120 }
1121 }
1122}
1123
1124impl std::error::Error for ModelParseError {}
1125
1126#[cfg(test)]
1127mod tests {
1128 use super::*;
1129 use crate::constants::models;
1130
1131 #[test]
1132 fn test_model_string_conversion() {
1133 assert_eq!(
1135 ModelId::Gemini25FlashPreview.as_str(),
1136 models::GEMINI_2_5_FLASH_PREVIEW
1137 );
1138 assert_eq!(ModelId::Gemini25Flash.as_str(), models::GEMINI_2_5_FLASH);
1139 assert_eq!(
1140 ModelId::Gemini25FlashLite.as_str(),
1141 models::GEMINI_2_5_FLASH_LITE
1142 );
1143 assert_eq!(ModelId::Gemini25Pro.as_str(), models::GEMINI_2_5_PRO);
1144 assert_eq!(ModelId::GPT5.as_str(), models::GPT_5);
1146 assert_eq!(ModelId::GPT5Codex.as_str(), models::GPT_5_CODEX);
1147 assert_eq!(ModelId::GPT5Mini.as_str(), models::GPT_5_MINI);
1148 assert_eq!(ModelId::GPT5Nano.as_str(), models::GPT_5_NANO);
1149 assert_eq!(ModelId::CodexMiniLatest.as_str(), models::CODEX_MINI_LATEST);
1150 assert_eq!(ModelId::ClaudeSonnet45.as_str(), models::CLAUDE_SONNET_4_5);
1152 assert_eq!(ModelId::ClaudeHaiku45.as_str(), models::CLAUDE_HAIKU_4_5);
1153 assert_eq!(
1154 ModelId::ClaudeSonnet4.as_str(),
1155 models::CLAUDE_SONNET_4_20250514
1156 );
1157 assert_eq!(
1158 ModelId::ClaudeOpus41.as_str(),
1159 models::CLAUDE_OPUS_4_1_20250805
1160 );
1161 assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
1163 assert_eq!(
1164 ModelId::DeepSeekReasoner.as_str(),
1165 models::DEEPSEEK_REASONER
1166 );
1167 assert_eq!(ModelId::XaiGrok4.as_str(), models::xai::GROK_4);
1169 assert_eq!(ModelId::XaiGrok4Mini.as_str(), models::xai::GROK_4_MINI);
1170 assert_eq!(ModelId::XaiGrok4Code.as_str(), models::xai::GROK_4_CODE);
1171 assert_eq!(
1172 ModelId::XaiGrok4CodeLatest.as_str(),
1173 models::xai::GROK_4_CODE_LATEST
1174 );
1175 assert_eq!(ModelId::XaiGrok4Vision.as_str(), models::xai::GROK_4_VISION);
1176 assert_eq!(ModelId::ZaiGlm46.as_str(), models::zai::GLM_4_6);
1178 assert_eq!(ModelId::ZaiGlm45.as_str(), models::zai::GLM_4_5);
1179 assert_eq!(ModelId::ZaiGlm45Air.as_str(), models::zai::GLM_4_5_AIR);
1180 assert_eq!(ModelId::ZaiGlm45X.as_str(), models::zai::GLM_4_5_X);
1181 assert_eq!(ModelId::ZaiGlm45Airx.as_str(), models::zai::GLM_4_5_AIRX);
1182 assert_eq!(ModelId::ZaiGlm45Flash.as_str(), models::zai::GLM_4_5_FLASH);
1183 assert_eq!(
1184 ModelId::ZaiGlm432b0414128k.as_str(),
1185 models::zai::GLM_4_32B_0414_128K
1186 );
1187 for entry in openrouter_generated::ENTRIES {
1188 assert_eq!(entry.variant.as_str(), entry.id);
1189 }
1190 }
1191
1192 #[test]
1193 fn test_model_from_string() {
1194 assert_eq!(
1196 models::GEMINI_2_5_FLASH_PREVIEW.parse::<ModelId>().unwrap(),
1197 ModelId::Gemini25FlashPreview
1198 );
1199 assert_eq!(
1200 models::GEMINI_2_5_FLASH.parse::<ModelId>().unwrap(),
1201 ModelId::Gemini25Flash
1202 );
1203 assert_eq!(
1204 models::GEMINI_2_5_FLASH_LITE.parse::<ModelId>().unwrap(),
1205 ModelId::Gemini25FlashLite
1206 );
1207 assert_eq!(
1208 models::GEMINI_2_5_PRO.parse::<ModelId>().unwrap(),
1209 ModelId::Gemini25Pro
1210 );
1211 assert_eq!(models::GPT_5.parse::<ModelId>().unwrap(), ModelId::GPT5);
1213 assert_eq!(
1214 models::GPT_5_CODEX.parse::<ModelId>().unwrap(),
1215 ModelId::GPT5Codex
1216 );
1217 assert_eq!(
1218 models::GPT_5_MINI.parse::<ModelId>().unwrap(),
1219 ModelId::GPT5Mini
1220 );
1221 assert_eq!(
1222 models::GPT_5_NANO.parse::<ModelId>().unwrap(),
1223 ModelId::GPT5Nano
1224 );
1225 assert_eq!(
1226 models::CODEX_MINI_LATEST.parse::<ModelId>().unwrap(),
1227 ModelId::CodexMiniLatest
1228 );
1229 assert_eq!(
1230 models::openai::GPT_OSS_20B.parse::<ModelId>().unwrap(),
1231 ModelId::OpenAIGptOss20b
1232 );
1233 assert_eq!(
1234 models::openai::GPT_OSS_120B.parse::<ModelId>().unwrap(),
1235 ModelId::OpenAIGptOss120b
1236 );
1237 assert_eq!(
1239 models::CLAUDE_SONNET_4_5.parse::<ModelId>().unwrap(),
1240 ModelId::ClaudeSonnet45
1241 );
1242 assert_eq!(
1243 models::CLAUDE_HAIKU_4_5.parse::<ModelId>().unwrap(),
1244 ModelId::ClaudeHaiku45
1245 );
1246 assert_eq!(
1247 models::CLAUDE_SONNET_4_20250514.parse::<ModelId>().unwrap(),
1248 ModelId::ClaudeSonnet4
1249 );
1250 assert_eq!(
1251 models::CLAUDE_OPUS_4_1_20250805.parse::<ModelId>().unwrap(),
1252 ModelId::ClaudeOpus41
1253 );
1254 assert_eq!(
1256 models::DEEPSEEK_CHAT.parse::<ModelId>().unwrap(),
1257 ModelId::DeepSeekChat
1258 );
1259 assert_eq!(
1260 models::DEEPSEEK_REASONER.parse::<ModelId>().unwrap(),
1261 ModelId::DeepSeekReasoner
1262 );
1263 assert_eq!(
1265 models::xai::GROK_4.parse::<ModelId>().unwrap(),
1266 ModelId::XaiGrok4
1267 );
1268 assert_eq!(
1269 models::xai::GROK_4_MINI.parse::<ModelId>().unwrap(),
1270 ModelId::XaiGrok4Mini
1271 );
1272 assert_eq!(
1273 models::xai::GROK_4_CODE.parse::<ModelId>().unwrap(),
1274 ModelId::XaiGrok4Code
1275 );
1276 assert_eq!(
1277 models::xai::GROK_4_CODE_LATEST.parse::<ModelId>().unwrap(),
1278 ModelId::XaiGrok4CodeLatest
1279 );
1280 assert_eq!(
1281 models::xai::GROK_4_VISION.parse::<ModelId>().unwrap(),
1282 ModelId::XaiGrok4Vision
1283 );
1284 assert_eq!(
1286 models::zai::GLM_4_6.parse::<ModelId>().unwrap(),
1287 ModelId::ZaiGlm46
1288 );
1289 assert_eq!(
1290 models::zai::GLM_4_5.parse::<ModelId>().unwrap(),
1291 ModelId::ZaiGlm45
1292 );
1293 assert_eq!(
1294 models::zai::GLM_4_5_AIR.parse::<ModelId>().unwrap(),
1295 ModelId::ZaiGlm45Air
1296 );
1297 assert_eq!(
1298 models::zai::GLM_4_5_X.parse::<ModelId>().unwrap(),
1299 ModelId::ZaiGlm45X
1300 );
1301 assert_eq!(
1302 models::zai::GLM_4_5_AIRX.parse::<ModelId>().unwrap(),
1303 ModelId::ZaiGlm45Airx
1304 );
1305 assert_eq!(
1306 models::zai::GLM_4_5_FLASH.parse::<ModelId>().unwrap(),
1307 ModelId::ZaiGlm45Flash
1308 );
1309 assert_eq!(
1310 models::zai::GLM_4_32B_0414_128K.parse::<ModelId>().unwrap(),
1311 ModelId::ZaiGlm432b0414128k
1312 );
1313 assert_eq!(
1314 models::MOONSHOT_KIMI_K2_TURBO_PREVIEW
1315 .parse::<ModelId>()
1316 .unwrap(),
1317 ModelId::MoonshotKimiK2TurboPreview
1318 );
1319 assert_eq!(
1320 models::MOONSHOT_KIMI_K2_0905_PREVIEW
1321 .parse::<ModelId>()
1322 .unwrap(),
1323 ModelId::MoonshotKimiK20905Preview
1324 );
1325 assert_eq!(
1326 models::MOONSHOT_KIMI_K2_0711_PREVIEW
1327 .parse::<ModelId>()
1328 .unwrap(),
1329 ModelId::MoonshotKimiK20711Preview
1330 );
1331 assert_eq!(
1332 models::MOONSHOT_KIMI_LATEST.parse::<ModelId>().unwrap(),
1333 ModelId::MoonshotKimiLatest
1334 );
1335 assert_eq!(
1336 models::MOONSHOT_KIMI_LATEST_8K.parse::<ModelId>().unwrap(),
1337 ModelId::MoonshotKimiLatest8k
1338 );
1339 assert_eq!(
1340 models::MOONSHOT_KIMI_LATEST_32K.parse::<ModelId>().unwrap(),
1341 ModelId::MoonshotKimiLatest32k
1342 );
1343 assert_eq!(
1344 models::MOONSHOT_KIMI_LATEST_128K
1345 .parse::<ModelId>()
1346 .unwrap(),
1347 ModelId::MoonshotKimiLatest128k
1348 );
1349 for entry in openrouter_generated::ENTRIES {
1350 assert_eq!(entry.id.parse::<ModelId>().unwrap(), entry.variant);
1351 }
1352 assert!("invalid-model".parse::<ModelId>().is_err());
1354 }
1355
1356 #[test]
1357 fn test_provider_parsing() {
1358 assert_eq!("gemini".parse::<Provider>().unwrap(), Provider::Gemini);
1359 assert_eq!("openai".parse::<Provider>().unwrap(), Provider::OpenAI);
1360 assert_eq!(
1361 "anthropic".parse::<Provider>().unwrap(),
1362 Provider::Anthropic
1363 );
1364 assert_eq!("deepseek".parse::<Provider>().unwrap(), Provider::DeepSeek);
1365 assert_eq!(
1366 "openrouter".parse::<Provider>().unwrap(),
1367 Provider::OpenRouter
1368 );
1369 assert_eq!("xai".parse::<Provider>().unwrap(), Provider::XAI);
1370 assert_eq!("zai".parse::<Provider>().unwrap(), Provider::ZAI);
1371 assert_eq!("moonshot".parse::<Provider>().unwrap(), Provider::Moonshot);
1372 assert_eq!("lmstudio".parse::<Provider>().unwrap(), Provider::LmStudio);
1373 assert!("invalid-provider".parse::<Provider>().is_err());
1374 }
1375
1376 #[test]
1377 fn test_model_providers() {
1378 assert_eq!(ModelId::Gemini25FlashPreview.provider(), Provider::Gemini);
1379 assert_eq!(ModelId::GPT5.provider(), Provider::OpenAI);
1380 assert_eq!(ModelId::GPT5Codex.provider(), Provider::OpenAI);
1381 assert_eq!(ModelId::ClaudeSonnet45.provider(), Provider::Anthropic);
1382 assert_eq!(ModelId::ClaudeHaiku45.provider(), Provider::Anthropic);
1383 assert_eq!(ModelId::ClaudeSonnet4.provider(), Provider::Anthropic);
1384 assert_eq!(ModelId::DeepSeekChat.provider(), Provider::DeepSeek);
1385 assert_eq!(ModelId::XaiGrok4.provider(), Provider::XAI);
1386 assert_eq!(ModelId::ZaiGlm46.provider(), Provider::ZAI);
1387 assert_eq!(
1388 ModelId::MoonshotKimiK20905Preview.provider(),
1389 Provider::Moonshot
1390 );
1391 assert_eq!(ModelId::OllamaGptOss20b.provider(), Provider::Ollama);
1392 assert_eq!(ModelId::OllamaGptOss120bCloud.provider(), Provider::Ollama);
1393 assert_eq!(ModelId::OllamaQwen317b.provider(), Provider::Ollama);
1394 assert_eq!(
1395 ModelId::LmStudioMetaLlama38BInstruct.provider(),
1396 Provider::LmStudio
1397 );
1398 assert_eq!(
1399 ModelId::LmStudioMetaLlama318BInstruct.provider(),
1400 Provider::LmStudio
1401 );
1402 assert_eq!(
1403 ModelId::LmStudioQwen257BInstruct.provider(),
1404 Provider::LmStudio
1405 );
1406 assert_eq!(ModelId::LmStudioGemma22BIt.provider(), Provider::LmStudio);
1407 assert_eq!(ModelId::LmStudioGemma29BIt.provider(), Provider::LmStudio);
1408 assert_eq!(
1409 ModelId::LmStudioPhi31Mini4kInstruct.provider(),
1410 Provider::LmStudio
1411 );
1412 assert_eq!(
1413 ModelId::OpenRouterGrokCodeFast1.provider(),
1414 Provider::OpenRouter
1415 );
1416 assert_eq!(
1417 ModelId::OpenRouterAnthropicClaudeSonnet45.provider(),
1418 Provider::OpenRouter
1419 );
1420
1421 for entry in openrouter_generated::ENTRIES {
1422 assert_eq!(entry.variant.provider(), Provider::OpenRouter);
1423 }
1424 }
1425
1426 #[test]
1427 fn test_provider_defaults() {
1428 assert_eq!(
1429 ModelId::default_orchestrator_for_provider(Provider::Gemini),
1430 ModelId::Gemini25Pro
1431 );
1432 assert_eq!(
1433 ModelId::default_orchestrator_for_provider(Provider::OpenAI),
1434 ModelId::GPT5
1435 );
1436 assert_eq!(
1437 ModelId::default_orchestrator_for_provider(Provider::Anthropic),
1438 ModelId::ClaudeSonnet4
1439 );
1440 assert_eq!(
1441 ModelId::default_orchestrator_for_provider(Provider::DeepSeek),
1442 ModelId::DeepSeekReasoner
1443 );
1444 assert_eq!(
1445 ModelId::default_orchestrator_for_provider(Provider::OpenRouter),
1446 ModelId::OpenRouterGrokCodeFast1
1447 );
1448 assert_eq!(
1449 ModelId::default_orchestrator_for_provider(Provider::XAI),
1450 ModelId::XaiGrok4
1451 );
1452 assert_eq!(
1453 ModelId::default_orchestrator_for_provider(Provider::Ollama),
1454 ModelId::OllamaGptOss20b
1455 );
1456 assert_eq!(
1457 ModelId::default_orchestrator_for_provider(Provider::LmStudio),
1458 ModelId::LmStudioMetaLlama318BInstruct
1459 );
1460 assert_eq!(
1461 ModelId::default_orchestrator_for_provider(Provider::ZAI),
1462 ModelId::ZaiGlm46
1463 );
1464 assert_eq!(
1465 ModelId::default_orchestrator_for_provider(Provider::Moonshot),
1466 ModelId::MoonshotKimiK20905Preview
1467 );
1468
1469 assert_eq!(
1470 ModelId::default_subagent_for_provider(Provider::Gemini),
1471 ModelId::Gemini25FlashPreview
1472 );
1473 assert_eq!(
1474 ModelId::default_subagent_for_provider(Provider::OpenAI),
1475 ModelId::GPT5Mini
1476 );
1477 assert_eq!(
1478 ModelId::default_subagent_for_provider(Provider::Anthropic),
1479 ModelId::ClaudeSonnet45
1480 );
1481 assert_eq!(
1482 ModelId::default_subagent_for_provider(Provider::DeepSeek),
1483 ModelId::DeepSeekChat
1484 );
1485 assert_eq!(
1486 ModelId::default_subagent_for_provider(Provider::OpenRouter),
1487 ModelId::OpenRouterGrokCodeFast1
1488 );
1489 assert_eq!(
1490 ModelId::default_subagent_for_provider(Provider::XAI),
1491 ModelId::XaiGrok4Code
1492 );
1493 assert_eq!(
1494 ModelId::default_subagent_for_provider(Provider::Ollama),
1495 ModelId::OllamaQwen317b
1496 );
1497 assert_eq!(
1498 ModelId::default_subagent_for_provider(Provider::LmStudio),
1499 ModelId::LmStudioQwen257BInstruct
1500 );
1501 assert_eq!(
1502 ModelId::default_subagent_for_provider(Provider::ZAI),
1503 ModelId::ZaiGlm45Flash
1504 );
1505 assert_eq!(
1506 ModelId::default_subagent_for_provider(Provider::Moonshot),
1507 ModelId::MoonshotKimiK2TurboPreview
1508 );
1509
1510 assert_eq!(
1511 ModelId::default_single_for_provider(Provider::DeepSeek),
1512 ModelId::DeepSeekReasoner
1513 );
1514 assert_eq!(
1515 ModelId::default_single_for_provider(Provider::Moonshot),
1516 ModelId::MoonshotKimiK2TurboPreview
1517 );
1518 assert_eq!(
1519 ModelId::default_single_for_provider(Provider::Ollama),
1520 ModelId::OllamaGptOss20b
1521 );
1522 assert_eq!(
1523 ModelId::default_single_for_provider(Provider::LmStudio),
1524 ModelId::LmStudioMetaLlama318BInstruct
1525 );
1526 }
1527
1528 #[test]
1529 fn test_model_defaults() {
1530 assert_eq!(ModelId::default(), ModelId::Gemini25FlashPreview);
1531 assert_eq!(ModelId::default_orchestrator(), ModelId::Gemini25Pro);
1532 assert_eq!(ModelId::default_subagent(), ModelId::Gemini25FlashPreview);
1533 }
1534
1535 #[test]
1536 fn test_model_variants() {
1537 assert!(ModelId::Gemini25FlashPreview.is_flash_variant());
1539 assert!(ModelId::Gemini25Flash.is_flash_variant());
1540 assert!(ModelId::Gemini25FlashLite.is_flash_variant());
1541 assert!(!ModelId::GPT5.is_flash_variant());
1542 assert!(ModelId::ZaiGlm45Flash.is_flash_variant());
1543 assert!(ModelId::MoonshotKimiK2TurboPreview.is_flash_variant());
1544 assert!(ModelId::MoonshotKimiLatest8k.is_flash_variant());
1545
1546 assert!(ModelId::Gemini25Pro.is_pro_variant());
1548 assert!(ModelId::GPT5.is_pro_variant());
1549 assert!(ModelId::GPT5Codex.is_pro_variant());
1550 assert!(ModelId::DeepSeekReasoner.is_pro_variant());
1551 assert!(ModelId::ZaiGlm46.is_pro_variant());
1552 assert!(ModelId::MoonshotKimiK20905Preview.is_pro_variant());
1553 assert!(ModelId::MoonshotKimiLatest128k.is_pro_variant());
1554 assert!(!ModelId::Gemini25FlashPreview.is_pro_variant());
1555
1556 assert!(ModelId::Gemini25FlashPreview.is_efficient_variant());
1558 assert!(ModelId::Gemini25Flash.is_efficient_variant());
1559 assert!(ModelId::Gemini25FlashLite.is_efficient_variant());
1560 assert!(ModelId::GPT5Mini.is_efficient_variant());
1561 assert!(ModelId::ClaudeHaiku45.is_efficient_variant());
1562 assert!(ModelId::XaiGrok4Code.is_efficient_variant());
1563 assert!(ModelId::DeepSeekChat.is_efficient_variant());
1564 assert!(ModelId::ZaiGlm45Air.is_efficient_variant());
1565 assert!(ModelId::ZaiGlm45Airx.is_efficient_variant());
1566 assert!(ModelId::ZaiGlm45Flash.is_efficient_variant());
1567 assert!(ModelId::MoonshotKimiK2TurboPreview.is_efficient_variant());
1568 assert!(ModelId::MoonshotKimiLatest8k.is_efficient_variant());
1569 assert!(!ModelId::GPT5.is_efficient_variant());
1570
1571 for entry in openrouter_generated::ENTRIES {
1572 assert_eq!(entry.variant.is_efficient_variant(), entry.efficient);
1573 }
1574
1575 assert!(ModelId::Gemini25Pro.is_top_tier());
1577 assert!(ModelId::GPT5.is_top_tier());
1578 assert!(ModelId::GPT5Codex.is_top_tier());
1579 assert!(ModelId::ClaudeSonnet45.is_top_tier());
1580 assert!(ModelId::ClaudeSonnet4.is_top_tier());
1581 assert!(ModelId::XaiGrok4.is_top_tier());
1582 assert!(ModelId::XaiGrok4CodeLatest.is_top_tier());
1583 assert!(ModelId::DeepSeekReasoner.is_top_tier());
1584 assert!(ModelId::ZaiGlm46.is_top_tier());
1585 assert!(ModelId::MoonshotKimiK20905Preview.is_top_tier());
1586 assert!(ModelId::MoonshotKimiLatest128k.is_top_tier());
1587 assert!(!ModelId::Gemini25FlashPreview.is_top_tier());
1588 assert!(!ModelId::ClaudeHaiku45.is_top_tier());
1589
1590 for entry in openrouter_generated::ENTRIES {
1591 assert_eq!(entry.variant.is_top_tier(), entry.top_tier);
1592 }
1593 }
1594
1595 #[test]
1596 fn test_model_generation() {
1597 assert_eq!(ModelId::Gemini25FlashPreview.generation(), "2.5");
1599 assert_eq!(ModelId::Gemini25Flash.generation(), "2.5");
1600 assert_eq!(ModelId::Gemini25FlashLite.generation(), "2.5");
1601 assert_eq!(ModelId::Gemini25Pro.generation(), "2.5");
1602
1603 assert_eq!(ModelId::GPT5.generation(), "5");
1605 assert_eq!(ModelId::GPT5Codex.generation(), "5");
1606 assert_eq!(ModelId::GPT5Mini.generation(), "5");
1607 assert_eq!(ModelId::GPT5Nano.generation(), "5");
1608 assert_eq!(ModelId::CodexMiniLatest.generation(), "5");
1609
1610 assert_eq!(ModelId::ClaudeSonnet45.generation(), "4.5");
1612 assert_eq!(ModelId::ClaudeHaiku45.generation(), "4.5");
1613 assert_eq!(ModelId::ClaudeSonnet4.generation(), "4");
1614 assert_eq!(ModelId::ClaudeOpus41.generation(), "4.1");
1615
1616 assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1618 assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1619
1620 assert_eq!(ModelId::XaiGrok4.generation(), "4");
1622 assert_eq!(ModelId::XaiGrok4Mini.generation(), "4");
1623 assert_eq!(ModelId::XaiGrok4Code.generation(), "4");
1624 assert_eq!(ModelId::XaiGrok4CodeLatest.generation(), "4");
1625 assert_eq!(ModelId::XaiGrok4Vision.generation(), "4");
1626 assert_eq!(ModelId::ZaiGlm46.generation(), "4.6");
1628 assert_eq!(ModelId::ZaiGlm45.generation(), "4.5");
1629 assert_eq!(ModelId::ZaiGlm45Air.generation(), "4.5");
1630 assert_eq!(ModelId::ZaiGlm45X.generation(), "4.5");
1631 assert_eq!(ModelId::ZaiGlm45Airx.generation(), "4.5");
1632 assert_eq!(ModelId::ZaiGlm45Flash.generation(), "4.5");
1633 assert_eq!(ModelId::ZaiGlm432b0414128k.generation(), "4-32B");
1634 assert_eq!(ModelId::MoonshotKimiK2TurboPreview.generation(), "k2");
1635 assert_eq!(ModelId::MoonshotKimiK20905Preview.generation(), "k2");
1636 assert_eq!(ModelId::MoonshotKimiK20711Preview.generation(), "k2");
1637 assert_eq!(ModelId::MoonshotKimiLatest.generation(), "latest");
1638 assert_eq!(ModelId::MoonshotKimiLatest8k.generation(), "latest");
1639 assert_eq!(ModelId::MoonshotKimiLatest32k.generation(), "latest");
1640 assert_eq!(ModelId::MoonshotKimiLatest128k.generation(), "latest");
1641 assert_eq!(
1642 ModelId::LmStudioMetaLlama38BInstruct.generation(),
1643 "meta-llama-3"
1644 );
1645 assert_eq!(
1646 ModelId::LmStudioMetaLlama318BInstruct.generation(),
1647 "meta-llama-3.1"
1648 );
1649 assert_eq!(ModelId::LmStudioQwen257BInstruct.generation(), "qwen2.5");
1650 assert_eq!(ModelId::LmStudioGemma22BIt.generation(), "gemma-2");
1651 assert_eq!(ModelId::LmStudioGemma29BIt.generation(), "gemma-2");
1652 assert_eq!(ModelId::LmStudioPhi31Mini4kInstruct.generation(), "phi-3.1");
1653
1654 for entry in openrouter_generated::ENTRIES {
1655 assert_eq!(entry.variant.generation(), entry.generation);
1656 }
1657 }
1658
1659 #[test]
1660 fn test_models_for_provider() {
1661 let gemini_models = ModelId::models_for_provider(Provider::Gemini);
1662 assert!(gemini_models.contains(&ModelId::Gemini25Pro));
1663 assert!(!gemini_models.contains(&ModelId::GPT5));
1664
1665 let openai_models = ModelId::models_for_provider(Provider::OpenAI);
1666 assert!(openai_models.contains(&ModelId::GPT5));
1667 assert!(openai_models.contains(&ModelId::GPT5Codex));
1668 assert!(!openai_models.contains(&ModelId::Gemini25Pro));
1669
1670 let anthropic_models = ModelId::models_for_provider(Provider::Anthropic);
1671 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet45));
1672 assert!(anthropic_models.contains(&ModelId::ClaudeHaiku45));
1673 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet4));
1674 assert!(!anthropic_models.contains(&ModelId::GPT5));
1675
1676 let deepseek_models = ModelId::models_for_provider(Provider::DeepSeek);
1677 assert!(deepseek_models.contains(&ModelId::DeepSeekChat));
1678 assert!(deepseek_models.contains(&ModelId::DeepSeekReasoner));
1679
1680 let openrouter_models = ModelId::models_for_provider(Provider::OpenRouter);
1681 for entry in openrouter_generated::ENTRIES {
1682 assert!(openrouter_models.contains(&entry.variant));
1683 }
1684
1685 let xai_models = ModelId::models_for_provider(Provider::XAI);
1686 assert!(xai_models.contains(&ModelId::XaiGrok4));
1687 assert!(xai_models.contains(&ModelId::XaiGrok4Mini));
1688 assert!(xai_models.contains(&ModelId::XaiGrok4Code));
1689 assert!(xai_models.contains(&ModelId::XaiGrok4CodeLatest));
1690 assert!(xai_models.contains(&ModelId::XaiGrok4Vision));
1691
1692 let zai_models = ModelId::models_for_provider(Provider::ZAI);
1693 assert!(zai_models.contains(&ModelId::ZaiGlm46));
1694 assert!(zai_models.contains(&ModelId::ZaiGlm45));
1695 assert!(zai_models.contains(&ModelId::ZaiGlm45Air));
1696 assert!(zai_models.contains(&ModelId::ZaiGlm45X));
1697 assert!(zai_models.contains(&ModelId::ZaiGlm45Airx));
1698 assert!(zai_models.contains(&ModelId::ZaiGlm45Flash));
1699 assert!(zai_models.contains(&ModelId::ZaiGlm432b0414128k));
1700
1701 let moonshot_models = ModelId::models_for_provider(Provider::Moonshot);
1702 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK2TurboPreview));
1703 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20905Preview));
1704 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20711Preview));
1705 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest));
1706 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest8k));
1707 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest32k));
1708 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest128k));
1709 assert_eq!(moonshot_models.len(), 7);
1710
1711 let ollama_models = ModelId::models_for_provider(Provider::Ollama);
1712 assert!(ollama_models.contains(&ModelId::OllamaGptOss20b));
1713 assert!(ollama_models.contains(&ModelId::OllamaGptOss120bCloud));
1714 assert!(ollama_models.contains(&ModelId::OllamaQwen317b));
1715 assert_eq!(ollama_models.len(), 3);
1716
1717 let lmstudio_models = ModelId::models_for_provider(Provider::LmStudio);
1718 assert!(lmstudio_models.contains(&ModelId::LmStudioMetaLlama38BInstruct));
1719 assert!(lmstudio_models.contains(&ModelId::LmStudioMetaLlama318BInstruct));
1720 assert!(lmstudio_models.contains(&ModelId::LmStudioQwen257BInstruct));
1721 assert!(lmstudio_models.contains(&ModelId::LmStudioGemma22BIt));
1722 assert!(lmstudio_models.contains(&ModelId::LmStudioGemma29BIt));
1723 assert!(lmstudio_models.contains(&ModelId::LmStudioPhi31Mini4kInstruct));
1724 assert_eq!(lmstudio_models.len(), 6);
1725 }
1726
1727 #[test]
1728 fn test_fallback_models() {
1729 let fallbacks = ModelId::fallback_models();
1730 assert!(!fallbacks.is_empty());
1731 assert!(fallbacks.contains(&ModelId::Gemini25Pro));
1732 assert!(fallbacks.contains(&ModelId::GPT5));
1733 assert!(fallbacks.contains(&ModelId::ClaudeOpus41));
1734 assert!(fallbacks.contains(&ModelId::ClaudeSonnet45));
1735 assert!(fallbacks.contains(&ModelId::DeepSeekReasoner));
1736 assert!(fallbacks.contains(&ModelId::MoonshotKimiK20905Preview));
1737 assert!(fallbacks.contains(&ModelId::XaiGrok4));
1738 assert!(fallbacks.contains(&ModelId::ZaiGlm46));
1739 assert!(fallbacks.contains(&ModelId::OpenRouterGrokCodeFast1));
1740 }
1741}