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::OpenAI,
111            Provider::Anthropic,
112            Provider::Gemini,
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    /// Kimi K2 Turbo Preview - Recommended high-speed K2 deployment
254    MoonshotKimiK2TurboPreview,
255    /// Kimi K2 0905 Preview - Flagship 256K K2 release with enhanced coding agents
256    MoonshotKimiK20905Preview,
257    /// Kimi K2 0711 Preview - Long-context K2 release tuned for balanced workloads
258    MoonshotKimiK20711Preview,
259    /// Kimi Latest - Auto-tier alias that selects 8K/32K/128K variants automatically
260    MoonshotKimiLatest,
261    /// Kimi Latest 8K - Vision-enabled 8K tier with automatic context caching
262    MoonshotKimiLatest8k,
263    /// Kimi Latest 32K - Vision-enabled mid-tier with extended context
264    MoonshotKimiLatest32k,
265    /// Kimi Latest 128K - Vision-enabled flagship tier with maximum context
266    MoonshotKimiLatest128k,
267
268    // OpenRouter models
269    /// Grok Code Fast 1 - Fast OpenRouter coding model
270    OpenRouterGrokCodeFast1,
271    /// Grok 4 Fast - Reasoning-focused Grok endpoint
272    OpenRouterGrok4Fast,
273    /// Grok 4 - Highest quality Grok endpoint on OpenRouter
274    OpenRouterGrok4,
275    /// Z.AI GLM 4.5 Air (free) - Community tier for GLM 4.5 Air
276    OpenRouterZaiGlm45AirFree,
277    /// Z.AI GLM 4.6 - Long-context GLM upgrade via OpenRouter
278    OpenRouterZaiGlm46,
279    /// MoonshotAI Kimi K2 0905 - Latest MoE update via OpenRouter
280    OpenRouterMoonshotaiKimiK20905,
281    /// Qwen3 Max - Flagship Qwen3 deployment via OpenRouter
282    OpenRouterQwen3Max,
283    /// Qwen3 235B A22B - General Qwen3 expert mixture
284    OpenRouterQwen3235bA22b,
285    /// Qwen3 235B A22B (free tier) - Community access listing
286    OpenRouterQwen3235bA22bFree,
287    /// Qwen3 235B A22B Instruct 2507 - Instruction tuned release
288    OpenRouterQwen3235bA22b2507,
289    /// Qwen3 235B A22B Thinking 2507 - Deliberative reasoning release
290    OpenRouterQwen3235bA22bThinking2507,
291    /// Qwen3 32B - Mid-tier dense Qwen3 variant
292    OpenRouterQwen332b,
293    /// Qwen3 30B A3B - Efficient active-parameter mixture
294    OpenRouterQwen330bA3b,
295    /// Qwen3 30B A3B (free tier) - Community access variant
296    OpenRouterQwen330bA3bFree,
297    /// Qwen3 30B A3B Instruct 2507 - Instruction tuned efficient variant
298    OpenRouterQwen330bA3bInstruct2507,
299    /// Qwen3 30B A3B Thinking 2507 - Deliberative efficient variant
300    OpenRouterQwen330bA3bThinking2507,
301    /// Qwen3 14B - Lightweight Qwen3 deployment
302    OpenRouterQwen314b,
303    /// Qwen3 14B (free tier) - Community 14B deployment
304    OpenRouterQwen314bFree,
305    /// Qwen3 8B - Compact Qwen3 deployment
306    OpenRouterQwen38b,
307    /// Qwen3 8B (free tier) - Community 8B deployment
308    OpenRouterQwen38bFree,
309    /// Qwen3 4B (free tier) - Entry level Qwen3 deployment
310    OpenRouterQwen34bFree,
311    /// Qwen3 Next 80B A3B Instruct - Next-gen Qwen3 instruction model
312    OpenRouterQwen3Next80bA3bInstruct,
313    /// Qwen3 Next 80B A3B Thinking - Next-gen Qwen3 reasoning model
314    OpenRouterQwen3Next80bA3bThinking,
315    /// Qwen3 Coder - Balanced OpenRouter coding model
316    OpenRouterQwen3Coder,
317    /// Qwen3 Coder (free tier) - Community coding access
318    OpenRouterQwen3CoderFree,
319    /// Qwen3 Coder Plus - High quality Qwen3 coding model
320    OpenRouterQwen3CoderPlus,
321    /// Qwen3 Coder Flash - Low-latency Qwen3 coding model
322    OpenRouterQwen3CoderFlash,
323    /// Qwen3 Coder 30B A3B Instruct - Large coding MoE deployment
324    OpenRouterQwen3Coder30bA3bInstruct,
325    /// DeepSeek V3.2 Exp - Experimental DeepSeek listing
326    OpenRouterDeepSeekV32Exp,
327    /// DeepSeek Chat v3.1 - Advanced DeepSeek model via OpenRouter
328    OpenRouterDeepSeekChatV31,
329    /// DeepSeek R1 - Reasoning model via OpenRouter
330    OpenRouterDeepSeekR1,
331    /// OpenAI gpt-oss-120b via OpenRouter
332    OpenRouterOpenAIGptOss120b,
333    /// OpenAI gpt-oss-20b via OpenRouter
334    OpenRouterOpenAIGptOss20b,
335    /// OpenAI gpt-oss-20b (free tier) via OpenRouter
336    OpenRouterOpenAIGptOss20bFree,
337    /// OpenAI GPT-5 via OpenRouter
338    OpenRouterOpenAIGpt5,
339    /// OpenAI GPT-5 Codex via OpenRouter
340    OpenRouterOpenAIGpt5Codex,
341    /// OpenAI GPT-5 Chat preview via OpenRouter
342    OpenRouterOpenAIGpt5Chat,
343    /// OpenAI GPT-4o Search Preview via OpenRouter
344    OpenRouterOpenAIGpt4oSearchPreview,
345    /// OpenAI GPT-4o Mini Search Preview via OpenRouter
346    OpenRouterOpenAIGpt4oMiniSearchPreview,
347    /// OpenAI ChatGPT-4o Latest via OpenRouter
348    OpenRouterOpenAIChatgpt4oLatest,
349    /// Anthropic Claude Sonnet 4.5 via OpenRouter
350    OpenRouterAnthropicClaudeSonnet45,
351    /// Anthropic Claude Opus 4.1 via OpenRouter
352    OpenRouterAnthropicClaudeOpus41,
353}
354impl ModelId {
355    fn openrouter_metadata(&self) -> Option<OpenRouterMetadata> {
356        use crate::config::constants::models;
357
358        macro_rules! metadata_match {
359            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
360                match self {
361                    $(ModelId::$variant => Some(OpenRouterMetadata {
362                        id: models::$const,
363                        display: $display,
364                        description: $description,
365                        efficient: $efficient,
366                        top_tier: $top,
367                        generation: $generation,
368                    }),)*
369                    _ => None,
370                }
371            };
372        }
373
374        each_openrouter_variant!(metadata_match)
375    }
376
377    fn parse_openrouter_model(value: &str) -> Option<Self> {
378        use crate::config::constants::models;
379
380        macro_rules! parse_match {
381            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
382                match value {
383                    $(models::$const => Some(ModelId::$variant),)*
384                    _ => None,
385                }
386            };
387        }
388
389        each_openrouter_variant!(parse_match)
390    }
391
392    fn openrouter_models() -> Vec<Self> {
393        macro_rules! to_vec {
394            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
395                vec![$(ModelId::$variant,)*]
396            };
397        }
398
399        each_openrouter_variant!(to_vec)
400    }
401
402    /// Convert the model identifier to its string representation
403    /// used in API calls and configurations
404    pub fn as_str(&self) -> &'static str {
405        use crate::config::constants::models;
406        if let Some(meta) = self.openrouter_metadata() {
407            return meta.id;
408        }
409        match self {
410            // Gemini models
411            ModelId::Gemini25FlashPreview => models::GEMINI_2_5_FLASH_PREVIEW,
412            ModelId::Gemini25Flash => models::GEMINI_2_5_FLASH,
413            ModelId::Gemini25FlashLite => models::GEMINI_2_5_FLASH_LITE,
414            ModelId::Gemini25Pro => models::GEMINI_2_5_PRO,
415            // OpenAI models
416            ModelId::GPT5 => models::GPT_5,
417            ModelId::GPT5Codex => models::GPT_5_CODEX,
418            ModelId::GPT5Mini => models::GPT_5_MINI,
419            ModelId::GPT5Nano => models::GPT_5_NANO,
420            ModelId::CodexMiniLatest => models::CODEX_MINI_LATEST,
421            // Anthropic models
422            ModelId::ClaudeOpus41 => models::CLAUDE_OPUS_4_1_20250805,
423            ModelId::ClaudeSonnet45 => models::CLAUDE_SONNET_4_5,
424            ModelId::ClaudeSonnet4 => models::CLAUDE_SONNET_4_20250514,
425            // DeepSeek models
426            ModelId::DeepSeekChat => models::DEEPSEEK_CHAT,
427            ModelId::DeepSeekReasoner => models::DEEPSEEK_REASONER,
428            // xAI models
429            ModelId::XaiGrok4 => models::xai::GROK_4,
430            ModelId::XaiGrok4Mini => models::xai::GROK_4_MINI,
431            ModelId::XaiGrok4Code => models::xai::GROK_4_CODE,
432            ModelId::XaiGrok4CodeLatest => models::xai::GROK_4_CODE_LATEST,
433            ModelId::XaiGrok4Vision => models::xai::GROK_4_VISION,
434            // Z.AI models
435            ModelId::ZaiGlm46 => models::zai::GLM_4_6,
436            ModelId::ZaiGlm45 => models::zai::GLM_4_5,
437            ModelId::ZaiGlm45Air => models::zai::GLM_4_5_AIR,
438            ModelId::ZaiGlm45X => models::zai::GLM_4_5_X,
439            ModelId::ZaiGlm45Airx => models::zai::GLM_4_5_AIRX,
440            ModelId::ZaiGlm45Flash => models::zai::GLM_4_5_FLASH,
441            ModelId::ZaiGlm432b0414128k => models::zai::GLM_4_32B_0414_128K,
442            // Moonshot models
443            ModelId::MoonshotKimiK2TurboPreview => models::MOONSHOT_KIMI_K2_TURBO_PREVIEW,
444            ModelId::MoonshotKimiK20905Preview => models::MOONSHOT_KIMI_K2_0905_PREVIEW,
445            ModelId::MoonshotKimiK20711Preview => models::MOONSHOT_KIMI_K2_0711_PREVIEW,
446            ModelId::MoonshotKimiLatest => models::MOONSHOT_KIMI_LATEST,
447            ModelId::MoonshotKimiLatest8k => models::MOONSHOT_KIMI_LATEST_8K,
448            ModelId::MoonshotKimiLatest32k => models::MOONSHOT_KIMI_LATEST_32K,
449            ModelId::MoonshotKimiLatest128k => models::MOONSHOT_KIMI_LATEST_128K,
450            // OpenRouter models
451            _ => unreachable!(),
452        }
453    }
454
455    /// Get the provider for this model
456    pub fn provider(&self) -> Provider {
457        if self.openrouter_metadata().is_some() {
458            return Provider::OpenRouter;
459        }
460        match self {
461            ModelId::Gemini25FlashPreview
462            | ModelId::Gemini25Flash
463            | ModelId::Gemini25FlashLite
464            | ModelId::Gemini25Pro => Provider::Gemini,
465            ModelId::GPT5
466            | ModelId::GPT5Codex
467            | ModelId::GPT5Mini
468            | ModelId::GPT5Nano
469            | ModelId::CodexMiniLatest => Provider::OpenAI,
470            ModelId::ClaudeOpus41 | ModelId::ClaudeSonnet45 | ModelId::ClaudeSonnet4 => {
471                Provider::Anthropic
472            }
473            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => Provider::DeepSeek,
474            ModelId::XaiGrok4
475            | ModelId::XaiGrok4Mini
476            | ModelId::XaiGrok4Code
477            | ModelId::XaiGrok4CodeLatest
478            | ModelId::XaiGrok4Vision => Provider::XAI,
479            ModelId::ZaiGlm46
480            | ModelId::ZaiGlm45
481            | ModelId::ZaiGlm45Air
482            | ModelId::ZaiGlm45X
483            | ModelId::ZaiGlm45Airx
484            | ModelId::ZaiGlm45Flash
485            | ModelId::ZaiGlm432b0414128k => Provider::ZAI,
486            ModelId::MoonshotKimiK2TurboPreview
487            | ModelId::MoonshotKimiK20905Preview
488            | ModelId::MoonshotKimiK20711Preview
489            | ModelId::MoonshotKimiLatest
490            | ModelId::MoonshotKimiLatest8k
491            | ModelId::MoonshotKimiLatest32k
492            | ModelId::MoonshotKimiLatest128k => Provider::Moonshot,
493            _ => unreachable!(),
494        }
495    }
496
497    /// Whether this model supports configurable reasoning effort levels
498    pub fn supports_reasoning_effort(&self) -> bool {
499        self.provider().supports_reasoning_effort(self.as_str())
500    }
501
502    /// Get the display name for the model (human-readable)
503    pub fn display_name(&self) -> &'static str {
504        if let Some(meta) = self.openrouter_metadata() {
505            return meta.display;
506        }
507        match self {
508            // Gemini models
509            ModelId::Gemini25FlashPreview => "Gemini 2.5 Flash Preview",
510            ModelId::Gemini25Flash => "Gemini 2.5 Flash",
511            ModelId::Gemini25FlashLite => "Gemini 2.5 Flash Lite",
512            ModelId::Gemini25Pro => "Gemini 2.5 Pro",
513            // OpenAI models
514            ModelId::GPT5 => "GPT-5",
515            ModelId::GPT5Codex => "GPT-5 Codex",
516            ModelId::GPT5Mini => "GPT-5 Mini",
517            ModelId::GPT5Nano => "GPT-5 Nano",
518            ModelId::CodexMiniLatest => "Codex Mini Latest",
519            // Anthropic models
520            ModelId::ClaudeOpus41 => "Claude Opus 4.1",
521            ModelId::ClaudeSonnet45 => "Claude Sonnet 4.5",
522            ModelId::ClaudeSonnet4 => "Claude Sonnet 4",
523            // DeepSeek models
524            ModelId::DeepSeekChat => "DeepSeek V3.2-Exp (Chat)",
525            ModelId::DeepSeekReasoner => "DeepSeek V3.2-Exp (Reasoner)",
526            // xAI models
527            ModelId::XaiGrok4 => "Grok-4",
528            ModelId::XaiGrok4Mini => "Grok-4 Mini",
529            ModelId::XaiGrok4Code => "Grok-4 Code",
530            ModelId::XaiGrok4CodeLatest => "Grok-4 Code Latest",
531            ModelId::XaiGrok4Vision => "Grok-4 Vision",
532            // Z.AI models
533            ModelId::ZaiGlm46 => "GLM 4.6",
534            ModelId::ZaiGlm45 => "GLM 4.5",
535            ModelId::ZaiGlm45Air => "GLM 4.5 Air",
536            ModelId::ZaiGlm45X => "GLM 4.5 X",
537            ModelId::ZaiGlm45Airx => "GLM 4.5 AirX",
538            ModelId::ZaiGlm45Flash => "GLM 4.5 Flash",
539            ModelId::ZaiGlm432b0414128k => "GLM 4 32B 0414 128K",
540            // Moonshot models
541            ModelId::MoonshotKimiK2TurboPreview => "Kimi K2 Turbo Preview",
542            ModelId::MoonshotKimiK20905Preview => "Kimi K2 0905 Preview",
543            ModelId::MoonshotKimiK20711Preview => "Kimi K2 0711 Preview",
544            ModelId::MoonshotKimiLatest => "Kimi Latest (auto-tier)",
545            ModelId::MoonshotKimiLatest8k => "Kimi Latest 8K",
546            ModelId::MoonshotKimiLatest32k => "Kimi Latest 32K",
547            ModelId::MoonshotKimiLatest128k => "Kimi Latest 128K",
548            // OpenRouter models
549            _ => unreachable!(),
550        }
551    }
552
553    /// Get a description of the model's characteristics
554    pub fn description(&self) -> &'static str {
555        if let Some(meta) = self.openrouter_metadata() {
556            return meta.description;
557        }
558        match self {
559            // Gemini models
560            ModelId::Gemini25FlashPreview => {
561                "Latest fast Gemini model with advanced multimodal capabilities"
562            }
563            ModelId::Gemini25Flash => {
564                "Legacy alias for Gemini 2.5 Flash Preview (same capabilities)"
565            }
566            ModelId::Gemini25FlashLite => {
567                "Legacy alias for Gemini 2.5 Flash Preview optimized for efficiency"
568            }
569            ModelId::Gemini25Pro => "Latest most capable Gemini model with reasoning",
570            // OpenAI models
571            ModelId::GPT5 => "Latest most capable OpenAI model with advanced reasoning",
572            ModelId::GPT5Codex => {
573                "Code-focused GPT-5 variant optimized for tool calling and structured outputs"
574            }
575            ModelId::GPT5Mini => "Latest efficient OpenAI model, great for most tasks",
576            ModelId::GPT5Nano => "Latest most cost-effective OpenAI model",
577            ModelId::CodexMiniLatest => "Latest Codex model optimized for code generation",
578            // Anthropic models
579            ModelId::ClaudeOpus41 => "Latest most capable Anthropic model with advanced reasoning",
580            ModelId::ClaudeSonnet45 => "Latest balanced Anthropic model for general tasks",
581            ModelId::ClaudeSonnet4 => {
582                "Previous balanced Anthropic model maintained for compatibility"
583            }
584            // DeepSeek models
585            ModelId::DeepSeekChat => {
586                "DeepSeek V3.2-Exp non-thinking mode optimized for fast coding responses"
587            }
588            ModelId::DeepSeekReasoner => {
589                "DeepSeek V3.2-Exp thinking mode with structured reasoning output"
590            }
591            // xAI models
592            ModelId::XaiGrok4 => "Flagship Grok 4 model with long context and tool use",
593            ModelId::XaiGrok4Mini => "Efficient Grok 4 Mini tuned for low latency",
594            ModelId::XaiGrok4Code => "Code-specialized Grok 4 deployment with tool support",
595            ModelId::XaiGrok4CodeLatest => {
596                "Latest Grok 4 code model offering enhanced reasoning traces"
597            }
598            ModelId::XaiGrok4Vision => "Multimodal Grok 4 model with image understanding",
599            // Z.AI models
600            ModelId::ZaiGlm46 => {
601                "Latest Z.AI GLM flagship with long-context reasoning and coding strengths"
602            }
603            ModelId::ZaiGlm45 => "Balanced GLM 4.5 release for general assistant tasks",
604            ModelId::ZaiGlm45Air => "Efficient GLM 4.5 Air variant tuned for lower latency",
605            ModelId::ZaiGlm45X => "Enhanced GLM 4.5 X variant with improved reasoning",
606            ModelId::ZaiGlm45Airx => "Hybrid GLM 4.5 AirX variant blending efficiency with quality",
607            ModelId::ZaiGlm45Flash => "Low-latency GLM 4.5 Flash optimized for responsiveness",
608            ModelId::ZaiGlm432b0414128k => {
609                "Legacy GLM 4 32B deployment offering extended 128K context window"
610            }
611            // Moonshot models
612            ModelId::MoonshotKimiK2TurboPreview => {
613                "Recommended high-speed Kimi K2 turbo variant with 256K context and 60+ tok/s output"
614            }
615            ModelId::MoonshotKimiK20905Preview => {
616                "Latest Kimi K2 0905 flagship with enhanced agentic coding, 256K context, and richer tool support"
617            }
618            ModelId::MoonshotKimiK20711Preview => {
619                "Kimi K2 0711 preview tuned for balanced cost and capability with 131K context"
620            }
621            ModelId::MoonshotKimiLatest => {
622                "Auto-tier alias that selects the right Kimi Latest vision tier (8K/32K/128K) with context caching"
623            }
624            ModelId::MoonshotKimiLatest8k => {
625                "Kimi Latest 8K vision tier for short tasks with automatic context caching"
626            }
627            ModelId::MoonshotKimiLatest32k => {
628                "Kimi Latest 32K vision tier blending longer context with latest assistant features"
629            }
630            ModelId::MoonshotKimiLatest128k => {
631                "Kimi Latest 128K flagship vision tier delivering maximum context and newest capabilities"
632            }
633            _ => unreachable!(),
634        }
635    }
636
637    /// Get all available models as a vector
638    pub fn all_models() -> Vec<ModelId> {
639        let mut models = vec![
640            // Gemini models
641            ModelId::Gemini25FlashPreview,
642            ModelId::Gemini25Flash,
643            ModelId::Gemini25FlashLite,
644            ModelId::Gemini25Pro,
645            // OpenAI models
646            ModelId::GPT5,
647            ModelId::GPT5Codex,
648            ModelId::GPT5Mini,
649            ModelId::GPT5Nano,
650            ModelId::CodexMiniLatest,
651            // Anthropic models
652            ModelId::ClaudeOpus41,
653            ModelId::ClaudeSonnet45,
654            ModelId::ClaudeSonnet4,
655            // DeepSeek models
656            ModelId::DeepSeekChat,
657            ModelId::DeepSeekReasoner,
658            // xAI models
659            ModelId::XaiGrok4,
660            ModelId::XaiGrok4Mini,
661            ModelId::XaiGrok4Code,
662            ModelId::XaiGrok4CodeLatest,
663            ModelId::XaiGrok4Vision,
664            // Z.AI models
665            ModelId::ZaiGlm46,
666            ModelId::ZaiGlm45,
667            ModelId::ZaiGlm45Air,
668            ModelId::ZaiGlm45X,
669            ModelId::ZaiGlm45Airx,
670            ModelId::ZaiGlm45Flash,
671            ModelId::ZaiGlm432b0414128k,
672            // Moonshot models
673            ModelId::MoonshotKimiK2TurboPreview,
674            ModelId::MoonshotKimiK20905Preview,
675            ModelId::MoonshotKimiK20711Preview,
676            ModelId::MoonshotKimiLatest,
677            ModelId::MoonshotKimiLatest8k,
678            ModelId::MoonshotKimiLatest32k,
679            ModelId::MoonshotKimiLatest128k,
680        ];
681        models.extend(Self::openrouter_models());
682        models
683    }
684
685    /// Get all models for a specific provider
686    pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
687        Self::all_models()
688            .into_iter()
689            .filter(|model| model.provider() == provider)
690            .collect()
691    }
692
693    /// Get recommended fallback models in order of preference
694    pub fn fallback_models() -> Vec<ModelId> {
695        vec![
696            ModelId::Gemini25FlashPreview,
697            ModelId::Gemini25Pro,
698            ModelId::GPT5,
699            ModelId::ClaudeOpus41,
700            ModelId::ClaudeSonnet45,
701            ModelId::DeepSeekReasoner,
702            ModelId::MoonshotKimiK20905Preview,
703            ModelId::XaiGrok4,
704            ModelId::ZaiGlm46,
705            ModelId::OpenRouterGrokCodeFast1,
706        ]
707    }
708
709    /// Get the default model for general use
710    pub fn default() -> Self {
711        ModelId::Gemini25FlashPreview
712    }
713
714    /// Get the default orchestrator model (more capable)
715    pub fn default_orchestrator() -> Self {
716        ModelId::Gemini25Pro
717    }
718
719    /// Get the default subagent model (fast and efficient)
720    pub fn default_subagent() -> Self {
721        ModelId::Gemini25FlashPreview
722    }
723
724    /// Get provider-specific defaults for orchestrator
725    pub fn default_orchestrator_for_provider(provider: Provider) -> Self {
726        match provider {
727            Provider::Gemini => ModelId::Gemini25Pro,
728            Provider::OpenAI => ModelId::GPT5,
729            Provider::Anthropic => ModelId::ClaudeOpus41,
730            Provider::DeepSeek => ModelId::DeepSeekReasoner,
731            Provider::Moonshot => ModelId::MoonshotKimiK20905Preview,
732            Provider::XAI => ModelId::XaiGrok4,
733            Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
734            Provider::ZAI => ModelId::ZaiGlm46,
735        }
736    }
737
738    /// Get provider-specific defaults for subagent
739    pub fn default_subagent_for_provider(provider: Provider) -> Self {
740        match provider {
741            Provider::Gemini => ModelId::Gemini25FlashPreview,
742            Provider::OpenAI => ModelId::GPT5Mini,
743            Provider::Anthropic => ModelId::ClaudeSonnet45,
744            Provider::DeepSeek => ModelId::DeepSeekChat,
745            Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
746            Provider::XAI => ModelId::XaiGrok4Code,
747            Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
748            Provider::ZAI => ModelId::ZaiGlm45Flash,
749        }
750    }
751
752    /// Get provider-specific defaults for single agent
753    pub fn default_single_for_provider(provider: Provider) -> Self {
754        match provider {
755            Provider::Gemini => ModelId::Gemini25FlashPreview,
756            Provider::OpenAI => ModelId::GPT5,
757            Provider::Anthropic => ModelId::ClaudeOpus41,
758            Provider::DeepSeek => ModelId::DeepSeekReasoner,
759            Provider::Moonshot => ModelId::MoonshotKimiK2TurboPreview,
760            Provider::XAI => ModelId::XaiGrok4,
761            Provider::OpenRouter => ModelId::OpenRouterGrokCodeFast1,
762            Provider::ZAI => ModelId::ZaiGlm46,
763        }
764    }
765
766    /// Check if this is a "flash" variant (optimized for speed)
767    pub fn is_flash_variant(&self) -> bool {
768        matches!(
769            self,
770            ModelId::Gemini25FlashPreview
771                | ModelId::Gemini25Flash
772                | ModelId::Gemini25FlashLite
773                | ModelId::ZaiGlm45Flash
774                | ModelId::MoonshotKimiK2TurboPreview
775                | ModelId::MoonshotKimiLatest8k
776        )
777    }
778
779    /// Check if this is a "pro" variant (optimized for capability)
780    pub fn is_pro_variant(&self) -> bool {
781        matches!(
782            self,
783            ModelId::Gemini25Pro
784                | ModelId::GPT5
785                | ModelId::GPT5Codex
786                | ModelId::ClaudeOpus41
787                | ModelId::DeepSeekReasoner
788                | ModelId::XaiGrok4
789                | ModelId::ZaiGlm46
790                | ModelId::MoonshotKimiK20905Preview
791                | ModelId::MoonshotKimiLatest128k
792        )
793    }
794
795    /// Check if this is an optimized/efficient variant
796    pub fn is_efficient_variant(&self) -> bool {
797        if let Some(meta) = self.openrouter_metadata() {
798            return meta.efficient;
799        }
800        matches!(
801            self,
802            ModelId::Gemini25FlashPreview
803                | ModelId::Gemini25Flash
804                | ModelId::Gemini25FlashLite
805                | ModelId::GPT5Mini
806                | ModelId::GPT5Nano
807                | ModelId::DeepSeekChat
808                | ModelId::XaiGrok4Code
809                | ModelId::ZaiGlm45Air
810                | ModelId::ZaiGlm45Airx
811                | ModelId::ZaiGlm45Flash
812                | ModelId::MoonshotKimiK2TurboPreview
813                | ModelId::MoonshotKimiLatest8k
814        )
815    }
816
817    /// Check if this is a top-tier model
818    pub fn is_top_tier(&self) -> bool {
819        if let Some(meta) = self.openrouter_metadata() {
820            return meta.top_tier;
821        }
822        matches!(
823            self,
824            ModelId::Gemini25Pro
825                | ModelId::GPT5
826                | ModelId::GPT5Codex
827                | ModelId::ClaudeOpus41
828                | ModelId::ClaudeSonnet45
829                | ModelId::ClaudeSonnet4
830                | ModelId::DeepSeekReasoner
831                | ModelId::XaiGrok4
832                | ModelId::XaiGrok4CodeLatest
833                | ModelId::ZaiGlm46
834                | ModelId::MoonshotKimiK20905Preview
835                | ModelId::MoonshotKimiLatest128k
836        )
837    }
838
839    /// Get the generation/version string for this model
840    pub fn generation(&self) -> &'static str {
841        if let Some(meta) = self.openrouter_metadata() {
842            return meta.generation;
843        }
844        match self {
845            // Gemini generations
846            ModelId::Gemini25FlashPreview
847            | ModelId::Gemini25Flash
848            | ModelId::Gemini25FlashLite
849            | ModelId::Gemini25Pro => "2.5",
850            // OpenAI generations
851            ModelId::GPT5
852            | ModelId::GPT5Codex
853            | ModelId::GPT5Mini
854            | ModelId::GPT5Nano
855            | ModelId::CodexMiniLatest => "5",
856            // Anthropic generations
857            ModelId::ClaudeSonnet45 => "4.5",
858            ModelId::ClaudeSonnet4 => "4",
859            ModelId::ClaudeOpus41 => "4.1",
860            // DeepSeek generations
861            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
862            // xAI generations
863            ModelId::XaiGrok4
864            | ModelId::XaiGrok4Mini
865            | ModelId::XaiGrok4Code
866            | ModelId::XaiGrok4CodeLatest
867            | ModelId::XaiGrok4Vision => "4",
868            // Z.AI generations
869            ModelId::ZaiGlm46 => "4.6",
870            ModelId::ZaiGlm45
871            | ModelId::ZaiGlm45Air
872            | ModelId::ZaiGlm45X
873            | ModelId::ZaiGlm45Airx
874            | ModelId::ZaiGlm45Flash => "4.5",
875            ModelId::ZaiGlm432b0414128k => "4-32B",
876            // Moonshot generations
877            ModelId::MoonshotKimiK2TurboPreview
878            | ModelId::MoonshotKimiK20905Preview
879            | ModelId::MoonshotKimiK20711Preview => "k2",
880            ModelId::MoonshotKimiLatest
881            | ModelId::MoonshotKimiLatest8k
882            | ModelId::MoonshotKimiLatest32k
883            | ModelId::MoonshotKimiLatest128k => "latest",
884            _ => unreachable!(),
885        }
886    }
887}
888
889impl fmt::Display for ModelId {
890    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891        write!(f, "{}", self.as_str())
892    }
893}
894
895impl FromStr for ModelId {
896    type Err = ModelParseError;
897
898    fn from_str(s: &str) -> Result<Self, Self::Err> {
899        use crate::config::constants::models;
900        match s {
901            // Gemini models
902            s if s == models::GEMINI_2_5_FLASH_PREVIEW => Ok(ModelId::Gemini25FlashPreview),
903            s if s == models::GEMINI_2_5_FLASH => Ok(ModelId::Gemini25Flash),
904            s if s == models::GEMINI_2_5_FLASH_LITE => Ok(ModelId::Gemini25FlashLite),
905            s if s == models::GEMINI_2_5_PRO => Ok(ModelId::Gemini25Pro),
906            // OpenAI models
907            s if s == models::GPT_5 => Ok(ModelId::GPT5),
908            s if s == models::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
909            s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
910            s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
911            s if s == models::CODEX_MINI_LATEST => Ok(ModelId::CodexMiniLatest),
912            // Anthropic models
913            s if s == models::CLAUDE_OPUS_4_1_20250805 => Ok(ModelId::ClaudeOpus41),
914            s if s == models::CLAUDE_SONNET_4_5 => Ok(ModelId::ClaudeSonnet45),
915            s if s == models::CLAUDE_SONNET_4_20250514 => Ok(ModelId::ClaudeSonnet4),
916            // DeepSeek models
917            s if s == models::DEEPSEEK_CHAT => Ok(ModelId::DeepSeekChat),
918            s if s == models::DEEPSEEK_REASONER => Ok(ModelId::DeepSeekReasoner),
919            // xAI models
920            s if s == models::xai::GROK_4 => Ok(ModelId::XaiGrok4),
921            s if s == models::xai::GROK_4_MINI => Ok(ModelId::XaiGrok4Mini),
922            s if s == models::xai::GROK_4_CODE => Ok(ModelId::XaiGrok4Code),
923            s if s == models::xai::GROK_4_CODE_LATEST => Ok(ModelId::XaiGrok4CodeLatest),
924            s if s == models::xai::GROK_4_VISION => Ok(ModelId::XaiGrok4Vision),
925            // Z.AI models
926            s if s == models::zai::GLM_4_6 => Ok(ModelId::ZaiGlm46),
927            s if s == models::zai::GLM_4_5 => Ok(ModelId::ZaiGlm45),
928            s if s == models::zai::GLM_4_5_AIR => Ok(ModelId::ZaiGlm45Air),
929            s if s == models::zai::GLM_4_5_X => Ok(ModelId::ZaiGlm45X),
930            s if s == models::zai::GLM_4_5_AIRX => Ok(ModelId::ZaiGlm45Airx),
931            s if s == models::zai::GLM_4_5_FLASH => Ok(ModelId::ZaiGlm45Flash),
932            s if s == models::zai::GLM_4_32B_0414_128K => Ok(ModelId::ZaiGlm432b0414128k),
933            // Moonshot models
934            s if s == models::MOONSHOT_KIMI_K2_TURBO_PREVIEW => {
935                Ok(ModelId::MoonshotKimiK2TurboPreview)
936            }
937            s if s == models::MOONSHOT_KIMI_K2_0905_PREVIEW => {
938                Ok(ModelId::MoonshotKimiK20905Preview)
939            }
940            s if s == models::MOONSHOT_KIMI_K2_0711_PREVIEW => {
941                Ok(ModelId::MoonshotKimiK20711Preview)
942            }
943            s if s == models::MOONSHOT_KIMI_LATEST => Ok(ModelId::MoonshotKimiLatest),
944            s if s == models::MOONSHOT_KIMI_LATEST_8K => Ok(ModelId::MoonshotKimiLatest8k),
945            s if s == models::MOONSHOT_KIMI_LATEST_32K => Ok(ModelId::MoonshotKimiLatest32k),
946            s if s == models::MOONSHOT_KIMI_LATEST_128K => Ok(ModelId::MoonshotKimiLatest128k),
947            _ => {
948                if let Some(model) = Self::parse_openrouter_model(s) {
949                    Ok(model)
950                } else {
951                    Err(ModelParseError::InvalidModel(s.to_string()))
952                }
953            }
954        }
955    }
956}
957
958/// Error type for model parsing failures
959#[derive(Debug, Clone, PartialEq)]
960pub enum ModelParseError {
961    InvalidModel(String),
962    InvalidProvider(String),
963}
964
965impl fmt::Display for ModelParseError {
966    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
967        match self {
968            ModelParseError::InvalidModel(model) => {
969                write!(
970                    f,
971                    "Invalid model identifier: '{}'. Supported models: {}",
972                    model,
973                    ModelId::all_models()
974                        .iter()
975                        .map(|m| m.as_str())
976                        .collect::<Vec<_>>()
977                        .join(", ")
978                )
979            }
980            ModelParseError::InvalidProvider(provider) => {
981                write!(
982                    f,
983                    "Invalid provider: '{}'. Supported providers: {}",
984                    provider,
985                    Provider::all_providers()
986                        .iter()
987                        .map(|p| p.to_string())
988                        .collect::<Vec<_>>()
989                        .join(", ")
990                )
991            }
992        }
993    }
994}
995
996impl std::error::Error for ModelParseError {}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001    use crate::config::constants::models;
1002
1003    #[test]
1004    fn test_model_string_conversion() {
1005        // Gemini models
1006        assert_eq!(
1007            ModelId::Gemini25FlashPreview.as_str(),
1008            models::GEMINI_2_5_FLASH_PREVIEW
1009        );
1010        assert_eq!(ModelId::Gemini25Flash.as_str(), models::GEMINI_2_5_FLASH);
1011        assert_eq!(
1012            ModelId::Gemini25FlashLite.as_str(),
1013            models::GEMINI_2_5_FLASH_LITE
1014        );
1015        assert_eq!(ModelId::Gemini25Pro.as_str(), models::GEMINI_2_5_PRO);
1016        // OpenAI models
1017        assert_eq!(ModelId::GPT5.as_str(), models::GPT_5);
1018        assert_eq!(ModelId::GPT5Codex.as_str(), models::GPT_5_CODEX);
1019        assert_eq!(ModelId::GPT5Mini.as_str(), models::GPT_5_MINI);
1020        assert_eq!(ModelId::GPT5Nano.as_str(), models::GPT_5_NANO);
1021        assert_eq!(ModelId::CodexMiniLatest.as_str(), models::CODEX_MINI_LATEST);
1022        // Anthropic models
1023        assert_eq!(ModelId::ClaudeSonnet45.as_str(), models::CLAUDE_SONNET_4_5);
1024        assert_eq!(
1025            ModelId::ClaudeSonnet4.as_str(),
1026            models::CLAUDE_SONNET_4_20250514
1027        );
1028        assert_eq!(
1029            ModelId::ClaudeOpus41.as_str(),
1030            models::CLAUDE_OPUS_4_1_20250805
1031        );
1032        // DeepSeek models
1033        assert_eq!(ModelId::DeepSeekChat.as_str(), models::DEEPSEEK_CHAT);
1034        assert_eq!(
1035            ModelId::DeepSeekReasoner.as_str(),
1036            models::DEEPSEEK_REASONER
1037        );
1038        // xAI models
1039        assert_eq!(ModelId::XaiGrok4.as_str(), models::xai::GROK_4);
1040        assert_eq!(ModelId::XaiGrok4Mini.as_str(), models::xai::GROK_4_MINI);
1041        assert_eq!(ModelId::XaiGrok4Code.as_str(), models::xai::GROK_4_CODE);
1042        assert_eq!(
1043            ModelId::XaiGrok4CodeLatest.as_str(),
1044            models::xai::GROK_4_CODE_LATEST
1045        );
1046        assert_eq!(ModelId::XaiGrok4Vision.as_str(), models::xai::GROK_4_VISION);
1047        // Z.AI models
1048        assert_eq!(ModelId::ZaiGlm46.as_str(), models::zai::GLM_4_6);
1049        assert_eq!(ModelId::ZaiGlm45.as_str(), models::zai::GLM_4_5);
1050        assert_eq!(ModelId::ZaiGlm45Air.as_str(), models::zai::GLM_4_5_AIR);
1051        assert_eq!(ModelId::ZaiGlm45X.as_str(), models::zai::GLM_4_5_X);
1052        assert_eq!(ModelId::ZaiGlm45Airx.as_str(), models::zai::GLM_4_5_AIRX);
1053        assert_eq!(ModelId::ZaiGlm45Flash.as_str(), models::zai::GLM_4_5_FLASH);
1054        assert_eq!(
1055            ModelId::ZaiGlm432b0414128k.as_str(),
1056            models::zai::GLM_4_32B_0414_128K
1057        );
1058        macro_rules! assert_openrouter_to_string {
1059            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1060                $(assert_eq!(ModelId::$variant.as_str(), models::$const);)*
1061            };
1062        }
1063        each_openrouter_variant!(assert_openrouter_to_string);
1064    }
1065
1066    #[test]
1067    fn test_model_from_string() {
1068        // Gemini models
1069        assert_eq!(
1070            models::GEMINI_2_5_FLASH_PREVIEW.parse::<ModelId>().unwrap(),
1071            ModelId::Gemini25FlashPreview
1072        );
1073        assert_eq!(
1074            models::GEMINI_2_5_FLASH.parse::<ModelId>().unwrap(),
1075            ModelId::Gemini25Flash
1076        );
1077        assert_eq!(
1078            models::GEMINI_2_5_FLASH_LITE.parse::<ModelId>().unwrap(),
1079            ModelId::Gemini25FlashLite
1080        );
1081        assert_eq!(
1082            models::GEMINI_2_5_PRO.parse::<ModelId>().unwrap(),
1083            ModelId::Gemini25Pro
1084        );
1085        // OpenAI models
1086        assert_eq!(models::GPT_5.parse::<ModelId>().unwrap(), ModelId::GPT5);
1087        assert_eq!(
1088            models::GPT_5_CODEX.parse::<ModelId>().unwrap(),
1089            ModelId::GPT5Codex
1090        );
1091        assert_eq!(
1092            models::GPT_5_MINI.parse::<ModelId>().unwrap(),
1093            ModelId::GPT5Mini
1094        );
1095        assert_eq!(
1096            models::GPT_5_NANO.parse::<ModelId>().unwrap(),
1097            ModelId::GPT5Nano
1098        );
1099        assert_eq!(
1100            models::CODEX_MINI_LATEST.parse::<ModelId>().unwrap(),
1101            ModelId::CodexMiniLatest
1102        );
1103        // Anthropic models
1104        assert_eq!(
1105            models::CLAUDE_SONNET_4_5.parse::<ModelId>().unwrap(),
1106            ModelId::ClaudeSonnet45
1107        );
1108        assert_eq!(
1109            models::CLAUDE_SONNET_4_20250514.parse::<ModelId>().unwrap(),
1110            ModelId::ClaudeSonnet4
1111        );
1112        assert_eq!(
1113            models::CLAUDE_OPUS_4_1_20250805.parse::<ModelId>().unwrap(),
1114            ModelId::ClaudeOpus41
1115        );
1116        // DeepSeek models
1117        assert_eq!(
1118            models::DEEPSEEK_CHAT.parse::<ModelId>().unwrap(),
1119            ModelId::DeepSeekChat
1120        );
1121        assert_eq!(
1122            models::DEEPSEEK_REASONER.parse::<ModelId>().unwrap(),
1123            ModelId::DeepSeekReasoner
1124        );
1125        // xAI models
1126        assert_eq!(
1127            models::xai::GROK_4.parse::<ModelId>().unwrap(),
1128            ModelId::XaiGrok4
1129        );
1130        assert_eq!(
1131            models::xai::GROK_4_MINI.parse::<ModelId>().unwrap(),
1132            ModelId::XaiGrok4Mini
1133        );
1134        assert_eq!(
1135            models::xai::GROK_4_CODE.parse::<ModelId>().unwrap(),
1136            ModelId::XaiGrok4Code
1137        );
1138        assert_eq!(
1139            models::xai::GROK_4_CODE_LATEST.parse::<ModelId>().unwrap(),
1140            ModelId::XaiGrok4CodeLatest
1141        );
1142        assert_eq!(
1143            models::xai::GROK_4_VISION.parse::<ModelId>().unwrap(),
1144            ModelId::XaiGrok4Vision
1145        );
1146        // Z.AI models
1147        assert_eq!(
1148            models::zai::GLM_4_6.parse::<ModelId>().unwrap(),
1149            ModelId::ZaiGlm46
1150        );
1151        assert_eq!(
1152            models::zai::GLM_4_5.parse::<ModelId>().unwrap(),
1153            ModelId::ZaiGlm45
1154        );
1155        assert_eq!(
1156            models::zai::GLM_4_5_AIR.parse::<ModelId>().unwrap(),
1157            ModelId::ZaiGlm45Air
1158        );
1159        assert_eq!(
1160            models::zai::GLM_4_5_X.parse::<ModelId>().unwrap(),
1161            ModelId::ZaiGlm45X
1162        );
1163        assert_eq!(
1164            models::zai::GLM_4_5_AIRX.parse::<ModelId>().unwrap(),
1165            ModelId::ZaiGlm45Airx
1166        );
1167        assert_eq!(
1168            models::zai::GLM_4_5_FLASH.parse::<ModelId>().unwrap(),
1169            ModelId::ZaiGlm45Flash
1170        );
1171        assert_eq!(
1172            models::zai::GLM_4_32B_0414_128K.parse::<ModelId>().unwrap(),
1173            ModelId::ZaiGlm432b0414128k
1174        );
1175        assert_eq!(
1176            models::MOONSHOT_KIMI_K2_TURBO_PREVIEW
1177                .parse::<ModelId>()
1178                .unwrap(),
1179            ModelId::MoonshotKimiK2TurboPreview
1180        );
1181        assert_eq!(
1182            models::MOONSHOT_KIMI_K2_0905_PREVIEW
1183                .parse::<ModelId>()
1184                .unwrap(),
1185            ModelId::MoonshotKimiK20905Preview
1186        );
1187        assert_eq!(
1188            models::MOONSHOT_KIMI_K2_0711_PREVIEW
1189                .parse::<ModelId>()
1190                .unwrap(),
1191            ModelId::MoonshotKimiK20711Preview
1192        );
1193        assert_eq!(
1194            models::MOONSHOT_KIMI_LATEST.parse::<ModelId>().unwrap(),
1195            ModelId::MoonshotKimiLatest
1196        );
1197        assert_eq!(
1198            models::MOONSHOT_KIMI_LATEST_8K.parse::<ModelId>().unwrap(),
1199            ModelId::MoonshotKimiLatest8k
1200        );
1201        assert_eq!(
1202            models::MOONSHOT_KIMI_LATEST_32K.parse::<ModelId>().unwrap(),
1203            ModelId::MoonshotKimiLatest32k
1204        );
1205        assert_eq!(
1206            models::MOONSHOT_KIMI_LATEST_128K
1207                .parse::<ModelId>()
1208                .unwrap(),
1209            ModelId::MoonshotKimiLatest128k
1210        );
1211        macro_rules! assert_openrouter_parse {
1212            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1213                $(assert_eq!(models::$const.parse::<ModelId>().unwrap(), ModelId::$variant);)*
1214            };
1215        }
1216        each_openrouter_variant!(assert_openrouter_parse);
1217        // Invalid model
1218        assert!("invalid-model".parse::<ModelId>().is_err());
1219    }
1220
1221    #[test]
1222    fn test_provider_parsing() {
1223        assert_eq!("gemini".parse::<Provider>().unwrap(), Provider::Gemini);
1224        assert_eq!("openai".parse::<Provider>().unwrap(), Provider::OpenAI);
1225        assert_eq!(
1226            "anthropic".parse::<Provider>().unwrap(),
1227            Provider::Anthropic
1228        );
1229        assert_eq!("deepseek".parse::<Provider>().unwrap(), Provider::DeepSeek);
1230        assert_eq!(
1231            "openrouter".parse::<Provider>().unwrap(),
1232            Provider::OpenRouter
1233        );
1234        assert_eq!("xai".parse::<Provider>().unwrap(), Provider::XAI);
1235        assert_eq!("zai".parse::<Provider>().unwrap(), Provider::ZAI);
1236        assert_eq!("moonshot".parse::<Provider>().unwrap(), Provider::Moonshot);
1237        assert!("invalid-provider".parse::<Provider>().is_err());
1238    }
1239
1240    #[test]
1241    fn test_model_providers() {
1242        assert_eq!(ModelId::Gemini25FlashPreview.provider(), Provider::Gemini);
1243        assert_eq!(ModelId::GPT5.provider(), Provider::OpenAI);
1244        assert_eq!(ModelId::GPT5Codex.provider(), Provider::OpenAI);
1245        assert_eq!(ModelId::ClaudeSonnet45.provider(), Provider::Anthropic);
1246        assert_eq!(ModelId::ClaudeSonnet4.provider(), Provider::Anthropic);
1247        assert_eq!(ModelId::DeepSeekChat.provider(), Provider::DeepSeek);
1248        assert_eq!(ModelId::XaiGrok4.provider(), Provider::XAI);
1249        assert_eq!(ModelId::ZaiGlm46.provider(), Provider::ZAI);
1250        assert_eq!(
1251            ModelId::MoonshotKimiK20905Preview.provider(),
1252            Provider::Moonshot
1253        );
1254        assert_eq!(
1255            ModelId::OpenRouterGrokCodeFast1.provider(),
1256            Provider::OpenRouter
1257        );
1258        assert_eq!(
1259            ModelId::OpenRouterAnthropicClaudeSonnet45.provider(),
1260            Provider::OpenRouter
1261        );
1262
1263        macro_rules! assert_openrouter_provider_all {
1264            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1265                $(assert_eq!(ModelId::$variant.provider(), Provider::OpenRouter);)*
1266            };
1267        }
1268        each_openrouter_variant!(assert_openrouter_provider_all);
1269    }
1270
1271    #[test]
1272    fn test_provider_defaults() {
1273        assert_eq!(
1274            ModelId::default_orchestrator_for_provider(Provider::Gemini),
1275            ModelId::Gemini25Pro
1276        );
1277        assert_eq!(
1278            ModelId::default_orchestrator_for_provider(Provider::OpenAI),
1279            ModelId::GPT5
1280        );
1281        assert_eq!(
1282            ModelId::default_orchestrator_for_provider(Provider::Anthropic),
1283            ModelId::ClaudeSonnet4
1284        );
1285        assert_eq!(
1286            ModelId::default_orchestrator_for_provider(Provider::DeepSeek),
1287            ModelId::DeepSeekReasoner
1288        );
1289        assert_eq!(
1290            ModelId::default_orchestrator_for_provider(Provider::OpenRouter),
1291            ModelId::OpenRouterGrokCodeFast1
1292        );
1293        assert_eq!(
1294            ModelId::default_orchestrator_for_provider(Provider::XAI),
1295            ModelId::XaiGrok4
1296        );
1297        assert_eq!(
1298            ModelId::default_orchestrator_for_provider(Provider::ZAI),
1299            ModelId::ZaiGlm46
1300        );
1301        assert_eq!(
1302            ModelId::default_orchestrator_for_provider(Provider::Moonshot),
1303            ModelId::MoonshotKimiK20905Preview
1304        );
1305
1306        assert_eq!(
1307            ModelId::default_subagent_for_provider(Provider::Gemini),
1308            ModelId::Gemini25FlashPreview
1309        );
1310        assert_eq!(
1311            ModelId::default_subagent_for_provider(Provider::OpenAI),
1312            ModelId::GPT5Mini
1313        );
1314        assert_eq!(
1315            ModelId::default_subagent_for_provider(Provider::Anthropic),
1316            ModelId::ClaudeSonnet45
1317        );
1318        assert_eq!(
1319            ModelId::default_subagent_for_provider(Provider::DeepSeek),
1320            ModelId::DeepSeekChat
1321        );
1322        assert_eq!(
1323            ModelId::default_subagent_for_provider(Provider::OpenRouter),
1324            ModelId::OpenRouterGrokCodeFast1
1325        );
1326        assert_eq!(
1327            ModelId::default_subagent_for_provider(Provider::XAI),
1328            ModelId::XaiGrok4Code
1329        );
1330        assert_eq!(
1331            ModelId::default_subagent_for_provider(Provider::ZAI),
1332            ModelId::ZaiGlm45Flash
1333        );
1334        assert_eq!(
1335            ModelId::default_subagent_for_provider(Provider::Moonshot),
1336            ModelId::MoonshotKimiK2TurboPreview
1337        );
1338
1339        assert_eq!(
1340            ModelId::default_single_for_provider(Provider::DeepSeek),
1341            ModelId::DeepSeekReasoner
1342        );
1343        assert_eq!(
1344            ModelId::default_single_for_provider(Provider::Moonshot),
1345            ModelId::MoonshotKimiK2TurboPreview
1346        );
1347    }
1348
1349    #[test]
1350    fn test_model_defaults() {
1351        assert_eq!(ModelId::default(), ModelId::Gemini25FlashPreview);
1352        assert_eq!(ModelId::default_orchestrator(), ModelId::Gemini25Pro);
1353        assert_eq!(ModelId::default_subagent(), ModelId::Gemini25FlashPreview);
1354    }
1355
1356    #[test]
1357    fn test_model_variants() {
1358        // Flash variants
1359        assert!(ModelId::Gemini25FlashPreview.is_flash_variant());
1360        assert!(ModelId::Gemini25Flash.is_flash_variant());
1361        assert!(ModelId::Gemini25FlashLite.is_flash_variant());
1362        assert!(!ModelId::GPT5.is_flash_variant());
1363        assert!(ModelId::ZaiGlm45Flash.is_flash_variant());
1364        assert!(ModelId::MoonshotKimiK2TurboPreview.is_flash_variant());
1365        assert!(ModelId::MoonshotKimiLatest8k.is_flash_variant());
1366
1367        // Pro variants
1368        assert!(ModelId::Gemini25Pro.is_pro_variant());
1369        assert!(ModelId::GPT5.is_pro_variant());
1370        assert!(ModelId::GPT5Codex.is_pro_variant());
1371        assert!(ModelId::DeepSeekReasoner.is_pro_variant());
1372        assert!(ModelId::ZaiGlm46.is_pro_variant());
1373        assert!(ModelId::MoonshotKimiK20905Preview.is_pro_variant());
1374        assert!(ModelId::MoonshotKimiLatest128k.is_pro_variant());
1375        assert!(!ModelId::Gemini25FlashPreview.is_pro_variant());
1376
1377        // Efficient variants
1378        assert!(ModelId::Gemini25FlashPreview.is_efficient_variant());
1379        assert!(ModelId::Gemini25Flash.is_efficient_variant());
1380        assert!(ModelId::Gemini25FlashLite.is_efficient_variant());
1381        assert!(ModelId::GPT5Mini.is_efficient_variant());
1382        assert!(ModelId::XaiGrok4Code.is_efficient_variant());
1383        assert!(ModelId::DeepSeekChat.is_efficient_variant());
1384        assert!(ModelId::ZaiGlm45Air.is_efficient_variant());
1385        assert!(ModelId::ZaiGlm45Airx.is_efficient_variant());
1386        assert!(ModelId::ZaiGlm45Flash.is_efficient_variant());
1387        assert!(ModelId::MoonshotKimiK2TurboPreview.is_efficient_variant());
1388        assert!(ModelId::MoonshotKimiLatest8k.is_efficient_variant());
1389        assert!(!ModelId::GPT5.is_efficient_variant());
1390
1391        macro_rules! assert_openrouter_efficiency {
1392            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1393                $(assert_eq!(ModelId::$variant.is_efficient_variant(), $efficient);)*
1394            };
1395        }
1396        each_openrouter_variant!(assert_openrouter_efficiency);
1397
1398        // Top tier models
1399        assert!(ModelId::Gemini25Pro.is_top_tier());
1400        assert!(ModelId::GPT5.is_top_tier());
1401        assert!(ModelId::GPT5Codex.is_top_tier());
1402        assert!(ModelId::ClaudeSonnet45.is_top_tier());
1403        assert!(ModelId::ClaudeSonnet4.is_top_tier());
1404        assert!(ModelId::XaiGrok4.is_top_tier());
1405        assert!(ModelId::XaiGrok4CodeLatest.is_top_tier());
1406        assert!(ModelId::DeepSeekReasoner.is_top_tier());
1407        assert!(ModelId::ZaiGlm46.is_top_tier());
1408        assert!(ModelId::MoonshotKimiK20905Preview.is_top_tier());
1409        assert!(ModelId::MoonshotKimiLatest128k.is_top_tier());
1410        assert!(!ModelId::Gemini25FlashPreview.is_top_tier());
1411
1412        macro_rules! assert_openrouter_top_tier {
1413            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1414                $(assert_eq!(ModelId::$variant.is_top_tier(), $top);)*
1415            };
1416        }
1417        each_openrouter_variant!(assert_openrouter_top_tier);
1418    }
1419
1420    #[test]
1421    fn test_model_generation() {
1422        // Gemini generations
1423        assert_eq!(ModelId::Gemini25FlashPreview.generation(), "2.5");
1424        assert_eq!(ModelId::Gemini25Flash.generation(), "2.5");
1425        assert_eq!(ModelId::Gemini25FlashLite.generation(), "2.5");
1426        assert_eq!(ModelId::Gemini25Pro.generation(), "2.5");
1427
1428        // OpenAI generations
1429        assert_eq!(ModelId::GPT5.generation(), "5");
1430        assert_eq!(ModelId::GPT5Codex.generation(), "5");
1431        assert_eq!(ModelId::GPT5Mini.generation(), "5");
1432        assert_eq!(ModelId::GPT5Nano.generation(), "5");
1433        assert_eq!(ModelId::CodexMiniLatest.generation(), "5");
1434
1435        // Anthropic generations
1436        assert_eq!(ModelId::ClaudeSonnet45.generation(), "4.5");
1437        assert_eq!(ModelId::ClaudeSonnet4.generation(), "4");
1438        assert_eq!(ModelId::ClaudeOpus41.generation(), "4.1");
1439
1440        // DeepSeek generations
1441        assert_eq!(ModelId::DeepSeekChat.generation(), "V3.2-Exp");
1442        assert_eq!(ModelId::DeepSeekReasoner.generation(), "V3.2-Exp");
1443
1444        // xAI generations
1445        assert_eq!(ModelId::XaiGrok4.generation(), "4");
1446        assert_eq!(ModelId::XaiGrok4Mini.generation(), "4");
1447        assert_eq!(ModelId::XaiGrok4Code.generation(), "4");
1448        assert_eq!(ModelId::XaiGrok4CodeLatest.generation(), "4");
1449        assert_eq!(ModelId::XaiGrok4Vision.generation(), "4");
1450        // Z.AI generations
1451        assert_eq!(ModelId::ZaiGlm46.generation(), "4.6");
1452        assert_eq!(ModelId::ZaiGlm45.generation(), "4.5");
1453        assert_eq!(ModelId::ZaiGlm45Air.generation(), "4.5");
1454        assert_eq!(ModelId::ZaiGlm45X.generation(), "4.5");
1455        assert_eq!(ModelId::ZaiGlm45Airx.generation(), "4.5");
1456        assert_eq!(ModelId::ZaiGlm45Flash.generation(), "4.5");
1457        assert_eq!(ModelId::ZaiGlm432b0414128k.generation(), "4-32B");
1458        assert_eq!(ModelId::MoonshotKimiK2TurboPreview.generation(), "k2");
1459        assert_eq!(ModelId::MoonshotKimiK20905Preview.generation(), "k2");
1460        assert_eq!(ModelId::MoonshotKimiK20711Preview.generation(), "k2");
1461        assert_eq!(ModelId::MoonshotKimiLatest.generation(), "latest");
1462        assert_eq!(ModelId::MoonshotKimiLatest8k.generation(), "latest");
1463        assert_eq!(ModelId::MoonshotKimiLatest32k.generation(), "latest");
1464        assert_eq!(ModelId::MoonshotKimiLatest128k.generation(), "latest");
1465
1466        macro_rules! assert_openrouter_generation {
1467            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1468                $(assert_eq!(ModelId::$variant.generation(), $generation);)*
1469            };
1470        }
1471        each_openrouter_variant!(assert_openrouter_generation);
1472    }
1473
1474    #[test]
1475    fn test_models_for_provider() {
1476        let gemini_models = ModelId::models_for_provider(Provider::Gemini);
1477        assert!(gemini_models.contains(&ModelId::Gemini25Pro));
1478        assert!(!gemini_models.contains(&ModelId::GPT5));
1479
1480        let openai_models = ModelId::models_for_provider(Provider::OpenAI);
1481        assert!(openai_models.contains(&ModelId::GPT5));
1482        assert!(openai_models.contains(&ModelId::GPT5Codex));
1483        assert!(!openai_models.contains(&ModelId::Gemini25Pro));
1484
1485        let anthropic_models = ModelId::models_for_provider(Provider::Anthropic);
1486        assert!(anthropic_models.contains(&ModelId::ClaudeSonnet45));
1487        assert!(anthropic_models.contains(&ModelId::ClaudeSonnet4));
1488        assert!(!anthropic_models.contains(&ModelId::GPT5));
1489
1490        let deepseek_models = ModelId::models_for_provider(Provider::DeepSeek);
1491        assert!(deepseek_models.contains(&ModelId::DeepSeekChat));
1492        assert!(deepseek_models.contains(&ModelId::DeepSeekReasoner));
1493
1494        let openrouter_models = ModelId::models_for_provider(Provider::OpenRouter);
1495        macro_rules! assert_openrouter_models_present {
1496            ($(($variant:ident, $const:ident, $display:expr, $description:expr, $efficient:expr, $top:expr, $generation:expr),)*) => {
1497                $(assert!(openrouter_models.contains(&ModelId::$variant));)*
1498            };
1499        }
1500        each_openrouter_variant!(assert_openrouter_models_present);
1501
1502        let xai_models = ModelId::models_for_provider(Provider::XAI);
1503        assert!(xai_models.contains(&ModelId::XaiGrok4));
1504        assert!(xai_models.contains(&ModelId::XaiGrok4Mini));
1505        assert!(xai_models.contains(&ModelId::XaiGrok4Code));
1506        assert!(xai_models.contains(&ModelId::XaiGrok4CodeLatest));
1507        assert!(xai_models.contains(&ModelId::XaiGrok4Vision));
1508
1509        let zai_models = ModelId::models_for_provider(Provider::ZAI);
1510        assert!(zai_models.contains(&ModelId::ZaiGlm46));
1511        assert!(zai_models.contains(&ModelId::ZaiGlm45));
1512        assert!(zai_models.contains(&ModelId::ZaiGlm45Air));
1513        assert!(zai_models.contains(&ModelId::ZaiGlm45X));
1514        assert!(zai_models.contains(&ModelId::ZaiGlm45Airx));
1515        assert!(zai_models.contains(&ModelId::ZaiGlm45Flash));
1516        assert!(zai_models.contains(&ModelId::ZaiGlm432b0414128k));
1517
1518        let moonshot_models = ModelId::models_for_provider(Provider::Moonshot);
1519        assert!(moonshot_models.contains(&ModelId::MoonshotKimiK2TurboPreview));
1520        assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20905Preview));
1521        assert!(moonshot_models.contains(&ModelId::MoonshotKimiK20711Preview));
1522        assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest));
1523        assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest8k));
1524        assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest32k));
1525        assert!(moonshot_models.contains(&ModelId::MoonshotKimiLatest128k));
1526        assert_eq!(moonshot_models.len(), 7);
1527    }
1528
1529    #[test]
1530    fn test_fallback_models() {
1531        let fallbacks = ModelId::fallback_models();
1532        assert!(!fallbacks.is_empty());
1533        assert!(fallbacks.contains(&ModelId::Gemini25Pro));
1534        assert!(fallbacks.contains(&ModelId::GPT5));
1535        assert!(fallbacks.contains(&ModelId::ClaudeOpus41));
1536        assert!(fallbacks.contains(&ModelId::ClaudeSonnet45));
1537        assert!(fallbacks.contains(&ModelId::DeepSeekReasoner));
1538        assert!(fallbacks.contains(&ModelId::MoonshotKimiK20905Preview));
1539        assert!(fallbacks.contains(&ModelId::XaiGrok4));
1540        assert!(fallbacks.contains(&ModelId::ZaiGlm46));
1541        assert!(fallbacks.contains(&ModelId::OpenRouterGrokCodeFast1));
1542    }
1543}