Skip to main content

vtcode_config/models/model_id/
collection.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3
4use crate::core::{CustomProviderConfig, ProviderOverrideConfig};
5use crate::models::Provider;
6use hashbrown::HashSet;
7
8use super::ModelId;
9
10impl ModelId {
11    /// Return the OpenRouter vendor slug when this identifier maps to a marketplace listing
12    pub fn openrouter_vendor(&self) -> Option<&'static str> {
13        self.openrouter_metadata().map(|meta| meta.vendor)
14    }
15
16    /// Get all available models as a vector
17    pub fn all_models() -> Vec<ModelId> {
18        let mut models = vec![
19            // Gemini models
20            ModelId::Gemini38Flash,
21            ModelId::Gemini4Argon,
22            // OpenAI models
23            ModelId::GPT6Astra,
24            ModelId::GPT6Sol,
25            ModelId::GPT61Sol,
26            ModelId::GPT61Terra,
27            ModelId::GPT6Luna,
28            ModelId::GPT56Sol,
29            ModelId::GPT56Terra,
30            ModelId::GPT56Luna,
31            ModelId::OpenAIGptOss20b,
32            ModelId::OpenAIGptOss120b,
33            // Anthropic models
34            ModelId::ClaudeSonnet5,
35            ModelId::ClaudeSonnet55,
36            ModelId::ClaudeHaiku55,
37            ModelId::ClaudeFable5,
38            ModelId::ClaudeFable51,
39            ModelId::ClaudeFable52,
40            ModelId::ClaudeOpus5,
41            ModelId::ClaudeOpus55,
42            ModelId::CopilotAuto,
43            ModelId::CopilotGPT52Codex,
44            ModelId::CopilotGPT51CodexMax,
45            ModelId::CopilotGPT54,
46            ModelId::CopilotGPT54Mini,
47            // DeepSeek models
48            ModelId::DeepSeekFlash,
49            // Official Meta AI models (kept before marketplace entries)
50            ModelId::MetaMuseSpark13,
51            ModelId::MetaMuseSpark13Contributor,
52            ModelId::MetaMuseWonder10,
53            ModelId::MetaMuseWonderFull10,
54            ModelId::MetaMuseEmber10,
55            // NVIDIA NIM models
56            ModelId::NvidiaNemotron3Ultra550bA55b,
57            ModelId::NvidiaNemotron3Super120bA12b,
58            ModelId::NvidiaNemotron3Nano30bA3b,
59            // Merge Gateway routes
60            ModelId::MergeGatewayDefaultRouting,
61            ModelId::MergeGatewayAnthropicClaudeOpus5,
62            ModelId::MergeGatewayAnthropicClaudeOpus55,
63            ModelId::MergeGatewayAnthropicClaudeSonnet5,
64            ModelId::MergeGatewayAnthropicClaudeSonnet55,
65            ModelId::MergeGatewayXaiGrok46,
66            ModelId::MergeGatewayXaiGrok47,
67            ModelId::MergeGatewayMinimaxH3,
68            ModelId::MergeGatewayMinimaxM31Flash,
69            ModelId::MergeGatewayMoonshotKimiK3,
70            ModelId::MergeGatewayMoonshotKimiK31Pro,
71            ModelId::MergeGatewayThinkingMachinesInkling,
72            ModelId::MergeGatewayZaiGlm53Flash,
73            ModelId::MergeGatewayZaiGlm53Flashx,
74            ModelId::MergeGatewayOpenAIGpt56Luna,
75            ModelId::MergeGatewayOpenAIGpt56Sol,
76            ModelId::MergeGatewayOpenAIGpt56Terra,
77            ModelId::MergeGatewayGoogleGemini38Flash,
78            ModelId::MergeGatewayGoogleGemini4Argon,
79            ModelId::MergeGatewayAnthropicClaudeHaiku4520251001,
80            ModelId::MergeGatewayAnthropicClaudeHaiku55,
81            ModelId::MergeGatewayAnthropicClaudeFable51,
82            ModelId::MergeGatewayAnthropicClaudeFable52,
83            ModelId::MergeGatewayDeepseekFlash,
84            ModelId::MergeGatewayOpenAIGpt6Astra,
85            ModelId::MergeGatewayOpenAIGpt6Sol,
86            ModelId::MergeGatewayOpenAIGpt61Sol,
87            ModelId::MergeGatewayOpenAIGpt61Terra,
88            ModelId::MergeGatewayOpenAIGpt6Luna,
89            ModelId::MergeGatewayXiaomimimoMimoV26Pro,
90            ModelId::MergeGatewayXiaomimimoMimoV26Flash,
91            ModelId::MergeGatewayMistralLarge4,
92            ModelId::MergeGatewayMetaMuseSparkOpen,
93            // Mistral models
94            ModelId::MistralLarge3,
95            ModelId::MistralLarge4,
96            // Z.AI models
97            ModelId::ZaiGlm53,
98            ModelId::ZaiGlm53Flash,
99            ModelId::ZaiGlm53Flashx,
100            // MiMo models
101            ModelId::MiMoV26Pro,
102            ModelId::MiMoV26Flash,
103            ModelId::MiMoV26ProUltraspeed,
104            // Moonshot models
105            ModelId::MoonshotKimiK31Pro,
106            ModelId::MoonshotKimiK3,
107            // OpenCode Zen models
108            // OpenCode Go models
109            ModelId::OpenCodeGoGlm53,
110            ModelId::OpenCodeGoGpt56Luna,
111            ModelId::OpenCodeGoKimiK3,
112            ModelId::OpenCodeGoMinimaxM3,
113            // Ollama models
114            ModelId::OllamaGptOss20b,
115            ModelId::OllamaGptOss20bCloud,
116            ModelId::OllamaGptOss120bCloud,
117            ModelId::OllamaGlm53Cloud,
118            ModelId::OllamaGlm53FlashCloud,
119            ModelId::OllamaMinimaxM3Cloud,
120            ModelId::OllamaMinimaxM31FlashCloud,
121            ModelId::OllamaMistralLarge4Cloud,
122            ModelId::OllamaMuseSparkOpenCloud,
123            ModelId::OllamaDeepSeekV41FlashCloud,
124            ModelId::OllamaKimiK3Cloud,
125            ModelId::OllamaGemma4,
126            // llama.cpp models
127            ModelId::LlamaCppGemma426bA4b,
128            ModelId::LlamaCppGemma4E4b,
129            ModelId::LlamaCppGptOss20b,
130            // MiniMax models
131            ModelId::MinimaxM3,
132            ModelId::MinimaxM31Flash,
133            // Hugging Face models
134            ModelId::HuggingFaceOpenAIGptOss20b,
135            ModelId::HuggingFaceOpenAIGptOss120b,
136            ModelId::HuggingFaceGlm53FlashTogether,
137            ModelId::HuggingFaceGlm53Together,
138            ModelId::HuggingFaceKimiK3Together,
139            ModelId::HuggingFaceMinimaxM3Novita,
140            ModelId::HuggingFaceMuseSparkOpenBaseten,
141            ModelId::StepFun37Flash,
142            ModelId::StepFun5Preview,
143            ModelId::EvolinkGemini31Pro,
144            ModelId::EvolinkMinimaxM3,
145            ModelId::EvolinkClaudeHaiku45,
146            ModelId::OpenRouterMoonshotaiKimiK3,
147            ModelId::OpenRouterZaiGlm53Flash,
148            ModelId::OpenRouterZaiGlm53Flashx,
149            // xAI models
150            ModelId::XaiGrok46,
151            ModelId::XaiGrok47,
152            // Vercel AI Gateway models
153            ModelId::VercelAnthropicClaudeSonnet5,
154            ModelId::VercelAnthropicClaudeOpus5,
155            ModelId::VercelAnthropicClaudeOpus55,
156            ModelId::VercelAnthropicClaudeHaiku45,
157            ModelId::VercelOpenAiGpt56Sol,
158            ModelId::VercelOpenAiGpt6Astra,
159            ModelId::VercelOpenAiGpt56Luna,
160            ModelId::VercelOpenAiGpt6Sol,
161            ModelId::VercelOpenAiGpt6Luna,
162            ModelId::VercelGoogleGemini38Flash,
163            ModelId::VercelDeepseekFlash,
164            ModelId::VercelMoonshotaiKimiK3,
165            ModelId::VercelMinimaxM3,
166            ModelId::VercelSpacexaiGrok47,
167        ];
168        models.extend(Self::openrouter_models());
169        let mut seen = HashSet::new();
170        models.retain(|model| seen.insert(model.clone()));
171        models
172    }
173
174    /// Get all models for a specific provider
175    pub fn models_for_provider(provider: Provider) -> Vec<ModelId> {
176        Self::all_models()
177            .into_iter()
178            .filter(|model| model.provider() == provider)
179            .collect()
180    }
181
182    /// Return all models including user-defined overrides from config.
183    ///
184    /// Merges the hardcoded model list with custom models defined in
185    /// `[providers.<name>]` config sections. Custom models are appended
186    /// as `ModelId::Custom` variants keyed by provider name.
187    pub fn all_models_with_overrides(overrides: &BTreeMap<String, ProviderOverrideConfig>) -> Vec<ModelId> {
188        let mut models = Self::all_models();
189        for (provider_key, config) in overrides {
190            for model_name in &config.models {
191                let trimmed = model_name.trim().to_string();
192                if !trimmed.is_empty() {
193                    models.push(ModelId::Custom(provider_key.clone(), trimmed));
194                }
195            }
196        }
197        models
198    }
199
200    /// Get all models for a specific provider, including user-defined overrides.
201    pub fn models_for_provider_with_overrides(
202        provider: Provider,
203        overrides: &BTreeMap<String, ProviderOverrideConfig>,
204    ) -> Vec<ModelId> {
205        Self::all_models_with_overrides(overrides)
206            .into_iter()
207            .filter(|model| model.provider() == provider)
208            .collect()
209    }
210
211    /// Resolve a model identifier against a configuration, falling back to
212    /// [`ModelId::from_str`].
213    ///
214    /// Models declared under the active provider in `[providers.<name>]`
215    /// overrides or by a `[[custom_providers]]` profile are not part of the
216    /// static catalog, so they are represented as [`ModelId::Custom`].
217    /// Matching is scoped to the active provider to avoid mis-routing a model
218    /// ID shared across providers, mirroring the catalog and custom-provider
219    /// branches of the subagent resolution path. Local-provider pass-through
220    /// (arbitrary Ollama/llama.cpp IDs) is intentionally not handled here.
221    pub fn from_config(
222        model: &str,
223        provider: &str,
224        provider_overrides: &BTreeMap<String, ProviderOverrideConfig>,
225        custom_providers: &[CustomProviderConfig],
226    ) -> Result<Self, crate::models::ModelParseError> {
227        let trimmed = model.trim();
228        if let Ok(parsed) = Self::from_str(trimmed) {
229            return Ok(parsed);
230        }
231        let hinted_provider = provider.trim().parse::<Provider>().ok();
232        for (provider_key, override_cfg) in provider_overrides {
233            let matches_hint = match hinted_provider {
234                Some(active) => provider_key.parse::<Provider>().ok() == Some(active),
235                None => provider_key.eq_ignore_ascii_case(provider.trim()),
236            };
237            if matches_hint && override_cfg.models.iter().any(|candidate| candidate.trim() == trimmed) {
238                return Ok(ModelId::Custom(provider_key.clone(), trimmed.to_owned()));
239            }
240        }
241        for custom in custom_providers {
242            if custom.name.eq_ignore_ascii_case(provider.trim())
243                && custom.effective_models().iter().any(|candidate| candidate == trimmed)
244            {
245                return Ok(ModelId::Custom(custom.name.to_lowercase(), trimmed.to_owned()));
246            }
247        }
248        Self::from_str(trimmed)
249    }
250}