vtcode_config/
models.rs

1//! Model configuration and identification module
2//!
3//! This module provides a centralized enum for model identifiers and their configurations,
4//! replacing hardcoded model strings throughout the codebase for better maintainability.
5//! Read the model list in `docs/models.json`.
6
7use 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/// Supported AI model providers
25#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
27pub enum Provider {
28    /// Google Gemini models
29    #[default]
30    Gemini,
31    /// OpenAI GPT models
32    OpenAI,
33    /// Anthropic Claude models
34    Anthropic,
35    /// DeepSeek native models
36    DeepSeek,
37    /// OpenRouter marketplace models
38    OpenRouter,
39    /// Local Ollama models
40    Ollama,
41    /// LM Studio local server (OpenAI-compatible)
42    LmStudio,
43    /// Moonshot.ai models
44    Moonshot,
45    /// xAI Grok models
46    XAI,
47    /// Z.AI GLM models
48    ZAI,
49}
50
51impl Provider {
52    /// Get the default API key environment variable for this provider
53    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    /// Get all supported providers
69    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    /// Human-friendly label for display purposes
85    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    /// Determine if the provider supports configurable reasoning effort for the model
101    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/// Centralized enum for all supported model identifiers
162#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
163#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
164pub enum ModelId {
165    // Gemini models
166    /// Gemini 2.5 Flash Preview - Latest fast model with advanced capabilities
167    #[default]
168    Gemini25FlashPreview,
169    /// Gemini 2.5 Flash - Legacy alias for flash preview
170    Gemini25Flash,
171    /// Gemini 2.5 Flash Lite - Legacy alias for flash preview (lite)
172    Gemini25FlashLite,
173    /// Gemini 2.5 Pro - Latest most capable Gemini model
174    Gemini25Pro,
175    /// Gemini 3 Pro Preview - Preview of next-generation Gemini model
176    Gemini3ProPreview,
177
178    // OpenAI models
179    /// GPT-5 - Latest most capable OpenAI model (2025-08-07)
180    GPT5,
181    /// GPT-5 Codex - Code-focused GPT-5 variant using the Responses API
182    GPT5Codex,
183    /// GPT-5 Mini - Latest efficient OpenAI model (2025-08-07)
184    GPT5Mini,
185    /// GPT-5 Nano - Latest most cost-effective OpenAI model (2025-08-07)
186    GPT5Nano,
187    /// Codex Mini Latest - Latest Codex model for code generation (2025-05-16)
188    CodexMiniLatest,
189    /// GPT-OSS 20B - OpenAI's open-source 20B parameter model using harmony
190    OpenAIGptOss20b,
191    /// GPT-OSS 120B - OpenAI's open-source 120B parameter model using harmony
192    OpenAIGptOss120b,
193
194    // Anthropic models
195    /// Claude Opus 4.5 - Latest flagship Anthropic model with exceptional reasoning (2025-11-01)
196    ClaudeOpus45,
197    /// Claude Opus 4.1 - Previous most capable Anthropic model (2025-08-05)
198    ClaudeOpus41,
199    /// Claude Sonnet 4.5 - Latest balanced Anthropic model (2025-10-15)
200    ClaudeSonnet45,
201    /// Claude Haiku 4.5 - Latest efficient Anthropic model (2025-10-15)
202    ClaudeHaiku45,
203    /// Claude Sonnet 4 - Previous balanced Anthropic model (2025-05-14)
204    ClaudeSonnet4,
205
206    // DeepSeek models
207    /// DeepSeek V3.2-Exp Chat - Non-thinking mode
208    DeepSeekChat,
209    /// DeepSeek V3.2-Exp Reasoner - Thinking mode with deliberate reasoning output
210    DeepSeekReasoner,
211
212    // xAI models
213    /// Grok-4 - Flagship xAI model with advanced reasoning
214    XaiGrok4,
215    /// Grok-4 Mini - Efficient xAI model variant
216    XaiGrok4Mini,
217    /// Grok-4 Code - Code-focused Grok deployment
218    XaiGrok4Code,
219    /// Grok-4 Code Latest - Latest Grok code model with enhanced reasoning tools
220    XaiGrok4CodeLatest,
221    /// Grok-4 Vision - Multimodal Grok model
222    XaiGrok4Vision,
223
224    // Z.AI models
225    /// GLM-4.6 - Latest flagship GLM reasoning model
226    ZaiGlm46,
227    /// GLM-4.5 - Balanced GLM release for general tasks
228    ZaiGlm45,
229    /// GLM-4.5-Air - Efficient GLM variant
230    ZaiGlm45Air,
231    /// GLM-4.5-X - Enhanced capability GLM variant
232    ZaiGlm45X,
233    /// GLM-4.5-AirX - Hybrid efficient GLM variant
234    ZaiGlm45Airx,
235    /// GLM-4.5-Flash - Low-latency GLM variant
236    ZaiGlm45Flash,
237    /// GLM-4-32B-0414-128K - Legacy long-context GLM deployment
238    ZaiGlm432b0414128k,
239
240    // Moonshot.ai models
241    /// Kimi K2 Turbo Preview - Recommended high-speed K2 deployment
242    MoonshotKimiK2TurboPreview,
243    /// Kimi K2 Thinking - Moonshot reasoning-tier K2 release for long-horizon agentic tasks
244    MoonshotKimiK2Thinking,
245    /// Kimi K2 0905 Preview - Flagship 256K K2 release with enhanced coding agents
246    MoonshotKimiK20905Preview,
247    /// Kimi K2 0711 Preview - Long-context K2 release tuned for balanced workloads
248    MoonshotKimiK20711Preview,
249    /// Kimi Latest - Auto-tier alias that selects 8K/32K/128K variants automatically
250    MoonshotKimiLatest,
251    /// Kimi Latest 8K - Vision-enabled 8K tier with automatic context caching
252    MoonshotKimiLatest8k,
253    /// Kimi Latest 32K - Vision-enabled mid-tier with extended context
254    MoonshotKimiLatest32k,
255    /// Kimi Latest 128K - Vision-enabled flagship tier with maximum context
256    MoonshotKimiLatest128k,
257
258    // Ollama models
259    /// GPT-OSS 20B - Open-weight GPT-OSS 20B model served via Ollama locally
260    OllamaGptOss20b,
261    /// GPT-OSS 20B Cloud - Cloud-hosted GPT-OSS 20B served via Ollama Cloud
262    OllamaGptOss20bCloud,
263    /// GPT-OSS 120B Cloud - Cloud-hosted GPT-OSS 120B served via Ollama Cloud
264    OllamaGptOss120bCloud,
265    /// Qwen3 1.7B - Qwen3 1.7B model served via Ollama
266    OllamaQwen317b,
267    /// DeepSeek V3.1 671B Cloud - Cloud-hosted DeepSeek model served via Ollama Cloud
268    OllamaDeepseekV31671bCloud,
269
270    /// Kimi K2 1T Cloud - Cloud-hosted Kimi K2 1T model served via Ollama Cloud
271    OllamaKimiK21tCloud,
272    /// Qwen3 Coder 480B Cloud - Cloud-hosted Qwen3 Coder model served via Ollama Cloud
273    OllamaQwen3Coder480bCloud,
274    /// GLM-4.6 Cloud - Cloud-hosted GLM-4.6 model served via Ollama Cloud
275    OllamaGlm46Cloud,
276    /// MiniMax-M2 Cloud - Cloud-hosted MiniMax-M2 model served via Ollama Cloud
277    OllamaMinimaxM2Cloud,
278
279    // LM Studio models
280    /// Meta Llama 3 8B Instruct served locally via LM Studio
281    LmStudioMetaLlama38BInstruct,
282    /// Meta Llama 3.1 8B Instruct served locally via LM Studio
283    LmStudioMetaLlama318BInstruct,
284    /// Qwen2.5 7B Instruct served locally via LM Studio
285    LmStudioQwen257BInstruct,
286    /// Gemma 2 2B IT served locally via LM Studio
287    LmStudioGemma22BIt,
288    /// Gemma 2 9B IT served locally via LM Studio
289    LmStudioGemma29BIt,
290    /// Phi-3.1 Mini 4K Instruct served locally via LM Studio
291    LmStudioPhi31Mini4kInstruct,
292
293    // OpenRouter models
294    /// Grok Code Fast 1 - Fast OpenRouter coding model powered by xAI Grok
295    OpenRouterGrokCodeFast1,
296    /// Grok 4 Fast - Reasoning-focused Grok endpoint with transparent traces
297    OpenRouterGrok4Fast,
298    /// Grok 4.1 Fast - Enhanced Grok 4.1 fast inference with improved reasoning
299    OpenRouterGrok41Fast,
300    /// Grok 4 - Flagship Grok 4 endpoint exposed through OpenRouter
301    OpenRouterGrok4,
302    /// GLM 4.6 - Z.AI GLM 4.6 long-context reasoning model
303    OpenRouterZaiGlm46,
304    /// Kimi K2 0905 - MoonshotAI Kimi K2 0905 MoE release optimised for coding agents
305    OpenRouterMoonshotaiKimiK20905,
306    /// Kimi K2 Thinking - MoonshotAI reasoning-tier Kimi K2 release optimized for long-horizon agents
307    OpenRouterMoonshotaiKimiK2Thinking,
308    /// Kimi K2 (free) - Community tier for MoonshotAI Kimi K2
309    OpenRouterMoonshotaiKimiK2Free,
310    /// Qwen3 Max - Flagship Qwen3 mixture for general reasoning
311    OpenRouterQwen3Max,
312    /// Qwen3 235B A22B - Mixture-of-experts Qwen3 235B general model
313    OpenRouterQwen3235bA22b,
314    /// Qwen3 235B A22B (free) - Community tier for Qwen3 235B A22B
315    OpenRouterQwen3235bA22bFree,
316    /// Qwen3 235B A22B Instruct 2507 - Instruction-tuned Qwen3 235B A22B
317    OpenRouterQwen3235bA22b2507,
318    /// Qwen3 235B A22B Thinking 2507 - Deliberative Qwen3 235B A22B reasoning release
319    OpenRouterQwen3235bA22bThinking2507,
320    /// Qwen3 32B - Dense 32B Qwen3 deployment
321    OpenRouterQwen332b,
322    /// Qwen3 30B A3B - Active-parameter 30B Qwen3 model
323    OpenRouterQwen330bA3b,
324    /// Qwen3 30B A3B (free) - Community tier for Qwen3 30B A3B
325    OpenRouterQwen330bA3bFree,
326    /// Qwen3 30B A3B Instruct 2507 - Instruction-tuned Qwen3 30B A3B
327    OpenRouterQwen330bA3bInstruct2507,
328    /// Qwen3 30B A3B Thinking 2507 - Deliberative Qwen3 30B A3B release
329    OpenRouterQwen330bA3bThinking2507,
330    /// Qwen3 14B - Lightweight Qwen3 14B model
331    OpenRouterQwen314b,
332    /// Qwen3 14B (free) - Community tier for Qwen3 14B
333    OpenRouterQwen314bFree,
334    /// Qwen3 8B - Compact Qwen3 8B deployment
335    OpenRouterQwen38b,
336    /// Qwen3 8B (free) - Community tier for Qwen3 8B
337    OpenRouterQwen38bFree,
338    /// Qwen3 4B (free) - Entry level Qwen3 4B deployment
339    OpenRouterQwen34bFree,
340    /// Qwen3 Next 80B A3B Instruct - Next-generation Qwen3 instruction model
341    OpenRouterQwen3Next80bA3bInstruct,
342    /// Qwen3 Next 80B A3B Thinking - Next-generation Qwen3 reasoning release
343    OpenRouterQwen3Next80bA3bThinking,
344    /// Qwen3 Coder - Qwen3-based coding model tuned for IDE workflows
345    OpenRouterQwen3Coder,
346    /// Qwen3 Coder (free) - Community tier for Qwen3 Coder
347    OpenRouterQwen3CoderFree,
348    /// Qwen3 Coder Plus - Premium Qwen3 coding model with long context
349    OpenRouterQwen3CoderPlus,
350    /// Qwen3 Coder Flash - Latency optimised Qwen3 coding model
351    OpenRouterQwen3CoderFlash,
352    /// Qwen3 Coder 30B A3B Instruct - Large Mixture-of-Experts coding deployment
353    OpenRouterQwen3Coder30bA3bInstruct,
354    /// DeepSeek V3.2 Exp - Experimental DeepSeek V3.2 listing
355    OpenRouterDeepSeekV32Exp,
356    /// DeepSeek Chat v3.1 - Advanced DeepSeek model via OpenRouter
357    OpenRouterDeepSeekChatV31,
358    /// DeepSeek R1 - DeepSeek R1 reasoning model with chain-of-thought
359    OpenRouterDeepSeekR1,
360    /// DeepSeek Chat v3.1 (free) - Community tier for DeepSeek Chat v3.1
361    OpenRouterDeepSeekChatV31Free,
362    /// Nemotron Nano 9B v2 (free) - NVIDIA Nemotron Nano 9B v2 community tier
363    OpenRouterNvidiaNemotronNano9bV2Free,
364    /// OpenAI gpt-oss-120b - Open-weight 120B reasoning model via OpenRouter
365    OpenRouterOpenAIGptOss120b,
366    /// OpenAI gpt-oss-20b - Open-weight 20B deployment via OpenRouter
367    OpenRouterOpenAIGptOss20b,
368    /// OpenAI gpt-oss-20b (free) - Community tier for OpenAI gpt-oss-20b
369    OpenRouterOpenAIGptOss20bFree,
370    /// OpenAI GPT-5 - OpenAI GPT-5 model accessed through OpenRouter
371    OpenRouterOpenAIGpt5,
372    /// OpenAI GPT-5 Codex - OpenRouter listing for GPT-5 Codex
373    OpenRouterOpenAIGpt5Codex,
374    /// OpenAI GPT-5 Chat - Chat optimised GPT-5 endpoint without tool use
375    OpenRouterOpenAIGpt5Chat,
376    /// OpenAI GPT-4o Search Preview - GPT-4o search preview endpoint via OpenRouter
377    OpenRouterOpenAIGpt4oSearchPreview,
378    /// OpenAI GPT-4o Mini Search Preview - GPT-4o mini search preview endpoint
379    OpenRouterOpenAIGpt4oMiniSearchPreview,
380    /// OpenAI ChatGPT-4o Latest - ChatGPT 4o latest listing via OpenRouter
381    OpenRouterOpenAIChatgpt4oLatest,
382    /// Claude Sonnet 4.5 - Anthropic Claude Sonnet 4.5 listing
383    OpenRouterAnthropicClaudeSonnet45,
384    /// Claude Haiku 4.5 - Anthropic Claude Haiku 4.5 listing
385    OpenRouterAnthropicClaudeHaiku45,
386    /// Claude Opus 4.1 - Anthropic Claude Opus 4.1 listing
387    OpenRouterAnthropicClaudeOpus41,
388    /// MiniMax-M2 (free) - Community tier for MiniMax-M2
389    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    /// Convert the model identifier to its string representation
481    /// used in API calls and configurations
482    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            // Gemini models
489            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            // OpenAI models
495            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            // Anthropic models
501            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            // DeepSeek models
507            ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
508            ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
509            // xAI models
510            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            // Z.AI models
516            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            // Moonshot models
524            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            // Ollama models
533            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            // LM Studio models
544            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            // OpenRouter models
551            _ => unreachable!(),
552        }
553    }
554
555    /// Get the provider for this model
556    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    /// Whether this model supports configurable reasoning effort levels
619    pub fn supports_reasoning_effort(&self) -> bool {
620        self.provider().supports_reasoning_effort(self.as_str())
621    }
622
623    /// Get the display name for the model (human-readable)
624    pub fn display_name(&self) -> &'static str {
625        if let Some(meta) = self.openrouter_metadata() {
626            return meta.display;
627        }
628        match self {
629            // Gemini models
630            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            // OpenAI models
636            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            // Anthropic models
642            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            // DeepSeek models
648            ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
649            ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
650            // xAI models
651            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            // Z.AI models
657            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            // Moonshot models
665            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            // Ollama models
674            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            // OpenRouter models
691            _ => unreachable!(),
692        }
693    }
694
695    /// Get a description of the model's characteristics
696    pub fn description(&self) -> &'static str {
697        if let Some(meta) = self.openrouter_metadata() {
698            return meta.description;
699        }
700        match self {
701            // Gemini models
702            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            // OpenAI models
716            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            // Anthropic models
730            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            // DeepSeek models
744            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            // xAI models
751            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            // Z.AI models
759            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            // Moonshot models
771            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    /// Return the OpenRouter vendor slug when this identifier maps to a marketplace listing
846    pub fn openrouter_vendor(&self) -> Option<&'static str> {
847        self.openrouter_metadata().map(|meta| meta.vendor)
848    }
849
850    /// Get all available models as a vector
851    pub fn all_models() -> Vec<ModelId> {
852        let mut models = vec![
853            // Gemini models
854            ModelId::Gemini25FlashPreview,
855            ModelId::Gemini25Flash,
856            ModelId::Gemini25FlashLite,
857            ModelId::Gemini25Pro,
858            // OpenAI models
859            ModelId::GPT5,
860            ModelId::GPT5Codex,
861            ModelId::GPT5Mini,
862            ModelId::GPT5Nano,
863            ModelId::CodexMiniLatest,
864            // Anthropic models
865            ModelId::ClaudeOpus45,
866            ModelId::ClaudeOpus41,
867            ModelId::ClaudeSonnet45,
868            ModelId::ClaudeHaiku45,
869            ModelId::ClaudeSonnet4,
870            // DeepSeek models
871            ModelId::DeepSeekChat,
872            ModelId::DeepSeekReasoner,
873            // xAI models
874            ModelId::XaiGrok4,
875            ModelId::XaiGrok4Mini,
876            ModelId::XaiGrok4Code,
877            ModelId::XaiGrok4CodeLatest,
878            ModelId::XaiGrok4Vision,
879            // Z.AI models
880            ModelId::ZaiGlm46,
881            ModelId::ZaiGlm45,
882            ModelId::ZaiGlm45Air,
883            ModelId::ZaiGlm45X,
884            ModelId::ZaiGlm45Airx,
885            ModelId::ZaiGlm45Flash,
886            ModelId::ZaiGlm432b0414128k,
887            // Moonshot models
888            ModelId::MoonshotKimiK2TurboPreview,
889            ModelId::MoonshotKimiK2Thinking,
890            ModelId::MoonshotKimiK20905Preview,
891            ModelId::MoonshotKimiK20711Preview,
892            ModelId::MoonshotKimiLatest,
893            ModelId::MoonshotKimiLatest8k,
894            ModelId::MoonshotKimiLatest32k,
895            ModelId::MoonshotKimiLatest128k,
896            // Ollama models
897            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            // LM Studio models
907            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    /// Get all models for a specific provider
919    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    /// Get recommended fallback models in order of preference
927    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    /// Get the default orchestrator model (more capable)
945    pub fn default_orchestrator() -> Self {
946        ModelId::Gemini25Pro
947    }
948
949    /// Get the default subagent model (fast and efficient)
950    pub fn default_subagent() -> Self {
951        ModelId::Gemini25FlashPreview
952    }
953
954    /// Get provider-specific defaults for orchestrator
955    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    /// Get provider-specific defaults for subagent
971    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    /// Get provider-specific defaults for single agent
987    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    /// Check if this is a "flash" variant (optimized for speed)
1003    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    /// Check if this is a "pro" variant (optimized for capability)
1016    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    /// Check if this is an optimized/efficient variant
1033    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    /// Check if this is a top-tier model
1056    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    /// Determine whether the model is a reasoning-capable variant
1079    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    /// Determine whether the model supports tool calls/function execution
1090    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    /// Get the generation/version string for this model
1098    pub fn generation(&self) -> &'static str {
1099        if let Some(meta) = self.openrouter_metadata() {
1100            return meta.generation;
1101        }
1102        match self {
1103            // Gemini generations
1104            ModelId::Gemini25FlashPreview
1105            | ModelId::Gemini25Flash
1106            | ModelId::Gemini25FlashLite
1107            | ModelId::Gemini25Pro => "2.5",
1108            ModelId::Gemini3ProPreview => "3",
1109            // OpenAI generations
1110            ModelId::GPT5
1111            | ModelId::GPT5Codex
1112            | ModelId::GPT5Mini
1113            | ModelId::GPT5Nano
1114            | ModelId::CodexMiniLatest => "5",
1115            // Anthropic generations
1116            ModelId::ClaudeOpus45 => "4.5",
1117            ModelId::ClaudeSonnet45 | ModelId::ClaudeHaiku45 => "4.5",
1118            ModelId::ClaudeSonnet4 => "4",
1119            ModelId::ClaudeOpus41 => "4.1",
1120            // DeepSeek generations
1121            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
1122            // xAI generations
1123            ModelId::XaiGrok4
1124            | ModelId::XaiGrok4Mini
1125            | ModelId::XaiGrok4Code
1126            | ModelId::XaiGrok4CodeLatest
1127            | ModelId::XaiGrok4Vision => "4",
1128            // Z.AI generations
1129            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            // Moonshot generations
1137            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            // Gemini models
1178            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            // OpenAI models
1184            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            // Anthropic models
1192            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            // DeepSeek models
1198            s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
1199            s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
1200            // xAI models
1201            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            // Z.AI models
1207            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            // Moonshot models
1215            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/// Error type for model parsing failures
1267#[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        // Gemini models
1314        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        // OpenAI models
1325        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        // Anthropic models
1331        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        // DeepSeek models
1339        assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
1340        assert_eq!(
1341            ModelId::DeepSeekReasoner.as_str(),
1342            models::DEEPSEEK_REASONER
1343        );
1344        // xAI models
1345        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        // Z.AI models
1354        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        // Gemini models
1372        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        // OpenAI models
1389        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        // Anthropic models
1415        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        // DeepSeek models
1434        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        // xAI models
1443        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        // Z.AI models
1464        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        // Invalid model
1538        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        // Flash variants
1723        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        // Pro variants
1732        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        // Efficient variants
1742        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        // Top tier models
1761        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        // Gemini generations
1783        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        // OpenAI generations
1789        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        // Anthropic generations
1796        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        // DeepSeek generations
1802        assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1803        assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1804
1805        // xAI generations
1806        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        // Z.AI generations
1812        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); // Updated from 3 to 9
1908
1909        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        // Test parsing of new Ollama cloud models
1924        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            // Verify display names are not empty
1958            assert!(!model_id.display_name().is_empty());
1959
1960            // Verify descriptions are not empty
1961            assert!(!model_id.description().is_empty());
1962
1963            // Verify generation is not empty
1964            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}