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