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