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
46            | ModelId::Gemini31ProPreviewCustomTools
47            | ModelId::Gemini31FlashLitePreview => Some(ModelId::Gemini3FlashPreview),
48            ModelId::GPT | ModelId::GPT52 | ModelId::GPT54 | ModelId::GPT54Pro | ModelId::GPT5 => {
49                Some(ModelId::GPT5Mini)
50            }
51            ModelId::DeepSeekReasoner => Some(ModelId::DeepSeekChat),
52            ModelId::ZaiGlm5 => Some(ModelId::OllamaGlm5Cloud),
53            ModelId::ClaudeOpus46 | ModelId::ClaudeSonnet46 => Some(ModelId::ClaudeSonnet46),
54            ModelId::MinimaxM25 => None,
55            _ => None,
56        };
57
58        direct.and_then(|candidate| {
59            if candidate.supports_reasoning_effort() {
60                None
61            } else {
62                Some(candidate)
63            }
64        })
65    }
66
67    /// Check if this is a "flash" variant (optimized for speed)
68    pub fn is_flash_variant(&self) -> bool {
69        matches!(
70            self,
71            ModelId::Gemini3FlashPreview
72                | ModelId::Gemini31FlashLitePreview
73                | ModelId::OpenRouterStepfunStep35FlashFree
74                | ModelId::OpenRouterNvidiaNemotron3Super120bA12bFree
75                | ModelId::OllamaGemini3FlashPreviewCloud
76                | ModelId::HuggingFaceStep35Flash
77        )
78    }
79
80    /// Check if this is a "pro" variant (optimized for capability)
81    pub fn is_pro_variant(&self) -> bool {
82        matches!(
83            self,
84            ModelId::Gemini31ProPreview
85                | ModelId::Gemini31ProPreviewCustomTools
86                | ModelId::OpenRouterGoogleGemini31ProPreview
87                | ModelId::GPT
88                | ModelId::GPT5
89                | ModelId::GPT52
90                | ModelId::GPT54
91                | ModelId::GPT54Pro
92                | ModelId::GPT53Codex
93                | ModelId::ClaudeOpus46
94                | ModelId::ClaudeSonnet46
95                | ModelId::DeepSeekReasoner
96                | ModelId::ZaiGlm5
97                | ModelId::OpenRouterStepfunStep35FlashFree
98                | ModelId::OpenRouterNvidiaNemotron3Super120bA12bFree
99                | ModelId::MinimaxM25
100                | ModelId::OllamaGlm5Cloud
101                | ModelId::OllamaMinimaxM25Cloud
102                | ModelId::HuggingFaceQwen3CoderNextNovita
103                | ModelId::HuggingFaceQwen35397BA17BTogether
104        )
105    }
106
107    /// Check if this is an optimized/efficient variant
108    pub fn is_efficient_variant(&self) -> bool {
109        if let Some(meta) = self.openrouter_metadata() {
110            return meta.efficient;
111        }
112        matches!(
113            self,
114            ModelId::Gemini3FlashPreview
115                | ModelId::Gemini31FlashLitePreview
116                | ModelId::GPT5Mini
117                | ModelId::GPT5Nano
118                | ModelId::ClaudeHaiku45
119                | ModelId::DeepSeekChat
120                | ModelId::HuggingFaceStep35Flash
121        )
122    }
123
124    /// Check if this is a top-tier model
125    pub fn is_top_tier(&self) -> bool {
126        if let Some(meta) = self.openrouter_metadata() {
127            return meta.top_tier;
128        }
129        matches!(
130            self,
131            ModelId::Gemini31ProPreview
132                | ModelId::Gemini31ProPreviewCustomTools
133                | ModelId::OpenRouterGoogleGemini31ProPreview
134                | ModelId::Gemini3FlashPreview
135                | ModelId::Gemini31FlashLitePreview
136                | ModelId::GPT
137                | ModelId::GPT5
138                | ModelId::GPT52
139                | ModelId::GPT54
140                | ModelId::GPT54Pro
141                | ModelId::GPT53Codex
142                | ModelId::ClaudeOpus46
143                | ModelId::ClaudeSonnet46
144                | ModelId::DeepSeekReasoner
145                | ModelId::ZaiGlm5
146                | ModelId::OpenRouterStepfunStep35FlashFree
147                | ModelId::HuggingFaceQwen3CoderNextNovita
148                | ModelId::HuggingFaceQwen35397BA17BTogether
149        )
150    }
151
152    /// Determine whether the model is a reasoning-capable variant
153    pub fn is_reasoning_variant(&self) -> bool {
154        if let Some(meta) = self.openrouter_metadata() {
155            return meta.reasoning;
156        }
157        self.provider().supports_reasoning_effort(self.as_str())
158    }
159
160    /// Determine whether the model supports tool calls/function execution
161    pub fn supports_tool_calls(&self) -> bool {
162        if let Some(meta) = self.openrouter_metadata() {
163            return meta.tool_call;
164        }
165        true
166    }
167
168    /// Get the generation/version string for this model
169    pub fn generation(&self) -> &'static str {
170        if let Some(meta) = self.openrouter_metadata() {
171            return meta.generation;
172        }
173        match self {
174            // Gemini generations
175            ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => "3.1",
176            ModelId::Gemini31FlashLitePreview => "3.1-lite",
177            ModelId::Gemini3FlashPreview => "3",
178            // OpenAI generations
179            ModelId::GPT => "5.4",
180            ModelId::GPT52 => "5.2",
181            ModelId::GPT54 | ModelId::GPT54Pro => "5.4",
182            ModelId::GPT53Codex => "5.3",
183            ModelId::GPT5
184            | ModelId::GPT5Mini
185            | ModelId::GPT5Nano
186            | ModelId::OpenAIGptOss20b
187            | ModelId::OpenAIGptOss120b => "5",
188            // Anthropic generations
189            ModelId::ClaudeOpus46 | ModelId::ClaudeSonnet46 => "4.6",
190            ModelId::ClaudeHaiku45 => "4.5",
191            // DeepSeek generations
192            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
193            // Z.AI generations
194            ModelId::ZaiGlm5 => "5",
195            ModelId::OllamaGptOss20b => "oss",
196            ModelId::OllamaGptOss20bCloud => "oss-cloud",
197            ModelId::OllamaGptOss120bCloud => "oss-cloud",
198            ModelId::OllamaQwen317b => "oss",
199            ModelId::OllamaQwen3CoderNext => "qwen3-coder-next:cloud",
200            ModelId::OllamaDeepseekV32Cloud => "deepseek-v3.2",
201            ModelId::OllamaQwen3Next80bCloud => "qwen3-next",
202            ModelId::OllamaMinimaxM2Cloud => "minimax-m2",
203            ModelId::OllamaGlm5Cloud => "glm-5",
204            ModelId::OllamaMinimaxM25Cloud => "minimax-m2.5",
205            ModelId::OllamaGemini3FlashPreviewCloud => "gemini-3",
206            // MiniMax models
207            ModelId::MinimaxM25 => "M2.5",
208            // Moonshot models
209            ModelId::MoonshotKimiK25 => "k2.5",
210            // Hugging Face generations
211            ModelId::HuggingFaceDeepseekV32 => "V3.2-Exp",
212            ModelId::HuggingFaceOpenAIGptOss20b => "oss",
213            ModelId::HuggingFaceOpenAIGptOss120b => "oss",
214            ModelId::HuggingFaceMinimaxM25Novita => "m2.5",
215            ModelId::HuggingFaceDeepseekV32Novita => "v3.2",
216            ModelId::HuggingFaceXiaomiMimoV2FlashNovita => "v2-flash",
217            ModelId::HuggingFaceGlm5Novita => "5",
218            ModelId::HuggingFaceStep35Flash => "3.5",
219            ModelId::HuggingFaceQwen3CoderNextNovita | ModelId::OpenRouterQwen3CoderNext => {
220                "qwen3-coder-next"
221            }
222            _ => "unknown",
223        }
224    }
225
226    /// Determine if this model supports GPT-5.1+/5.2+/5.3+ shell tool type
227    pub fn supports_shell_tool(&self) -> bool {
228        matches!(
229            self,
230            ModelId::GPT
231                | ModelId::GPT52
232                | ModelId::GPT54
233                | ModelId::GPT54Pro
234                | ModelId::GPT53Codex
235        )
236    }
237
238    /// Determine if this model supports optimized apply_patch tool
239    pub fn supports_apply_patch_tool(&self) -> bool {
240        false // Placeholder for future optimization
241    }
242}