Skip to main content

vtcode_config/models/model_id/
capabilities.rs

1use super::ModelId;
2
3impl ModelId {
4    /// Attempt to find a non-reasoning variant for this model.
5    pub fn non_reasoning_variant(&self) -> Option<Self> {
6        if let Some(meta) = self.openrouter_metadata() {
7            if !meta.reasoning {
8                return None;
9            }
10
11            let vendor = meta.vendor;
12            let mut candidates: Vec<Self> = Self::openrouter_vendor_groups()
13                .into_iter()
14                .find(|(candidate_vendor, _)| *candidate_vendor == vendor)
15                .map(|(_, models)| {
16                    models
17                        .iter()
18                        .copied()
19                        .filter(|candidate| candidate != self)
20                        .filter(|candidate| {
21                            candidate
22                                .openrouter_metadata()
23                                .map(|other| !other.reasoning)
24                                .unwrap_or(false)
25                        })
26                        .collect()
27                })
28                .unwrap_or_default();
29
30            if candidates.is_empty() {
31                return None;
32            }
33
34            candidates.sort_by_key(|candidate| {
35                candidate
36                    .openrouter_metadata()
37                    .map(|data| (!data.efficient, data.display))
38                    .unwrap_or((true, ""))
39            });
40
41            return candidates.into_iter().next();
42        }
43
44        let direct = match self {
45            ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => {
46                Some(ModelId::Gemini3FlashPreview)
47            }
48            ModelId::GPT52 | ModelId::GPT5 => Some(ModelId::GPT5Mini),
49            ModelId::DeepSeekReasoner => Some(ModelId::DeepSeekChat),
50            ModelId::ZaiGlm5 => Some(ModelId::OllamaGlm5Cloud),
51            ModelId::ClaudeOpus46
52            | ModelId::ClaudeSonnet46
53            | ModelId::ClaudeOpus45
54            | ModelId::ClaudeOpus41 => Some(ModelId::ClaudeSonnet45),
55            ModelId::ClaudeSonnet4 => Some(ModelId::ClaudeSonnet45),
56            ModelId::MinimaxM25 => Some(ModelId::MinimaxM2),
57            _ => None,
58        };
59
60        direct.and_then(|candidate| {
61            if candidate.supports_reasoning_effort() {
62                None
63            } else {
64                Some(candidate)
65            }
66        })
67    }
68
69    /// Check if this is a "flash" variant (optimized for speed)
70    pub fn is_flash_variant(&self) -> bool {
71        matches!(
72            self,
73            ModelId::Gemini3FlashPreview
74                | ModelId::OpenRouterStepfunStep35FlashFree
75                | ModelId::OllamaGemini3FlashPreviewCloud
76        )
77    }
78
79    /// Check if this is a "pro" variant (optimized for capability)
80    pub fn is_pro_variant(&self) -> bool {
81        matches!(
82            self,
83            ModelId::Gemini31ProPreview
84                | ModelId::Gemini31ProPreviewCustomTools
85                | ModelId::OpenRouterGoogleGemini31ProPreview
86                | ModelId::GPT5
87                | ModelId::GPT52
88                | ModelId::GPT53Codex
89                | ModelId::ClaudeOpus46
90                | ModelId::ClaudeSonnet46
91                | ModelId::ClaudeOpus41
92                | ModelId::DeepSeekReasoner
93                | ModelId::ZaiGlm5
94                | ModelId::OpenRouterStepfunStep35FlashFree
95                | ModelId::MinimaxM25
96                | ModelId::OllamaGlm5Cloud
97                | ModelId::OllamaMinimaxM25Cloud
98                | ModelId::HuggingFaceQwen3CoderNextNovita
99                | ModelId::HuggingFaceQwen35397BA17BTogether
100        )
101    }
102
103    /// Check if this is an optimized/efficient variant
104    pub fn is_efficient_variant(&self) -> bool {
105        if let Some(meta) = self.openrouter_metadata() {
106            return meta.efficient;
107        }
108        matches!(
109            self,
110            ModelId::Gemini3FlashPreview
111                | ModelId::GPT5Mini
112                | ModelId::GPT5Nano
113                | ModelId::ClaudeHaiku45
114                | ModelId::DeepSeekChat
115        )
116    }
117
118    /// Check if this is a top-tier model
119    pub fn is_top_tier(&self) -> bool {
120        if let Some(meta) = self.openrouter_metadata() {
121            return meta.top_tier;
122        }
123        matches!(
124            self,
125            ModelId::Gemini31ProPreview
126                | ModelId::Gemini31ProPreviewCustomTools
127                | ModelId::OpenRouterGoogleGemini31ProPreview
128                | ModelId::Gemini3FlashPreview
129                | ModelId::GPT5
130                | ModelId::GPT52
131                | ModelId::GPT53Codex
132                | ModelId::ClaudeOpus46
133                | ModelId::ClaudeSonnet46
134                | ModelId::ClaudeOpus45
135                | ModelId::ClaudeOpus41
136                | ModelId::ClaudeSonnet45
137                | ModelId::ClaudeSonnet4
138                | ModelId::DeepSeekReasoner
139                | ModelId::ZaiGlm5
140                | ModelId::OpenRouterStepfunStep35FlashFree
141                | ModelId::HuggingFaceQwen3CoderNextNovita
142                | ModelId::HuggingFaceQwen35397BA17BTogether
143        )
144    }
145
146    /// Determine whether the model is a reasoning-capable variant
147    pub fn is_reasoning_variant(&self) -> bool {
148        if let Some(meta) = self.openrouter_metadata() {
149            return meta.reasoning;
150        }
151        self.provider().supports_reasoning_effort(self.as_str())
152    }
153
154    /// Determine whether the model supports tool calls/function execution
155    pub fn supports_tool_calls(&self) -> bool {
156        if let Some(meta) = self.openrouter_metadata() {
157            return meta.tool_call;
158        }
159        true
160    }
161
162    /// Get the generation/version string for this model
163    pub fn generation(&self) -> &'static str {
164        if let Some(meta) = self.openrouter_metadata() {
165            return meta.generation;
166        }
167        match self {
168            // Gemini generations
169            ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => "3.1",
170            ModelId::Gemini3FlashPreview => "3",
171            // OpenAI generations
172            ModelId::GPT52 => "5.2",
173            ModelId::GPT53Codex => "5.3",
174            ModelId::GPT5
175            | ModelId::GPT5Mini
176            | ModelId::GPT5Nano
177            | ModelId::OpenAIGptOss20b
178            | ModelId::OpenAIGptOss120b => "5",
179            // Anthropic generations
180            ModelId::ClaudeOpus46 | ModelId::ClaudeSonnet46 => "4.6",
181            ModelId::ClaudeOpus45 | ModelId::ClaudeSonnet45 | ModelId::ClaudeHaiku45 => "4.5",
182            ModelId::ClaudeOpus41 => "4.1",
183            ModelId::ClaudeSonnet4 => "4",
184            // DeepSeek generations
185            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
186            // Z.AI generations
187            ModelId::ZaiGlm5 => "5",
188            ModelId::OllamaGptOss20b => "oss",
189            ModelId::OllamaGptOss20bCloud => "oss-cloud",
190            ModelId::OllamaGptOss120bCloud => "oss-cloud",
191            ModelId::OllamaQwen317b => "oss",
192            ModelId::OllamaQwen3CoderNext => "qwen3-coder-next:cloud",
193            ModelId::OllamaDeepseekV32Cloud => "deepseek-v3.2",
194            ModelId::OllamaQwen3Next80bCloud => "qwen3-next",
195            ModelId::OllamaMistralLarge3675bCloud => "mistral-large-3",
196            ModelId::OllamaQwen3Coder480bCloud => "qwen3",
197            ModelId::OllamaDevstral2123bCloud => "devstral-2",
198            ModelId::OllamaMinimaxM2Cloud => "minimax-m2",
199            ModelId::OllamaNemotron3Nano30bCloud => "nemotron-3",
200            ModelId::OllamaGlm5Cloud => "glm-5",
201            ModelId::OllamaMinimaxM25Cloud => "minimax-m2.5",
202            ModelId::OllamaGemini3FlashPreviewCloud => "gemini-3",
203            // MiniMax models
204            ModelId::MinimaxM25 => "M2.5",
205            ModelId::MinimaxM2 => "m2",
206            // Moonshot models
207            ModelId::MoonshotMinimaxM25 | ModelId::OpenRouterMinimaxM25 => "M2.5",
208            // Hugging Face generations
209            ModelId::HuggingFaceDeepseekV32 => "V3.2-Exp",
210            ModelId::HuggingFaceOpenAIGptOss20b => "oss",
211            ModelId::HuggingFaceOpenAIGptOss120b => "oss",
212            ModelId::HuggingFaceMinimaxM25Novita => "m2.5",
213            ModelId::HuggingFaceDeepseekV32Novita => "v3.2",
214            ModelId::HuggingFaceXiaomiMimoV2FlashNovita => "v2-flash",
215            ModelId::HuggingFaceGlm5Novita => "5",
216            ModelId::HuggingFaceQwen3CoderNextNovita
217            | ModelId::OpenRouterQwen3CoderNext
218            | ModelId::MoonshotQwen3CoderNext => "qwen3-coder-next",
219            _ => "unknown",
220        }
221    }
222
223    /// Determine if this model supports GPT-5.1+/5.2+/5.3+ shell tool type
224    pub fn supports_shell_tool(&self) -> bool {
225        matches!(self, ModelId::GPT52 | ModelId::GPT53Codex)
226    }
227
228    /// Determine if this model supports optimized apply_patch tool
229    pub fn supports_apply_patch_tool(&self) -> bool {
230        false // Placeholder for future optimization
231    }
232}