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