vtcode_core/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)]
12struct OpenRouterMetadata {
13    id: &'static str,
14    display: &'static str,
15    description: &'static str,
16    efficient: bool,
17    top_tier: bool,
18    generation: &'static str,
19}
20
21macro_rules! each_openrouter_variant {
22    ($macro:ident) => {
23        $macro! {
24            (OpenRouterGrokCodeFast1, OPENROUTER_X_AI_GROK_CODE_FAST_1, "Grok Code Fast 1", "Fast OpenRouter coding model powered by xAI Grok", true, false, "marketplace"),
25            (OpenRouterGrok4Fast, OPENROUTER_X_AI_GROK_4_FAST, "Grok 4 Fast", "Reasoning-focused Grok endpoint with transparent traces", false, true, "marketplace"),
26            (OpenRouterGrok4, OPENROUTER_X_AI_GROK_4, "Grok 4", "Flagship Grok 4 endpoint exposed through OpenRouter", false, true, "marketplace"),
27            (OpenRouterZaiGlm45AirFree, OPENROUTER_Z_AI_GLM_4_5_AIR_FREE, "GLM 4.5 Air (free)", "Community tier for Z.AI GLM 4.5 Air", false, false, "GLM-4.5"),
28            (OpenRouterZaiGlm46, OPENROUTER_Z_AI_GLM_4_6, "GLM 4.6", "Z.AI GLM 4.6 long-context reasoning model", false, true, "GLM-4.6"),
29            (OpenRouterMoonshotaiKimiK20905, OPENROUTER_MOONSHOTAI_KIMI_K2_0905, "Kimi K2 0905", "MoonshotAI Kimi K2 0905 MoE release optimised for coding agents", false, true, "K2-0905"),
30            (OpenRouterQwen3Max, OPENROUTER_QWEN3_MAX, "Qwen3 Max", "Flagship Qwen3 mixture for general reasoning", false, true, "Qwen3"),
31            (OpenRouterQwen3235bA22b, OPENROUTER_QWEN3_235B_A22B, "Qwen3 235B A22B", "Mixture-of-experts Qwen3 235B general model", false, true, "Qwen3"),
32            (OpenRouterQwen3235bA22bFree, OPENROUTER_QWEN3_235B_A22B_FREE, "Qwen3 235B A22B (free)", "Community tier for Qwen3 235B A22B", false, true, "Qwen3"),
33            (OpenRouterQwen3235bA22b2507, OPENROUTER_QWEN3_235B_A22B_2507, "Qwen3 235B A22B Instruct 2507", "Instruction-tuned Qwen3 235B A22B", false, true, "Qwen3-2507"),
34            (OpenRouterQwen3235bA22bThinking2507, OPENROUTER_QWEN3_235B_A22B_THINKING_2507, "Qwen3 235B A22B Thinking 2507", "Deliberative Qwen3 235B A22B reasoning release", false, true, "Qwen3-2507"),
35            (OpenRouterQwen332b, OPENROUTER_QWEN3_32B, "Qwen3 32B", "Dense 32B Qwen3 deployment", false, false, "Qwen3-32B"),
36            (OpenRouterQwen330bA3b, OPENROUTER_QWEN3_30B_A3B, "Qwen3 30B A3B", "Active-parameter 30B Qwen3 model", false, false, "Qwen3-30B"),
37            (OpenRouterQwen330bA3bFree, OPENROUTER_QWEN3_30B_A3B_FREE, "Qwen3 30B A3B (free)", "Community tier for Qwen3 30B A3B", false, false, "Qwen3-30B"),
38            (OpenRouterQwen330bA3bInstruct2507, OPENROUTER_QWEN3_30B_A3B_INSTRUCT_2507, "Qwen3 30B A3B Instruct 2507", "Instruction-tuned Qwen3 30B A3B", false, false, "Qwen3-30B"),
39            (OpenRouterQwen330bA3bThinking2507, OPENROUTER_QWEN3_30B_A3B_THINKING_2507, "Qwen3 30B A3B Thinking 2507", "Deliberative Qwen3 30B A3B release", false, true, "Qwen3-30B"),
40            (OpenRouterQwen314b, OPENROUTER_QWEN3_14B, "Qwen3 14B", "Lightweight Qwen3 14B model", true, false, "Qwen3-14B"),
41            (OpenRouterQwen314bFree, OPENROUTER_QWEN3_14B_FREE, "Qwen3 14B (free)", "Community tier for Qwen3 14B", true, false, "Qwen3-14B"),
42            (OpenRouterQwen38b, OPENROUTER_QWEN3_8B, "Qwen3 8B", "Compact Qwen3 8B deployment", true, false, "Qwen3-8B"),
43            (OpenRouterQwen38bFree, OPENROUTER_QWEN3_8B_FREE, "Qwen3 8B (free)", "Community tier for Qwen3 8B", true, false, "Qwen3-8B"),
44            (OpenRouterQwen34bFree, OPENROUTER_QWEN3_4B_FREE, "Qwen3 4B (free)", "Entry level Qwen3 4B deployment", true, false, "Qwen3-4B"),
45            (OpenRouterQwen3Next80bA3bInstruct, OPENROUTER_QWEN3_NEXT_80B_A3B_INSTRUCT, "Qwen3 Next 80B A3B Instruct", "Next-generation Qwen3 instruction model", false, false, "Qwen3-Next"),
46            (OpenRouterQwen3Next80bA3bThinking, OPENROUTER_QWEN3_NEXT_80B_A3B_THINKING, "Qwen3 Next 80B A3B Thinking", "Next-generation Qwen3 reasoning release", false, true, "Qwen3-Next"),
47            (OpenRouterQwen3Coder, OPENROUTER_QWEN3_CODER, "Qwen3 Coder", "Qwen3-based coding model tuned for IDE workflows", false, true, "Qwen3-Coder"),
48            (OpenRouterQwen3CoderFree, OPENROUTER_QWEN3_CODER_FREE, "Qwen3 Coder (free)", "Community tier for Qwen3 Coder", false, false, "Qwen3-Coder"),
49            (OpenRouterQwen3CoderPlus, OPENROUTER_QWEN3_CODER_PLUS, "Qwen3 Coder Plus", "Premium Qwen3 coding model with long context", false, true, "Qwen3-Coder"),
50            (OpenRouterQwen3CoderFlash, OPENROUTER_QWEN3_CODER_FLASH, "Qwen3 Coder Flash", "Latency optimised Qwen3 coding model", true, false, "Qwen3-Coder"),
51            (OpenRouterQwen3Coder30bA3bInstruct, OPENROUTER_QWEN3_CODER_30B_A3B_INSTRUCT, "Qwen3 Coder 30B A3B Instruct", "Large Mixture-of-Experts coding deployment", false, true, "Qwen3-Coder"),
52            (OpenRouterDeepSeekV32Exp, OPENROUTER_DEEPSEEK_V3_2_EXP, "DeepSeek V3.2 Exp", "Experimental DeepSeek V3.2 listing", false, true, "V3.2-Exp"),
53            (OpenRouterDeepSeekChatV31, OPENROUTER_DEEPSEEK_CHAT_V3_1, "DeepSeek Chat v3.1", "Advanced DeepSeek model via OpenRouter", false, true, "2025-08-21"),
54            (OpenRouterDeepSeekR1, OPENROUTER_DEEPSEEK_R1, "DeepSeek R1", "DeepSeek R1 reasoning model with chain-of-thought", false, true, "2025-01-20"),
55            (OpenRouterOpenAIGptOss120b, OPENROUTER_OPENAI_GPT_OSS_120B, "OpenAI gpt-oss-120b", "Open-weight 120B reasoning model via OpenRouter", false, true, "OSS-120B"),
56            (OpenRouterOpenAIGptOss20b, OPENROUTER_OPENAI_GPT_OSS_20B, "OpenAI gpt-oss-20b", "Open-weight 20B deployment via OpenRouter", false, false, "OSS-20B"),
57            (OpenRouterOpenAIGptOss20bFree, OPENROUTER_OPENAI_GPT_OSS_20B_FREE, "OpenAI gpt-oss-20b (free)", "Community tier for OpenAI gpt-oss-20b", false, false, "OSS-20B"),
58            (OpenRouterOpenAIGpt5, OPENROUTER_OPENAI_GPT_5, "OpenAI GPT-5", "OpenAI GPT-5 model accessed through OpenRouter", false, true, "2025-09-20"),
59            (OpenRouterOpenAIGpt5Codex, OPENROUTER_OPENAI_GPT_5_CODEX, "OpenAI GPT-5 Codex", "OpenRouter listing for GPT-5 Codex", false, true, "2025-09-20"),
60            (OpenRouterOpenAIGpt5Chat, OPENROUTER_OPENAI_GPT_5_CHAT, "OpenAI GPT-5 Chat", "Chat optimised GPT-5 endpoint without tool use", false, false, "2025-09-20"),
61            (OpenRouterOpenAIGpt4oSearchPreview, OPENROUTER_OPENAI_GPT_4O_SEARCH_PREVIEW, "OpenAI GPT-4o Search Preview", "GPT-4o search preview endpoint via OpenRouter", false, false, "4o-Search"),
62            (OpenRouterOpenAIGpt4oMiniSearchPreview, OPENROUTER_OPENAI_GPT_4O_MINI_SEARCH_PREVIEW, "OpenAI GPT-4o Mini Search Preview", "GPT-4o mini search preview endpoint", false, false, "4o-Search"),
63            (OpenRouterOpenAIChatgpt4oLatest, OPENROUTER_OPENAI_CHATGPT_4O_LATEST, "OpenAI ChatGPT-4o Latest", "ChatGPT 4o latest listing via OpenRouter", false, false, "4o"),
64            (OpenRouterAnthropicClaudeSonnet45, OPENROUTER_ANTHROPIC_CLAUDE_SONNET_4_5, "Claude Sonnet 4.5", "Anthropic Claude Sonnet 4.5 listing", false, true, "2025-09-29"),
65            (OpenRouterAnthropicClaudeOpus41, OPENROUTER_ANTHROPIC_CLAUDE_OPUS_4_1, "Claude Opus 4.1", "Anthropic Claude Opus 4.1 listing", false, true, "2025-08-05"),
66        }
67    };
68}
69
70/// Supported AI model providers
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
72pub enum Provider {
73    /// Google Gemini models
74    #[default]
75    Gemini,
76    /// OpenAI GPT models
77    OpenAI,
78    /// Anthropic Claude models
79    Anthropic,
80    /// DeepSeek native models
81    DeepSeek,
82    /// OpenRouter marketplace models
83    OpenRouter,
84    /// Moonshot.ai models
85    Moonshot,
86    /// xAI Grok models
87    XAI,
88    /// Z.AI GLM models
89    ZAI,
90}
91
92impl Provider {
93    /// Get the default API key environment variable for this provider
94    pub fn default_api_key_env(&self) -> &'static str {
95        match self {
96            Provider::Gemini => "GEMINI_API_KEY",
97            Provider::OpenAI => "OPENAI_API_KEY",
98            Provider::Anthropic => "ANTHROPIC_API_KEY",
99            Provider::DeepSeek => "DEEPSEEK_API_KEY",
100            Provider::OpenRouter => "OPENROUTER_API_KEY",
101            Provider::Moonshot => "MOONSHOT_API_KEY",
102            Provider::XAI => "XAI_API_KEY",
103            Provider::ZAI => "ZAI_API_KEY",
104        }
105    }
106
107    /// Get all supported providers
108    pub fn all_providers() -> Vec<Provider> {
109        vec![
110            Provider::Gemini,
111            Provider::OpenAI,
112            Provider::Anthropic,
113            Provider::DeepSeek,
114            Provider::OpenRouter,
115            Provider::Moonshot,
116            Provider::XAI,
117            Provider::ZAI,
118        ]
119    }
120
121    /// Human-friendly label for display purposes
122    pub fn label(&self) -> &'static str {
123        match self {
124            Provider::Gemini => "Gemini",
125            Provider::OpenAI => "OpenAI",
126            Provider::Anthropic => "Anthropic",
127            Provider::DeepSeek => "DeepSeek",
128            Provider::OpenRouter => "OpenRouter",
129            Provider::Moonshot => "Moonshot",
130            Provider::XAI => "xAI",
131            Provider::ZAI => "Z.AI",
132        }
133    }
134
135    /// Determine if the provider supports configurable reasoning effort for the model
136    pub fn supports_reasoning_effort(&self, model: &str) -> bool {
137        use crate::config::constants::models;
138
139        match self {
140            Provider::Gemini => model == models::google::GEMINI_2_5_PRO,
141            Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
142            Provider::Anthropic => models::anthropic::SUPPORTED_MODELS.contains(&model),
143            Provider::DeepSeek => model == models::deepseek::DEEPSEEK_REASONER,
144            Provider::OpenRouter => models::openrouter::REASONING_MODELS.contains(&model),
145            Provider::Moonshot => false,
146            Provider::XAI => model == models::xai::GROK_4 || model == models::xai::GROK_4_CODE,
147            Provider::ZAI => model == models::zai::GLM_4_6,
148        }
149    }
150}
151
152impl fmt::Display for Provider {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            Provider::Gemini => write!(f, "gemini"),
156            Provider::OpenAI => write!(f, "openai"),
157            Provider::Anthropic => write!(f, "anthropic"),
158            Provider::DeepSeek => write!(f, "deepseek"),
159            Provider::OpenRouter => write!(f, "openrouter"),
160            Provider::Moonshot => write!(f, "moonshot"),
161            Provider::XAI => write!(f, "xai"),
162            Provider::ZAI => write!(f, "zai"),
163        }
164    }
165}
166
167impl FromStr for Provider {
168    type Err = ModelParseError;
169
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        match s.to_lowercase().as_str() {
172            "gemini" => Ok(Provider::Gemini),
173            "openai" => Ok(Provider::OpenAI),
174            "anthropic" => Ok(Provider::Anthropic),
175            "deepseek" => Ok(Provider::DeepSeek),
176            "openrouter" => Ok(Provider::OpenRouter),
177            "moonshot" => Ok(Provider::Moonshot),
178            "xai" => Ok(Provider::XAI),
179            "zai" => Ok(Provider::ZAI),
180            _ => Err(ModelParseError::InvalidProvider(s.to_string())),
181        }
182    }
183}
184
185/// Centralized enum for all supported model identifiers
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub enum ModelId {
188    // Gemini models
189    /// Gemini 2.5 Flash Preview - Latest fast model with advanced capabilities
190    Gemini25FlashPreview,
191    /// Gemini 2.5 Flash - Legacy alias for flash preview
192    Gemini25Flash,
193    /// Gemini 2.5 Flash Lite - Legacy alias for flash preview (lite)
194    Gemini25FlashLite,
195    /// Gemini 2.5 Pro - Latest most capable Gemini model
196    Gemini25Pro,
197
198    // OpenAI models
199    /// GPT-5 - Latest most capable OpenAI model (2025-08-07)
200    GPT5,
201    /// GPT-5 Codex - Code-focused GPT-5 variant using the Responses API
202    GPT5Codex,
203    /// GPT-5 Mini - Latest efficient OpenAI model (2025-08-07)
204    GPT5Mini,
205    /// GPT-5 Nano - Latest most cost-effective OpenAI model (2025-08-07)
206    GPT5Nano,
207    /// Codex Mini Latest - Latest Codex model for code generation (2025-05-16)
208    CodexMiniLatest,
209
210    // Anthropic models
211    /// Claude Opus 4.1 - Latest most capable Anthropic model (2025-08-05)
212    ClaudeOpus41,
213    /// Claude Sonnet 4.5 - Latest balanced Anthropic model (2025-09-29)
214    ClaudeSonnet45,
215    /// Claude Sonnet 4 - Previous balanced Anthropic model (2025-05-14)
216    ClaudeSonnet4,
217
218    // DeepSeek models
219    /// DeepSeek V3.2-Exp Chat - Non-thinking mode
220    DeepSeekChat,
221    /// DeepSeek V3.2-Exp Reasoner - Thinking mode with deliberate reasoning output
222    DeepSeekReasoner,
223
224    // xAI models
225    /// Grok-4 - Flagship xAI model with advanced reasoning
226    XaiGrok4,
227    /// Grok-4 Mini - Efficient xAI model variant
228    XaiGrok4Mini,
229    /// Grok-4 Code - Code-focused Grok deployment
230    XaiGrok4Code,
231    /// Grok-4 Code Latest - Latest Grok code model with enhanced reasoning tools
232    XaiGrok4CodeLatest,
233    /// Grok-4 Vision - Multimodal Grok model
234    XaiGrok4Vision,
235
236    // Z.AI models
237    /// GLM-4.6 - Latest flagship GLM reasoning model
238    ZaiGlm46,
239    /// GLM-4.5 - Balanced GLM release for general tasks
240    ZaiGlm45,
241    /// GLM-4.5-Air - Efficient GLM variant
242    ZaiGlm45Air,
243    /// GLM-4.5-X - Enhanced capability GLM variant
244    ZaiGlm45X,
245    /// GLM-4.5-AirX - Hybrid efficient GLM variant
246    ZaiGlm45Airx,
247    /// GLM-4.5-Flash - Low-latency GLM variant
248    ZaiGlm45Flash,
249    /// GLM-4-32B-0414-128K - Legacy long-context GLM deployment
250    ZaiGlm432b0414128k,
251
252    // Moonshot.ai models
253    /// Moonshot v1 8K - Fast deployment with 8K context window
254    MoonshotV18k,
255    /// Moonshot v1 32K - Balanced option for longer tasks
256    MoonshotV132k,
257    /// Moonshot v1 128K - Maximum context Moonshot flagship
258    MoonshotV1128k,
259
260    // OpenRouter models
261    /// Grok Code Fast 1 - Fast OpenRouter coding model
262    OpenRouterGrokCodeFast1,
263    /// Grok 4 Fast - Reasoning-focused Grok endpoint
264    OpenRouterGrok4Fast,
265    /// Grok 4 - Highest quality Grok endpoint on OpenRouter
266    OpenRouterGrok4,
267    /// Z.AI GLM 4.5 Air (free) - Community tier for GLM 4.5 Air
268    OpenRouterZaiGlm45AirFree,
269    /// Z.AI GLM 4.6 - Long-context GLM upgrade via OpenRouter
270    OpenRouterZaiGlm46,
271    /// MoonshotAI Kimi K2 0905 - Latest MoE update via OpenRouter
272    OpenRouterMoonshotaiKimiK20905,
273    /// Qwen3 Max - Flagship Qwen3 deployment via OpenRouter
274    OpenRouterQwen3Max,
275    /// Qwen3 235B A22B - General Qwen3 expert mixture
276    OpenRouterQwen3235bA22b,
277    /// Qwen3 235B A22B (free tier) - Community access listing
278    OpenRouterQwen3235bA22bFree,
279    /// Qwen3 235B A22B Instruct 2507 - Instruction tuned release
280    OpenRouterQwen3235bA22b2507,
281    /// Qwen3 235B A22B Thinking 2507 - Deliberative reasoning release
282    OpenRouterQwen3235bA22bThinking2507,
283    /// Qwen3 32B - Mid-tier dense Qwen3 variant
284    OpenRouterQwen332b,
285    /// Qwen3 30B A3B - Efficient active-parameter mixture
286    OpenRouterQwen330bA3b,
287    /// Qwen3 30B A3B (free tier) - Community access variant
288    OpenRouterQwen330bA3bFree,
289    /// Qwen3 30B A3B Instruct 2507 - Instruction tuned efficient variant
290    OpenRouterQwen330bA3bInstruct2507,
291    /// Qwen3 30B A3B Thinking 2507 - Deliberative efficient variant
292    OpenRouterQwen330bA3bThinking2507,
293    /// Qwen3 14B - Lightweight Qwen3 deployment
294    OpenRouterQwen314b,
295    /// Qwen3 14B (free tier) - Community 14B deployment
296    OpenRouterQwen314bFree,
297    /// Qwen3 8B - Compact Qwen3 deployment
298    OpenRouterQwen38b,
299    /// Qwen3 8B (free tier) - Community 8B deployment
300    OpenRouterQwen38bFree,
301    /// Qwen3 4B (free tier) - Entry level Qwen3 deployment
302    OpenRouterQwen34bFree,
303    /// Qwen3 Next 80B A3B Instruct - Next-gen Qwen3 instruction model
304    OpenRouterQwen3Next80bA3bInstruct,
305    /// Qwen3 Next 80B A3B Thinking - Next-gen Qwen3 reasoning model
306    OpenRouterQwen3Next80bA3bThinking,
307    /// Qwen3 Coder - Balanced OpenRouter coding model
308    OpenRouterQwen3Coder,
309    /// Qwen3 Coder (free tier) - Community coding access
310    OpenRouterQwen3CoderFree,
311    /// Qwen3 Coder Plus - High quality Qwen3 coding model
312    OpenRouterQwen3CoderPlus,
313    /// Qwen3 Coder Flash - Low-latency Qwen3 coding model
314    OpenRouterQwen3CoderFlash,
315    /// Qwen3 Coder 30B A3B Instruct - Large coding MoE deployment
316    OpenRouterQwen3Coder30bA3bInstruct,
317    /// DeepSeek V3.2 Exp - Experimental DeepSeek listing
318    OpenRouterDeepSeekV32Exp,
319    /// DeepSeek Chat v3.1 - Advanced DeepSeek model via OpenRouter
320    OpenRouterDeepSeekChatV31,
321    /// DeepSeek R1 - Reasoning model via OpenRouter
322    OpenRouterDeepSeekR1,
323    /// OpenAI gpt-oss-120b via OpenRouter
324    OpenRouterOpenAIGptOss120b,
325    /// OpenAI gpt-oss-20b via OpenRouter
326    OpenRouterOpenAIGptOss20b,
327    /// OpenAI gpt-oss-20b (free tier) via OpenRouter
328    OpenRouterOpenAIGptOss20bFree,
329    /// OpenAI GPT-5 via OpenRouter
330    OpenRouterOpenAIGpt5,
331    /// OpenAI GPT-5 Codex via OpenRouter
332    OpenRouterOpenAIGpt5Codex,
333    /// OpenAI GPT-5 Chat preview via OpenRouter
334    OpenRouterOpenAIGpt5Chat,
335    /// OpenAI GPT-4o Search Preview via OpenRouter
336    OpenRouterOpenAIGpt4oSearchPreview,
337    /// OpenAI GPT-4o Mini Search Preview via OpenRouter
338    OpenRouterOpenAIGpt4oMiniSearchPreview,
339    /// OpenAI ChatGPT-4o Latest via OpenRouter
340    OpenRouterOpenAIChatgpt4oLatest,
341    /// Anthropic Claude Sonnet 4.5 via OpenRouter
342    OpenRouterAnthropicClaudeSonnet45,
343    /// Anthropic Claude Opus 4.1 via OpenRouter
344    OpenRouterAnthropicClaudeOpus41,
345}
346impl ModelId {
347    fn openrouter_metadata(&self) -> Option<OpenRouterMetadata> {
348        use crate::config::constants::models;
349
350        macro_rules! metadata_match {
351            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
352                match self {
353                    $(ModelId::$variant => Some(OpenRouterMetadata {
354                        id: models::$const,
355                        display: $display,
356                        description: $description,
357                        efficient: $efficient,
358                        top_tier: $top,
359                        generation: $generation,
360                    }),)*
361                    _ => None,
362                }
363            };
364        }
365
366        each_openrouter_variant!(metadata_match)
367    }
368
369    fn parse_openrouter_model(value: &str) -> Option<Self> {
370        use crate::config::constants::models;
371
372        macro_rules! parse_match {
373            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
374                match value {
375                    $(models::$const => Some(ModelId::$variant),)*
376                    _ => None,
377                }
378            };
379        }
380
381        each_openrouter_variant!(parse_match)
382    }
383
384    fn openrouter_models() -> Vec<Self> {
385        macro_rules! to_vec {
386            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
387                vec![$(ModelId::$variant,)*]
388            };
389        }
390
391        each_openrouter_variant!(to_vec)
392    }
393
394    /// Convert the model identifier to its string representation
395    /// used in API calls and configurations
396    pub fn as_str(&self) -> &'static str {
397        use crate::config::constants::models;
398        if let Some(meta) = self.openrouter_metadata() {
399            return meta.id;
400        }
401        match self {
402            // Gemini models
403            ModelId::Gemini25FlashPreview => models::GEMINI_2_5_FLASH_PREVIEW,
404            ModelId::Gemini25Flash => models::GEMINI_2_5_FLASH,
405            ModelId::Gemini25FlashLite => models::GEMINI_2_5_FLASH_LITE,
406            ModelId::Gemini25Pro => models::GEMINI_2_5_PRO,
407            // OpenAI models
408            ModelId::GPT5 => models::GPT_5,
409            ModelId::GPT5Codex => models::GPT_5_CODEX,
410            ModelId::GPT5Mini => models::GPT_5_MINI,
411            ModelId::GPT5Nano => models::GPT_5_NANO,
412            ModelId::CodexMiniLatest => models::CODEX_MINI_LATEST,
413            // Anthropic models
414            ModelId::ClaudeOpus41 => models::CLAUDE_OPUS_4_1_20250805,
415            ModelId::ClaudeSonnet45 => models::CLAUDE_SONNET_4_5,
416            ModelId::ClaudeSonnet4 => models::CLAUDE_SONNET_4_20250514,
417            // DeepSeek models
418            ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
419            ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
420            // xAI models
421            ModelId::XaiGrok4 => models::xai::GROK_4,
422            ModelId::XaiGrok4Mini => models::xai::GROK_4_MINI,
423            ModelId::XaiGrok4Code => models::xai::GROK_4_CODE,
424            ModelId::XaiGrok4CodeLatest => models::xai::GROK_4_CODE_LATEST,
425            ModelId::XaiGrok4Vision => models::xai::GROK_4_VISION,
426            // Z.AI models
427            ModelId::ZaiGlm46 => models::zai::GLM_4_6,
428            ModelId::ZaiGlm45 => models::zai::GLM_4_5,
429            ModelId::ZaiGlm45Air => models::zai::GLM_4_5_AIR,
430            ModelId::ZaiGlm45X => models::zai::GLM_4_5_X,
431            ModelId::ZaiGlm45Airx => models::zai::GLM_4_5_AIRX,
432            ModelId::ZaiGlm45Flash => models::zai::GLM_4_5_FLASH,
433            ModelId::ZaiGlm432b0414128k => models::zai::GLM_4_32B_0414_128K,
434            // Moonshot models
435            ModelId::MoonshotV18k => models::MOONSHOT_V1_8K,
436            ModelId::MoonshotV132k => models::MOONSHOT_V1_32K,
437            ModelId::MoonshotV1128k => models::MOONSHOT_V1_128K,
438            // OpenRouter models
439            _ => unreachable!(),
440        }
441    }
442
443    /// Get the provider for this model
444    pub fn provider(&self) -> Provider {
445        if self.openrouter_metadata().is_some() {
446            return Provider::OpenRouter;
447        }
448        match self {
449            ModelId::Gemini25FlashPreview
450            | ModelId::Gemini25Flash
451            | ModelId::Gemini25FlashLite
452            | ModelId::Gemini25Pro => Provider::Gemini,
453            ModelId::GPT5
454            | ModelId::GPT5Codex
455            | ModelId::GPT5Mini
456            | ModelId::GPT5Nano
457            | ModelId::CodexMiniLatest => Provider::OpenAI,
458            ModelId::ClaudeOpus41 | ModelId::ClaudeSonnet45 | ModelId::ClaudeSonnet4 => {
459                Provider::Anthropic
460            }
461            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => Provider::DeepSeek,
462            ModelId::XaiGrok4
463            | ModelId::XaiGrok4Mini
464            | ModelId::XaiGrok4Code
465            | ModelId::XaiGrok4CodeLatest
466            | ModelId::XaiGrok4Vision => Provider::XAI,
467            ModelId::ZaiGlm46
468            | ModelId::ZaiGlm45
469            | ModelId::ZaiGlm45Air
470            | ModelId::ZaiGlm45X
471            | ModelId::ZaiGlm45Airx
472            | ModelId::ZaiGlm45Flash
473            | ModelId::ZaiGlm432b0414128k => Provider::ZAI,
474            ModelId::MoonshotV18k | ModelId::MoonshotV132k | ModelId::MoonshotV1128k => {
475                Provider::Moonshot
476            }
477            _ => unreachable!(),
478        }
479    }
480
481    /// Whether this model supports configurable reasoning effort levels
482    pub fn supports_reasoning_effort(&self) -> bool {
483        self.provider().supports_reasoning_effort(self.as_str())
484    }
485
486    /// Get the display name for the model (human-readable)
487    pub fn display_name(&self) -> &'static str {
488        if let Some(meta) = self.openrouter_metadata() {
489            return meta.display;
490        }
491        match self {
492            // Gemini models
493            ModelId::Gemini25FlashPreview => "Gemini 2.5 Flash Preview",
494            ModelId::Gemini25Flash => "Gemini 2.5 Flash",
495            ModelId::Gemini25FlashLite => "Gemini 2.5 Flash Lite",
496            ModelId::Gemini25Pro => "Gemini 2.5 Pro",
497            // OpenAI models
498            ModelId::GPT5 => "GPT-5",
499            ModelId::GPT5Codex => "GPT-5 Codex",
500            ModelId::GPT5Mini => "GPT-5 Mini",
501            ModelId::GPT5Nano => "GPT-5 Nano",
502            ModelId::CodexMiniLatest => "Codex Mini Latest",
503            // Anthropic models
504            ModelId::ClaudeOpus41 => "Claude Opus 4.1",
505            ModelId::ClaudeSonnet45 => "Claude Sonnet 4.5",
506            ModelId::ClaudeSonnet4 => "Claude Sonnet 4",
507            // DeepSeek models
508            ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
509            ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
510            // xAI models
511            ModelId::XaiGrok4 => "Grok-4",
512            ModelId::XaiGrok4Mini => "Grok-4 Mini",
513            ModelId::XaiGrok4Code => "Grok-4 Code",
514            ModelId::XaiGrok4CodeLatest => "Grok-4 Code Latest",
515            ModelId::XaiGrok4Vision => "Grok-4 Vision",
516            // Z.AI models
517            ModelId::ZaiGlm46 => "GLM 4.6",
518            ModelId::ZaiGlm45 => "GLM 4.5",
519            ModelId::ZaiGlm45Air => "GLM 4.5 Air",
520            ModelId::ZaiGlm45X => "GLM 4.5 X",
521            ModelId::ZaiGlm45Airx => "GLM 4.5 AirX",
522            ModelId::ZaiGlm45Flash => "GLM 4.5 Flash",
523            ModelId::ZaiGlm432b0414128k => "GLM 4 32B 0414 128K",
524            // Moonshot models
525            ModelId::MoonshotV18k => "Moonshot v1 8K",
526            ModelId::MoonshotV132k => "Moonshot v1 32K",
527            ModelId::MoonshotV1128k => "Moonshot v1 128K",
528            // OpenRouter models
529            _ => unreachable!(),
530        }
531    }
532
533    /// Get a description of the model's characteristics
534    pub fn description(&self) -> &'static str {
535        if let Some(meta) = self.openrouter_metadata() {
536            return meta.description;
537        }
538        match self {
539            // Gemini models
540            ModelId::Gemini25FlashPreview => {
541                "Latest fast Gemini model with advanced multimodal capabilities"
542            }
543            ModelId::Gemini25Flash => {
544                "Legacy alias for Gemini 2.5 Flash Preview (same capabilities)"
545            }
546            ModelId::Gemini25FlashLite => {
547                "Legacy alias for Gemini 2.5 Flash Preview optimized for efficiency"
548            }
549            ModelId::Gemini25Pro => "Latest most capable Gemini model with reasoning",
550            // OpenAI models
551            ModelId::GPT5 => "Latest most capable OpenAI model with advanced reasoning",
552            ModelId::GPT5Codex => {
553                "Code-focused GPT-5 variant optimized for tool calling and structured outputs"
554            }
555            ModelId::GPT5Mini => "Latest efficient OpenAI model, great for most tasks",
556            ModelId::GPT5Nano => "Latest most cost-effective OpenAI model",
557            ModelId::CodexMiniLatest => "Latest Codex model optimized for code generation",
558            // Anthropic models
559            ModelId::ClaudeOpus41 => "Latest most capable Anthropic model with advanced reasoning",
560            ModelId::ClaudeSonnet45 => "Latest balanced Anthropic model for general tasks",
561            ModelId::ClaudeSonnet4 => {
562                "Previous balanced Anthropic model maintained for compatibility"
563            }
564            // DeepSeek models
565            ModelId::DeepSeekChat => {
566                "DeepSeek V3.2-Exp non-thinking mode optimized for fast coding responses"
567            }
568            ModelId::DeepSeekReasoner => {
569                "DeepSeek V3.2-Exp thinking mode with structured reasoning output"
570            }
571            // xAI models
572            ModelId::XaiGrok4 => "Flagship Grok 4 model with long context and tool use",
573            ModelId::XaiGrok4Mini => "Efficient Grok 4 Mini tuned for low latency",
574            ModelId::XaiGrok4Code => "Code-specialized Grok 4 deployment with tool support",
575            ModelId::XaiGrok4CodeLatest => {
576                "Latest Grok 4 code model offering enhanced reasoning traces"
577            }
578            ModelId::XaiGrok4Vision => "Multimodal Grok 4 model with image understanding",
579            // Z.AI models
580            ModelId::ZaiGlm46 => {
581                "Latest Z.AI GLM flagship with long-context reasoning and coding strengths"
582            }
583            ModelId::ZaiGlm45 => "Balanced GLM 4.5 release for general assistant tasks",
584            ModelId::ZaiGlm45Air => "Efficient GLM 4.5 Air variant tuned for lower latency",
585            ModelId::ZaiGlm45X => "Enhanced GLM 4.5 X variant with improved reasoning",
586            ModelId::ZaiGlm45Airx => "Hybrid GLM 4.5 AirX variant blending efficiency with quality",
587            ModelId::ZaiGlm45Flash => "Low-latency GLM 4.5 Flash optimized for responsiveness",
588            ModelId::ZaiGlm432b0414128k => {
589                "Legacy GLM 4 32B deployment offering extended 128K context window"
590            }
591            // Moonshot models
592            ModelId::MoonshotV18k => {
593                "Fast Moonshot v1 deployment tuned for quick iterations with 8K context"
594            }
595            ModelId::MoonshotV132k => {
596                "Balanced Moonshot v1 configuration blending speed and 32K context support"
597            }
598            ModelId::MoonshotV1128k => {
599                "Flagship Moonshot v1 model delivering maximum 128K context window"
600            }
601            _ => unreachable!(),
602        }
603    }
604
605    /// Get all available models as a vector
606    pub fn all_models() -> Vec<ModelId> {
607        let mut models = vec![
608            // Gemini models
609            ModelId::Gemini25FlashPreview,
610            ModelId::Gemini25Flash,
611            ModelId::Gemini25FlashLite,
612            ModelId::Gemini25Pro,
613            // OpenAI models
614            ModelId::GPT5,
615            ModelId::GPT5Codex,
616            ModelId::GPT5Mini,
617            ModelId::GPT5Nano,
618            ModelId::CodexMiniLatest,
619            // Anthropic models
620            ModelId::ClaudeOpus41,
621            ModelId::ClaudeSonnet45,
622            ModelId::ClaudeSonnet4,
623            // DeepSeek models
624            ModelId::DeepSeekChat,
625            ModelId::DeepSeekReasoner,
626            // xAI models
627            ModelId::XaiGrok4,
628            ModelId::XaiGrok4Mini,
629            ModelId::XaiGrok4Code,
630            ModelId::XaiGrok4CodeLatest,
631            ModelId::XaiGrok4Vision,
632            // Z.AI models
633            ModelId::ZaiGlm46,
634            ModelId::ZaiGlm45,
635            ModelId::ZaiGlm45Air,
636            ModelId::ZaiGlm45X,
637            ModelId::ZaiGlm45Airx,
638            ModelId::ZaiGlm45Flash,
639            ModelId::ZaiGlm432b0414128k,
640            // Moonshot models
641            ModelId::MoonshotV18k,
642            ModelId::MoonshotV132k,
643            ModelId::MoonshotV1128k,
644        ];
645        models.extend(Self::openrouter_models());
646        models
647    }
648
649    /// Get all models for a specific provider
650    pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
651        Self::all_models()
652            .into_iter()
653            .filter(|model| model.provider() == provider)
654            .collect()
655    }
656
657    /// Get recommended fallback models in order of preference
658    pub fn fallback_models() -> Vec<ModelId> {
659        vec![
660            ModelId::Gemini25FlashPreview,
661            ModelId::Gemini25Pro,
662            ModelId::GPT5,
663            ModelId::ClaudeOpus41,
664            ModelId::ClaudeSonnet45,
665            ModelId::DeepSeekReasoner,
666            ModelId::MoonshotV132k,
667            ModelId::XaiGrok4,
668            ModelId::ZaiGlm46,
669            ModelId::OpenRouterGrokCodeFast1,
670        ]
671    }
672
673    /// Get the default model for general use
674    pub fn default() -> Self {
675        ModelId::Gemini25FlashPreview
676    }
677
678    /// Get the default orchestrator model (more capable)
679    pub fn default_orchestrator() -> Self {
680        ModelId::Gemini25Pro
681    }
682
683    /// Get the default subagent model (fast and efficient)
684    pub fn default_subagent() -> Self {
685        ModelId::Gemini25FlashPreview
686    }
687
688    /// Get provider-specific defaults for orchestrator
689    pub fn default_orchestrator_for_provider(provider: Provider) -> Self {
690        match provider {
691            Provider::Gemini => ModelId::Gemini25Pro,
692            Provider::OpenAI => ModelId::GPT5,
693            Provider::Anthropic => ModelId::ClaudeOpus41,
694            Provider::DeepSeek => ModelId::DeepSeekReasoner,
695            Provider::Moonshot => ModelId::MoonshotV132k,
696            Provider::XAI => ModelId::XaiGrok4,
697            Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
698            Provider::ZAI => ModelId::ZaiGlm46,
699        }
700    }
701
702    /// Get provider-specific defaults for subagent
703    pub fn default_subagent_for_provider(provider: Provider) -> Self {
704        match provider {
705            Provider::Gemini => ModelId::Gemini25FlashPreview,
706            Provider::OpenAI => ModelId::GPT5Mini,
707            Provider::Anthropic => ModelId::ClaudeSonnet45,
708            Provider::DeepSeek => ModelId::DeepSeekChat,
709            Provider::Moonshot => ModelId::MoonshotV18k,
710            Provider::XAI => ModelId::XaiGrok4Code,
711            Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
712            Provider::ZAI => ModelId::ZaiGlm45Flash,
713        }
714    }
715
716    /// Get provider-specific defaults for single agent
717    pub fn default_single_for_provider(provider: Provider) -> Self {
718        match provider {
719            Provider::Gemini => ModelId::Gemini25FlashPreview,
720            Provider::OpenAI => ModelId::GPT5,
721            Provider::Anthropic => ModelId::ClaudeOpus41,
722            Provider::DeepSeek => ModelId::DeepSeekReasoner,
723            Provider::Moonshot => ModelId::MoonshotV132k,
724            Provider::XAI => ModelId::XaiGrok4,
725            Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
726            Provider::ZAI => ModelId::ZaiGlm46,
727        }
728    }
729
730    /// Check if this is a "flash" variant (optimized for speed)
731    pub fn is_flash_variant(&self) -> bool {
732        matches!(
733            self,
734            ModelId::Gemini25FlashPreview
735                | ModelId::Gemini25Flash
736                | ModelId::Gemini25FlashLite
737                | ModelId::ZaiGlm45Flash
738                | ModelId::MoonshotV18k
739        )
740    }
741
742    /// Check if this is a "pro" variant (optimized for capability)
743    pub fn is_pro_variant(&self) -> bool {
744        matches!(
745            self,
746            ModelId::Gemini25Pro
747                | ModelId::GPT5
748                | ModelId::GPT5Codex
749                | ModelId::ClaudeOpus41
750                | ModelId::DeepSeekReasoner
751                | ModelId::XaiGrok4
752                | ModelId::ZaiGlm46
753                | ModelId::MoonshotV1128k
754        )
755    }
756
757    /// Check if this is an optimized/efficient variant
758    pub fn is_efficient_variant(&self) -> bool {
759        if let Some(meta) = self.openrouter_metadata() {
760            return meta.efficient;
761        }
762        matches!(
763            self,
764            ModelId::Gemini25FlashPreview
765                | ModelId::Gemini25Flash
766                | ModelId::Gemini25FlashLite
767                | ModelId::GPT5Mini
768                | ModelId::GPT5Nano
769                | ModelId::DeepSeekChat
770                | ModelId::XaiGrok4Code
771                | ModelId::ZaiGlm45Air
772                | ModelId::ZaiGlm45Airx
773                | ModelId::ZaiGlm45Flash
774                | ModelId::MoonshotV18k
775        )
776    }
777
778    /// Check if this is a top-tier model
779    pub fn is_top_tier(&self) -> bool {
780        if let Some(meta) = self.openrouter_metadata() {
781            return meta.top_tier;
782        }
783        matches!(
784            self,
785            ModelId::Gemini25Pro
786                | ModelId::GPT5
787                | ModelId::GPT5Codex
788                | ModelId::ClaudeOpus41
789                | ModelId::ClaudeSonnet45
790                | ModelId::ClaudeSonnet4
791                | ModelId::DeepSeekReasoner
792                | ModelId::XaiGrok4
793                | ModelId::XaiGrok4CodeLatest
794                | ModelId::ZaiGlm46
795                | ModelId::MoonshotV1128k
796        )
797    }
798
799    /// Get the generation/version string for this model
800    pub fn generation(&self) -> &'static str {
801        if let Some(meta) = self.openrouter_metadata() {
802            return meta.generation;
803        }
804        match self {
805            // Gemini generations
806            ModelId::Gemini25FlashPreview
807            | ModelId::Gemini25Flash
808            | ModelId::Gemini25FlashLite
809            | ModelId::Gemini25Pro => "2.5",
810            // OpenAI generations
811            ModelId::GPT5
812            | ModelId::GPT5Codex
813            | ModelId::GPT5Mini
814            | ModelId::GPT5Nano
815            | ModelId::CodexMiniLatest => "5",
816            // Anthropic generations
817            ModelId::ClaudeSonnet45 => "4.5",
818            ModelId::ClaudeSonnet4 => "4",
819            ModelId::ClaudeOpus41 => "4.1",
820            // DeepSeek generations
821            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
822            // xAI generations
823            ModelId::XaiGrok4
824            | ModelId::XaiGrok4Mini
825            | ModelId::XaiGrok4Code
826            | ModelId::XaiGrok4CodeLatest
827            | ModelId::XaiGrok4Vision => "4",
828            // Z.AI generations
829            ModelId::ZaiGlm46 => "4.6",
830            ModelId::ZaiGlm45
831            | ModelId::ZaiGlm45Air
832            | ModelId::ZaiGlm45X
833            | ModelId::ZaiGlm45Airx
834            | ModelId::ZaiGlm45Flash => "4.5",
835            ModelId::ZaiGlm432b0414128k => "4-32B",
836            // Moonshot generations
837            ModelId::MoonshotV18k | ModelId::MoonshotV132k | ModelId::MoonshotV1128k => "v1",
838            _ => unreachable!(),
839        }
840    }
841}
842
843impl fmt::Display for ModelId {
844    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
845        write!(f, "{}", self.as_str())
846    }
847}
848
849impl FromStr for ModelId {
850    type Err = ModelParseError;
851
852    fn from_str(s: &str) -> Result<Self, Self::Err> {
853        use crate::config::constants::models;
854        match s {
855            // Gemini models
856            s if s == models::GEMINI_2_5_FLASH_PREVIEW => Ok(ModelId::Gemini25FlashPreview),
857            s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
858            s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
859            s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
860            // OpenAI models
861            s if s == models::GPT_5 => Ok(ModelId::GPT5),
862            s if s == models::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
863            s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
864            s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
865            s if s == models::CODEX_MINI_LATEST => Ok(ModelId::CodexMiniLatest),
866            // Anthropic models
867            s if s == models::CLAUDE_OPUS_4_1_20250805 => Ok(ModelId::ClaudeOpus41),
868            s if s == models::CLAUDE_SONNET_4_5 => Ok(ModelId::ClaudeSonnet45),
869            s if s == models::CLAUDE_SONNET_4_20250514 => Ok(ModelId::ClaudeSonnet4),
870            // DeepSeek models
871            s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
872            s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
873            // xAI models
874            s if s == models::xai::GROK_4 => Ok(ModelId::XaiGrok4),
875            s if s == models::xai::GROK_4_MINI => Ok(ModelId::XaiGrok4Mini),
876            s if s == models::xai::GROK_4_CODE => Ok(ModelId::XaiGrok4Code),
877            s if s == models::xai::GROK_4_CODE_LATEST => Ok(ModelId::XaiGrok4CodeLatest),
878            s if s == models::xai::GROK_4_VISION => Ok(ModelId::XaiGrok4Vision),
879            // Z.AI models
880            s if s == models::zai::GLM_4_6 => Ok(ModelId::ZaiGlm46),
881            s if s == models::zai::GLM_4_5 => Ok(ModelId::ZaiGlm45),
882            s if s == models::zai::GLM_4_5_AIR => Ok(ModelId::ZaiGlm45Air),
883            s if s == models::zai::GLM_4_5_X => Ok(ModelId::ZaiGlm45X),
884            s if s == models::zai::GLM_4_5_AIRX => Ok(ModelId::ZaiGlm45Airx),
885            s if s == models::zai::GLM_4_5_FLASH => Ok(ModelId::ZaiGlm45Flash),
886            s if s == models::zai::GLM_4_32B_0414_128K => Ok(ModelId::ZaiGlm432b0414128k),
887            // Moonshot models
888            s if s == models::MOONSHOT_V1_8K => Ok(ModelId::MoonshotV18k),
889            s if s == models::MOONSHOT_V1_32K => Ok(ModelId::MoonshotV132k),
890            s if s == models::MOONSHOT_V1_128K => Ok(ModelId::MoonshotV1128k),
891            _ => {
892                if let Some(model) = Self::parse_openrouter_model(s) {
893                    Ok(model)
894                } else {
895                    Err(ModelParseError::InvalidModel(s.to_string()))
896                }
897            }
898        }
899    }
900}
901
902/// Error type for model parsing failures
903#[derive(Debug, Clone, PartialEq)]
904pub enum ModelParseError {
905    InvalidModel(String),
906    InvalidProvider(String),
907}
908
909impl fmt::Display for ModelParseError {
910    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911        match self {
912            ModelParseError::InvalidModel(model) => {
913                write!(
914                    f,
915                    "Invalid model identifier: '{}'. Supported models: {}",
916                    model,
917                    ModelId::all_models()
918                        .iter()
919                        .map(|m| m.as_str())
920                        .collect::<Vec<_>>()
921                        .join(", ")
922                )
923            }
924            ModelParseError::InvalidProvider(provider) => {
925                write!(
926                    f,
927                    "Invalid provider: '{}'. Supported providers: {}",
928                    provider,
929                    Provider::all_providers()
930                        .iter()
931                        .map(|p| p.to_string())
932                        .collect::<Vec<_>>()
933                        .join(", ")
934                )
935            }
936        }
937    }
938}
939
940impl std::error::Error for ModelParseError {}
941
942#[cfg(test)]
943mod tests {
944    use super::*;
945    use crate::config::constants::models;
946
947    #[test]
948    fn test_model_string_conversion() {
949        // Gemini models
950        assert_eq!(
951            ModelId::Gemini25FlashPreview.as_str(),
952            models::GEMINI_2_5_FLASH_PREVIEW
953        );
954        assert_eq!(ModelId::Gemini25Flash.as_str(), models::GEMINI_2_5_FLASH);
955        assert_eq!(
956            ModelId::Gemini25FlashLite.as_str(),
957            models::GEMINI_2_5_FLASH_LITE
958        );
959        assert_eq!(ModelId::Gemini25Pro.as_str(), models::GEMINI_2_5_PRO);
960        // OpenAI models
961        assert_eq!(ModelId::GPT5.as_str(), models::GPT_5);
962        assert_eq!(ModelId::GPT5Codex.as_str(), models::GPT_5_CODEX);
963        assert_eq!(ModelId::GPT5Mini.as_str(), models::GPT_5_MINI);
964        assert_eq!(ModelId::GPT5Nano.as_str(), models::GPT_5_NANO);
965        assert_eq!(ModelId::CodexMiniLatest.as_str(), models::CODEX_MINI_LATEST);
966        // Anthropic models
967        assert_eq!(ModelId::ClaudeSonnet45.as_str(), models::CLAUDE_SONNET_4_5);
968        assert_eq!(
969            ModelId::ClaudeSonnet4.as_str(),
970            models::CLAUDE_SONNET_4_20250514
971        );
972        assert_eq!(
973            ModelId::ClaudeOpus41.as_str(),
974            models::CLAUDE_OPUS_4_1_20250805
975        );
976        // DeepSeek models
977        assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
978        assert_eq!(
979            ModelId::DeepSeekReasoner.as_str(),
980            models::DEEPSEEK_REASONER
981        );
982        // xAI models
983        assert_eq!(ModelId::XaiGrok4.as_str(), models::xai::GROK_4);
984        assert_eq!(ModelId::XaiGrok4Mini.as_str(), models::xai::GROK_4_MINI);
985        assert_eq!(ModelId::XaiGrok4Code.as_str(), models::xai::GROK_4_CODE);
986        assert_eq!(
987            ModelId::XaiGrok4CodeLatest.as_str(),
988            models::xai::GROK_4_CODE_LATEST
989        );
990        assert_eq!(ModelId::XaiGrok4Vision.as_str(), models::xai::GROK_4_VISION);
991        // Z.AI models
992        assert_eq!(ModelId::ZaiGlm46.as_str(), models::zai::GLM_4_6);
993        assert_eq!(ModelId::ZaiGlm45.as_str(), models::zai::GLM_4_5);
994        assert_eq!(ModelId::ZaiGlm45Air.as_str(), models::zai::GLM_4_5_AIR);
995        assert_eq!(ModelId::ZaiGlm45X.as_str(), models::zai::GLM_4_5_X);
996        assert_eq!(ModelId::ZaiGlm45Airx.as_str(), models::zai::GLM_4_5_AIRX);
997        assert_eq!(ModelId::ZaiGlm45Flash.as_str(), models::zai::GLM_4_5_FLASH);
998        assert_eq!(
999            ModelId::ZaiGlm432b0414128k.as_str(),
1000            models::zai::GLM_4_32B_0414_128K
1001        );
1002        macro_rules! assert_openrouter_to_string {
1003            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1004                $(assert_eq!(ModelId::$variant.as_str(), models::$const);)*
1005            };
1006        }
1007        each_openrouter_variant!(assert_openrouter_to_string);
1008    }
1009
1010    #[test]
1011    fn test_model_from_string() {
1012        // Gemini models
1013        assert_eq!(
1014            models::GEMINI_2_5_FLASH_PREVIEW.parse::<ModelId>().unwrap(),
1015            ModelId::Gemini25FlashPreview
1016        );
1017        assert_eq!(
1018            models::GEMINI_2_5_FLASH.parse::<ModelId>().unwrap(),
1019            ModelId::Gemini25Flash
1020        );
1021        assert_eq!(
1022            models::GEMINI_2_5_FLASH_LITE.parse::<ModelId>().unwrap(),
1023            ModelId::Gemini25FlashLite
1024        );
1025        assert_eq!(
1026            models::GEMINI_2_5_PRO.parse::<ModelId>().unwrap(),
1027            ModelId::Gemini25Pro
1028        );
1029        // OpenAI models
1030        assert_eq!(models::GPT_5.parse::<ModelId>().unwrap(), ModelId::GPT5);
1031        assert_eq!(
1032            models::GPT_5_CODEX.parse::<ModelId>().unwrap(),
1033            ModelId::GPT5Codex
1034        );
1035        assert_eq!(
1036            models::GPT_5_MINI.parse::<ModelId>().unwrap(),
1037            ModelId::GPT5Mini
1038        );
1039        assert_eq!(
1040            models::GPT_5_NANO.parse::<ModelId>().unwrap(),
1041            ModelId::GPT5Nano
1042        );
1043        assert_eq!(
1044            models::CODEX_MINI_LATEST.parse::<ModelId>().unwrap(),
1045            ModelId::CodexMiniLatest
1046        );
1047        // Anthropic models
1048        assert_eq!(
1049            models::CLAUDE_SONNET_4_5.parse::<ModelId>().unwrap(),
1050            ModelId::ClaudeSonnet45
1051        );
1052        assert_eq!(
1053            models::CLAUDE_SONNET_4_20250514.parse::<ModelId>().unwrap(),
1054            ModelId::ClaudeSonnet4
1055        );
1056        assert_eq!(
1057            models::CLAUDE_OPUS_4_1_20250805.parse::<ModelId>().unwrap(),
1058            ModelId::ClaudeOpus41
1059        );
1060        // DeepSeek models
1061        assert_eq!(
1062            models::DEEPSEEK_CHAT.parse::<ModelId>().unwrap(),
1063            ModelId::DeepSeekChat
1064        );
1065        assert_eq!(
1066            models::DEEPSEEK_REASONER.parse::<ModelId>().unwrap(),
1067            ModelId::DeepSeekReasoner
1068        );
1069        // xAI models
1070        assert_eq!(
1071            models::xai::GROK_4.parse::<ModelId>().unwrap(),
1072            ModelId::XaiGrok4
1073        );
1074        assert_eq!(
1075            models::xai::GROK_4_MINI.parse::<ModelId>().unwrap(),
1076            ModelId::XaiGrok4Mini
1077        );
1078        assert_eq!(
1079            models::xai::GROK_4_CODE.parse::<ModelId>().unwrap(),
1080            ModelId::XaiGrok4Code
1081        );
1082        assert_eq!(
1083            models::xai::GROK_4_CODE_LATEST.parse::<ModelId>().unwrap(),
1084            ModelId::XaiGrok4CodeLatest
1085        );
1086        assert_eq!(
1087            models::xai::GROK_4_VISION.parse::<ModelId>().unwrap(),
1088            ModelId::XaiGrok4Vision
1089        );
1090        // Z.AI models
1091        assert_eq!(
1092            models::zai::GLM_4_6.parse::<ModelId>().unwrap(),
1093            ModelId::ZaiGlm46
1094        );
1095        assert_eq!(
1096            models::zai::GLM_4_5.parse::<ModelId>().unwrap(),
1097            ModelId::ZaiGlm45
1098        );
1099        assert_eq!(
1100            models::zai::GLM_4_5_AIR.parse::<ModelId>().unwrap(),
1101            ModelId::ZaiGlm45Air
1102        );
1103        assert_eq!(
1104            models::zai::GLM_4_5_X.parse::<ModelId>().unwrap(),
1105            ModelId::ZaiGlm45X
1106        );
1107        assert_eq!(
1108            models::zai::GLM_4_5_AIRX.parse::<ModelId>().unwrap(),
1109            ModelId::ZaiGlm45Airx
1110        );
1111        assert_eq!(
1112            models::zai::GLM_4_5_FLASH.parse::<ModelId>().unwrap(),
1113            ModelId::ZaiGlm45Flash
1114        );
1115        assert_eq!(
1116            models::zai::GLM_4_32B_0414_128K.parse::<ModelId>().unwrap(),
1117            ModelId::ZaiGlm432b0414128k
1118        );
1119        assert_eq!(
1120            models::MOONSHOT_V1_8K.parse::<ModelId>().unwrap(),
1121            ModelId::MoonshotV18k
1122        );
1123        assert_eq!(
1124            models::MOONSHOT_V1_32K.parse::<ModelId>().unwrap(),
1125            ModelId::MoonshotV132k
1126        );
1127        assert_eq!(
1128            models::MOONSHOT_V1_128K.parse::<ModelId>().unwrap(),
1129            ModelId::MoonshotV1128k
1130        );
1131        macro_rules! assert_openrouter_parse {
1132            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1133                $(assert_eq!(models::$const.parse::<ModelId>().unwrap(), ModelId::$variant);)*
1134            };
1135        }
1136        each_openrouter_variant!(assert_openrouter_parse);
1137        // Invalid model
1138        assert!("invalid-model".parse::<ModelId>().is_err());
1139    }
1140
1141    #[test]
1142    fn test_provider_parsing() {
1143        assert_eq!("gemini".parse::<Provider>().unwrap(), Provider::Gemini);
1144        assert_eq!("openai".parse::<Provider>().unwrap(), Provider::OpenAI);
1145        assert_eq!(
1146            "anthropic".parse::<Provider>().unwrap(),
1147            Provider::Anthropic
1148        );
1149        assert_eq!("deepseek".parse::<Provider>().unwrap(), Provider::DeepSeek);
1150        assert_eq!(
1151            "openrouter".parse::<Provider>().unwrap(),
1152            Provider::OpenRouter
1153        );
1154        assert_eq!("xai".parse::<Provider>().unwrap(), Provider::XAI);
1155        assert_eq!("zai".parse::<Provider>().unwrap(), Provider::ZAI);
1156        assert_eq!("moonshot".parse::<Provider>().unwrap(), Provider::Moonshot);
1157        assert!("invalid-provider".parse::<Provider>().is_err());
1158    }
1159
1160    #[test]
1161    fn test_model_providers() {
1162        assert_eq!(ModelId::Gemini25FlashPreview.provider(), Provider::Gemini);
1163        assert_eq!(ModelId::GPT5.provider(), Provider::OpenAI);
1164        assert_eq!(ModelId::GPT5Codex.provider(), Provider::OpenAI);
1165        assert_eq!(ModelId::ClaudeSonnet45.provider(), Provider::Anthropic);
1166        assert_eq!(ModelId::ClaudeSonnet4.provider(), Provider::Anthropic);
1167        assert_eq!(ModelId::DeepSeekChat.provider(), Provider::DeepSeek);
1168        assert_eq!(ModelId::XaiGrok4.provider(), Provider::XAI);
1169        assert_eq!(ModelId::ZaiGlm46.provider(), Provider::ZAI);
1170        assert_eq!(ModelId::MoonshotV132k.provider(), Provider::Moonshot);
1171        assert_eq!(
1172            ModelId::OpenRouterGrokCodeFast1.provider(),
1173            Provider::OpenRouter
1174        );
1175        assert_eq!(
1176            ModelId::OpenRouterAnthropicClaudeSonnet45.provider(),
1177            Provider::OpenRouter
1178        );
1179
1180        macro_rules! assert_openrouter_provider_all {
1181            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1182                $(assert_eq!(ModelId::$variant.provider(), Provider::OpenRouter);)*
1183            };
1184        }
1185        each_openrouter_variant!(assert_openrouter_provider_all);
1186    }
1187
1188    #[test]
1189    fn test_provider_defaults() {
1190        assert_eq!(
1191            ModelId::default_orchestrator_for_provider(Provider::Gemini),
1192            ModelId::Gemini25Pro
1193        );
1194        assert_eq!(
1195            ModelId::default_orchestrator_for_provider(Provider::OpenAI),
1196            ModelId::GPT5
1197        );
1198        assert_eq!(
1199            ModelId::default_orchestrator_for_provider(Provider::Anthropic),
1200            ModelId::ClaudeSonnet4
1201        );
1202        assert_eq!(
1203            ModelId::default_orchestrator_for_provider(Provider::DeepSeek),
1204            ModelId::DeepSeekReasoner
1205        );
1206        assert_eq!(
1207            ModelId::default_orchestrator_for_provider(Provider::OpenRouter),
1208            ModelId::OpenRouterGrokCodeFast1
1209        );
1210        assert_eq!(
1211            ModelId::default_orchestrator_for_provider(Provider::XAI),
1212            ModelId::XaiGrok4
1213        );
1214        assert_eq!(
1215            ModelId::default_orchestrator_for_provider(Provider::ZAI),
1216            ModelId::ZaiGlm46
1217        );
1218        assert_eq!(
1219            ModelId::default_orchestrator_for_provider(Provider::Moonshot),
1220            ModelId::MoonshotV132k
1221        );
1222
1223        assert_eq!(
1224            ModelId::default_subagent_for_provider(Provider::Gemini),
1225            ModelId::Gemini25FlashPreview
1226        );
1227        assert_eq!(
1228            ModelId::default_subagent_for_provider(Provider::OpenAI),
1229            ModelId::GPT5Mini
1230        );
1231        assert_eq!(
1232            ModelId::default_subagent_for_provider(Provider::Anthropic),
1233            ModelId::ClaudeSonnet45
1234        );
1235        assert_eq!(
1236            ModelId::default_subagent_for_provider(Provider::DeepSeek),
1237            ModelId::DeepSeekChat
1238        );
1239        assert_eq!(
1240            ModelId::default_subagent_for_provider(Provider::OpenRouter),
1241            ModelId::OpenRouterGrokCodeFast1
1242        );
1243        assert_eq!(
1244            ModelId::default_subagent_for_provider(Provider::XAI),
1245            ModelId::XaiGrok4Code
1246        );
1247        assert_eq!(
1248            ModelId::default_subagent_for_provider(Provider::ZAI),
1249            ModelId::ZaiGlm45Flash
1250        );
1251        assert_eq!(
1252            ModelId::default_subagent_for_provider(Provider::Moonshot),
1253            ModelId::MoonshotV18k
1254        );
1255
1256        assert_eq!(
1257            ModelId::default_single_for_provider(Provider::DeepSeek),
1258            ModelId::DeepSeekReasoner
1259        );
1260        assert_eq!(
1261            ModelId::default_single_for_provider(Provider::Moonshot),
1262            ModelId::MoonshotV132k
1263        );
1264    }
1265
1266    #[test]
1267    fn test_model_defaults() {
1268        assert_eq!(ModelId::default(), ModelId::Gemini25FlashPreview);
1269        assert_eq!(ModelId::default_orchestrator(), ModelId::Gemini25Pro);
1270        assert_eq!(ModelId::default_subagent(), ModelId::Gemini25FlashPreview);
1271    }
1272
1273    #[test]
1274    fn test_model_variants() {
1275        // Flash variants
1276        assert!(ModelId::Gemini25FlashPreview.is_flash_variant());
1277        assert!(ModelId::Gemini25Flash.is_flash_variant());
1278        assert!(ModelId::Gemini25FlashLite.is_flash_variant());
1279        assert!(!ModelId::GPT5.is_flash_variant());
1280        assert!(ModelId::ZaiGlm45Flash.is_flash_variant());
1281        assert!(ModelId::MoonshotV18k.is_flash_variant());
1282
1283        // Pro variants
1284        assert!(ModelId::Gemini25Pro.is_pro_variant());
1285        assert!(ModelId::GPT5.is_pro_variant());
1286        assert!(ModelId::GPT5Codex.is_pro_variant());
1287        assert!(ModelId::DeepSeekReasoner.is_pro_variant());
1288        assert!(ModelId::ZaiGlm46.is_pro_variant());
1289        assert!(ModelId::MoonshotV1128k.is_pro_variant());
1290        assert!(!ModelId::Gemini25FlashPreview.is_pro_variant());
1291
1292        // Efficient variants
1293        assert!(ModelId::Gemini25FlashPreview.is_efficient_variant());
1294        assert!(ModelId::Gemini25Flash.is_efficient_variant());
1295        assert!(ModelId::Gemini25FlashLite.is_efficient_variant());
1296        assert!(ModelId::GPT5Mini.is_efficient_variant());
1297        assert!(ModelId::XaiGrok4Code.is_efficient_variant());
1298        assert!(ModelId::DeepSeekChat.is_efficient_variant());
1299        assert!(ModelId::ZaiGlm45Air.is_efficient_variant());
1300        assert!(ModelId::ZaiGlm45Airx.is_efficient_variant());
1301        assert!(ModelId::ZaiGlm45Flash.is_efficient_variant());
1302        assert!(ModelId::MoonshotV18k.is_efficient_variant());
1303        assert!(!ModelId::GPT5.is_efficient_variant());
1304
1305        macro_rules! assert_openrouter_efficiency {
1306            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1307                $(assert_eq!(ModelId::$variant.is_efficient_variant(), $efficient);)*
1308            };
1309        }
1310        each_openrouter_variant!(assert_openrouter_efficiency);
1311
1312        // Top tier models
1313        assert!(ModelId::Gemini25Pro.is_top_tier());
1314        assert!(ModelId::GPT5.is_top_tier());
1315        assert!(ModelId::GPT5Codex.is_top_tier());
1316        assert!(ModelId::ClaudeSonnet45.is_top_tier());
1317        assert!(ModelId::ClaudeSonnet4.is_top_tier());
1318        assert!(ModelId::XaiGrok4.is_top_tier());
1319        assert!(ModelId::XaiGrok4CodeLatest.is_top_tier());
1320        assert!(ModelId::DeepSeekReasoner.is_top_tier());
1321        assert!(ModelId::ZaiGlm46.is_top_tier());
1322        assert!(ModelId::MoonshotV1128k.is_top_tier());
1323        assert!(!ModelId::Gemini25FlashPreview.is_top_tier());
1324
1325        macro_rules! assert_openrouter_top_tier {
1326            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1327                $(assert_eq!(ModelId::$variant.is_top_tier(), $top);)*
1328            };
1329        }
1330        each_openrouter_variant!(assert_openrouter_top_tier);
1331    }
1332
1333    #[test]
1334    fn test_model_generation() {
1335        // Gemini generations
1336        assert_eq!(ModelId::Gemini25FlashPreview.generation(), "2.5");
1337        assert_eq!(ModelId::Gemini25Flash.generation(), "2.5");
1338        assert_eq!(ModelId::Gemini25FlashLite.generation(), "2.5");
1339        assert_eq!(ModelId::Gemini25Pro.generation(), "2.5");
1340
1341        // OpenAI generations
1342        assert_eq!(ModelId::GPT5.generation(), "5");
1343        assert_eq!(ModelId::GPT5Codex.generation(), "5");
1344        assert_eq!(ModelId::GPT5Mini.generation(), "5");
1345        assert_eq!(ModelId::GPT5Nano.generation(), "5");
1346        assert_eq!(ModelId::CodexMiniLatest.generation(), "5");
1347
1348        // Anthropic generations
1349        assert_eq!(ModelId::ClaudeSonnet45.generation(), "4.5");
1350        assert_eq!(ModelId::ClaudeSonnet4.generation(), "4");
1351        assert_eq!(ModelId::ClaudeOpus41.generation(), "4.1");
1352
1353        // DeepSeek generations
1354        assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1355        assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1356
1357        // xAI generations
1358        assert_eq!(ModelId::XaiGrok4.generation(), "4");
1359        assert_eq!(ModelId::XaiGrok4Mini.generation(), "4");
1360        assert_eq!(ModelId::XaiGrok4Code.generation(), "4");
1361        assert_eq!(ModelId::XaiGrok4CodeLatest.generation(), "4");
1362        assert_eq!(ModelId::XaiGrok4Vision.generation(), "4");
1363        // Z.AI generations
1364        assert_eq!(ModelId::ZaiGlm46.generation(), "4.6");
1365        assert_eq!(ModelId::ZaiGlm45.generation(), "4.5");
1366        assert_eq!(ModelId::ZaiGlm45Air.generation(), "4.5");
1367        assert_eq!(ModelId::ZaiGlm45X.generation(), "4.5");
1368        assert_eq!(ModelId::ZaiGlm45Airx.generation(), "4.5");
1369        assert_eq!(ModelId::ZaiGlm45Flash.generation(), "4.5");
1370        assert_eq!(ModelId::ZaiGlm432b0414128k.generation(), "4-32B");
1371        assert_eq!(ModelId::MoonshotV18k.generation(), "v1");
1372        assert_eq!(ModelId::MoonshotV132k.generation(), "v1");
1373        assert_eq!(ModelId::MoonshotV1128k.generation(), "v1");
1374
1375        macro_rules! assert_openrouter_generation {
1376            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1377                $(assert_eq!(ModelId::$variant.generation(), $generation);)*
1378            };
1379        }
1380        each_openrouter_variant!(assert_openrouter_generation);
1381    }
1382
1383    #[test]
1384    fn test_models_for_provider() {
1385        let gemini_models = ModelId::models_for_provider(Provider::Gemini);
1386        assert!(gemini_models.contains(&ModelId::Gemini25Pro));
1387        assert!(!gemini_models.contains(&ModelId::GPT5));
1388
1389        let openai_models = ModelId::models_for_provider(Provider::OpenAI);
1390        assert!(openai_models.contains(&ModelId::GPT5));
1391        assert!(openai_models.contains(&ModelId::GPT5Codex));
1392        assert!(!openai_models.contains(&ModelId::Gemini25Pro));
1393
1394        let anthropic_models = ModelId::models_for_provider(Provider::Anthropic);
1395        assert!(anthropic_models.contains(&ModelId::ClaudeSonnet45));
1396        assert!(anthropic_models.contains(&ModelId::ClaudeSonnet4));
1397        assert!(!anthropic_models.contains(&ModelId::GPT5));
1398
1399        let deepseek_models = ModelId::models_for_provider(Provider::DeepSeek);
1400        assert!(deepseek_models.contains(&ModelId::DeepSeekChat));
1401        assert!(deepseek_models.contains(&ModelId::DeepSeekReasoner));
1402
1403        let openrouter_models = ModelId::models_for_provider(Provider::OpenRouter);
1404        macro_rules! assert_openrouter_models_present {
1405            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1406                $(assert!(openrouter_models.contains(&ModelId::$variant));)*
1407            };
1408        }
1409        each_openrouter_variant!(assert_openrouter_models_present);
1410
1411        let xai_models = ModelId::models_for_provider(Provider::XAI);
1412        assert!(xai_models.contains(&ModelId::XaiGrok4));
1413        assert!(xai_models.contains(&ModelId::XaiGrok4Mini));
1414        assert!(xai_models.contains(&ModelId::XaiGrok4Code));
1415        assert!(xai_models.contains(&ModelId::XaiGrok4CodeLatest));
1416        assert!(xai_models.contains(&ModelId::XaiGrok4Vision));
1417
1418        let zai_models = ModelId::models_for_provider(Provider::ZAI);
1419        assert!(zai_models.contains(&ModelId::ZaiGlm46));
1420        assert!(zai_models.contains(&ModelId::ZaiGlm45));
1421        assert!(zai_models.contains(&ModelId::ZaiGlm45Air));
1422        assert!(zai_models.contains(&ModelId::ZaiGlm45X));
1423        assert!(zai_models.contains(&ModelId::ZaiGlm45Airx));
1424        assert!(zai_models.contains(&ModelId::ZaiGlm45Flash));
1425        assert!(zai_models.contains(&ModelId::ZaiGlm432b0414128k));
1426
1427        let moonshot_models = ModelId::models_for_provider(Provider::Moonshot);
1428        assert!(moonshot_models.contains(&ModelId::MoonshotV18k));
1429        assert!(moonshot_models.contains(&ModelId::MoonshotV132k));
1430        assert!(moonshot_models.contains(&ModelId::MoonshotV1128k));
1431        assert_eq!(moonshot_models.len(), 3);
1432    }
1433
1434    #[test]
1435    fn test_fallback_models() {
1436        let fallbacks = ModelId::fallback_models();
1437        assert!(!fallbacks.is_empty());
1438        assert!(fallbacks.contains(&ModelId::Gemini25Pro));
1439        assert!(fallbacks.contains(&ModelId::GPT5));
1440        assert!(fallbacks.contains(&ModelId::ClaudeOpus41));
1441        assert!(fallbacks.contains(&ModelId::ClaudeSonnet45));
1442        assert!(fallbacks.contains(&ModelId::DeepSeekReasoner));
1443        assert!(fallbacks.contains(&ModelId::MoonshotV132k));
1444        assert!(fallbacks.contains(&ModelId::XaiGrok4));
1445        assert!(fallbacks.contains(&ModelId::ZaiGlm46));
1446        assert!(fallbacks.contains(&ModelId::OpenRouterGrokCodeFast1));
1447    }
1448}