vtcode_core/models_manager/model_presets/
mod.rs1use serde::{Deserialize, Serialize};
11
12use crate::config::models::Provider;
13use crate::config::types::ReasoningEffortLevel;
14
15mod presets;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ReasoningEffortPreset {
20 pub effort: ReasoningEffortLevel,
22 pub description: String,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct ModelUpgrade {
29 pub id: String,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub reasoning_effort_mapping: Option<String>,
34 pub migration_config_key: String,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub model_link: Option<String>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub upgrade_copy: Option<String>,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct ModelInfo {
47 pub slug: String,
49 pub display_name: String,
51 pub description: String,
53 pub provider: Provider,
55 #[serde(default)]
57 pub default_reasoning_level: ReasoningEffortLevel,
58 #[serde(default)]
60 pub supported_reasoning_levels: Vec<ReasoningEffortPreset>,
61 #[serde(default)]
63 pub context_window: Option<i64>,
64 #[serde(default = "default_true")]
66 pub supports_tool_use: bool,
67 #[serde(default = "default_true")]
69 pub supports_streaming: bool,
70 #[serde(default)]
72 pub supports_vision: bool,
73 #[serde(default)]
75 pub supports_structured_output: bool,
76 #[serde(default)]
78 pub supports_reasoning: bool,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub max_output_tokens: Option<i64>,
82 #[serde(default)]
84 pub priority: i32,
85 #[serde(default = "default_visibility")]
87 pub visibility: String,
88 #[serde(default = "default_true")]
90 pub supported_in_api: bool,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub upgrade: Option<ModelUpgrade>,
94}
95
96fn default_true() -> bool {
97 true
98}
99
100fn default_visibility() -> String {
101 "list".to_string()
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106pub struct ModelPreset {
107 pub id: String,
109 pub model: String,
111 pub display_name: String,
113 pub description: String,
115 pub provider: Provider,
117 pub default_reasoning_effort: ReasoningEffortLevel,
119 pub supported_reasoning_efforts: Vec<ReasoningEffortPreset>,
121 #[serde(default)]
123 pub is_default: bool,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub upgrade: Option<ModelUpgrade>,
127 #[serde(default = "default_true")]
129 pub show_in_picker: bool,
130 #[serde(default = "default_true")]
132 pub supported_in_api: bool,
133 #[serde(default)]
135 pub context_window: Option<i64>,
136}
137
138impl From<ModelInfo> for ModelPreset {
139 fn from(info: ModelInfo) -> Self {
140 Self {
141 id: info.slug.clone(),
142 model: info.slug,
143 display_name: info.display_name,
144 description: info.description,
145 provider: info.provider,
146 default_reasoning_effort: info.default_reasoning_level,
147 supported_reasoning_efforts: info.supported_reasoning_levels,
148 is_default: false,
149 upgrade: info.upgrade,
150 show_in_picker: info.visibility == "list",
151 supported_in_api: info.supported_in_api,
152 context_window: info.context_window,
153 }
154 }
155}
156
157pub fn builtin_model_presets() -> Vec<ModelPreset> {
159 let mut presets = Vec::new();
160
161 presets.extend(presets::gemini_presets());
163
164 presets.extend(presets::openai_presets());
166
167 presets.extend(presets::anthropic_presets());
169
170 presets.extend(presets::copilot_presets());
172
173 presets.extend(presets::deepseek_presets());
175
176 presets.extend(presets::meta_presets());
178
179 presets.extend(presets::zai_presets());
181
182 presets.extend(presets::lmstudio_presets());
184
185 presets.extend(presets::llamacpp_presets());
187
188 presets.extend(presets::minimax_presets());
190
191 presets.extend(presets::opencode_zen_presets());
193
194 presets.extend(presets::opencode_go_presets());
196
197 presets.extend(presets::poolside_presets());
199
200 presets.extend(presets::stepfun_presets());
202
203 presets.extend(presets::xai_presets());
205
206 presets.extend(presets::nvidia_presets());
208
209 presets.extend(presets::merge_gateway_presets());
211
212 presets.extend(presets::evolink_presets());
214
215 presets.extend(presets::vercel_presets());
217
218 presets
219}
220
221pub fn presets_for_provider(provider: Provider) -> Vec<ModelPreset> {
223 match provider {
224 Provider::Gemini => presets::gemini_presets(),
225 Provider::OpenAI => presets::openai_presets(),
226 Provider::Anthropic => presets::anthropic_presets(),
227 Provider::Copilot => presets::copilot_presets(),
228 Provider::DeepSeek => presets::deepseek_presets(),
229 Provider::Meta => presets::meta_presets(),
230 Provider::ZAI => presets::zai_presets(),
231 Provider::Minimax => presets::minimax_presets(),
232 Provider::OpenRouter => presets::openrouter_presets(),
233 Provider::Ollama => presets::ollama_presets(),
234 Provider::OllamaCloud => presets::ollama_presets(),
235 Provider::LmStudio => presets::lmstudio_presets(),
236 Provider::LlamaCpp => presets::llamacpp_presets(),
237 Provider::Moonshot => presets::moonshot_presets(),
238 Provider::Mistral => presets::mistral_presets(),
239 Provider::HuggingFace => presets::huggingface_presets(),
240 Provider::OpenCodeZen => presets::opencode_zen_presets(),
241 Provider::OpenCodeGo => presets::opencode_go_presets(),
242 Provider::MiMo => presets::mimo_presets(),
243 Provider::Qwen => presets::qwen_presets(),
244 Provider::StepFun => presets::stepfun_presets(),
245 Provider::Evolink => presets::evolink_presets(),
246 Provider::Poolside => presets::poolside_presets(),
247 Provider::XAI => presets::xai_presets(),
248 Provider::NVIDIA => presets::nvidia_presets(),
249 Provider::MergeGateway => presets::merge_gateway_presets(),
250 Provider::Vercel => presets::vercel_presets(),
251 }
252}
253
254pub fn all_model_presets() -> Vec<ModelPreset> {
255 builtin_model_presets()
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn only_one_default_per_provider() {
264 let presets = builtin_model_presets();
265 let providers: Vec<Provider> = Provider::all_providers();
266
267 for provider in providers {
268 let default_count = presets.iter().filter(|p| p.provider == provider && p.is_default).count();
269 assert!(default_count <= 1, "Provider {provider:?} has {default_count} defaults, expected 0 or 1");
270 }
271 }
272
273 #[test]
274 fn gemini_presets_exist() {
275 let presets = presets::gemini_presets();
276 assert!(!presets.is_empty());
277 assert!(presets.iter().any(|p| p.id.contains("gemini")));
278 }
279
280 #[test]
281 fn model_info_converts_to_preset() {
282 let info = ModelInfo {
283 slug: "test-model".to_string(),
284 display_name: "Test Model".to_string(),
285 description: "A test model".to_string(),
286 provider: Provider::Gemini,
287 default_reasoning_level: ReasoningEffortLevel::Medium,
288 supported_reasoning_levels: vec![],
289 context_window: Some(128_000),
290 supports_tool_use: true,
291 supports_streaming: true,
292 supports_vision: false,
293 supports_structured_output: false,
294 supports_reasoning: false,
295 max_output_tokens: None,
296 priority: 0,
297 visibility: "list".to_string(),
298 supported_in_api: true,
299 upgrade: None,
300 };
301
302 let preset: ModelPreset = info.into();
303 assert_eq!(preset.id, "test-model");
304 assert_eq!(preset.model, "test-model");
305 assert!(preset.show_in_picker);
306 }
307
308 #[test]
309 fn openai_codex_presets_default_to_high_reasoning() {
310 let codex = presets::openai_presets()
311 .into_iter()
312 .find(|preset| preset.id == "gpt-5-codex")
313 .expect("gpt-5.3-codex preset");
314
315 assert_eq!(codex.default_reasoning_effort, ReasoningEffortLevel::High);
316 }
317
318 #[test]
319 fn mistral_presets_include_large_4_without_changing_default() {
320 let presets = presets::mistral_presets();
321 let large_4 = presets
322 .iter()
323 .find(|preset| preset.model == "mistral-large-4")
324 .expect("mistral large 4 preset");
325 assert_eq!(large_4.provider, Provider::Mistral);
326 assert!(!large_4.is_default);
327 assert!(large_4.show_in_picker);
328 assert_eq!(large_4.context_window, Some(1_000_000));
329
330 let default = presets.iter().find(|preset| preset.is_default).expect("mistral default preset");
331 assert_eq!(default.model, "mistral-large-2512");
332 }
333
334 #[test]
335 fn moonshot_presets_exist_and_default_to_kimi_k3() {
336 let presets = presets::moonshot_presets();
337 assert_eq!(presets.len(), 1);
338
339 let default = presets
340 .iter()
341 .find(|preset| preset.is_default)
342 .expect("moonshot default preset");
343 assert_eq!(default.id, "kimi-k3");
344 assert_eq!(default.provider, Provider::Moonshot);
345 }
346
347 #[test]
348 fn xai_presets_exist_and_default_to_grok46() {
349 let presets = presets::xai_presets();
350 assert!(presets.iter().any(|preset| preset.model == "grok-4.6"));
351 assert!(presets.iter().any(|preset| preset.model == "grok-4.7"));
352
353 let default = presets.iter().find(|preset| preset.is_default).expect("xAI default preset");
354 assert_eq!(default.model, "grok-4.7");
355 assert_eq!(default.context_window, Some(500_000));
356 assert!(
357 default
358 .supported_reasoning_efforts
359 .iter()
360 .any(|effort| effort.effort == ReasoningEffortLevel::XHigh)
361 );
362 assert!(
363 presets
364 .iter()
365 .all(|preset| preset.provider == Provider::XAI && preset.show_in_picker)
366 );
367 }
368
369 #[test]
370 fn nvidia_presets_expose_curated_models_and_default() {
371 let presets = presets::nvidia_presets();
372 assert_eq!(presets.len(), 3);
373 let default = presets.iter().find(|preset| preset.is_default).expect("NVIDIA default preset");
374 assert_eq!(default.model, "nvidia/nemotron-3-ultra-550b-a55b");
375 assert_eq!(default.context_window, Some(1_000_000));
376 assert!(
377 presets
378 .iter()
379 .all(|preset| preset.provider == Provider::NVIDIA && preset.show_in_picker)
380 );
381 }
382
383 #[test]
384 fn merge_gateway_presets_expose_curated_routes_and_default() {
385 let presets = presets::merge_gateway_presets();
386 assert_eq!(presets.len(), 26);
387 let default = presets
388 .iter()
389 .find(|preset| preset.is_default)
390 .expect("Merge Gateway default preset");
391 assert_eq!(default.model, "default_routing");
392 assert_eq!(default.context_window, Some(128_000));
393 assert!(
394 presets
395 .iter()
396 .all(|preset| preset.provider == Provider::MergeGateway && preset.show_in_picker)
397 );
398
399 use crate::config::constants::models;
400 let reasoning_models: Vec<&str> = presets
401 .iter()
402 .filter(|preset| !preset.supported_reasoning_efforts.is_empty())
403 .map(|preset| preset.model.as_str())
404 .collect();
405 assert_eq!(reasoning_models, models::merge_gateway::REASONING_MODELS.to_vec());
406 assert!(
407 presets
408 .iter()
409 .filter(|preset| !preset.supported_reasoning_efforts.is_empty())
410 .all(|preset| preset.default_reasoning_effort == ReasoningEffortLevel::Medium)
411 );
412 }
413
414 #[test]
415 fn meta_presets_keep_standard_default_and_contributor_opt_in() {
416 let presets = presets::meta_presets();
417 assert_eq!(presets.len(), 3);
418
419 let default = presets.iter().find(|preset| preset.is_default).expect("Meta default preset");
420 assert_eq!(default.model, "muse-spark-1.3");
421 assert!(presets.iter().any(|preset| preset.model == "muse-spark-1.3-contributor"));
422 assert!(
423 presets
424 .iter()
425 .all(|preset| preset.provider == Provider::Meta && preset.show_in_picker)
426 );
427 }
428
429 #[test]
430 fn openrouter_presets_include_meta_muse_models() {
431 let presets = presets::openrouter_presets();
432
433 let glimmer = presets
434 .iter()
435 .find(|preset| preset.model == "meta/muse-glimmer-30b")
436 .expect("OpenRouter Glimmer preset");
437 assert_eq!(glimmer.context_window, Some(131_072));
438 }
439}