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