vtcode_config/models/model_id/
capabilities.rs1use 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 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 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 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 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 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 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 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 pub fn input_modalities(&self) -> &'static [&'static str] {
229 self.generated_capabilities()
230 .map(|meta| meta.input_modalities)
231 .unwrap_or(&[])
232 }
233
234 pub fn generation(&self) -> &'static str {
236 if let Some(meta) = self.openrouter_metadata() {
237 return meta.generation;
238 }
239 match self {
240 ModelId::Gemini31ProPreview | ModelId::Gemini31ProPreviewCustomTools => "3.1",
242 ModelId::Gemini31FlashLitePreview => "3.1-lite",
243 ModelId::Gemini3FlashPreview => "3",
244 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 ModelId::ClaudeOpus46 | ModelId::ClaudeSonnet46 => "4.6",
257 ModelId::ClaudeHaiku45 => "4.5",
258 ModelId::DeepSeekChat | ModelId::DeepSeekReasoner => "V3.2-Exp",
260 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 ModelId::MinimaxM27 => "M2.7",
276 ModelId::MinimaxM25 => "M2.5",
277 ModelId::MoonshotKimiK25 => "k2.5",
279 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 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 pub fn supports_apply_patch_tool(&self) -> bool {
312 false }
314}