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