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    /// Poolside AI models
51    Poolside,
52}
53
54impl Provider {
55    /// Get the default API key environment variable for this provider
56    pub fn default_api_key_env(&self) -> &'static str {
57        match self {
58            Provider::Gemini => "GEMINI_API_KEY",
59            Provider::OpenAI => "OPENAI_API_KEY",
60            Provider::Anthropic => "ANTHROPIC_API_KEY",
61            Provider::Copilot => "",
62            Provider::DeepSeek => "DEEPSEEK_API_KEY",
63            Provider::OpenRouter => "OPENROUTER_API_KEY",
64            Provider::Ollama => "OLLAMA_API_KEY",
65            Provider::LmStudio => "LMSTUDIO_API_KEY",
66            Provider::LlamaCpp => "LLAMACPP_API_KEY",
67            Provider::Moonshot => "MOONSHOT_API_KEY",
68            Provider::ZAI => "ZAI_API_KEY",
69            Provider::Minimax => "MINIMAX_API_KEY",
70            Provider::MiMo => "MIMO_API_KEY",
71            Provider::Mistral => "MISTRAL_API_KEY",
72            Provider::HuggingFace => "HF_TOKEN",
73            Provider::OpenCodeZen => "OPENCODE_ZEN_API_KEY",
74            Provider::OpenCodeGo => "OPENCODE_GO_API_KEY",
75            Provider::Qwen => "QWEN_API_KEY",
76            Provider::StepFun => "STEPFUN_API_KEY",
77            Provider::Poolside => "POOLSIDE_API_KEY",
78        }
79    }
80
81    /// Get all supported providers
82    pub fn all_providers() -> Vec<Provider> {
83        vec![
84            Provider::OpenAI,
85            Provider::Anthropic,
86            Provider::Copilot,
87            Provider::Minimax,
88            Provider::MiMo,
89            Provider::Mistral,
90            Provider::Gemini,
91            Provider::DeepSeek,
92            Provider::HuggingFace,
93            Provider::OpenRouter,
94            Provider::Ollama,
95            Provider::LmStudio,
96            Provider::LlamaCpp,
97            Provider::Moonshot,
98            Provider::ZAI,
99            Provider::OpenCodeZen,
100            Provider::OpenCodeGo,
101            Provider::Qwen,
102            Provider::StepFun,
103            Provider::Poolside,
104        ]
105    }
106
107    /// Human-friendly label for display purposes
108    pub fn label(&self) -> &'static str {
109        match self {
110            Provider::Gemini => "Gemini",
111            Provider::OpenAI => "OpenAI",
112            Provider::Anthropic => "Anthropic",
113            Provider::Copilot => "GitHub Copilot",
114            Provider::DeepSeek => "DeepSeek",
115            Provider::OpenRouter => "OpenRouter",
116            Provider::Ollama => "Ollama",
117            Provider::LmStudio => "LM Studio",
118            Provider::LlamaCpp => "llama.cpp",
119            Provider::Moonshot => "Moonshot",
120            Provider::ZAI => "Z.AI",
121            Provider::Minimax => "MiniMax",
122            Provider::MiMo => "Xiaomi MiMo",
123            Provider::Mistral => "Mistral",
124            Provider::HuggingFace => "Hugging Face",
125            Provider::OpenCodeZen => "OpenCode Zen",
126            Provider::OpenCodeGo => "OpenCode Go",
127            Provider::Qwen => "Qwen",
128            Provider::StepFun => "StepFun",
129            Provider::Poolside => "Poolside",
130        }
131    }
132
133    pub fn is_dynamic(&self) -> bool {
134        matches!(self, Provider::Copilot) || self.is_local()
135    }
136
137    pub fn is_local(&self) -> bool {
138        matches!(
139            self,
140            Provider::Ollama | Provider::LmStudio | Provider::LlamaCpp
141        )
142    }
143
144    pub fn local_install_instructions(&self) -> Option<&'static str> {
145        match self {
146            Provider::Ollama => Some(
147                "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')",
148            ),
149            Provider::LmStudio => Some(
150                "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",
151            ),
152            Provider::LlamaCpp => Some(
153                "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",
154            ),
155            _ => None,
156        }
157    }
158
159    /// Determine if the provider supports configurable reasoning effort for the model
160    pub fn supports_reasoning_effort(&self, model: &str) -> bool {
161        use crate::constants::models;
162
163        match self {
164            Provider::Gemini => models::google::REASONING_MODELS.contains(&model),
165            Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
166            Provider::Anthropic => models::anthropic::REASONING_MODELS.contains(&model),
167            Provider::Copilot => false,
168            Provider::DeepSeek => {
169                model == models::deepseek::DEEPSEEK_V4_PRO || model == "deepseek-reasoner"
170            }
171            Provider::OpenRouter => {
172                if let Ok(model_id) = ModelId::from_str(model) {
173                    if let Some(meta) = crate::models::openrouter_generated::metadata_for(model_id)
174                    {
175                        return meta.reasoning;
176                    }
177                    return matches!(
178                        model_id,
179                        ModelId::OpenRouterMinimaxM25 | ModelId::OpenRouterQwen3CoderNext
180                    );
181                }
182                models::openrouter::REASONING_MODELS.contains(&model)
183            }
184            Provider::Ollama => models::ollama::REASONING_LEVEL_MODELS.contains(&model),
185            Provider::LmStudio => models::lmstudio::REASONING_MODELS.contains(&model),
186            Provider::LlamaCpp => models::llamacpp::REASONING_MODELS.contains(&model),
187            Provider::Moonshot => models::moonshot::REASONING_MODELS.contains(&model),
188            Provider::ZAI => models::zai::REASONING_MODELS.contains(&model),
189            Provider::Minimax => models::minimax::SUPPORTED_MODELS.contains(&model),
190            Provider::MiMo => models::mimo::SUPPORTED_MODELS.contains(&model),
191            Provider::Mistral => models::mistral::SUPPORTED_MODELS.contains(&model),
192            Provider::HuggingFace => models::huggingface::REASONING_MODELS.contains(&model),
193            Provider::OpenCodeZen => {
194                if models::opencode_zen::OPENAI_MODELS.contains(&model) {
195                    Provider::OpenAI.supports_reasoning_effort(model)
196                } else if models::opencode_zen::ANTHROPIC_MODELS.contains(&model) {
197                    Provider::Anthropic.supports_reasoning_effort(model)
198                } else {
199                    false
200                }
201            }
202            Provider::OpenCodeGo => false,
203            Provider::Qwen => models::qwen::REASONING_MODELS.contains(&model),
204            Provider::StepFun => models::stepfun::REASONING_MODELS.contains(&model),
205            Provider::Poolside => false,
206        }
207    }
208
209    /// Determine if the provider supports the `service_tier` request parameter for the model.
210    pub fn supports_service_tier(&self, model: &str) -> bool {
211        use crate::constants::models;
212
213        match self {
214            Provider::OpenAI => models::openai::SERVICE_TIER_MODELS.contains(&model),
215            _ => false,
216        }
217    }
218
219    pub fn uses_managed_auth(&self) -> bool {
220        matches!(self, Provider::Copilot)
221    }
222}
223
224impl fmt::Display for Provider {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        match self {
227            Provider::Gemini => write!(f, "gemini"),
228            Provider::OpenAI => write!(f, "openai"),
229            Provider::Anthropic => write!(f, "anthropic"),
230            Provider::Copilot => write!(f, "copilot"),
231            Provider::DeepSeek => write!(f, "deepseek"),
232            Provider::OpenRouter => write!(f, "openrouter"),
233            Provider::Ollama => write!(f, "ollama"),
234            Provider::LmStudio => write!(f, "lmstudio"),
235            Provider::LlamaCpp => write!(f, "llamacpp"),
236            Provider::Moonshot => write!(f, "moonshot"),
237            Provider::ZAI => write!(f, "zai"),
238            Provider::Minimax => write!(f, "minimax"),
239            Provider::MiMo => write!(f, "mimo"),
240            Provider::Mistral => write!(f, "mistral"),
241            Provider::HuggingFace => write!(f, "huggingface"),
242            Provider::OpenCodeZen => write!(f, "opencode-zen"),
243            Provider::OpenCodeGo => write!(f, "opencode-go"),
244            Provider::Qwen => write!(f, "qwen"),
245            Provider::StepFun => write!(f, "stepfun"),
246            Provider::Poolside => write!(f, "poolside"),
247        }
248    }
249}
250
251impl AsRef<str> for Provider {
252    fn as_ref(&self) -> &str {
253        match self {
254            Provider::Gemini => "gemini",
255            Provider::OpenAI => "openai",
256            Provider::Anthropic => "anthropic",
257            Provider::Copilot => "copilot",
258            Provider::DeepSeek => "deepseek",
259            Provider::OpenRouter => "openrouter",
260            Provider::Ollama => "ollama",
261            Provider::LmStudio => "lmstudio",
262            Provider::LlamaCpp => "llamacpp",
263            Provider::Moonshot => "moonshot",
264            Provider::ZAI => "zai",
265            Provider::Minimax => "minimax",
266            Provider::MiMo => "mimo",
267            Provider::Mistral => "mistral",
268            Provider::HuggingFace => "huggingface",
269            Provider::OpenCodeZen => "opencode-zen",
270            Provider::OpenCodeGo => "opencode-go",
271            Provider::Qwen => "qwen",
272            Provider::StepFun => "stepfun",
273            Provider::Poolside => "poolside",
274        }
275    }
276}
277
278impl FromStr for Provider {
279    type Err = ModelParseError;
280
281    fn from_str(s: &str) -> Result<Self, Self::Err> {
282        match s.to_lowercase().as_str() {
283            "gemini" => Ok(Provider::Gemini),
284            "openai" => Ok(Provider::OpenAI),
285            "anthropic" => Ok(Provider::Anthropic),
286            "copilot" => Ok(Provider::Copilot),
287            "deepseek" => Ok(Provider::DeepSeek),
288            "openrouter" => Ok(Provider::OpenRouter),
289            "ollama" => Ok(Provider::Ollama),
290            "lmstudio" => Ok(Provider::LmStudio),
291            "llamacpp" | "llama.cpp" | "llama-cpp" => Ok(Provider::LlamaCpp),
292            "moonshot" => Ok(Provider::Moonshot),
293            "zai" => Ok(Provider::ZAI),
294            "minimax" => Ok(Provider::Minimax),
295            "mimo" => Ok(Provider::MiMo),
296            "mistral" => Ok(Provider::Mistral),
297            "huggingface" => Ok(Provider::HuggingFace),
298            "opencode-zen" | "opencodezen" => Ok(Provider::OpenCodeZen),
299            "opencode-go" | "opencodego" => Ok(Provider::OpenCodeGo),
300            "qwen" => Ok(Provider::Qwen),
301            "stepfun" => Ok(Provider::StepFun),
302            "poolside" => Ok(Provider::Poolside),
303            _ => Err(ModelParseError::InvalidProvider(s.to_string())),
304        }
305    }
306}