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