1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5use super::{ModelId, ModelParseError};
6
7#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
10pub enum Provider {
11 Gemini,
13 #[default]
15 OpenAI,
16 Anthropic,
18 Copilot,
20 DeepSeek,
22 OpenRouter,
24 Ollama,
26 LmStudio,
28 LlamaCpp,
30 Moonshot,
32 ZAI,
34 Minimax,
36 MiMo,
38 Mistral,
40 HuggingFace,
42 OpenCodeZen,
44 OpenCodeGo,
46 Qwen,
48 StepFun,
50 Evolink,
52 Poolside,
54}
55
56impl Provider {
57 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 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 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 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 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}