Skip to main content

vtcode_config/models/model_id/
capabilities.rs

1use crate::models::Provider;
2
3use super::ModelId;
4
5#[cfg(not(docsrs))]
6mod capability_generated {
7    include!(concat!(env!("OUT_DIR"), "/model_capabilities.rs"));
8}
9
10#[cfg(docsrs)]
11mod capability_generated {
12    #[derive(Clone, Copy)]
13    pub struct Entry {
14        pub provider: &'static str,
15        pub id: &'static str,
16        pub tool_call: bool,
17        pub input_modalities: &'static [&'static str],
18    }
19
20    pub const ENTRIES: &[Entry] = &[];
21
22    pub fn metadata_for(_provider: &str, _id: &str) -> Option<Entry> {
23        None
24    }
25}
26
27fn capability_provider_key(provider: Provider) -> &'static str {
28    match provider {
29        Provider::Gemini => "gemini",
30        Provider::OpenAI => "openai",
31        Provider::Anthropic => "anthropic",
32        Provider::DeepSeek => "deepseek",
33        Provider::OpenRouter => "openrouter",
34        Provider::Ollama => "ollama",
35        Provider::LmStudio => "lmstudio",
36        Provider::Moonshot => "moonshot",
37        Provider::ZAI => "zai",
38        Provider::Minimax => "minimax",
39        Provider::HuggingFace => "huggingface",
40        Provider::LiteLLM => "litellm",
41    }
42}
43
44impl ModelId {
45    fn generated_capabilities(&self) -> Option<capability_generated::Entry> {
46        capability_generated::metadata_for(capability_provider_key(self.provider()), self.as_str())
47    }
48
49    /// Attempt to find a non-reasoning variant for this model.
50    pub fn non_reasoning_variant(&self) -> Option<Self> {
51        if let Some(meta) = self.openrouter_metadata() {
52            if !meta.reasoning {
53                return None;
54            }
55
56            let vendor = meta.vendor;
57            let mut candidates: Vec<Self> = Self::openrouter_vendor_groups()
58                .into_iter()
59                .find(|(candidate_vendor, _)| *candidate_vendor == vendor)
60                .map(|(_, models)| {
61                    models
62                        .iter()
63                        .copied()
64                        .filter(|candidate| candidate != self)
65                        .filter(|candidate| {
66                            candidate
67                                .openrouter_metadata()
68                                .map(|other| !other.reasoning)
69                                .unwrap_or(false)
70                        })
71                        .collect()
72                })
73                .unwrap_or_default();
74
75            if candidates.is_empty() {
76                return None;
77            }
78
79            candidates.sort_by_key(|candidate| {
80                candidate
81                    .openrouter_metadata()
82                    .map(|data| (!data.efficient, data.display))
83                    .unwrap_or((true, ""))
84            });
85
86            return candidates.into_iter().next();
87        }
88
89        let direct = match self {
90            ModelId::Gemini31ProPreview
91            | ModelId::Gemini31ProPreviewCustomTools
92            | ModelId::Gemini31FlashLitePreview => Some(ModelId::Gemini3FlashPreview),
93            ModelId::GPT52
94            | ModelId::GPT54
95            | ModelId::GPT54Pro
96            | ModelId::GPT54Nano
97            | ModelId::GPT54Mini
98            | ModelId::GPT5 => Some(ModelId::GPT5Mini),
99            ModelId::DeepSeekReasoner => Some(ModelId::DeepSeekChat),
100            ModelId::ZaiGlm5 => Some(ModelId::OllamaGlm5Cloud),
101            ModelId::ClaudeOpus46 | ModelId::ClaudeSonnet46 => Some(ModelId::ClaudeSonnet46),
102            ModelId::MinimaxM27 | ModelId::MinimaxM25 => None,
103            _ => None,
104        };
105
106        direct.and_then(|candidate| {
107            if candidate.supports_reasoning_effort() {
108                None
109            } else {
110                Some(candidate)
111            }
112        })
113    }
114
115    /// Check if this is a "flash" variant (optimized for speed)
116    pub fn is_flash_variant(&self) -> bool {
117        matches!(
118            self,
119            ModelId::Gemini3FlashPreview
120                | ModelId::Gemini31FlashLitePreview
121                | ModelId::OpenRouterStepfunStep35FlashFree
122                | ModelId::OpenRouterNvidiaNemotron3Super120bA12bFree
123                | ModelId::OllamaGemini3FlashPreviewCloud
124                | ModelId::HuggingFaceStep35Flash
125        )
126    }
127
128    /// Check if this is a "pro" variant (optimized for capability)
129    pub fn is_pro_variant(&self) -> bool {
130        matches!(
131            self,
132            ModelId::Gemini31ProPreview
133                | ModelId::Gemini31ProPreviewCustomTools
134                | ModelId::OpenRouterGoogleGemini31ProPreview
135                | ModelId::GPT5
136                | ModelId::GPT52
137                | ModelId::GPT52Codex
138                | ModelId::GPT54
139                | ModelId::GPT54Pro
140                | ModelId::GPT53Codex
141                | ModelId::GPT51Codex
142                | ModelId::GPT51CodexMax
143                | ModelId::GPT5Codex
144                | ModelId::ClaudeOpus46
145                | ModelId::ClaudeSonnet46
146                | ModelId::DeepSeekReasoner
147                | ModelId::ZaiGlm5
148                | ModelId::OpenRouterStepfunStep35FlashFree
149                | ModelId::OpenRouterNvidiaNemotron3Super120bA12bFree
150                | ModelId::MinimaxM27
151                | ModelId::MinimaxM25
152                | ModelId::OllamaGlm5Cloud
153                | ModelId::OllamaNemotron3SuperCloud
154                | ModelId::OllamaMinimaxM25Cloud
155                | ModelId::HuggingFaceQwen3CoderNextNovita
156                | ModelId::HuggingFaceQwen35397BA17BTogether
157        )
158    }
159
160    /// Check if this is an optimized/efficient variant
161    pub fn is_efficient_variant(&self) -> bool {
162        if let Some(meta) = self.openrouter_metadata() {
163            return meta.efficient;
164        }
165        matches!(
166            self,
167            ModelId::Gemini3FlashPreview
168                | ModelId::Gemini31FlashLitePreview
169                | ModelId::GPT5Mini
170                | ModelId::GPT5Nano
171                | ModelId::ClaudeHaiku45
172                | ModelId::DeepSeekChat
173                | ModelId::HuggingFaceStep35Flash
174        )
175    }
176
177    /// Check if this is a top-tier model
178    pub fn is_top_tier(&self) -> bool {
179        if let Some(meta) = self.openrouter_metadata() {
180            return meta.top_tier;
181        }
182        matches!(
183            self,
184            ModelId::Gemini31ProPreview
185                | ModelId::Gemini31ProPreviewCustomTools
186                | ModelId::OpenRouterGoogleGemini31ProPreview
187                | ModelId::Gemini3FlashPreview
188                | ModelId::Gemini31FlashLitePreview
189                | ModelId::GPT5
190                | ModelId::GPT52
191                | ModelId::GPT52Codex
192                | ModelId::GPT54
193                | ModelId::GPT54Pro
194                | ModelId::GPT53Codex
195                | ModelId::GPT51Codex
196                | ModelId::GPT51CodexMax
197                | ModelId::GPT5Codex
198                | ModelId::ClaudeOpus46
199                | ModelId::ClaudeSonnet46
200                | ModelId::DeepSeekReasoner
201                | ModelId::ZaiGlm5
202                | ModelId::OpenRouterStepfunStep35FlashFree
203                | ModelId::HuggingFaceQwen3CoderNextNovita
204                | ModelId::HuggingFaceQwen35397BA17BTogether
205        )
206    }
207
208    /// Determine whether the model is a reasoning-capable variant
209    pub fn is_reasoning_variant(&self) -> bool {
210        if let Some(meta) = self.openrouter_metadata() {
211            return meta.reasoning;
212        }
213        self.provider().supports_reasoning_effort(self.as_str())
214    }
215
216    /// Determine whether the model supports tool calls/function execution
217    pub fn supports_tool_calls(&self) -> bool {
218        if let Some(meta) = self.generated_capabilities() {
219            return meta.tool_call;
220        }
221        if let Some(meta) = self.openrouter_metadata() {
222            return meta.tool_call;
223        }
224        true
225    }
226
227    /// Ordered list of supported input modalities when VT Code has metadata for this model.
228    pub fn input_modalities(&self) -> &'static [&'static str] {
229        self.generated_capabilities()
230            .map(|meta| meta.input_modalities)
231            .unwrap_or(&[])
232    }
233
234    /// Get the generation/version string for this model
235    pub fn generation(&self) -> &'static str {
236        if let Some(meta) = self.openrouter_metadata() {
237            return meta.generation;
238        }
239        match self {
240            // Gemini generations
241            ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => "3.1",
242            ModelId::Gemini31FlashLitePreview => "3.1-lite",
243            ModelId::Gemini3FlashPreview => "3",
244            // OpenAI generations
245            ModelId::GPT52 | ModelId::GPT52Codex => "5.2",
246            ModelId::GPT54 | ModelId::GPT54Pro | ModelId::GPT54Nano | ModelId::GPT54Mini => "5.4",
247            ModelId::GPT53Codex => "5.3",
248            ModelId::GPT51Codex | ModelId::GPT51CodexMax => "5.1",
249            ModelId::GPT5
250            | ModelId::GPT5Codex
251            | ModelId::GPT5Mini
252            | ModelId::GPT5Nano
253            | ModelId::OpenAIGptOss20b
254            | ModelId::OpenAIGptOss120b => "5",
255            // Anthropic generations
256            ModelId::ClaudeOpus46 | ModelId::ClaudeSonnet46 => "4.6",
257            ModelId::ClaudeHaiku45 => "4.5",
258            // DeepSeek generations
259            ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
260            // Z.AI generations
261            ModelId::ZaiGlm5 => "5",
262            ModelId::OllamaGptOss20b => "oss",
263            ModelId::OllamaGptOss20bCloud => "oss-cloud",
264            ModelId::OllamaGptOss120bCloud => "oss-cloud",
265            ModelId::OllamaQwen317b => "oss",
266            ModelId::OllamaQwen3CoderNext => "qwen3-coder-next:cloud",
267            ModelId::OllamaDeepseekV32Cloud => "deepseek-v3.2",
268            ModelId::OllamaQwen3Next80bCloud => "qwen3-next",
269            ModelId::OllamaMinimaxM2Cloud => "minimax-m2",
270            ModelId::OllamaGlm5Cloud => "glm-5",
271            ModelId::OllamaMinimaxM25Cloud => "minimax-m2.5",
272            ModelId::OllamaNemotron3SuperCloud => "nemotron-3",
273            ModelId::OllamaGemini3FlashPreviewCloud => "gemini-3",
274            // MiniMax models
275            ModelId::MinimaxM27 => "M2.7",
276            ModelId::MinimaxM25 => "M2.5",
277            // Moonshot models
278            ModelId::MoonshotKimiK25 => "k2.5",
279            // Hugging Face generations
280            ModelId::HuggingFaceDeepseekV32 => "V3.2-Exp",
281            ModelId::HuggingFaceOpenAIGptOss20b => "oss",
282            ModelId::HuggingFaceOpenAIGptOss120b => "oss",
283            ModelId::HuggingFaceMinimaxM25Novita => "m2.5",
284            ModelId::HuggingFaceDeepseekV32Novita => "v3.2",
285            ModelId::HuggingFaceXiaomiMimoV2FlashNovita => "v2-flash",
286            ModelId::HuggingFaceGlm5Novita => "5",
287            ModelId::HuggingFaceStep35Flash => "3.5",
288            ModelId::HuggingFaceQwen3CoderNextNovita | ModelId::OpenRouterQwen3CoderNext => {
289                "qwen3-coder-next"
290            }
291            _ => "unknown",
292        }
293    }
294
295    /// Determine if this model supports GPT-5.1+/5.2+/5.3+ shell tool type
296    pub fn supports_shell_tool(&self) -> bool {
297        matches!(
298            self,
299            ModelId::GPT52
300                | ModelId::GPT52Codex
301                | ModelId::GPT54
302                | ModelId::GPT54Pro
303                | ModelId::GPT53Codex
304                | ModelId::GPT51Codex
305                | ModelId::GPT51CodexMax
306                | ModelId::GPT5Codex
307        )
308    }
309
310    /// Determine if this model supports optimized apply_patch tool
311    pub fn supports_apply_patch_tool(&self) -> bool {
312        false // Placeholder for future optimization
313    }
314}