vtcode_config/
models.rs

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