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 Poolside,
52}
53
54impl Provider {
55 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 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 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 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 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}