Skip to main content

vtcode_config/models/
provider.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5use super::{ModelId, ModelParseError};
6
7/// Supported AI model providers
8#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
10pub enum Provider {
11    /// Google Gemini models
12    Gemini,
13    /// OpenAI GPT models
14    #[default]
15    OpenAI,
16    /// Anthropic Claude models
17    Anthropic,
18    /// GitHub Copilot preview integration
19    Copilot,
20    /// DeepSeek native models
21    DeepSeek,
22    /// OpenRouter marketplace models
23    OpenRouter,
24    /// Local Ollama models
25    Ollama,
26    /// LM Studio local models
27    LmStudio,
28    /// llama.cpp local models
29    LlamaCpp,
30    /// Moonshot.ai models
31    Moonshot,
32    /// Z.AI GLM models
33    ZAI,
34    /// MiniMax models
35    Minimax,
36    /// Xiaomi MiMo models
37    MiMo,
38    /// Mistral AI models
39    Mistral,
40    /// Hugging Face Inference Providers
41    HuggingFace,
42    /// OpenCode Zen gateway (pay-as-you-go)
43    OpenCodeZen,
44    /// OpenCode Go subscription
45    OpenCodeGo,
46    /// Alibaba Cloud Qwen models
47    Qwen,
48    /// StepFun models
49    StepFun,
50    /// Evolink multi-model gateway
51    Evolink,
52    /// Poolside AI models
53    Poolside,
54}
55
56impl Provider {
57    /// Get the default API key environment variable for this provider
58    pub fn default_api_key_env(&self) -> &'static str {
59        match self {
60            Provider::Gemini => "GEMINI_API_KEY",
61            Provider::OpenAI => "OPENAI_API_KEY",
62            Provider::Anthropic => "ANTHROPIC_API_KEY",
63            Provider::Copilot => "",
64            Provider::DeepSeek => "DEEPSEEK_API_KEY",
65            Provider::OpenRouter => "OPENROUTER_API_KEY",
66            Provider::Ollama => "OLLAMA_API_KEY",
67            Provider::LmStudio => "LMSTUDIO_API_KEY",
68            Provider::LlamaCpp => "LLAMACPP_API_KEY",
69            Provider::Moonshot => "MOONSHOT_API_KEY",
70            Provider::ZAI => "ZAI_API_KEY",
71            Provider::Minimax => "MINIMAX_API_KEY",
72            Provider::MiMo => "MIMO_API_KEY",
73            Provider::Mistral => "MISTRAL_API_KEY",
74            Provider::HuggingFace => "HF_TOKEN",
75            Provider::OpenCodeZen => "OPENCODE_ZEN_API_KEY",
76            Provider::OpenCodeGo => "OPENCODE_GO_API_KEY",
77            Provider::Qwen => "QWEN_API_KEY",
78            Provider::StepFun => "STEPFUN_API_KEY",
79            Provider::Evolink => "EVOLINK_API_KEY",
80            Provider::Poolside => "POOLSIDE_API_KEY",
81        }
82    }
83
84    /// Get all supported providers
85    pub fn all_providers() -> Vec<Provider> {
86        vec![
87            Provider::OpenAI,
88            Provider::Anthropic,
89            Provider::Copilot,
90            Provider::Minimax,
91            Provider::MiMo,
92            Provider::Mistral,
93            Provider::Gemini,
94            Provider::DeepSeek,
95            Provider::HuggingFace,
96            Provider::OpenRouter,
97            Provider::Ollama,
98            Provider::LmStudio,
99            Provider::LlamaCpp,
100            Provider::Moonshot,
101            Provider::ZAI,
102            Provider::OpenCodeZen,
103            Provider::OpenCodeGo,
104            Provider::Qwen,
105            Provider::StepFun,
106            Provider::Evolink,
107            Provider::Poolside,
108        ]
109    }
110
111    /// Human-friendly label for display purposes
112    pub fn label(&self) -> &'static str {
113        match self {
114            Provider::Gemini => "Gemini",
115            Provider::OpenAI => "OpenAI",
116            Provider::Anthropic => "Anthropic",
117            Provider::Copilot => "GitHub Copilot",
118            Provider::DeepSeek => "DeepSeek",
119            Provider::OpenRouter => "OpenRouter",
120            Provider::Ollama => "Ollama",
121            Provider::LmStudio => "LM Studio",
122            Provider::LlamaCpp => "llama.cpp",
123            Provider::Moonshot => "Moonshot",
124            Provider::ZAI => "Z.AI",
125            Provider::Minimax => "MiniMax",
126            Provider::MiMo => "Xiaomi MiMo",
127            Provider::Mistral => "Mistral",
128            Provider::HuggingFace => "Hugging Face",
129            Provider::OpenCodeZen => "OpenCode Zen",
130            Provider::OpenCodeGo => "OpenCode Go",
131            Provider::Qwen => "Qwen",
132            Provider::StepFun => "StepFun",
133            Provider::Evolink => "Evolink",
134            Provider::Poolside => "Poolside",
135        }
136    }
137
138    pub fn is_dynamic(&self) -> bool {
139        matches!(self, Provider::Copilot) || self.is_local()
140    }
141
142    pub fn is_local(&self) -> bool {
143        matches!(
144            self,
145            Provider::Ollama | Provider::LmStudio | Provider::LlamaCpp
146        )
147    }
148
149    pub fn local_install_instructions(&self) -> Option<&'static str> {
150        match self {
151            Provider::Ollama => Some(
152                "Ollama server is not running. To start:\n  1. Install Ollama from https://ollama.com\n  2. Run 'ollama serve' in a terminal\n  3. Pull models using 'ollama pull <model-name>' (e.g., 'ollama pull gpt-oss:20b')",
153            ),
154            Provider::LmStudio => Some(
155                "LM Studio server is not running. To start:\n  1. Install LM Studio from https://lmstudio.ai\n  2. Open LM Studio and start the Local Server on port 1234\n  3. Load the model you want to use",
156            ),
157            Provider::LlamaCpp => Some(
158                "llama.cpp server is not running. To start:\n  1. Install llama.cpp from https://llama.app or your package manager\n  2. Run 'llama-server -m /path/to/model.gguf --port 8080'\n  3. Keep the server running while VT Code connects",
159            ),
160            _ => None,
161        }
162    }
163
164    /// Determine if the provider supports configurable reasoning effort for the model
165    pub fn supports_reasoning_effort(&self, model: &str) -> bool {
166        use crate::constants::models;
167
168        match self {
169            Provider::Gemini => models::google::REASONING_MODELS.contains(&model),
170            Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
171            Provider::Anthropic => models::anthropic::REASONING_MODELS.contains(&model),
172            Provider::Copilot => false,
173            Provider::DeepSeek => {
174                model == models::deepseek::DEEPSEEK_V4_PRO || model == "deepseek-reasoner"
175            }
176            Provider::OpenRouter => {
177                if let Ok(model_id) = ModelId::from_str(model) {
178                    if let Some(meta) = crate::models::openrouter_generated::metadata_for(model_id)
179                    {
180                        return meta.reasoning;
181                    }
182                    return matches!(
183                        model_id,
184                        ModelId::OpenRouterMinimaxM25 | ModelId::OpenRouterQwen3CoderNext
185                    );
186                }
187                models::openrouter::REASONING_MODELS.contains(&model)
188            }
189            Provider::Ollama => models::ollama::REASONING_LEVEL_MODELS.contains(&model),
190            Provider::LmStudio => models::lmstudio::REASONING_MODELS.contains(&model),
191            Provider::LlamaCpp => models::llamacpp::REASONING_MODELS.contains(&model),
192            Provider::Moonshot => models::moonshot::REASONING_MODELS.contains(&model),
193            Provider::ZAI => models::zai::REASONING_MODELS.contains(&model),
194            Provider::Minimax => models::minimax::SUPPORTED_MODELS.contains(&model),
195            Provider::MiMo => models::mimo::SUPPORTED_MODELS.contains(&model),
196            Provider::Mistral => models::mistral::SUPPORTED_MODELS.contains(&model),
197            Provider::HuggingFace => models::huggingface::REASONING_MODELS.contains(&model),
198            Provider::OpenCodeZen => {
199                if models::opencode_zen::OPENAI_MODELS.contains(&model) {
200                    Provider::OpenAI.supports_reasoning_effort(model)
201                } else if models::opencode_zen::ANTHROPIC_MODELS.contains(&model) {
202                    Provider::Anthropic.supports_reasoning_effort(model)
203                } else {
204                    false
205                }
206            }
207            Provider::OpenCodeGo => false,
208            Provider::Qwen => models::qwen::REASONING_MODELS.contains(&model),
209            Provider::StepFun => models::stepfun::REASONING_MODELS.contains(&model),
210            Provider::Evolink => models::evolink::REASONING_MODELS.contains(&model),
211            Provider::Poolside => false,
212        }
213    }
214
215    /// Determine if the provider supports the `service_tier` request parameter for the model.
216    pub fn supports_service_tier(&self, model: &str) -> bool {
217        use crate::constants::models;
218
219        match self {
220            Provider::OpenAI => models::openai::SERVICE_TIER_MODELS.contains(&model),
221            _ => false,
222        }
223    }
224
225    pub fn uses_managed_auth(&self) -> bool {
226        matches!(self, Provider::Copilot)
227    }
228}
229
230impl fmt::Display for Provider {
231    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232        match self {
233            Provider::Gemini => write!(f, "gemini"),
234            Provider::OpenAI => write!(f, "openai"),
235            Provider::Anthropic => write!(f, "anthropic"),
236            Provider::Copilot => write!(f, "copilot"),
237            Provider::DeepSeek => write!(f, "deepseek"),
238            Provider::OpenRouter => write!(f, "openrouter"),
239            Provider::Ollama => write!(f, "ollama"),
240            Provider::LmStudio => write!(f, "lmstudio"),
241            Provider::LlamaCpp => write!(f, "llamacpp"),
242            Provider::Moonshot => write!(f, "moonshot"),
243            Provider::ZAI => write!(f, "zai"),
244            Provider::Minimax => write!(f, "minimax"),
245            Provider::MiMo => write!(f, "mimo"),
246            Provider::Mistral => write!(f, "mistral"),
247            Provider::HuggingFace => write!(f, "huggingface"),
248            Provider::OpenCodeZen => write!(f, "opencode-zen"),
249            Provider::OpenCodeGo => write!(f, "opencode-go"),
250            Provider::Qwen => write!(f, "qwen"),
251            Provider::StepFun => write!(f, "stepfun"),
252            Provider::Evolink => write!(f, "evolink"),
253            Provider::Poolside => write!(f, "poolside"),
254        }
255    }
256}
257
258impl AsRef<str> for Provider {
259    fn as_ref(&self) -> &str {
260        match self {
261            Provider::Gemini => "gemini",
262            Provider::OpenAI => "openai",
263            Provider::Anthropic => "anthropic",
264            Provider::Copilot => "copilot",
265            Provider::DeepSeek => "deepseek",
266            Provider::OpenRouter => "openrouter",
267            Provider::Ollama => "ollama",
268            Provider::LmStudio => "lmstudio",
269            Provider::LlamaCpp => "llamacpp",
270            Provider::Moonshot => "moonshot",
271            Provider::ZAI => "zai",
272            Provider::Minimax => "minimax",
273            Provider::MiMo => "mimo",
274            Provider::Mistral => "mistral",
275            Provider::HuggingFace => "huggingface",
276            Provider::OpenCodeZen => "opencode-zen",
277            Provider::OpenCodeGo => "opencode-go",
278            Provider::Qwen => "qwen",
279            Provider::StepFun => "stepfun",
280            Provider::Evolink => "evolink",
281            Provider::Poolside => "poolside",
282        }
283    }
284}
285
286impl FromStr for Provider {
287    type Err = ModelParseError;
288
289    fn from_str(s: &str) -> Result<Self, Self::Err> {
290        match s.to_lowercase().as_str() {
291            "gemini" => Ok(Provider::Gemini),
292            "openai" => Ok(Provider::OpenAI),
293            "anthropic" => Ok(Provider::Anthropic),
294            "copilot" => Ok(Provider::Copilot),
295            "deepseek" => Ok(Provider::DeepSeek),
296            "openrouter" => Ok(Provider::OpenRouter),
297            "ollama" => Ok(Provider::Ollama),
298            "lmstudio" => Ok(Provider::LmStudio),
299            "llamacpp" | "llama.cpp" | "llama-cpp" => Ok(Provider::LlamaCpp),
300            "moonshot" => Ok(Provider::Moonshot),
301            "zai" => Ok(Provider::ZAI),
302            "minimax" => Ok(Provider::Minimax),
303            "mimo" => Ok(Provider::MiMo),
304            "mistral" => Ok(Provider::Mistral),
305            "huggingface" => Ok(Provider::HuggingFace),
306            "opencode-zen" | "opencodezen" => Ok(Provider::OpenCodeZen),
307            "opencode-go" | "opencodego" => Ok(Provider::OpenCodeGo),
308            "qwen" => Ok(Provider::Qwen),
309            "stepfun" => Ok(Provider::StepFun),
310            "evolink" => Ok(Provider::Evolink),
311            "poolside" => Ok(Provider::Poolside),
312            _ => Err(ModelParseError::InvalidProvider(s.to_string())),
313        }
314    }
315}