Skip to main content

vtcode_config/models/model_id/
parse.rs

1use std::str::FromStr;
2
3use crate::models::ModelParseError;
4
5use super::ModelId;
6
7impl FromStr for ModelId {
8    type Err = ModelParseError;
9
10    fn from_str(s: &str) -> Result<Self, Self::Err> {
11        use crate::constants::models;
12        let trimmed = s.trim();
13
14        // Explicitly handle built-in models that might be shadowed by OpenRouter
15        if trimmed == models::zai::GLM_5 || trimmed == models::zai::GLM_5_LEGACY {
16            return Ok(ModelId::ZaiGlm5);
17        }
18        if trimmed == models::zai::GLM_5_1 {
19            return Ok(ModelId::ZaiGlm51);
20        }
21
22        if let Some(opencode_model) = trimmed
23            .strip_prefix("opencode/")
24            .or_else(|| trimmed.strip_prefix("opencode-zen/"))
25        {
26            return match opencode_model {
27                m if m == models::opencode_zen::GPT_5_4 => Ok(ModelId::OpenCodeZenGPT54),
28                m if m == models::opencode_zen::GPT_5_4_MINI => Ok(ModelId::OpenCodeZenGPT54Mini),
29                m if m == models::opencode_zen::CLAUDE_SONNET_4_6 => {
30                    Ok(ModelId::OpenCodeZenClaudeSonnet46)
31                }
32                m if m == models::opencode_zen::GLM_5_1 => Ok(ModelId::OpenCodeZenGlm51),
33                m if m == models::opencode_zen::KIMI_K2_5 => Ok(ModelId::OpenCodeZenKimiK25),
34                _ => Err(ModelParseError::InvalidModel(trimmed.to_string())),
35            };
36        }
37
38        if let Some(opencode_model) = trimmed.strip_prefix("opencode-go/") {
39            return match opencode_model {
40                m if m == models::opencode_go::GLM_5_1 => Ok(ModelId::OpenCodeGoGlm51),
41                m if m == models::opencode_go::KIMI_K2_5 => Ok(ModelId::OpenCodeGoKimiK25),
42                m if m == models::opencode_go::MINIMAX_M2_5 => Ok(ModelId::OpenCodeGoMinimaxM25),
43                m if m == models::opencode_go::MINIMAX_M2_7 => Ok(ModelId::OpenCodeGoMinimaxM27),
44                _ => Err(ModelParseError::InvalidModel(trimmed.to_string())),
45            };
46        }
47
48        if let Some(model) = Self::parse_openrouter_model(trimmed) {
49            return Ok(model);
50        }
51
52        match trimmed {
53            // Gemini models
54            s if s == models::GEMINI_3_1_PRO_PREVIEW => Ok(ModelId::Gemini31ProPreview),
55            s if s == models::GEMINI_3_1_PRO_PREVIEW_CUSTOMTOOLS => {
56                Ok(ModelId::Gemini31ProPreviewCustomTools)
57            }
58            s if s == models::GEMINI_3_1_FLASH_LITE_PREVIEW => {
59                Ok(ModelId::Gemini31FlashLitePreview)
60            }
61            s if s == models::GEMINI_3_5_FLASH => Ok(ModelId::Gemini35Flash),
62            s if s == models::GEMINI_3_FLASH_PREVIEW => Ok(ModelId::Gemini3FlashPreview),
63            // OpenAI models
64            s if s == models::GPT => Ok(ModelId::GPT54),
65            s if s == models::openai::GPT_5_5 || s == models::openai::GPT_5_5_DATED => {
66                Ok(ModelId::GPT55)
67            }
68            s if s == models::GPT_5 => Ok(ModelId::GPT5),
69            s if s == models::GPT_5_2 => Ok(ModelId::GPT52),
70            s if s == models::openai::GPT_5_2_CODEX => Ok(ModelId::GPT52Codex),
71            s if s == models::GPT_5_4 => Ok(ModelId::GPT54),
72            s if s == models::GPT_5_4_PRO => Ok(ModelId::GPT54Pro),
73            s if s == models::openai::GPT_5_4_NANO => Ok(ModelId::GPT54Nano),
74            s if s == models::openai::GPT_5_4_MINI => Ok(ModelId::GPT54Mini),
75            s if s == models::openai::GPT_5_3_CODEX => Ok(ModelId::GPT53Codex),
76            s if s == models::openai::GPT_5_1_CODEX => Ok(ModelId::GPT51Codex),
77            s if s == models::openai::GPT_5_1_CODEX_MAX => Ok(ModelId::GPT51CodexMax),
78            s if s == models::GPT_5_MINI => Ok(ModelId::GPT5Mini),
79            s if s == models::GPT_5_NANO => Ok(ModelId::GPT5Nano),
80            s if s == models::openai::GPT_5_CODEX => Ok(ModelId::GPT5Codex),
81            s if s == models::openai::GPT_OSS_20B => Ok(ModelId::OpenAIGptOss20b),
82            s if s == models::openai::GPT_OSS_120B => Ok(ModelId::OpenAIGptOss120b),
83            // Anthropic models
84            s if s == models::CLAUDE_OPUS_4_7 => Ok(ModelId::ClaudeOpus47),
85            s if s == models::CLAUDE_OPUS_4_6 => Ok(ModelId::ClaudeOpus46),
86            s if s == models::CLAUDE_SONNET_4_6 => Ok(ModelId::ClaudeSonnet46),
87            s if s == models::CLAUDE_HAIKU_4_5 => Ok(ModelId::ClaudeHaiku45),
88            s if s == models::CLAUDE_HAIKU_4_5_20251001 => Ok(ModelId::ClaudeHaiku45),
89            s if s == models::CLAUDE_MYTHOS_PREVIEW => Ok(ModelId::ClaudeMythosPreview),
90            s if s == models::copilot::AUTO => Ok(ModelId::CopilotAuto),
91            s if s == models::copilot::GPT_5_2_CODEX => Ok(ModelId::CopilotGPT52Codex),
92            s if s == models::copilot::GPT_5_1_CODEX_MAX => Ok(ModelId::CopilotGPT51CodexMax),
93            s if s == models::copilot::GPT_5_4 => Ok(ModelId::CopilotGPT54),
94            s if s == models::copilot::GPT_5_4_MINI => Ok(ModelId::CopilotGPT54Mini),
95            s if s == models::copilot::CLAUDE_SONNET_4_6 => Ok(ModelId::CopilotClaudeSonnet46),
96            // DeepSeek models
97            s if s == models::deepseek::DEEPSEEK_V4_PRO => Ok(ModelId::DeepSeekV4Pro),
98            s if s == models::deepseek::DEEPSEEK_V4_FLASH => Ok(ModelId::DeepSeekV4Flash),
99            // Mistral models
100            s if s == models::mistral::MISTRAL_LARGE_3 => Ok(ModelId::MistralLarge3),
101            // MiMo models
102            s if s == models::mimo::MIMO_V2_5_PRO => Ok(ModelId::MiMoV25Pro),
103            s if s == models::mimo::MIMO_V2_5 => Ok(ModelId::MiMoV25),
104            s if s == models::mimo::MIMO_V2_PRO => Ok(ModelId::MiMoV2Pro),
105            s if s == models::mimo::MIMO_V2_OMNI => Ok(ModelId::MiMoV2Omni),
106            s if s == models::mimo::MIMO_V2_FLASH => Ok(ModelId::MiMoV2Flash),
107            // Qwen models
108            s if s == models::qwen::QWEN3_7_MAX => Ok(ModelId::Qwen37Max),
109            s if s == models::qwen::QWEN3_6_FLASH => Ok(ModelId::Qwen36Flash),
110            s if s == models::qwen::QWEN3_6_PLUS => Ok(ModelId::Qwen36Plus),
111            // Note: deepseek-v4-flash, deepseek-v4-pro, glm-5.1 resolve to their native
112            // variants (DeepSeekV4Flash, DeepSeekV4Pro, ZaiGlm51) via the matches above.
113            // The Qwen-prefixed variants (QwenDeepSeekV4Flash, etc.) are picker-only entries.
114            // Z.AI models
115            s if s == models::zai::GLM_5 || s == models::zai::GLM_5_LEGACY => Ok(ModelId::ZaiGlm5),
116            s if s == models::zai::GLM_5_1 => Ok(ModelId::ZaiGlm51),
117            // Moonshot models
118            s if s == models::moonshot::KIMI_K2_6 => Ok(ModelId::MoonshotKimiK26),
119            s if s == models::moonshot::KIMI_K2_5 => Ok(ModelId::MoonshotKimiK25),
120            s if s == models::ollama::GPT_OSS_20B => Ok(ModelId::OllamaGptOss20b),
121            s if s == models::ollama::GPT_OSS_20B_CLOUD => Ok(ModelId::OllamaGptOss20bCloud),
122            s if s == models::ollama::GPT_OSS_120B_CLOUD => Ok(ModelId::OllamaGptOss120bCloud),
123            s if s == models::ollama::QWEN3_1_7B => Ok(ModelId::OllamaQwen317b),
124            s if s == models::ollama::QWEN3_CODER_NEXT => Ok(ModelId::OllamaQwen3CoderNext),
125            "qwen3-coder-next" => Ok(ModelId::OllamaQwen3CoderNext),
126            s if s == models::ollama::DEEPSEEK_V32_CLOUD => Ok(ModelId::OllamaDeepseekV32Cloud),
127            s if s == models::ollama::DEEPSEEK_V4_FLASH_CLOUD => {
128                Ok(ModelId::OllamaDeepseekV4FlashCloud)
129            }
130            s if s == models::ollama::DEEPSEEK_V4_PRO_CLOUD => {
131                Ok(ModelId::OllamaDeepseekV4ProCloud)
132            }
133            s if s == models::ollama::QWEN3_NEXT_80B_CLOUD => Ok(ModelId::OllamaQwen3Next80bCloud),
134            s if s == models::ollama::GLM_5_CLOUD => Ok(ModelId::OllamaGlm5Cloud),
135            s if s == models::ollama::GLM_5_1_CLOUD => Ok(ModelId::OllamaGlm51Cloud),
136            s if s == models::ollama::GEMINI_3_FLASH_PREVIEW_CLOUD => {
137                Ok(ModelId::OllamaGemini3FlashPreviewCloud)
138            }
139            s if s == models::ollama::MINIMAX_M2_CLOUD => Ok(ModelId::OllamaMinimaxM2Cloud),
140            s if s == models::ollama::MINIMAX_M27_CLOUD => Ok(ModelId::OllamaMinimaxM27Cloud),
141            s if s == models::ollama::MINIMAX_M25_CLOUD => Ok(ModelId::OllamaMinimaxM25Cloud),
142            s if s == models::ollama::KIMI_K2_6_CLOUD => Ok(ModelId::OllamaKimiK26Cloud),
143            s if s == models::ollama::NEMOTRON_3_SUPER_CLOUD => {
144                Ok(ModelId::OllamaNemotron3SuperCloud)
145            }
146            s if s == models::minimax::MINIMAX_M2_7 => Ok(ModelId::MinimaxM27),
147            s if s == models::minimax::MINIMAX_M2_5 => Ok(ModelId::MinimaxM25),
148            // Hugging Face models
149            s if s == models::huggingface::DEEPSEEK_V32 => Ok(ModelId::HuggingFaceDeepseekV32),
150            s if s == models::huggingface::OPENAI_GPT_OSS_20B => {
151                Ok(ModelId::HuggingFaceOpenAIGptOss20b)
152            }
153            s if s == models::huggingface::OPENAI_GPT_OSS_120B => {
154                Ok(ModelId::HuggingFaceOpenAIGptOss120b)
155            }
156            s if s == models::huggingface::MINIMAX_M2_5_NOVITA => {
157                Ok(ModelId::HuggingFaceMinimaxM25Novita)
158            }
159            s if s == models::huggingface::DEEPSEEK_V32_NOVITA => {
160                Ok(ModelId::HuggingFaceDeepseekV32Novita)
161            }
162            s if s == models::huggingface::XIAOMI_MIMO_V2_FLASH_NOVITA => {
163                Ok(ModelId::HuggingFaceXiaomiMimoV2FlashNovita)
164            }
165            s if s == models::huggingface::ZAI_GLM_5_NOVITA => Ok(ModelId::HuggingFaceGlm5Novita),
166            s if s == models::huggingface::ZAI_GLM_5_1_ZAI_ORG => {
167                Ok(ModelId::HuggingFaceGlm51ZaiOrg)
168            }
169            s if s == models::huggingface::QWEN3_CODER_NEXT_NOVITA => {
170                Ok(ModelId::HuggingFaceQwen3CoderNextNovita)
171            }
172            s if s == models::huggingface::QWEN3_5_397B_A17B_TOGETHER => {
173                Ok(ModelId::HuggingFaceQwen35397BA17BTogether)
174            }
175            s if s == models::huggingface::KIMI_K2_6_NOVITA => {
176                Ok(ModelId::HuggingFaceKimiK26Novita)
177            }
178            s if s == models::huggingface::DEEPSEEK_V4_FLASH_NOVITA => {
179                Ok(ModelId::HuggingFaceDeepseekV4FlashNovita)
180            }
181            s if s == models::huggingface::DEEPSEEK_V4_PRO_TOGETHER => {
182                Ok(ModelId::HuggingFaceDeepseekV4ProTogether)
183            }
184            s if s == models::huggingface::STEP_3_5_FLASH
185                || s == models::huggingface::STEP_3_5_FLASH_BASE
186                || s == models::huggingface::STEP_3_5_FLASH_LEGACY_FASTEST =>
187            {
188                Ok(ModelId::HuggingFaceStep35Flash)
189            }
190            "minimax/minimax-m2.5" => Ok(ModelId::OpenRouterMinimaxM25),
191            "qwen/qwen3-coder-next" => Ok(ModelId::OpenRouterQwen3CoderNext),
192            "moonshotai/kimi-k2.6" => Ok(ModelId::OpenRouterMoonshotaiKimiK26),
193            _ => {
194                if let Some(model) = Self::parse_openrouter_model(s) {
195                    Ok(model)
196                } else {
197                    Err(ModelParseError::InvalidModel(s.to_string()))
198                }
199            }
200        }
201    }
202}