Skip to main content

vtcode_config/models/
provider.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5use super::{ModelId, ModelParseError};
6
7/// Supported AI model providers
8#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
10pub enum Provider {
11    /// Google Gemini models
12    Gemini,
13    /// OpenAI GPT models
14    #[default]
15    OpenAI,
16    /// Anthropic Claude models
17    Anthropic,
18    /// GitHub Copilot preview integration
19    Copilot,
20    /// DeepSeek native models
21    DeepSeek,
22    /// OpenRouter marketplace models
23    OpenRouter,
24    /// Local Ollama models
25    Ollama,
26    /// LM Studio local models
27    LmStudio,
28    /// Moonshot.ai models
29    Moonshot,
30    /// Z.AI GLM models
31    ZAI,
32    /// MiniMax models
33    Minimax,
34    /// Xiaomi MiMo models
35    MiMo,
36    /// Mistral AI models
37    Mistral,
38    /// Hugging Face Inference Providers
39    HuggingFace,
40    /// OpenCode Zen gateway (pay-as-you-go)
41    OpenCodeZen,
42    /// OpenCode Go subscription
43    OpenCodeGo,
44    /// Alibaba Cloud Qwen models
45    Qwen,
46    /// Poolside AI models
47    Poolside,
48}
49
50impl Provider {
51    /// Get the default API key environment variable for this provider
52    pub fn default_api_key_env(&self) -> &'static str {
53        match self {
54            Provider::Gemini => "GEMINI_API_KEY",
55            Provider::OpenAI => "OPENAI_API_KEY",
56            Provider::Anthropic => "ANTHROPIC_API_KEY",
57            Provider::Copilot => "",
58            Provider::DeepSeek => "DEEPSEEK_API_KEY",
59            Provider::OpenRouter => "OPENROUTER_API_KEY",
60            Provider::Ollama => "OLLAMA_API_KEY",
61            Provider::LmStudio => "LMSTUDIO_API_KEY",
62            Provider::Moonshot => "MOONSHOT_API_KEY",
63            Provider::ZAI => "ZAI_API_KEY",
64            Provider::Minimax => "MINIMAX_API_KEY",
65            Provider::MiMo => "MIMO_API_KEY",
66            Provider::Mistral => "MISTRAL_API_KEY",
67            Provider::HuggingFace => "HF_TOKEN",
68            Provider::OpenCodeZen => "OPENCODE_ZEN_API_KEY",
69            Provider::OpenCodeGo => "OPENCODE_GO_API_KEY",
70            Provider::Qwen => "QWEN_API_KEY",
71            Provider::Poolside => "POOLSIDE_API_KEY",
72        }
73    }
74
75    /// Get all supported providers
76    pub fn all_providers() -> Vec<Provider> {
77        vec![
78            Provider::OpenAI,
79            Provider::Anthropic,
80            Provider::Copilot,
81            Provider::Minimax,
82            Provider::MiMo,
83            Provider::Mistral,
84            Provider::Gemini,
85            Provider::DeepSeek,
86            Provider::HuggingFace,
87            Provider::OpenRouter,
88            Provider::Ollama,
89            Provider::LmStudio,
90            Provider::Moonshot,
91            Provider::ZAI,
92            Provider::OpenCodeZen,
93            Provider::OpenCodeGo,
94            Provider::Qwen,
95            Provider::Poolside,
96        ]
97    }
98
99    /// Human-friendly label for display purposes
100    pub fn label(&self) -> &'static str {
101        match self {
102            Provider::Gemini => "Gemini",
103            Provider::OpenAI => "OpenAI",
104            Provider::Anthropic => "Anthropic",
105            Provider::Copilot => "GitHub Copilot",
106            Provider::DeepSeek => "DeepSeek",
107            Provider::OpenRouter => "OpenRouter",
108            Provider::Ollama => "Ollama",
109            Provider::LmStudio => "LM Studio",
110            Provider::Moonshot => "Moonshot",
111            Provider::ZAI => "Z.AI",
112            Provider::Minimax => "MiniMax",
113            Provider::MiMo => "Xiaomi MiMo",
114            Provider::Mistral => "Mistral",
115            Provider::HuggingFace => "Hugging Face",
116            Provider::OpenCodeZen => "OpenCode Zen",
117            Provider::OpenCodeGo => "OpenCode Go",
118            Provider::Qwen => "Qwen",
119            Provider::Poolside => "Poolside",
120        }
121    }
122
123    pub fn is_dynamic(&self) -> bool {
124        matches!(self, Provider::Copilot) || self.is_local()
125    }
126
127    pub fn is_local(&self) -> bool {
128        matches!(self, Provider::Ollama | Provider::LmStudio)
129    }
130
131    pub fn local_install_instructions(&self) -> Option<&'static str> {
132        match self {
133            Provider::Ollama => Some(
134                "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')",
135            ),
136            Provider::LmStudio => Some(
137                "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",
138            ),
139            _ => None,
140        }
141    }
142
143    /// Determine if the provider supports configurable reasoning effort for the model
144    pub fn supports_reasoning_effort(&self, model: &str) -> bool {
145        use crate::constants::models;
146
147        match self {
148            Provider::Gemini => models::google::REASONING_MODELS.contains(&model),
149            Provider::OpenAI => models::openai::REASONING_MODELS.contains(&model),
150            Provider::Anthropic => models::anthropic::REASONING_MODELS.contains(&model),
151            Provider::Copilot => false,
152            Provider::DeepSeek => {
153                model == models::deepseek::DEEPSEEK_V4_PRO || model == "deepseek-reasoner"
154            }
155            Provider::OpenRouter => {
156                if let Ok(model_id) = ModelId::from_str(model) {
157                    if let Some(meta) = crate::models::openrouter_generated::metadata_for(model_id)
158                    {
159                        return meta.reasoning;
160                    }
161                    return matches!(
162                        model_id,
163                        ModelId::OpenRouterMinimaxM25 | ModelId::OpenRouterQwen3CoderNext
164                    );
165                }
166                models::openrouter::REASONING_MODELS.contains(&model)
167            }
168            Provider::Ollama => models::ollama::REASONING_LEVEL_MODELS.contains(&model),
169            Provider::LmStudio => models::lmstudio::REASONING_MODELS.contains(&model),
170            Provider::Moonshot => models::moonshot::REASONING_MODELS.contains(&model),
171            Provider::ZAI => models::zai::REASONING_MODELS.contains(&model),
172            Provider::Minimax => models::minimax::SUPPORTED_MODELS.contains(&model),
173            Provider::MiMo => models::mimo::SUPPORTED_MODELS.contains(&model),
174            Provider::Mistral => models::mistral::SUPPORTED_MODELS.contains(&model),
175            Provider::HuggingFace => models::huggingface::REASONING_MODELS.contains(&model),
176            Provider::OpenCodeZen => {
177                if models::opencode_zen::OPENAI_MODELS.contains(&model) {
178                    Provider::OpenAI.supports_reasoning_effort(model)
179                } else if models::opencode_zen::ANTHROPIC_MODELS.contains(&model) {
180                    Provider::Anthropic.supports_reasoning_effort(model)
181                } else {
182                    false
183                }
184            }
185            Provider::OpenCodeGo => false,
186            Provider::Qwen => models::qwen::REASONING_MODELS.contains(&model),
187            Provider::Poolside => false,
188        }
189    }
190
191    /// Determine if the provider supports the `service_tier` request parameter for the model.
192    pub fn supports_service_tier(&self, model: &str) -> bool {
193        use crate::constants::models;
194
195        match self {
196            Provider::OpenAI => models::openai::SERVICE_TIER_MODELS.contains(&model),
197            _ => false,
198        }
199    }
200
201    pub fn uses_managed_auth(&self) -> bool {
202        matches!(self, Provider::Copilot)
203    }
204}
205
206impl fmt::Display for Provider {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        match self {
209            Provider::Gemini => write!(f, "gemini"),
210            Provider::OpenAI => write!(f, "openai"),
211            Provider::Anthropic => write!(f, "anthropic"),
212            Provider::Copilot => write!(f, "copilot"),
213            Provider::DeepSeek => write!(f, "deepseek"),
214            Provider::OpenRouter => write!(f, "openrouter"),
215            Provider::Ollama => write!(f, "ollama"),
216            Provider::LmStudio => write!(f, "lmstudio"),
217            Provider::Moonshot => write!(f, "moonshot"),
218            Provider::ZAI => write!(f, "zai"),
219            Provider::Minimax => write!(f, "minimax"),
220            Provider::MiMo => write!(f, "mimo"),
221            Provider::Mistral => write!(f, "mistral"),
222            Provider::HuggingFace => write!(f, "huggingface"),
223            Provider::OpenCodeZen => write!(f, "opencode-zen"),
224            Provider::OpenCodeGo => write!(f, "opencode-go"),
225            Provider::Qwen => write!(f, "qwen"),
226            Provider::Poolside => write!(f, "poolside"),
227        }
228    }
229}
230
231impl AsRef<str> for Provider {
232    fn as_ref(&self) -> &str {
233        match self {
234            Provider::Gemini => "gemini",
235            Provider::OpenAI => "openai",
236            Provider::Anthropic => "anthropic",
237            Provider::Copilot => "copilot",
238            Provider::DeepSeek => "deepseek",
239            Provider::OpenRouter => "openrouter",
240            Provider::Ollama => "ollama",
241            Provider::LmStudio => "lmstudio",
242            Provider::Moonshot => "moonshot",
243            Provider::ZAI => "zai",
244            Provider::Minimax => "minimax",
245            Provider::MiMo => "mimo",
246            Provider::Mistral => "mistral",
247            Provider::HuggingFace => "huggingface",
248            Provider::OpenCodeZen => "opencode-zen",
249            Provider::OpenCodeGo => "opencode-go",
250            Provider::Qwen => "qwen",
251            Provider::Poolside => "poolside",
252        }
253    }
254}
255
256impl FromStr for Provider {
257    type Err = ModelParseError;
258
259    fn from_str(s: &str) -> Result<Self, Self::Err> {
260        match s.to_lowercase().as_str() {
261            "gemini" => Ok(Provider::Gemini),
262            "openai" => Ok(Provider::OpenAI),
263            "anthropic" => Ok(Provider::Anthropic),
264            "copilot" => Ok(Provider::Copilot),
265            "deepseek" => Ok(Provider::DeepSeek),
266            "openrouter" => Ok(Provider::OpenRouter),
267            "ollama" => Ok(Provider::Ollama),
268            "lmstudio" => Ok(Provider::LmStudio),
269            "moonshot" => Ok(Provider::Moonshot),
270            "zai" => Ok(Provider::ZAI),
271            "minimax" => Ok(Provider::Minimax),
272            "mimo" => Ok(Provider::MiMo),
273            "mistral" => Ok(Provider::Mistral),
274            "huggingface" => Ok(Provider::HuggingFace),
275            "opencode-zen" | "opencodezen" => Ok(Provider::OpenCodeZen),
276            "opencode-go" | "opencodego" => Ok(Provider::OpenCodeGo),
277            "qwen" => Ok(Provider::Qwen),
278            "poolside" => Ok(Provider::Poolside),
279            _ => Err(ModelParseError::InvalidProvider(s.to_string())),
280        }
281    }
282}