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    /// Mistral AI models
35    Mistral,
36    /// Hugging Face Inference Providers
37    HuggingFace,
38    /// OpenCode Zen gateway (pay-as-you-go)
39    OpenCodeZen,
40    /// OpenCode Go subscription
41    OpenCodeGo,
42}
43
44impl Provider {
45    /// Get the default API key environment variable for this provider
46    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    /// Get all supported providers
67    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    /// Human-friendly label for display purposes
88    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    /// Determine if the provider supports configurable reasoning effort for the model
129    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    /// Determine if the provider supports the `service_tier` request parameter for the model.
174    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}