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 => models::google::REASONING_MODELS.contains(&model),
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 => model == models::moonshot::KIMI_K2_THINKING,
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 Gemini3ProPreview,
177
178 GPT5,
181 GPT5Codex,
183 GPT5Mini,
185 GPT5Nano,
187 CodexMiniLatest,
189 OpenAIGptOss20b,
191 OpenAIGptOss120b,
193
194 ClaudeOpus45,
197 ClaudeOpus41,
199 ClaudeSonnet45,
201 ClaudeHaiku45,
203 ClaudeSonnet4,
205
206 DeepSeekChat,
209 DeepSeekReasoner,
211
212 XaiGrok4,
215 XaiGrok4Mini,
217 XaiGrok4Code,
219 XaiGrok4CodeLatest,
221 XaiGrok4Vision,
223
224 ZaiGlm46,
227 ZaiGlm45,
229 ZaiGlm45Air,
231 ZaiGlm45X,
233 ZaiGlm45Airx,
235 ZaiGlm45Flash,
237 ZaiGlm432b0414128k,
239
240 MoonshotKimiK2TurboPreview,
243 MoonshotKimiK2Thinking,
245 MoonshotKimiK20905Preview,
247 MoonshotKimiK20711Preview,
249 MoonshotKimiLatest,
251 MoonshotKimiLatest8k,
253 MoonshotKimiLatest32k,
255 MoonshotKimiLatest128k,
257
258 OllamaGptOss20b,
261 OllamaGptOss20bCloud,
263 OllamaGptOss120bCloud,
265 OllamaQwen317b,
267 OllamaDeepseekV31671bCloud,
269
270 OllamaKimiK21tCloud,
272 OllamaQwen3Coder480bCloud,
274 OllamaGlm46Cloud,
276 OllamaMinimaxM2Cloud,
278
279 LmStudioMetaLlama38BInstruct,
282 LmStudioMetaLlama318BInstruct,
284 LmStudioQwen257BInstruct,
286 LmStudioGemma22BIt,
288 LmStudioGemma29BIt,
290 LmStudioPhi31Mini4kInstruct,
292
293 OpenRouterGrokCodeFast1,
296 OpenRouterGrok4Fast,
298 OpenRouterGrok41Fast,
300 OpenRouterGrok4,
302 OpenRouterZaiGlm46,
304 OpenRouterMoonshotaiKimiK20905,
306 OpenRouterMoonshotaiKimiK2Thinking,
308 OpenRouterMoonshotaiKimiK2Free,
310 OpenRouterQwen3Max,
312 OpenRouterQwen3235bA22b,
314 OpenRouterQwen3235bA22bFree,
316 OpenRouterQwen3235bA22b2507,
318 OpenRouterQwen3235bA22bThinking2507,
320 OpenRouterQwen332b,
322 OpenRouterQwen330bA3b,
324 OpenRouterQwen330bA3bFree,
326 OpenRouterQwen330bA3bInstruct2507,
328 OpenRouterQwen330bA3bThinking2507,
330 OpenRouterQwen314b,
332 OpenRouterQwen314bFree,
334 OpenRouterQwen38b,
336 OpenRouterQwen38bFree,
338 OpenRouterQwen34bFree,
340 OpenRouterQwen3Next80bA3bInstruct,
342 OpenRouterQwen3Next80bA3bThinking,
344 OpenRouterQwen3Coder,
346 OpenRouterQwen3CoderFree,
348 OpenRouterQwen3CoderPlus,
350 OpenRouterQwen3CoderFlash,
352 OpenRouterQwen3Coder30bA3bInstruct,
354 OpenRouterDeepSeekV32Exp,
356 OpenRouterDeepSeekChatV31,
358 OpenRouterDeepSeekR1,
360 OpenRouterDeepSeekChatV31Free,
362 OpenRouterNvidiaNemotronNano9bV2Free,
364 OpenRouterOpenAIGptOss120b,
366 OpenRouterOpenAIGptOss20b,
368 OpenRouterOpenAIGptOss20bFree,
370 OpenRouterOpenAIGpt5,
372 OpenRouterOpenAIGpt5Codex,
374 OpenRouterOpenAIGpt5Chat,
376 OpenRouterOpenAIGpt4oSearchPreview,
378 OpenRouterOpenAIGpt4oMiniSearchPreview,
380 OpenRouterOpenAIChatgpt4oLatest,
382 OpenRouterAnthropicClaudeSonnet45,
384 OpenRouterAnthropicClaudeHaiku45,
386 OpenRouterAnthropicClaudeOpus41,
388 OpenRouterMinimaxM2Free,
390}
391
392#[cfg(not(docsrs))]
393pub mod openrouter_generated {
394 include!(concat!(env!("OUT_DIR"), "/openrouter_metadata.rs"));
395}
396
397#[cfg(docsrs)]
398pub mod openrouter_generated {
399 #[derive(Clone, Copy)]
400 pub struct Entry {
401 pub variant: super::ModelId,
402 pub id: &'static str,
403 pub vendor: &'static str,
404 pub display: &'static str,
405 pub description: &'static str,
406 pub efficient: bool,
407 pub top_tier: bool,
408 pub generation: &'static str,
409 pub reasoning: bool,
410 pub tool_call: bool,
411 }
412
413 pub const ENTRIES: &[Entry] = &[];
414
415 #[derive(Clone, Copy)]
416 pub struct VendorModels {
417 pub vendor: &'static str,
418 pub models: &'static [super::ModelId],
419 }
420
421 pub const VENDOR_MODELS: &[VendorModels] = &[];
422
423 pub fn metadata_for(_model: super::ModelId) -> Option<super::OpenRouterMetadata> {
424 None
425 }
426
427 pub fn parse_model(_value: &str) -> Option<super::ModelId> {
428 None
429 }
430
431 pub fn vendor_groups() -> &'static [VendorModels] {
432 VENDOR_MODELS
433 }
434}
435
436impl ModelId {
437 fn openrouter_metadata(&self) -> Option<OpenRouterMetadata> {
438 #[cfg(not(docsrs))]
439 {
440 openrouter_generated::metadata_for(*self)
441 }
442 #[cfg(docsrs)]
443 {
444 None
445 }
446 }
447
448 fn parse_openrouter_model(value: &str) -> Option<Self> {
449 #[cfg(not(docsrs))]
450 {
451 openrouter_generated::parse_model(value)
452 }
453 #[cfg(docsrs)]
454 {
455 None
456 }
457 }
458
459 fn openrouter_vendor_groups() -> Vec<(&'static str, &'static [Self])> {
460 #[cfg(not(docsrs))]
461 {
462 openrouter_generated::vendor_groups()
463 .iter()
464 .map(|group| (group.vendor, group.models))
465 .collect()
466 }
467 #[cfg(docsrs)]
468 {
469 Vec::new()
470 }
471 }
472
473 fn openrouter_models() -> Vec<Self> {
474 Self::openrouter_vendor_groups()
475 .into_iter()
476 .flat_map(|(_, models)| models.iter().copied())
477 .collect()
478 }
479
480 pub fn as_str(&self) -> &'static str {
483 use crate::constants::models;
484 if let Some(meta) = self.openrouter_metadata() {
485 return meta.id;
486 }
487 match self {
488 ModelId::Gemini25FlashPreview => models::GEMINI_2_5_FLASH_PREVIEW,
490 ModelId::Gemini25Flash => models::GEMINI_2_5_FLASH,
491 ModelId::Gemini25FlashLite => models::GEMINI_2_5_FLASH_LITE,
492 ModelId::Gemini25Pro => models::GEMINI_2_5_PRO,
493 ModelId::Gemini3ProPreview => models::GEMINI_3_PRO_PREVIEW,
494 ModelId::GPT5 => models::GPT_5,
496 ModelId::GPT5Codex => models::GPT_5_CODEX,
497 ModelId::GPT5Mini => models::GPT_5_MINI,
498 ModelId::GPT5Nano => models::GPT_5_NANO,
499 ModelId::CodexMiniLatest => models::CODEX_MINI_LATEST,
500 ModelId::ClaudeOpus45 => models::CLAUDE_OPUS_4_5,
502 ModelId::ClaudeOpus41 => models::CLAUDE_OPUS_4_1,
503 ModelId::ClaudeSonnet45 => models::CLAUDE_SONNET_4_5,
504 ModelId::ClaudeHaiku45 => models::CLAUDE_HAIKU_4_5,
505 ModelId::ClaudeSonnet4 => models::CLAUDE_SONNET_4_5_20250929,
506 ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
508 ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
509 ModelId::XaiGrok4 => models::xai::GROK_4,
511 ModelId::XaiGrok4Mini => models::xai::GROK_4_MINI,
512 ModelId::XaiGrok4Code => models::xai::GROK_4_CODE,
513 ModelId::XaiGrok4CodeLatest => models::xai::GROK_4_CODE_LATEST,
514 ModelId::XaiGrok4Vision => models::xai::GROK_4_VISION,
515 ModelId::ZaiGlm46 => models::zai::GLM_4_6,
517 ModelId::ZaiGlm45 => models::zai::GLM_4_5,
518 ModelId::ZaiGlm45Air => models::zai::GLM_4_5_AIR,
519 ModelId::ZaiGlm45X => models::zai::GLM_4_5_X,
520 ModelId::ZaiGlm45Airx => models::zai::GLM_4_5_AIRX,
521 ModelId::ZaiGlm45Flash => models::zai::GLM_4_5_FLASH,
522 ModelId::ZaiGlm432b0414128k => models::zai::GLM_4_32B_0414_128K,
523 ModelId::MoonshotKimiK2TurboPreview => models::MOONSHOT_KIMI_K2_TURBO_PREVIEW,
525 ModelId::MoonshotKimiK2Thinking => models::MOONSHOT_KIMI_K2_THINKING,
526 ModelId::MoonshotKimiK20905Preview => models::MOONSHOT_KIMI_K2_0905_PREVIEW,
527 ModelId::MoonshotKimiK20711Preview => models::MOONSHOT_KIMI_K2_0711_PREVIEW,
528 ModelId::MoonshotKimiLatest => models::MOONSHOT_KIMI_LATEST,
529 ModelId::MoonshotKimiLatest8k => models::MOONSHOT_KIMI_LATEST_8K,
530 ModelId::MoonshotKimiLatest32k => models::MOONSHOT_KIMI_LATEST_32K,
531 ModelId::MoonshotKimiLatest128k => models::MOONSHOT_KIMI_LATEST_128K,
532 ModelId::OllamaGptOss20b => models::ollama::GPT_OSS_20B,
534 ModelId::OllamaGptOss20bCloud => models::ollama::GPT_OSS_20B_CLOUD,
535 ModelId::OllamaGptOss120bCloud => models::ollama::GPT_OSS_120B_CLOUD,
536 ModelId::OllamaQwen317b => models::ollama::QWEN3_1_7B,
537 ModelId::OllamaDeepseekV31671bCloud => models::ollama::DEEPSEEK_V31_671B_CLOUD,
538
539 ModelId::OllamaKimiK21tCloud => models::ollama::KIMI_K2_1T_CLOUD,
540 ModelId::OllamaQwen3Coder480bCloud => models::ollama::QWEN3_CODER_480B_CLOUD,
541 ModelId::OllamaGlm46Cloud => models::ollama::GLM_46_CLOUD,
542 ModelId::OllamaMinimaxM2Cloud => models::ollama::MINIMAX_M2_CLOUD,
543 ModelId::LmStudioMetaLlama38BInstruct => models::lmstudio::META_LLAMA_3_8B_INSTRUCT,
545 ModelId::LmStudioMetaLlama318BInstruct => models::lmstudio::META_LLAMA_31_8B_INSTRUCT,
546 ModelId::LmStudioQwen257BInstruct => models::lmstudio::QWEN25_7B_INSTRUCT,
547 ModelId::LmStudioGemma22BIt => models::lmstudio::GEMMA_2_2B_IT,
548 ModelId::LmStudioGemma29BIt => models::lmstudio::GEMMA_2_9B_IT,
549 ModelId::LmStudioPhi31Mini4kInstruct => models::lmstudio::PHI_31_MINI_4K_INSTRUCT,
550 _ => unreachable!(),
552 }
553 }
554
555 pub fn provider(&self) -> Provider {
557 if self.openrouter_metadata().is_some() {
558 return Provider::OpenRouter;
559 }
560 match self {
561 ModelId::Gemini25FlashPreview
562 | ModelId::Gemini25Flash
563 | ModelId::Gemini25FlashLite
564 | ModelId::Gemini25Pro
565 | ModelId::Gemini3ProPreview => Provider::Gemini,
566 ModelId::GPT5
567 | ModelId::GPT5Codex
568 | ModelId::GPT5Mini
569 | ModelId::GPT5Nano
570 | ModelId::CodexMiniLatest
571 | ModelId::OpenAIGptOss20b
572 | ModelId::OpenAIGptOss120b => Provider::OpenAI,
573 ModelId::ClaudeOpus45
574 | ModelId::ClaudeOpus41
575 | ModelId::ClaudeSonnet45
576 | ModelId::ClaudeHaiku45
577 | ModelId::ClaudeSonnet4 => Provider::Anthropic,
578 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => Provider::DeepSeek,
579 ModelId::XaiGrok4
580 | ModelId::XaiGrok4Mini
581 | ModelId::XaiGrok4Code
582 | ModelId::XaiGrok4CodeLatest
583 | ModelId::XaiGrok4Vision => Provider::XAI,
584 ModelId::ZaiGlm46
585 | ModelId::ZaiGlm45
586 | ModelId::ZaiGlm45Air
587 | ModelId::ZaiGlm45X
588 | ModelId::ZaiGlm45Airx
589 | ModelId::ZaiGlm45Flash
590 | ModelId::ZaiGlm432b0414128k => Provider::ZAI,
591 ModelId::MoonshotKimiK2TurboPreview
592 | ModelId::MoonshotKimiK2Thinking
593 | ModelId::MoonshotKimiK20905Preview
594 | ModelId::MoonshotKimiK20711Preview
595 | ModelId::MoonshotKimiLatest
596 | ModelId::MoonshotKimiLatest8k
597 | ModelId::MoonshotKimiLatest32k
598 | ModelId::MoonshotKimiLatest128k => Provider::Moonshot,
599 ModelId::OllamaGptOss20b
600 | ModelId::OllamaGptOss20bCloud
601 | ModelId::OllamaGptOss120bCloud
602 | ModelId::OllamaQwen317b
603 | ModelId::OllamaDeepseekV31671bCloud
604 | ModelId::OllamaKimiK21tCloud
605 | ModelId::OllamaQwen3Coder480bCloud
606 | ModelId::OllamaGlm46Cloud
607 | ModelId::OllamaMinimaxM2Cloud => Provider::Ollama,
608 ModelId::LmStudioMetaLlama38BInstruct
609 | ModelId::LmStudioMetaLlama318BInstruct
610 | ModelId::LmStudioQwen257BInstruct
611 | ModelId::LmStudioGemma22BIt
612 | ModelId::LmStudioGemma29BIt
613 | ModelId::LmStudioPhi31Mini4kInstruct => Provider::LmStudio,
614 _ => unreachable!(),
615 }
616 }
617
618 pub fn supports_reasoning_effort(&self) -> bool {
620 self.provider().supports_reasoning_effort(self.as_str())
621 }
622
623 pub fn display_name(&self) -> &'static str {
625 if let Some(meta) = self.openrouter_metadata() {
626 return meta.display;
627 }
628 match self {
629 ModelId::Gemini25FlashPreview => "Gemini 2.5 Flash Preview",
631 ModelId::Gemini25Flash => "Gemini 2.5 Flash",
632 ModelId::Gemini25FlashLite => "Gemini 2.5 Flash Lite",
633 ModelId::Gemini25Pro => "Gemini 2.5 Pro",
634 ModelId::Gemini3ProPreview => "Gemini 3 Pro Preview",
635 ModelId::GPT5 => "GPT-5",
637 ModelId::GPT5Codex => "GPT-5 Codex",
638 ModelId::GPT5Mini => "GPT-5 Mini",
639 ModelId::GPT5Nano => "GPT-5 Nano",
640 ModelId::CodexMiniLatest => "Codex Mini Latest",
641 ModelId::ClaudeOpus45 => "Claude Opus 4.5",
643 ModelId::ClaudeOpus41 => "Claude Opus 4.1",
644 ModelId::ClaudeSonnet45 => "Claude Sonnet 4.5",
645 ModelId::ClaudeHaiku45 => "Claude Haiku 4.5",
646 ModelId::ClaudeSonnet4 => "Claude Sonnet 4",
647 ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
649 ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
650 ModelId::XaiGrok4 => "Grok-4",
652 ModelId::XaiGrok4Mini => "Grok-4 Mini",
653 ModelId::XaiGrok4Code => "Grok-4 Code",
654 ModelId::XaiGrok4CodeLatest => "Grok-4 Code Latest",
655 ModelId::XaiGrok4Vision => "Grok-4 Vision",
656 ModelId::ZaiGlm46 => "GLM 4.6",
658 ModelId::ZaiGlm45 => "GLM 4.5",
659 ModelId::ZaiGlm45Air => "GLM 4.5 Air",
660 ModelId::ZaiGlm45X => "GLM 4.5 X",
661 ModelId::ZaiGlm45Airx => "GLM 4.5 AirX",
662 ModelId::ZaiGlm45Flash => "GLM 4.5 Flash",
663 ModelId::ZaiGlm432b0414128k => "GLM 4 32B 0414 128K",
664 ModelId::MoonshotKimiK2TurboPreview => "Kimi K2 Turbo Preview",
666 ModelId::MoonshotKimiK2Thinking => "Kimi K2 Thinking",
667 ModelId::MoonshotKimiK20905Preview => "Kimi K2 0905 Preview",
668 ModelId::MoonshotKimiK20711Preview => "Kimi K2 0711 Preview",
669 ModelId::MoonshotKimiLatest => "Kimi Latest (auto-tier)",
670 ModelId::MoonshotKimiLatest8k => "Kimi Latest 8K",
671 ModelId::MoonshotKimiLatest32k => "Kimi Latest 32K",
672 ModelId::MoonshotKimiLatest128k => "Kimi Latest 128K",
673 ModelId::OllamaGptOss20b => "GPT-OSS 20B (local)",
675 ModelId::OllamaGptOss20bCloud => "GPT-OSS 20B (cloud)",
676 ModelId::OllamaGptOss120bCloud => "GPT-OSS 120B (cloud)",
677 ModelId::OllamaQwen317b => "Qwen3 1.7B (local)",
678 ModelId::OllamaDeepseekV31671bCloud => "DeepSeek V3.1 671B (cloud)",
679
680 ModelId::OllamaKimiK21tCloud => "Kimi K2 1T (cloud)",
681 ModelId::OllamaQwen3Coder480bCloud => "Qwen3 Coder 480B (cloud)",
682 ModelId::OllamaGlm46Cloud => "GLM-4.6 (cloud)",
683 ModelId::OllamaMinimaxM2Cloud => "MiniMax-M2 (cloud)",
684 ModelId::LmStudioMetaLlama38BInstruct => "Meta Llama 3 8B (LM Studio)",
685 ModelId::LmStudioMetaLlama318BInstruct => "Meta Llama 3.1 8B (LM Studio)",
686 ModelId::LmStudioQwen257BInstruct => "Qwen2.5 7B (LM Studio)",
687 ModelId::LmStudioGemma22BIt => "Gemma 2 2B (LM Studio)",
688 ModelId::LmStudioGemma29BIt => "Gemma 2 9B (LM Studio)",
689 ModelId::LmStudioPhi31Mini4kInstruct => "Phi-3.1 Mini 4K (LM Studio)",
690 _ => unreachable!(),
692 }
693 }
694
695 pub fn description(&self) -> &'static str {
697 if let Some(meta) = self.openrouter_metadata() {
698 return meta.description;
699 }
700 match self {
701 ModelId::Gemini25FlashPreview => {
703 "Latest fast Gemini model with advanced multimodal capabilities"
704 }
705 ModelId::Gemini25Flash => {
706 "Legacy alias for Gemini 2.5 Flash Preview (same capabilities)"
707 }
708 ModelId::Gemini25FlashLite => {
709 "Legacy alias for Gemini 2.5 Flash Preview optimized for efficiency"
710 }
711 ModelId::Gemini25Pro => "Latest most capable Gemini model with reasoning",
712 ModelId::Gemini3ProPreview => {
713 "Preview of next-generation Gemini 3 Pro model with advanced reasoning and capabilities"
714 }
715 ModelId::GPT5 => "Latest most capable OpenAI model with advanced reasoning",
717 ModelId::GPT5Codex => {
718 "Code-focused GPT-5 variant optimized for tool calling and structured outputs"
719 }
720 ModelId::GPT5Mini => "Latest efficient OpenAI model, great for most tasks",
721 ModelId::GPT5Nano => "Latest most cost-effective OpenAI model",
722 ModelId::CodexMiniLatest => "Latest Codex model optimized for code generation",
723 ModelId::OpenAIGptOss20b => {
724 "OpenAI's open-source 20B parameter GPT-OSS model using harmony tokenization"
725 }
726 ModelId::OpenAIGptOss120b => {
727 "OpenAI's open-source 120B parameter GPT-OSS model using harmony tokenization"
728 }
729 ModelId::ClaudeOpus45 => {
731 "Latest flagship Anthropic model with exceptional reasoning capabilities"
732 }
733 ModelId::ClaudeOpus41 => {
734 "Previous most capable Anthropic model with advanced reasoning"
735 }
736 ModelId::ClaudeSonnet45 => "Latest balanced Anthropic model for general tasks",
737 ModelId::ClaudeHaiku45 => {
738 "Latest efficient Anthropic model optimized for low-latency agent workflows"
739 }
740 ModelId::ClaudeSonnet4 => {
741 "Previous balanced Anthropic model maintained for compatibility"
742 }
743 ModelId::DeepSeekChat => {
745 "DeepSeek V3.2-Exp non-thinking mode optimized for fast coding responses"
746 }
747 ModelId::DeepSeekReasoner => {
748 "DeepSeek V3.2-Exp thinking mode with structured reasoning output"
749 }
750 ModelId::XaiGrok4 => "Flagship Grok 4 model with long context and tool use",
752 ModelId::XaiGrok4Mini => "Efficient Grok 4 Mini tuned for low latency",
753 ModelId::XaiGrok4Code => "Code-specialized Grok 4 deployment with tool support",
754 ModelId::XaiGrok4CodeLatest => {
755 "Latest Grok 4 code model offering enhanced reasoning traces"
756 }
757 ModelId::XaiGrok4Vision => "Multimodal Grok 4 model with image understanding",
758 ModelId::ZaiGlm46 => {
760 "Latest Z.AI GLM flagship with long-context reasoning and coding strengths"
761 }
762 ModelId::ZaiGlm45 => "Balanced GLM 4.5 release for general assistant tasks",
763 ModelId::ZaiGlm45Air => "Efficient GLM 4.5 Air variant tuned for lower latency",
764 ModelId::ZaiGlm45X => "Enhanced GLM 4.5 X variant with improved reasoning",
765 ModelId::ZaiGlm45Airx => "Hybrid GLM 4.5 AirX variant blending efficiency with quality",
766 ModelId::ZaiGlm45Flash => "Low-latency GLM 4.5 Flash optimized for responsiveness",
767 ModelId::ZaiGlm432b0414128k => {
768 "Legacy GLM 4 32B deployment offering extended 128K context window"
769 }
770 ModelId::MoonshotKimiK2TurboPreview => {
772 "Recommended high-speed Kimi K2 turbo variant with 256K context and 60+ tok/s output"
773 }
774 ModelId::MoonshotKimiK2Thinking => {
775 "Moonshot reasoning-tier Kimi K2 release optimized for deliberate, multi-step agentic reasoning"
776 }
777 ModelId::MoonshotKimiK20905Preview => {
778 "Latest Kimi K2 0905 flagship with enhanced agentic coding, 256K context, and richer tool support"
779 }
780 ModelId::MoonshotKimiK20711Preview => {
781 "Kimi K2 0711 preview tuned for balanced cost and capability with 131K context"
782 }
783 ModelId::MoonshotKimiLatest => {
784 "Auto-tier alias that selects the right Kimi Latest vision tier (8K/32K/128K) with context caching"
785 }
786 ModelId::MoonshotKimiLatest8k => {
787 "Kimi Latest 8K vision tier for short tasks with automatic context caching"
788 }
789 ModelId::MoonshotKimiLatest32k => {
790 "Kimi Latest 32K vision tier blending longer context with latest assistant features"
791 }
792 ModelId::MoonshotKimiLatest128k => {
793 "Kimi Latest 128K flagship vision tier delivering maximum context and newest capabilities"
794 }
795 ModelId::OllamaGptOss20b => {
796 "Local GPT-OSS 20B deployment served via Ollama with no external API dependency"
797 }
798 ModelId::OllamaGptOss20bCloud => {
799 "Cloud-hosted GPT-OSS 20B accessed through Ollama Cloud for efficient reasoning tasks"
800 }
801 ModelId::OllamaGptOss120bCloud => {
802 "Cloud-hosted GPT-OSS 120B accessed through Ollama Cloud for larger reasoning tasks"
803 }
804 ModelId::OllamaQwen317b => {
805 "Qwen3 1.7B served locally through Ollama without external API requirements"
806 }
807 ModelId::OllamaDeepseekV31671bCloud => {
808 "Cloud-hosted DeepSeek V3.1 671B model accessed through Ollama Cloud for advanced reasoning"
809 }
810
811 ModelId::OllamaKimiK21tCloud => {
812 "Cloud-hosted Kimi K2 1T model accessed through Ollama Cloud for high-capacity reasoning tasks"
813 }
814 ModelId::OllamaQwen3Coder480bCloud => {
815 "Cloud-hosted Qwen3 Coder 480B model accessed through Ollama Cloud for coding tasks"
816 }
817 ModelId::OllamaGlm46Cloud => {
818 "Cloud-hosted GLM-4.6 model accessed through Ollama Cloud for reasoning and coding"
819 }
820 ModelId::OllamaMinimaxM2Cloud => {
821 "Cloud-hosted MiniMax-M2 model accessed through Ollama Cloud for reasoning tasks"
822 }
823 ModelId::LmStudioMetaLlama38BInstruct => {
824 "Meta Llama 3 8B running through LM Studio's local OpenAI-compatible server"
825 }
826 ModelId::LmStudioMetaLlama318BInstruct => {
827 "Meta Llama 3.1 8B running through LM Studio's local OpenAI-compatible server"
828 }
829 ModelId::LmStudioQwen257BInstruct => {
830 "Qwen2.5 7B hosted in LM Studio for local experimentation and coding tasks"
831 }
832 ModelId::LmStudioGemma22BIt => {
833 "Gemma 2 2B IT deployed via LM Studio for lightweight on-device assistance"
834 }
835 ModelId::LmStudioGemma29BIt => {
836 "Gemma 2 9B IT served locally via LM Studio when you need additional capacity"
837 }
838 ModelId::LmStudioPhi31Mini4kInstruct => {
839 "Phi-3.1 Mini 4K hosted in LM Studio for compact reasoning and experimentation"
840 }
841 _ => unreachable!(),
842 }
843 }
844
845 pub fn openrouter_vendor(&self) -> Option<&'static str> {
847 self.openrouter_metadata().map(|meta| meta.vendor)
848 }
849
850 pub fn all_models() -> Vec<ModelId> {
852 let mut models = vec![
853 ModelId::Gemini25FlashPreview,
855 ModelId::Gemini25Flash,
856 ModelId::Gemini25FlashLite,
857 ModelId::Gemini25Pro,
858 ModelId::GPT5,
860 ModelId::GPT5Codex,
861 ModelId::GPT5Mini,
862 ModelId::GPT5Nano,
863 ModelId::CodexMiniLatest,
864 ModelId::ClaudeOpus45,
866 ModelId::ClaudeOpus41,
867 ModelId::ClaudeSonnet45,
868 ModelId::ClaudeHaiku45,
869 ModelId::ClaudeSonnet4,
870 ModelId::DeepSeekChat,
872 ModelId::DeepSeekReasoner,
873 ModelId::XaiGrok4,
875 ModelId::XaiGrok4Mini,
876 ModelId::XaiGrok4Code,
877 ModelId::XaiGrok4CodeLatest,
878 ModelId::XaiGrok4Vision,
879 ModelId::ZaiGlm46,
881 ModelId::ZaiGlm45,
882 ModelId::ZaiGlm45Air,
883 ModelId::ZaiGlm45X,
884 ModelId::ZaiGlm45Airx,
885 ModelId::ZaiGlm45Flash,
886 ModelId::ZaiGlm432b0414128k,
887 ModelId::MoonshotKimiK2TurboPreview,
889 ModelId::MoonshotKimiK2Thinking,
890 ModelId::MoonshotKimiK20905Preview,
891 ModelId::MoonshotKimiK20711Preview,
892 ModelId::MoonshotKimiLatest,
893 ModelId::MoonshotKimiLatest8k,
894 ModelId::MoonshotKimiLatest32k,
895 ModelId::MoonshotKimiLatest128k,
896 ModelId::OllamaGptOss20b,
898 ModelId::OllamaGptOss20bCloud,
899 ModelId::OllamaGptOss120bCloud,
900 ModelId::OllamaQwen317b,
901 ModelId::OllamaDeepseekV31671bCloud,
902 ModelId::OllamaKimiK21tCloud,
903 ModelId::OllamaQwen3Coder480bCloud,
904 ModelId::OllamaGlm46Cloud,
905 ModelId::OllamaMinimaxM2Cloud,
906 ModelId::LmStudioMetaLlama38BInstruct,
908 ModelId::LmStudioMetaLlama318BInstruct,
909 ModelId::LmStudioQwen257BInstruct,
910 ModelId::LmStudioGemma22BIt,
911 ModelId::LmStudioGemma29BIt,
912 ModelId::LmStudioPhi31Mini4kInstruct,
913 ];
914 models.extend(Self::openrouter_models());
915 models
916 }
917
918 pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
920 Self::all_models()
921 .into_iter()
922 .filter(|model| model.provider() == provider)
923 .collect()
924 }
925
926 pub fn fallback_models() -> Vec<ModelId> {
928 vec![
929 ModelId::Gemini25FlashPreview,
930 ModelId::Gemini25Pro,
931 ModelId::GPT5,
932 ModelId::OpenAIGptOss20b,
933 ModelId::ClaudeOpus45,
934 ModelId::ClaudeOpus41,
935 ModelId::ClaudeSonnet45,
936 ModelId::DeepSeekReasoner,
937 ModelId::MoonshotKimiK20905Preview,
938 ModelId::XaiGrok4,
939 ModelId::ZaiGlm46,
940 ModelId::OpenRouterGrokCodeFast1,
941 ]
942 }
943
944 pub fn default_orchestrator() -> Self {
946 ModelId::Gemini25Pro
947 }
948
949 pub fn default_subagent() -> Self {
951 ModelId::Gemini25FlashPreview
952 }
953
954 pub fn default_orchestrator_for_provider(provider: Provider) -> Self {
956 match provider {
957 Provider::Gemini => ModelId::Gemini25Pro,
958 Provider::OpenAI => ModelId::GPT5,
959 Provider::Anthropic => ModelId::ClaudeOpus45,
960 Provider::DeepSeek => ModelId::DeepSeekReasoner,
961 Provider::Moonshot => ModelId::MoonshotKimiK20905Preview,
962 Provider::XAI => ModelId::XaiGrok4,
963 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
964 Provider::Ollama => ModelId::OllamaGptOss20b,
965 Provider::LmStudio => ModelId::LmStudioMetaLlama318BInstruct,
966 Provider::ZAI => ModelId::ZaiGlm46,
967 }
968 }
969
970 pub fn default_subagent_for_provider(provider: Provider) -> Self {
972 match provider {
973 Provider::Gemini => ModelId::Gemini25FlashPreview,
974 Provider::OpenAI => ModelId::GPT5Mini,
975 Provider::Anthropic => ModelId::ClaudeSonnet45,
976 Provider::DeepSeek => ModelId::DeepSeekChat,
977 Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
978 Provider::XAI => ModelId::XaiGrok4Code,
979 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
980 Provider::Ollama => ModelId::OllamaQwen317b,
981 Provider::LmStudio => ModelId::LmStudioQwen257BInstruct,
982 Provider::ZAI => ModelId::ZaiGlm45Flash,
983 }
984 }
985
986 pub fn default_single_for_provider(provider: Provider) -> Self {
988 match provider {
989 Provider::Gemini => ModelId::Gemini25FlashPreview,
990 Provider::OpenAI => ModelId::GPT5,
991 Provider::Anthropic => ModelId::ClaudeOpus45,
992 Provider::DeepSeek => ModelId::DeepSeekReasoner,
993 Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
994 Provider::XAI => ModelId::XaiGrok4,
995 Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
996 Provider::Ollama => ModelId::OllamaGptOss20b,
997 Provider::LmStudio => ModelId::LmStudioMetaLlama318BInstruct,
998 Provider::ZAI => ModelId::ZaiGlm46,
999 }
1000 }
1001
1002 pub fn is_flash_variant(&self) -> bool {
1004 matches!(
1005 self,
1006 ModelId::Gemini25FlashPreview
1007 | ModelId::Gemini25Flash
1008 | ModelId::Gemini25FlashLite
1009 | ModelId::ZaiGlm45Flash
1010 | ModelId::MoonshotKimiK2TurboPreview
1011 | ModelId::MoonshotKimiLatest8k
1012 )
1013 }
1014
1015 pub fn is_pro_variant(&self) -> bool {
1017 matches!(
1018 self,
1019 ModelId::Gemini25Pro
1020 | ModelId::GPT5
1021 | ModelId::GPT5Codex
1022 | ModelId::ClaudeOpus41
1023 | ModelId::DeepSeekReasoner
1024 | ModelId::XaiGrok4
1025 | ModelId::ZaiGlm46
1026 | ModelId::MoonshotKimiK2Thinking
1027 | ModelId::MoonshotKimiK20905Preview
1028 | ModelId::MoonshotKimiLatest128k
1029 )
1030 }
1031
1032 pub fn is_efficient_variant(&self) -> bool {
1034 if let Some(meta) = self.openrouter_metadata() {
1035 return meta.efficient;
1036 }
1037 matches!(
1038 self,
1039 ModelId::Gemini25FlashPreview
1040 | ModelId::Gemini25Flash
1041 | ModelId::Gemini25FlashLite
1042 | ModelId::GPT5Mini
1043 | ModelId::GPT5Nano
1044 | ModelId::ClaudeHaiku45
1045 | ModelId::DeepSeekChat
1046 | ModelId::XaiGrok4Code
1047 | ModelId::ZaiGlm45Air
1048 | ModelId::ZaiGlm45Airx
1049 | ModelId::ZaiGlm45Flash
1050 | ModelId::MoonshotKimiK2TurboPreview
1051 | ModelId::MoonshotKimiLatest8k
1052 )
1053 }
1054
1055 pub fn is_top_tier(&self) -> bool {
1057 if let Some(meta) = self.openrouter_metadata() {
1058 return meta.top_tier;
1059 }
1060 matches!(
1061 self,
1062 ModelId::Gemini25Pro
1063 | ModelId::GPT5
1064 | ModelId::GPT5Codex
1065 | ModelId::ClaudeOpus41
1066 | ModelId::ClaudeSonnet45
1067 | ModelId::ClaudeSonnet4
1068 | ModelId::DeepSeekReasoner
1069 | ModelId::XaiGrok4
1070 | ModelId::XaiGrok4CodeLatest
1071 | ModelId::ZaiGlm46
1072 | ModelId::MoonshotKimiK2Thinking
1073 | ModelId::MoonshotKimiK20905Preview
1074 | ModelId::MoonshotKimiLatest128k
1075 )
1076 }
1077
1078 pub fn is_reasoning_variant(&self) -> bool {
1080 if matches!(self, ModelId::MoonshotKimiK2Thinking) {
1081 return true;
1082 }
1083 if let Some(meta) = self.openrouter_metadata() {
1084 return meta.reasoning;
1085 }
1086 self.provider().supports_reasoning_effort(self.as_str())
1087 }
1088
1089 pub fn supports_tool_calls(&self) -> bool {
1091 if let Some(meta) = self.openrouter_metadata() {
1092 return meta.tool_call;
1093 }
1094 true
1095 }
1096
1097 pub fn generation(&self) -> &'static str {
1099 if let Some(meta) = self.openrouter_metadata() {
1100 return meta.generation;
1101 }
1102 match self {
1103 ModelId::Gemini25FlashPreview
1105 | ModelId::Gemini25Flash
1106 | ModelId::Gemini25FlashLite
1107 | ModelId::Gemini25Pro => "2.5",
1108 ModelId::Gemini3ProPreview => "3",
1109 ModelId::GPT5
1111 | ModelId::GPT5Codex
1112 | ModelId::GPT5Mini
1113 | ModelId::GPT5Nano
1114 | ModelId::CodexMiniLatest => "5",
1115 ModelId::ClaudeOpus45 => "4.5",
1117 ModelId::ClaudeSonnet45 | ModelId::ClaudeHaiku45 => "4.5",
1118 ModelId::ClaudeSonnet4 => "4",
1119 ModelId::ClaudeOpus41 => "4.1",
1120 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
1122 ModelId::XaiGrok4
1124 | ModelId::XaiGrok4Mini
1125 | ModelId::XaiGrok4Code
1126 | ModelId::XaiGrok4CodeLatest
1127 | ModelId::XaiGrok4Vision => "4",
1128 ModelId::ZaiGlm46 => "4.6",
1130 ModelId::ZaiGlm45
1131 | ModelId::ZaiGlm45Air
1132 | ModelId::ZaiGlm45X
1133 | ModelId::ZaiGlm45Airx
1134 | ModelId::ZaiGlm45Flash => "4.5",
1135 ModelId::ZaiGlm432b0414128k => "4-32B",
1136 ModelId::MoonshotKimiK2TurboPreview
1138 | ModelId::MoonshotKimiK20905Preview
1139 | ModelId::MoonshotKimiK20711Preview => "k2",
1140 ModelId::MoonshotKimiK2Thinking => "k2-thinking",
1141 ModelId::MoonshotKimiLatest
1142 | ModelId::MoonshotKimiLatest8k
1143 | ModelId::MoonshotKimiLatest32k
1144 | ModelId::MoonshotKimiLatest128k => "latest",
1145 ModelId::OllamaGptOss20b => "oss",
1146 ModelId::OllamaGptOss20bCloud => "oss-cloud",
1147 ModelId::OllamaGptOss120bCloud => "oss-cloud",
1148 ModelId::OllamaQwen317b => "oss",
1149 ModelId::OllamaDeepseekV31671bCloud => "deepseek-cloud",
1150 ModelId::OllamaKimiK21tCloud => "kimi-k2-cloud",
1151 ModelId::OllamaQwen3Coder480bCloud => "qwen3-coder-cloud",
1152 ModelId::OllamaGlm46Cloud => "glm-cloud",
1153 ModelId::OllamaMinimaxM2Cloud => "minimax-cloud",
1154 ModelId::LmStudioMetaLlama38BInstruct => "meta-llama-3",
1155 ModelId::LmStudioMetaLlama318BInstruct => "meta-llama-3.1",
1156 ModelId::LmStudioQwen257BInstruct => "qwen2.5",
1157 ModelId::LmStudioGemma22BIt => "gemma-2",
1158 ModelId::LmStudioGemma29BIt => "gemma-2",
1159 ModelId::LmStudioPhi31Mini4kInstruct => "phi-3.1",
1160 _ => unreachable!(),
1161 }
1162 }
1163}
1164
1165impl fmt::Display for ModelId {
1166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1167 write!(f, "{}", self.as_str())
1168 }
1169}
1170
1171impl FromStr for ModelId {
1172 type Err = ModelParseError;
1173
1174 fn from_str(s: &str) -> Result<Self, Self::Err> {
1175 use crate::constants::models;
1176 match s {
1177 s if s == models::GEMINI_2_5_FLASH_PREVIEW => Ok(ModelId::Gemini25FlashPreview),
1179 s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
1180 s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
1181 s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
1182 s if s == models::GEMINI_3_PRO_PREVIEW => Ok(ModelId::Gemini3ProPreview),
1183 s if s == models::GPT_5 => Ok(ModelId::GPT5),
1185 s if s == models::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
1186 s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
1187 s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
1188 s if s == models::CODEX_MINI_LATEST => Ok(ModelId::CodexMiniLatest),
1189 s if s == models::openai::GPT_OSS_20B => Ok(ModelId::OpenAIGptOss20b),
1190 s if s == models::openai::GPT_OSS_120B => Ok(ModelId::OpenAIGptOss120b),
1191 s if s == models::CLAUDE_OPUS_4_5 => Ok(ModelId::ClaudeOpus45),
1193 s if s == models::CLAUDE_OPUS_4_1 => Ok(ModelId::ClaudeOpus41),
1194 s if s == models::CLAUDE_SONNET_4_5 => Ok(ModelId::ClaudeSonnet45),
1195 s if s == models::CLAUDE_HAIKU_4_5 => Ok(ModelId::ClaudeHaiku45),
1196 s if s == models::CLAUDE_SONNET_4_5_20250929 => Ok(ModelId::ClaudeSonnet4),
1197 s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
1199 s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
1200 s if s == models::xai::GROK_4 => Ok(ModelId::XaiGrok4),
1202 s if s == models::xai::GROK_4_MINI => Ok(ModelId::XaiGrok4Mini),
1203 s if s == models::xai::GROK_4_CODE => Ok(ModelId::XaiGrok4Code),
1204 s if s == models::xai::GROK_4_CODE_LATEST => Ok(ModelId::XaiGrok4CodeLatest),
1205 s if s == models::xai::GROK_4_VISION => Ok(ModelId::XaiGrok4Vision),
1206 s if s == models::zai::GLM_4_6 => Ok(ModelId::ZaiGlm46),
1208 s if s == models::zai::GLM_4_5 => Ok(ModelId::ZaiGlm45),
1209 s if s == models::zai::GLM_4_5_AIR => Ok(ModelId::ZaiGlm45Air),
1210 s if s == models::zai::GLM_4_5_X => Ok(ModelId::ZaiGlm45X),
1211 s if s == models::zai::GLM_4_5_AIRX => Ok(ModelId::ZaiGlm45Airx),
1212 s if s == models::zai::GLM_4_5_FLASH => Ok(ModelId::ZaiGlm45Flash),
1213 s if s == models::zai::GLM_4_32B_0414_128K => Ok(ModelId::ZaiGlm432b0414128k),
1214 s if s == models::MOONSHOT_KIMI_K2_TURBO_PREVIEW => {
1216 Ok(ModelId::MoonshotKimiK2TurboPreview)
1217 }
1218 s if s == models::MOONSHOT_KIMI_K2_THINKING => Ok(ModelId::MoonshotKimiK2Thinking),
1219 s if s == models::MOONSHOT_KIMI_K2_0905_PREVIEW => {
1220 Ok(ModelId::MoonshotKimiK20905Preview)
1221 }
1222 s if s == models::MOONSHOT_KIMI_K2_0711_PREVIEW => {
1223 Ok(ModelId::MoonshotKimiK20711Preview)
1224 }
1225 s if s == models::MOONSHOT_KIMI_LATEST => Ok(ModelId::MoonshotKimiLatest),
1226 s if s == models::MOONSHOT_KIMI_LATEST_8K => Ok(ModelId::MoonshotKimiLatest8k),
1227 s if s == models::MOONSHOT_KIMI_LATEST_32K => Ok(ModelId::MoonshotKimiLatest32k),
1228 s if s == models::MOONSHOT_KIMI_LATEST_128K => Ok(ModelId::MoonshotKimiLatest128k),
1229 s if s == models::ollama::GPT_OSS_20B => Ok(ModelId::OllamaGptOss20b),
1230 s if s == models::ollama::GPT_OSS_20B_CLOUD => Ok(ModelId::OllamaGptOss20bCloud),
1231 s if s == models::ollama::GPT_OSS_120B_CLOUD => Ok(ModelId::OllamaGptOss120bCloud),
1232 s if s == models::ollama::QWEN3_1_7B => Ok(ModelId::OllamaQwen317b),
1233 s if s == models::ollama::DEEPSEEK_V31_671B_CLOUD => {
1234 Ok(ModelId::OllamaDeepseekV31671bCloud)
1235 }
1236
1237 s if s == models::ollama::KIMI_K2_1T_CLOUD => Ok(ModelId::OllamaKimiK21tCloud),
1238 s if s == models::ollama::QWEN3_CODER_480B_CLOUD => {
1239 Ok(ModelId::OllamaQwen3Coder480bCloud)
1240 }
1241 s if s == models::ollama::GLM_46_CLOUD => Ok(ModelId::OllamaGlm46Cloud),
1242 s if s == models::ollama::MINIMAX_M2_CLOUD => Ok(ModelId::OllamaMinimaxM2Cloud),
1243 s if s == models::lmstudio::META_LLAMA_3_8B_INSTRUCT => {
1244 Ok(ModelId::LmStudioMetaLlama38BInstruct)
1245 }
1246 s if s == models::lmstudio::META_LLAMA_31_8B_INSTRUCT => {
1247 Ok(ModelId::LmStudioMetaLlama318BInstruct)
1248 }
1249 s if s == models::lmstudio::QWEN25_7B_INSTRUCT => Ok(ModelId::LmStudioQwen257BInstruct),
1250 s if s == models::lmstudio::GEMMA_2_2B_IT => Ok(ModelId::LmStudioGemma22BIt),
1251 s if s == models::lmstudio::GEMMA_2_9B_IT => Ok(ModelId::LmStudioGemma29BIt),
1252 s if s == models::lmstudio::PHI_31_MINI_4K_INSTRUCT => {
1253 Ok(ModelId::LmStudioPhi31Mini4kInstruct)
1254 }
1255 _ => {
1256 if let Some(model) = Self::parse_openrouter_model(s) {
1257 Ok(model)
1258 } else {
1259 Err(ModelParseError::InvalidModel(s.to_string()))
1260 }
1261 }
1262 }
1263 }
1264}
1265
1266#[derive(Debug, Clone, PartialEq)]
1268pub enum ModelParseError {
1269 InvalidModel(String),
1270 InvalidProvider(String),
1271}
1272
1273impl fmt::Display for ModelParseError {
1274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1275 match self {
1276 ModelParseError::InvalidModel(model) => {
1277 write!(
1278 f,
1279 "Invalid model identifier: '{}'. Supported models: {}",
1280 model,
1281 ModelId::all_models()
1282 .iter()
1283 .map(|m| m.as_str())
1284 .collect::<Vec<_>>()
1285 .join(", ")
1286 )
1287 }
1288 ModelParseError::InvalidProvider(provider) => {
1289 write!(
1290 f,
1291 "Invalid provider: '{}'. Supported providers: {}",
1292 provider,
1293 Provider::all_providers()
1294 .iter()
1295 .map(|p| p.to_string())
1296 .collect::<Vec<_>>()
1297 .join(", ")
1298 )
1299 }
1300 }
1301 }
1302}
1303
1304impl std::error::Error for ModelParseError {}
1305
1306#[cfg(test)]
1307mod tests {
1308 use super::*;
1309 use crate::constants::models;
1310
1311 #[test]
1312 fn test_model_string_conversion() {
1313 assert_eq!(
1315 ModelId::Gemini25FlashPreview.as_str(),
1316 models::GEMINI_2_5_FLASH_PREVIEW
1317 );
1318 assert_eq!(ModelId::Gemini25Flash.as_str(), models::GEMINI_2_5_FLASH);
1319 assert_eq!(
1320 ModelId::Gemini25FlashLite.as_str(),
1321 models::GEMINI_2_5_FLASH_LITE
1322 );
1323 assert_eq!(ModelId::Gemini25Pro.as_str(), models::GEMINI_2_5_PRO);
1324 assert_eq!(ModelId::GPT5.as_str(), models::GPT_5);
1326 assert_eq!(ModelId::GPT5Codex.as_str(), models::GPT_5_CODEX);
1327 assert_eq!(ModelId::GPT5Mini.as_str(), models::GPT_5_MINI);
1328 assert_eq!(ModelId::GPT5Nano.as_str(), models::GPT_5_NANO);
1329 assert_eq!(ModelId::CodexMiniLatest.as_str(), models::CODEX_MINI_LATEST);
1330 assert_eq!(ModelId::ClaudeSonnet45.as_str(), models::CLAUDE_SONNET_4_5);
1332 assert_eq!(ModelId::ClaudeHaiku45.as_str(), models::CLAUDE_HAIKU_4_5);
1333 assert_eq!(
1334 ModelId::ClaudeSonnet4.as_str(),
1335 models::CLAUDE_SONNET_4_5_20250929
1336 );
1337 assert_eq!(ModelId::ClaudeOpus41.as_str(), models::CLAUDE_OPUS_4_1);
1338 assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
1340 assert_eq!(
1341 ModelId::DeepSeekReasoner.as_str(),
1342 models::DEEPSEEK_REASONER
1343 );
1344 assert_eq!(ModelId::XaiGrok4.as_str(), models::xai::GROK_4);
1346 assert_eq!(ModelId::XaiGrok4Mini.as_str(), models::xai::GROK_4_MINI);
1347 assert_eq!(ModelId::XaiGrok4Code.as_str(), models::xai::GROK_4_CODE);
1348 assert_eq!(
1349 ModelId::XaiGrok4CodeLatest.as_str(),
1350 models::xai::GROK_4_CODE_LATEST
1351 );
1352 assert_eq!(ModelId::XaiGrok4Vision.as_str(), models::xai::GROK_4_VISION);
1353 assert_eq!(ModelId::ZaiGlm46.as_str(), models::zai::GLM_4_6);
1355 assert_eq!(ModelId::ZaiGlm45.as_str(), models::zai::GLM_4_5);
1356 assert_eq!(ModelId::ZaiGlm45Air.as_str(), models::zai::GLM_4_5_AIR);
1357 assert_eq!(ModelId::ZaiGlm45X.as_str(), models::zai::GLM_4_5_X);
1358 assert_eq!(ModelId::ZaiGlm45Airx.as_str(), models::zai::GLM_4_5_AIRX);
1359 assert_eq!(ModelId::ZaiGlm45Flash.as_str(), models::zai::GLM_4_5_FLASH);
1360 assert_eq!(
1361 ModelId::ZaiGlm432b0414128k.as_str(),
1362 models::zai::GLM_4_32B_0414_128K
1363 );
1364 for entry in openrouter_generated::ENTRIES {
1365 assert_eq!(entry.variant.as_str(), entry.id);
1366 }
1367 }
1368
1369 #[test]
1370 fn test_model_from_string() {
1371 assert_eq!(
1373 models::GEMINI_2_5_FLASH_PREVIEW.parse::<ModelId>().unwrap(),
1374 ModelId::Gemini25FlashPreview
1375 );
1376 assert_eq!(
1377 models::GEMINI_2_5_FLASH.parse::<ModelId>().unwrap(),
1378 ModelId::Gemini25Flash
1379 );
1380 assert_eq!(
1381 models::GEMINI_2_5_FLASH_LITE.parse::<ModelId>().unwrap(),
1382 ModelId::Gemini25FlashLite
1383 );
1384 assert_eq!(
1385 models::GEMINI_2_5_PRO.parse::<ModelId>().unwrap(),
1386 ModelId::Gemini25Pro
1387 );
1388 assert_eq!(models::GPT_5.parse::<ModelId>().unwrap(), ModelId::GPT5);
1390 assert_eq!(
1391 models::GPT_5_CODEX.parse::<ModelId>().unwrap(),
1392 ModelId::GPT5Codex
1393 );
1394 assert_eq!(
1395 models::GPT_5_MINI.parse::<ModelId>().unwrap(),
1396 ModelId::GPT5Mini
1397 );
1398 assert_eq!(
1399 models::GPT_5_NANO.parse::<ModelId>().unwrap(),
1400 ModelId::GPT5Nano
1401 );
1402 assert_eq!(
1403 models::CODEX_MINI_LATEST.parse::<ModelId>().unwrap(),
1404 ModelId::CodexMiniLatest
1405 );
1406 assert_eq!(
1407 models::openai::GPT_OSS_20B.parse::<ModelId>().unwrap(),
1408 ModelId::OpenAIGptOss20b
1409 );
1410 assert_eq!(
1411 models::openai::GPT_OSS_120B.parse::<ModelId>().unwrap(),
1412 ModelId::OpenAIGptOss120b
1413 );
1414 assert_eq!(
1416 models::CLAUDE_SONNET_4_5.parse::<ModelId>().unwrap(),
1417 ModelId::ClaudeSonnet45
1418 );
1419 assert_eq!(
1420 models::CLAUDE_HAIKU_4_5.parse::<ModelId>().unwrap(),
1421 ModelId::ClaudeHaiku45
1422 );
1423 assert_eq!(
1424 models::CLAUDE_SONNET_4_5_20250929
1425 .parse::<ModelId>()
1426 .unwrap(),
1427 ModelId::ClaudeSonnet4
1428 );
1429 assert_eq!(
1430 models::CLAUDE_OPUS_4_1.parse::<ModelId>().unwrap(),
1431 ModelId::ClaudeOpus41
1432 );
1433 assert_eq!(
1435 models::DEEPSEEK_CHAT.parse::<ModelId>().unwrap(),
1436 ModelId::DeepSeekChat
1437 );
1438 assert_eq!(
1439 models::DEEPSEEK_REASONER.parse::<ModelId>().unwrap(),
1440 ModelId::DeepSeekReasoner
1441 );
1442 assert_eq!(
1444 models::xai::GROK_4.parse::<ModelId>().unwrap(),
1445 ModelId::XaiGrok4
1446 );
1447 assert_eq!(
1448 models::xai::GROK_4_MINI.parse::<ModelId>().unwrap(),
1449 ModelId::XaiGrok4Mini
1450 );
1451 assert_eq!(
1452 models::xai::GROK_4_CODE.parse::<ModelId>().unwrap(),
1453 ModelId::XaiGrok4Code
1454 );
1455 assert_eq!(
1456 models::xai::GROK_4_CODE_LATEST.parse::<ModelId>().unwrap(),
1457 ModelId::XaiGrok4CodeLatest
1458 );
1459 assert_eq!(
1460 models::xai::GROK_4_VISION.parse::<ModelId>().unwrap(),
1461 ModelId::XaiGrok4Vision
1462 );
1463 assert_eq!(
1465 models::zai::GLM_4_6.parse::<ModelId>().unwrap(),
1466 ModelId::ZaiGlm46
1467 );
1468 assert_eq!(
1469 models::zai::GLM_4_5.parse::<ModelId>().unwrap(),
1470 ModelId::ZaiGlm45
1471 );
1472 assert_eq!(
1473 models::zai::GLM_4_5_AIR.parse::<ModelId>().unwrap(),
1474 ModelId::ZaiGlm45Air
1475 );
1476 assert_eq!(
1477 models::zai::GLM_4_5_X.parse::<ModelId>().unwrap(),
1478 ModelId::ZaiGlm45X
1479 );
1480 assert_eq!(
1481 models::zai::GLM_4_5_AIRX.parse::<ModelId>().unwrap(),
1482 ModelId::ZaiGlm45Airx
1483 );
1484 assert_eq!(
1485 models::zai::GLM_4_5_FLASH.parse::<ModelId>().unwrap(),
1486 ModelId::ZaiGlm45Flash
1487 );
1488 assert_eq!(
1489 models::zai::GLM_4_32B_0414_128K.parse::<ModelId>().unwrap(),
1490 ModelId::ZaiGlm432b0414128k
1491 );
1492 assert_eq!(
1493 models::MOONSHOT_KIMI_K2_TURBO_PREVIEW
1494 .parse::<ModelId>()
1495 .unwrap(),
1496 ModelId::MoonshotKimiK2TurboPreview
1497 );
1498 assert_eq!(
1499 models::MOONSHOT_KIMI_K2_THINKING
1500 .parse::<ModelId>()
1501 .unwrap(),
1502 ModelId::MoonshotKimiK2Thinking
1503 );
1504 assert_eq!(
1505 models::MOONSHOT_KIMI_K2_0905_PREVIEW
1506 .parse::<ModelId>()
1507 .unwrap(),
1508 ModelId::MoonshotKimiK20905Preview
1509 );
1510 assert_eq!(
1511 models::MOONSHOT_KIMI_K2_0711_PREVIEW
1512 .parse::<ModelId>()
1513 .unwrap(),
1514 ModelId::MoonshotKimiK20711Preview
1515 );
1516 assert_eq!(
1517 models::MOONSHOT_KIMI_LATEST.parse::<ModelId>().unwrap(),
1518 ModelId::MoonshotKimiLatest
1519 );
1520 assert_eq!(
1521 models::MOONSHOT_KIMI_LATEST_8K.parse::<ModelId>().unwrap(),
1522 ModelId::MoonshotKimiLatest8k
1523 );
1524 assert_eq!(
1525 models::MOONSHOT_KIMI_LATEST_32K.parse::<ModelId>().unwrap(),
1526 ModelId::MoonshotKimiLatest32k
1527 );
1528 assert_eq!(
1529 models::MOONSHOT_KIMI_LATEST_128K
1530 .parse::<ModelId>()
1531 .unwrap(),
1532 ModelId::MoonshotKimiLatest128k
1533 );
1534 for entry in openrouter_generated::ENTRIES {
1535 assert_eq!(entry.id.parse::<ModelId>().unwrap(), entry.variant);
1536 }
1537 assert!("invalid-model".parse::<ModelId>().is_err());
1539 }
1540
1541 #[test]
1542 fn test_provider_parsing() {
1543 assert_eq!("gemini".parse::<Provider>().unwrap(), Provider::Gemini);
1544 assert_eq!("openai".parse::<Provider>().unwrap(), Provider::OpenAI);
1545 assert_eq!(
1546 "anthropic".parse::<Provider>().unwrap(),
1547 Provider::Anthropic
1548 );
1549 assert_eq!("deepseek".parse::<Provider>().unwrap(), Provider::DeepSeek);
1550 assert_eq!(
1551 "openrouter".parse::<Provider>().unwrap(),
1552 Provider::OpenRouter
1553 );
1554 assert_eq!("xai".parse::<Provider>().unwrap(), Provider::XAI);
1555 assert_eq!("zai".parse::<Provider>().unwrap(), Provider::ZAI);
1556 assert_eq!("moonshot".parse::<Provider>().unwrap(), Provider::Moonshot);
1557 assert_eq!("lmstudio".parse::<Provider>().unwrap(), Provider::LmStudio);
1558 assert!("invalid-provider".parse::<Provider>().is_err());
1559 }
1560
1561 #[test]
1562 fn test_model_providers() {
1563 assert_eq!(ModelId::Gemini25FlashPreview.provider(), Provider::Gemini);
1564 assert_eq!(ModelId::GPT5.provider(), Provider::OpenAI);
1565 assert_eq!(ModelId::GPT5Codex.provider(), Provider::OpenAI);
1566 assert_eq!(ModelId::ClaudeSonnet45.provider(), Provider::Anthropic);
1567 assert_eq!(ModelId::ClaudeHaiku45.provider(), Provider::Anthropic);
1568 assert_eq!(ModelId::ClaudeSonnet4.provider(), Provider::Anthropic);
1569 assert_eq!(ModelId::DeepSeekChat.provider(), Provider::DeepSeek);
1570 assert_eq!(ModelId::XaiGrok4.provider(), Provider::XAI);
1571 assert_eq!(ModelId::ZaiGlm46.provider(), Provider::ZAI);
1572 assert_eq!(
1573 ModelId::MoonshotKimiK20905Preview.provider(),
1574 Provider::Moonshot
1575 );
1576 assert_eq!(ModelId::OllamaGptOss20b.provider(), Provider::Ollama);
1577 assert_eq!(ModelId::OllamaGptOss120bCloud.provider(), Provider::Ollama);
1578 assert_eq!(ModelId::OllamaQwen317b.provider(), Provider::Ollama);
1579 assert_eq!(
1580 ModelId::LmStudioMetaLlama38BInstruct.provider(),
1581 Provider::LmStudio
1582 );
1583 assert_eq!(
1584 ModelId::LmStudioMetaLlama318BInstruct.provider(),
1585 Provider::LmStudio
1586 );
1587 assert_eq!(
1588 ModelId::LmStudioQwen257BInstruct.provider(),
1589 Provider::LmStudio
1590 );
1591 assert_eq!(ModelId::LmStudioGemma22BIt.provider(), Provider::LmStudio);
1592 assert_eq!(ModelId::LmStudioGemma29BIt.provider(), Provider::LmStudio);
1593 assert_eq!(
1594 ModelId::LmStudioPhi31Mini4kInstruct.provider(),
1595 Provider::LmStudio
1596 );
1597 assert_eq!(
1598 ModelId::OpenRouterGrokCodeFast1.provider(),
1599 Provider::OpenRouter
1600 );
1601 assert_eq!(
1602 ModelId::OpenRouterAnthropicClaudeSonnet45.provider(),
1603 Provider::OpenRouter
1604 );
1605
1606 for entry in openrouter_generated::ENTRIES {
1607 assert_eq!(entry.variant.provider(), Provider::OpenRouter);
1608 }
1609 }
1610
1611 #[test]
1612 fn test_provider_defaults() {
1613 assert_eq!(
1614 ModelId::default_orchestrator_for_provider(Provider::Gemini),
1615 ModelId::Gemini25Pro
1616 );
1617 assert_eq!(
1618 ModelId::default_orchestrator_for_provider(Provider::OpenAI),
1619 ModelId::GPT5
1620 );
1621 assert_eq!(
1622 ModelId::default_orchestrator_for_provider(Provider::Anthropic),
1623 ModelId::ClaudeOpus41
1624 );
1625 assert_eq!(
1626 ModelId::default_orchestrator_for_provider(Provider::DeepSeek),
1627 ModelId::DeepSeekReasoner
1628 );
1629 assert_eq!(
1630 ModelId::default_orchestrator_for_provider(Provider::OpenRouter),
1631 ModelId::OpenRouterGrokCodeFast1
1632 );
1633 assert_eq!(
1634 ModelId::default_orchestrator_for_provider(Provider::XAI),
1635 ModelId::XaiGrok4
1636 );
1637 assert_eq!(
1638 ModelId::default_orchestrator_for_provider(Provider::Ollama),
1639 ModelId::OllamaGptOss20b
1640 );
1641 assert_eq!(
1642 ModelId::default_orchestrator_for_provider(Provider::LmStudio),
1643 ModelId::LmStudioMetaLlama318BInstruct
1644 );
1645 assert_eq!(
1646 ModelId::default_orchestrator_for_provider(Provider::ZAI),
1647 ModelId::ZaiGlm46
1648 );
1649 assert_eq!(
1650 ModelId::default_orchestrator_for_provider(Provider::Moonshot),
1651 ModelId::MoonshotKimiK20905Preview
1652 );
1653
1654 assert_eq!(
1655 ModelId::default_subagent_for_provider(Provider::Gemini),
1656 ModelId::Gemini25FlashPreview
1657 );
1658 assert_eq!(
1659 ModelId::default_subagent_for_provider(Provider::OpenAI),
1660 ModelId::GPT5Mini
1661 );
1662 assert_eq!(
1663 ModelId::default_subagent_for_provider(Provider::Anthropic),
1664 ModelId::ClaudeSonnet45
1665 );
1666 assert_eq!(
1667 ModelId::default_subagent_for_provider(Provider::DeepSeek),
1668 ModelId::DeepSeekChat
1669 );
1670 assert_eq!(
1671 ModelId::default_subagent_for_provider(Provider::OpenRouter),
1672 ModelId::OpenRouterGrokCodeFast1
1673 );
1674 assert_eq!(
1675 ModelId::default_subagent_for_provider(Provider::XAI),
1676 ModelId::XaiGrok4Code
1677 );
1678 assert_eq!(
1679 ModelId::default_subagent_for_provider(Provider::Ollama),
1680 ModelId::OllamaQwen317b
1681 );
1682 assert_eq!(
1683 ModelId::default_subagent_for_provider(Provider::LmStudio),
1684 ModelId::LmStudioQwen257BInstruct
1685 );
1686 assert_eq!(
1687 ModelId::default_subagent_for_provider(Provider::ZAI),
1688 ModelId::ZaiGlm45Flash
1689 );
1690 assert_eq!(
1691 ModelId::default_subagent_for_provider(Provider::Moonshot),
1692 ModelId::MoonshotKimiK2TurboPreview
1693 );
1694
1695 assert_eq!(
1696 ModelId::default_single_for_provider(Provider::DeepSeek),
1697 ModelId::DeepSeekReasoner
1698 );
1699 assert_eq!(
1700 ModelId::default_single_for_provider(Provider::Moonshot),
1701 ModelId::MoonshotKimiK2TurboPreview
1702 );
1703 assert_eq!(
1704 ModelId::default_single_for_provider(Provider::Ollama),
1705 ModelId::OllamaGptOss20b
1706 );
1707 assert_eq!(
1708 ModelId::default_single_for_provider(Provider::LmStudio),
1709 ModelId::LmStudioMetaLlama318BInstruct
1710 );
1711 }
1712
1713 #[test]
1714 fn test_model_defaults() {
1715 assert_eq!(ModelId::default(), ModelId::Gemini25FlashPreview);
1716 assert_eq!(ModelId::default_orchestrator(), ModelId::Gemini25Pro);
1717 assert_eq!(ModelId::default_subagent(), ModelId::Gemini25FlashPreview);
1718 }
1719
1720 #[test]
1721 fn test_model_variants() {
1722 assert!(ModelId::Gemini25FlashPreview.is_flash_variant());
1724 assert!(ModelId::Gemini25Flash.is_flash_variant());
1725 assert!(ModelId::Gemini25FlashLite.is_flash_variant());
1726 assert!(!ModelId::GPT5.is_flash_variant());
1727 assert!(ModelId::ZaiGlm45Flash.is_flash_variant());
1728 assert!(ModelId::MoonshotKimiK2TurboPreview.is_flash_variant());
1729 assert!(ModelId::MoonshotKimiLatest8k.is_flash_variant());
1730
1731 assert!(ModelId::Gemini25Pro.is_pro_variant());
1733 assert!(ModelId::GPT5.is_pro_variant());
1734 assert!(ModelId::GPT5Codex.is_pro_variant());
1735 assert!(ModelId::DeepSeekReasoner.is_pro_variant());
1736 assert!(ModelId::ZaiGlm46.is_pro_variant());
1737 assert!(ModelId::MoonshotKimiK20905Preview.is_pro_variant());
1738 assert!(ModelId::MoonshotKimiLatest128k.is_pro_variant());
1739 assert!(!ModelId::Gemini25FlashPreview.is_pro_variant());
1740
1741 assert!(ModelId::Gemini25FlashPreview.is_efficient_variant());
1743 assert!(ModelId::Gemini25Flash.is_efficient_variant());
1744 assert!(ModelId::Gemini25FlashLite.is_efficient_variant());
1745 assert!(ModelId::GPT5Mini.is_efficient_variant());
1746 assert!(ModelId::ClaudeHaiku45.is_efficient_variant());
1747 assert!(ModelId::XaiGrok4Code.is_efficient_variant());
1748 assert!(ModelId::DeepSeekChat.is_efficient_variant());
1749 assert!(ModelId::ZaiGlm45Air.is_efficient_variant());
1750 assert!(ModelId::ZaiGlm45Airx.is_efficient_variant());
1751 assert!(ModelId::ZaiGlm45Flash.is_efficient_variant());
1752 assert!(ModelId::MoonshotKimiK2TurboPreview.is_efficient_variant());
1753 assert!(ModelId::MoonshotKimiLatest8k.is_efficient_variant());
1754 assert!(!ModelId::GPT5.is_efficient_variant());
1755
1756 for entry in openrouter_generated::ENTRIES {
1757 assert_eq!(entry.variant.is_efficient_variant(), entry.efficient);
1758 }
1759
1760 assert!(ModelId::Gemini25Pro.is_top_tier());
1762 assert!(ModelId::GPT5.is_top_tier());
1763 assert!(ModelId::GPT5Codex.is_top_tier());
1764 assert!(ModelId::ClaudeSonnet45.is_top_tier());
1765 assert!(ModelId::ClaudeSonnet4.is_top_tier());
1766 assert!(ModelId::XaiGrok4.is_top_tier());
1767 assert!(ModelId::XaiGrok4CodeLatest.is_top_tier());
1768 assert!(ModelId::DeepSeekReasoner.is_top_tier());
1769 assert!(ModelId::ZaiGlm46.is_top_tier());
1770 assert!(ModelId::MoonshotKimiK20905Preview.is_top_tier());
1771 assert!(ModelId::MoonshotKimiLatest128k.is_top_tier());
1772 assert!(!ModelId::Gemini25FlashPreview.is_top_tier());
1773 assert!(!ModelId::ClaudeHaiku45.is_top_tier());
1774
1775 for entry in openrouter_generated::ENTRIES {
1776 assert_eq!(entry.variant.is_top_tier(), entry.top_tier);
1777 }
1778 }
1779
1780 #[test]
1781 fn test_model_generation() {
1782 assert_eq!(ModelId::Gemini25FlashPreview.generation(), "2.5");
1784 assert_eq!(ModelId::Gemini25Flash.generation(), "2.5");
1785 assert_eq!(ModelId::Gemini25FlashLite.generation(), "2.5");
1786 assert_eq!(ModelId::Gemini25Pro.generation(), "2.5");
1787
1788 assert_eq!(ModelId::GPT5.generation(), "5");
1790 assert_eq!(ModelId::GPT5Codex.generation(), "5");
1791 assert_eq!(ModelId::GPT5Mini.generation(), "5");
1792 assert_eq!(ModelId::GPT5Nano.generation(), "5");
1793 assert_eq!(ModelId::CodexMiniLatest.generation(), "5");
1794
1795 assert_eq!(ModelId::ClaudeSonnet45.generation(), "4.5");
1797 assert_eq!(ModelId::ClaudeHaiku45.generation(), "4.5");
1798 assert_eq!(ModelId::ClaudeSonnet4.generation(), "4");
1799 assert_eq!(ModelId::ClaudeOpus41.generation(), "4.1");
1800
1801 assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1803 assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1804
1805 assert_eq!(ModelId::XaiGrok4.generation(), "4");
1807 assert_eq!(ModelId::XaiGrok4Mini.generation(), "4");
1808 assert_eq!(ModelId::XaiGrok4Code.generation(), "4");
1809 assert_eq!(ModelId::XaiGrok4CodeLatest.generation(), "4");
1810 assert_eq!(ModelId::XaiGrok4Vision.generation(), "4");
1811 assert_eq!(ModelId::ZaiGlm46.generation(), "4.6");
1813 assert_eq!(ModelId::ZaiGlm45.generation(), "4.5");
1814 assert_eq!(ModelId::ZaiGlm45Air.generation(), "4.5");
1815 assert_eq!(ModelId::ZaiGlm45X.generation(), "4.5");
1816 assert_eq!(ModelId::ZaiGlm45Airx.generation(), "4.5");
1817 assert_eq!(ModelId::ZaiGlm45Flash.generation(), "4.5");
1818 assert_eq!(ModelId::ZaiGlm432b0414128k.generation(), "4-32B");
1819 assert_eq!(ModelId::MoonshotKimiK2TurboPreview.generation(), "k2");
1820 assert_eq!(ModelId::MoonshotKimiK20905Preview.generation(), "k2");
1821 assert_eq!(ModelId::MoonshotKimiK20711Preview.generation(), "k2");
1822 assert_eq!(ModelId::MoonshotKimiLatest.generation(), "latest");
1823 assert_eq!(ModelId::MoonshotKimiLatest8k.generation(), "latest");
1824 assert_eq!(ModelId::MoonshotKimiLatest32k.generation(), "latest");
1825 assert_eq!(ModelId::MoonshotKimiLatest128k.generation(), "latest");
1826 assert_eq!(
1827 ModelId::LmStudioMetaLlama38BInstruct.generation(),
1828 "meta-llama-3"
1829 );
1830 assert_eq!(
1831 ModelId::LmStudioMetaLlama318BInstruct.generation(),
1832 "meta-llama-3.1"
1833 );
1834 assert_eq!(ModelId::LmStudioQwen257BInstruct.generation(), "qwen2.5");
1835 assert_eq!(ModelId::LmStudioGemma22BIt.generation(), "gemma-2");
1836 assert_eq!(ModelId::LmStudioGemma29BIt.generation(), "gemma-2");
1837 assert_eq!(ModelId::LmStudioPhi31Mini4kInstruct.generation(), "phi-3.1");
1838
1839 for entry in openrouter_generated::ENTRIES {
1840 assert_eq!(entry.variant.generation(), entry.generation);
1841 }
1842 }
1843
1844 #[test]
1845 fn test_models_for_provider() {
1846 let gemini_models = ModelId::models_for_provider(Provider::Gemini);
1847 assert!(gemini_models.contains(&ModelId::Gemini25Pro));
1848 assert!(!gemini_models.contains(&ModelId::GPT5));
1849
1850 let openai_models = ModelId::models_for_provider(Provider::OpenAI);
1851 assert!(openai_models.contains(&ModelId::GPT5));
1852 assert!(openai_models.contains(&ModelId::GPT5Codex));
1853 assert!(!openai_models.contains(&ModelId::Gemini25Pro));
1854
1855 let anthropic_models = ModelId::models_for_provider(Provider::Anthropic);
1856 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet45));
1857 assert!(anthropic_models.contains(&ModelId::ClaudeHaiku45));
1858 assert!(anthropic_models.contains(&ModelId::ClaudeSonnet4));
1859 assert!(!anthropic_models.contains(&ModelId::GPT5));
1860
1861 let deepseek_models = ModelId::models_for_provider(Provider::DeepSeek);
1862 assert!(deepseek_models.contains(&ModelId::DeepSeekChat));
1863 assert!(deepseek_models.contains(&ModelId::DeepSeekReasoner));
1864
1865 let openrouter_models = ModelId::models_for_provider(Provider::OpenRouter);
1866 for entry in openrouter_generated::ENTRIES {
1867 assert!(openrouter_models.contains(&entry.variant));
1868 }
1869
1870 let xai_models = ModelId::models_for_provider(Provider::XAI);
1871 assert!(xai_models.contains(&ModelId::XaiGrok4));
1872 assert!(xai_models.contains(&ModelId::XaiGrok4Mini));
1873 assert!(xai_models.contains(&ModelId::XaiGrok4Code));
1874 assert!(xai_models.contains(&ModelId::XaiGrok4CodeLatest));
1875 assert!(xai_models.contains(&ModelId::XaiGrok4Vision));
1876
1877 let zai_models = ModelId::models_for_provider(Provider::ZAI);
1878 assert!(zai_models.contains(&ModelId::ZaiGlm46));
1879 assert!(zai_models.contains(&ModelId::ZaiGlm45));
1880 assert!(zai_models.contains(&ModelId::ZaiGlm45Air));
1881 assert!(zai_models.contains(&ModelId::ZaiGlm45X));
1882 assert!(zai_models.contains(&ModelId::ZaiGlm45Airx));
1883 assert!(zai_models.contains(&ModelId::ZaiGlm45Flash));
1884 assert!(zai_models.contains(&ModelId::ZaiGlm432b0414128k));
1885
1886 let moonshot_models = ModelId::models_for_provider(Provider::Moonshot);
1887 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK2TurboPreview));
1888 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK2Thinking));
1889 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20905Preview));
1890 assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20711Preview));
1891 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest));
1892 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest8k));
1893 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest32k));
1894 assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest128k));
1895 assert_eq!(moonshot_models.len(), 8);
1896
1897 let ollama_models = ModelId::models_for_provider(Provider::Ollama);
1898 assert!(ollama_models.contains(&ModelId::OllamaGptOss20b));
1899 assert!(ollama_models.contains(&ModelId::OllamaGptOss20bCloud));
1900 assert!(ollama_models.contains(&ModelId::OllamaGptOss120bCloud));
1901 assert!(ollama_models.contains(&ModelId::OllamaQwen317b));
1902 assert!(ollama_models.contains(&ModelId::OllamaDeepseekV31671bCloud));
1903
1904 assert!(ollama_models.contains(&ModelId::OllamaQwen3Coder480bCloud));
1905 assert!(ollama_models.contains(&ModelId::OllamaGlm46Cloud));
1906 assert!(ollama_models.contains(&ModelId::OllamaMinimaxM2Cloud));
1907 assert_eq!(ollama_models.len(), 9); let lmstudio_models = ModelId::models_for_provider(Provider::LmStudio);
1910 assert!(lmstudio_models.contains(&ModelId::LmStudioMetaLlama38BInstruct));
1911 assert!(lmstudio_models.contains(&ModelId::LmStudioMetaLlama318BInstruct));
1912 assert!(lmstudio_models.contains(&ModelId::LmStudioQwen257BInstruct));
1913 assert!(lmstudio_models.contains(&ModelId::LmStudioGemma22BIt));
1914 assert!(lmstudio_models.contains(&ModelId::LmStudioGemma29BIt));
1915 assert!(lmstudio_models.contains(&ModelId::LmStudioPhi31Mini4kInstruct));
1916 assert_eq!(lmstudio_models.len(), 6);
1917 }
1918
1919 #[test]
1920 fn test_ollama_cloud_models() {
1921 use crate::constants::models;
1922
1923 let model_pairs = vec![
1925 (
1926 ModelId::OllamaGptOss20bCloud,
1927 models::ollama::GPT_OSS_20B_CLOUD,
1928 ),
1929 (
1930 ModelId::OllamaGptOss120bCloud,
1931 models::ollama::GPT_OSS_120B_CLOUD,
1932 ),
1933 (
1934 ModelId::OllamaDeepseekV31671bCloud,
1935 models::ollama::DEEPSEEK_V31_671B_CLOUD,
1936 ),
1937 (
1938 ModelId::OllamaKimiK21tCloud,
1939 models::ollama::KIMI_K2_1T_CLOUD,
1940 ),
1941 (
1942 ModelId::OllamaQwen3Coder480bCloud,
1943 models::ollama::QWEN3_CODER_480B_CLOUD,
1944 ),
1945 (ModelId::OllamaGlm46Cloud, models::ollama::GLM_46_CLOUD),
1946 (
1947 ModelId::OllamaMinimaxM2Cloud,
1948 models::ollama::MINIMAX_M2_CLOUD,
1949 ),
1950 ];
1951
1952 for (model_id, expected_str) in model_pairs {
1953 assert_eq!(model_id.as_str(), expected_str);
1954 assert_eq!(ModelId::from_str(expected_str).unwrap(), model_id);
1955 assert_eq!(model_id.provider(), Provider::Ollama);
1956
1957 assert!(!model_id.display_name().is_empty());
1959
1960 assert!(!model_id.description().is_empty());
1962
1963 assert!(!model_id.generation().is_empty());
1965 }
1966 }
1967
1968 #[test]
1969 fn test_fallback_models() {
1970 let fallbacks = ModelId::fallback_models();
1971 assert!(!fallbacks.is_empty());
1972 assert!(fallbacks.contains(&ModelId::Gemini25Pro));
1973 assert!(fallbacks.contains(&ModelId::GPT5));
1974 assert!(fallbacks.contains(&ModelId::ClaudeOpus41));
1975 assert!(fallbacks.contains(&ModelId::ClaudeSonnet45));
1976 assert!(fallbacks.contains(&ModelId::DeepSeekReasoner));
1977 assert!(fallbacks.contains(&ModelId::MoonshotKimiK20905Preview));
1978 assert!(fallbacks.contains(&ModelId::XaiGrok4));
1979 assert!(fallbacks.contains(&ModelId::ZaiGlm46));
1980 assert!(fallbacks.contains(&ModelId::OpenRouterGrokCodeFast1));
1981 }
1982}