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