Skip to main content

vtcode_core/llm/
provider_config.rs

1use crate::config::TimeoutsConfig;
2use crate::config::core::{GeminiPromptCacheSettings, PromptCachingConfig};
3use crate::llm::cgp::{CanDescribeProvider, ProviderMetadataProvider};
4use crate::llm::factory::{self, ProviderConfig as FactoryProviderConfig};
5use crate::llm::provider::{LLMError, LLMProvider};
6use crate::llm::provider_builder::ProviderConfig as LegacyProviderConfig;
7
8macro_rules! define_provider_config {
9    ($name:ident, $key:literal, $display:literal, $default_model:expr, $api_base:expr, $env_var:expr, $prompt_cache_settings:ty) => {
10        pub struct $name;
11
12        impl ProviderMetadataProvider<$name> for $name {
13            const PROVIDER_KEY: &'static str = $key;
14            const DISPLAY_NAME: &'static str = $display;
15            const DEFAULT_MODEL: &'static str = $default_model;
16            const API_BASE_URL: &'static str = $api_base;
17            const BASE_URL_ENV_VAR: Option<&'static str> = $env_var;
18        }
19
20        impl LegacyProviderConfig for $name {
21            const PROVIDER_KEY: &'static str = <Self as CanDescribeProvider>::PROVIDER_KEY;
22            const DISPLAY_NAME: &'static str = <Self as CanDescribeProvider>::DISPLAY_NAME;
23            const DEFAULT_MODEL: &'static str = <Self as CanDescribeProvider>::DEFAULT_MODEL;
24            const API_BASE_URL: &'static str = <Self as CanDescribeProvider>::API_BASE_URL;
25            const BASE_URL_ENV_VAR: Option<&'static str> =
26                <Self as CanDescribeProvider>::BASE_URL_ENV_VAR;
27
28            type PromptCacheSettings = $prompt_cache_settings;
29        }
30    };
31}
32
33define_provider_config!(
34    GeminiProviderConfig,
35    "gemini",
36    "Gemini",
37    crate::config::constants::models::google::GEMINI_3_FLASH_PREVIEW,
38    crate::config::constants::urls::GEMINI_API_BASE,
39    Some(crate::config::constants::env_vars::GEMINI_BASE_URL),
40    GeminiPromptCacheSettings
41);
42define_provider_config!(
43    AnthropicProviderConfig,
44    "anthropic",
45    "Anthropic",
46    crate::config::constants::models::anthropic::DEFAULT_MODEL,
47    crate::config::constants::urls::ANTHROPIC_API_BASE,
48    Some(crate::config::constants::env_vars::ANTHROPIC_BASE_URL),
49    ()
50);
51define_provider_config!(
52    CopilotProviderConfig,
53    "copilot",
54    "GitHub Copilot",
55    crate::config::constants::models::copilot::DEFAULT_MODEL,
56    "",
57    None,
58    ()
59);
60define_provider_config!(
61    OpenAIProviderConfig,
62    "openai",
63    "OpenAI",
64    crate::config::constants::models::openai::DEFAULT_MODEL,
65    crate::config::constants::urls::OPENAI_API_BASE,
66    Some(crate::config::constants::env_vars::OPENAI_BASE_URL),
67    ()
68);
69define_provider_config!(
70    HuggingFaceProviderConfig,
71    "huggingface",
72    "HuggingFace",
73    crate::config::constants::models::huggingface::DEFAULT_MODEL,
74    crate::config::constants::urls::HUGGINGFACE_API_BASE,
75    Some(crate::config::constants::env_vars::HUGGINGFACE_BASE_URL),
76    ()
77);
78define_provider_config!(
79    DeepSeekProviderConfig,
80    "deepseek",
81    "DeepSeek",
82    crate::config::constants::models::deepseek::DEEPSEEK_V4_PRO,
83    crate::config::constants::urls::DEEPSEEK_API_BASE,
84    Some(crate::config::constants::env_vars::DEEPSEEK_BASE_URL),
85    ()
86);
87define_provider_config!(
88    MistralProviderConfig,
89    "mistral",
90    "Mistral",
91    crate::config::constants::models::mistral::MISTRAL_LARGE_3,
92    crate::config::constants::urls::MISTRAL_API_BASE,
93    Some(crate::config::constants::env_vars::MISTRAL_BASE_URL),
94    ()
95);
96define_provider_config!(
97    MoonshotProviderConfig,
98    "moonshot",
99    "Moonshot",
100    crate::config::constants::models::moonshot::DEFAULT_MODEL,
101    crate::config::constants::urls::MOONSHOT_API_BASE,
102    Some(crate::config::constants::env_vars::MOONSHOT_BASE_URL),
103    ()
104);
105define_provider_config!(
106    ZAIProviderConfig,
107    "zai",
108    "Z.AI",
109    crate::config::constants::models::zai::DEFAULT_MODEL,
110    crate::config::constants::urls::Z_AI_API_BASE,
111    Some(crate::config::constants::env_vars::ZAI_BASE_URL),
112    ()
113);
114define_provider_config!(
115    OpenRouterProviderConfig,
116    "openrouter",
117    "OpenRouter",
118    "openrouter/auto",
119    crate::config::constants::urls::OPENROUTER_API_BASE,
120    Some(crate::config::constants::env_vars::OPENROUTER_BASE_URL),
121    ()
122);
123define_provider_config!(
124    OpenResponsesProviderConfig,
125    "openresponses",
126    "OpenResponses",
127    crate::config::constants::models::openresponses::DEFAULT_MODEL,
128    crate::config::constants::urls::OPENRESPONSES_API_BASE,
129    Some(crate::config::constants::env_vars::OPENRESPONSES_BASE_URL),
130    ()
131);
132define_provider_config!(
133    OllamaProviderConfig,
134    "ollama",
135    "Ollama",
136    "gpt-oss:20b",
137    "http://localhost:11434",
138    None,
139    ()
140);
141define_provider_config!(
142    LmStudioProviderConfig,
143    "lmstudio",
144    "LM Studio",
145    crate::config::constants::models::lmstudio::DEFAULT_MODEL,
146    crate::config::constants::urls::LMSTUDIO_API_BASE,
147    Some(crate::config::constants::env_vars::LMSTUDIO_BASE_URL),
148    ()
149);
150define_provider_config!(
151    LlamaCppProviderConfig,
152    "llamacpp",
153    "llama.cpp",
154    crate::config::constants::models::llamacpp::DEFAULT_MODEL,
155    crate::config::constants::urls::LLAMACPP_API_BASE,
156    Some(crate::config::constants::env_vars::LLAMACPP_BASE_URL),
157    ()
158);
159define_provider_config!(
160    MiMoProviderConfig,
161    "mimo",
162    "Xiaomi MiMo",
163    crate::config::constants::models::mimo::DEFAULT_MODEL,
164    crate::config::constants::urls::MIMO_API_BASE,
165    Some(crate::config::constants::env_vars::MIMO_BASE_URL),
166    ()
167);
168define_provider_config!(
169    MinimaxProviderConfig,
170    "minimax",
171    "Minimax",
172    crate::config::constants::models::minimax::DEFAULT_MODEL,
173    crate::config::constants::urls::MINIMAX_API_BASE,
174    Some(crate::config::constants::env_vars::MINIMAX_BASE_URL),
175    ()
176);
177define_provider_config!(
178    OpenCodeZenProviderConfig,
179    "opencode-zen",
180    "OpenCode Zen",
181    crate::config::constants::models::opencode_zen::DEFAULT_MODEL,
182    crate::config::constants::urls::OPENCODE_ZEN_API_BASE,
183    Some(crate::config::constants::env_vars::OPENCODE_ZEN_BASE_URL),
184    ()
185);
186define_provider_config!(
187    OpenCodeGoProviderConfig,
188    "opencode-go",
189    "OpenCode Go",
190    crate::config::constants::models::opencode_go::DEFAULT_MODEL,
191    crate::config::constants::urls::OPENCODE_GO_API_BASE,
192    Some(crate::config::constants::env_vars::OPENCODE_GO_BASE_URL),
193    ()
194);
195define_provider_config!(
196    QwenProviderConfig,
197    "qwen",
198    "Qwen",
199    crate::config::constants::models::qwen::DEFAULT_MODEL,
200    crate::config::constants::urls::QWEN_API_BASE,
201    Some(crate::config::constants::env_vars::QWEN_BASE_URL),
202    ()
203);
204define_provider_config!(
205    StepFunProviderConfig,
206    "stepfun",
207    "StepFun",
208    crate::config::constants::models::stepfun::DEFAULT_MODEL,
209    crate::config::constants::urls::STEPFUN_API_BASE,
210    Some(crate::config::constants::env_vars::STEPFUN_BASE_URL),
211    ()
212);
213define_provider_config!(
214    EvolinkProviderConfig,
215    "evolink",
216    "Evolink",
217    crate::config::constants::models::evolink::DEFAULT_MODEL,
218    crate::config::constants::urls::EVOLINK_API_BASE,
219    Some(crate::config::constants::env_vars::EVOLINK_BASE_URL),
220    ()
221);
222define_provider_config!(
223    PoolsideProviderConfig,
224    "poolside",
225    "Poolside",
226    crate::config::constants::models::poolside::DEFAULT_MODEL,
227    crate::config::constants::urls::POOLSIDE_API_BASE,
228    Some(crate::config::constants::env_vars::POOLSIDE_BASE_URL),
229    ()
230);
231
232/// Macro kept for source compatibility with older builder-based call sites.
233#[macro_export]
234macro_rules! create_provider_builder {
235    ($config_type:ty) => {
236        $crate::llm::provider_builder::ProviderBuilder::<$config_type>::new()
237    };
238}
239
240fn non_empty(value: Option<String>) -> Option<String> {
241    value.and_then(|value| {
242        let trimmed = value.trim();
243        if trimmed.is_empty() {
244            None
245        } else if trimmed.len() == value.len() {
246            Some(value)
247        } else {
248            Some(trimmed.to_string())
249        }
250    })
251}
252
253/// Compatibility shim that forwards legacy builder/config callers into the
254/// canonical provider factory.
255pub fn create_provider_unified(
256    provider_name: &str,
257    api_key: Option<String>,
258    model: Option<String>,
259    base_url: Option<String>,
260    prompt_cache: Option<PromptCachingConfig>,
261    timeouts: Option<TimeoutsConfig>,
262) -> Result<Box<dyn LLMProvider>, LLMError> {
263    factory::create_provider_with_config(
264        provider_name,
265        FactoryProviderConfig {
266            api_key: non_empty(api_key),
267            openai_chatgpt_auth: None,
268            copilot_auth: None,
269            base_url: non_empty(base_url),
270            model: non_empty(model),
271            prompt_cache,
272            timeouts,
273            openai: None,
274            anthropic: None,
275            model_behavior: None,
276            workspace_root: None,
277        },
278    )
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284    use crate::llm::provider_builder::ProviderBuilder;
285
286    #[test]
287    fn unified_provider_creation_supports_huggingface_through_factory() {
288        let provider = create_provider_unified(
289            "huggingface",
290            Some("test-key".to_string()),
291            Some("openai/gpt-oss-20b".to_string()),
292            None,
293            None,
294            None,
295        )
296        .expect("shim should route through the factory");
297
298        assert_eq!(provider.name(), "huggingface");
299    }
300
301    #[test]
302    fn builder_shim_routes_through_factory() {
303        let provider = ProviderBuilder::<OpenAIProviderConfig>::new()
304            .api_key("test-key".to_string())
305            .model(crate::config::constants::models::openai::DEFAULT_MODEL.to_string())
306            .try_build()
307            .expect("builder shim should resolve via the factory");
308
309        assert_eq!(provider.name(), "openai");
310    }
311
312    #[test]
313    fn legacy_provider_config_create_provider_routes_through_factory() {
314        let provider =
315            <OpenAIProviderConfig as crate::llm::provider_builder::ProviderConfig>::create_provider(
316                "test-key".to_string(),
317                crate::config::constants::models::openai::DEFAULT_MODEL.to_string(),
318                crate::config::constants::urls::OPENAI_API_BASE.to_string(),
319                false,
320                (),
321                TimeoutsConfig::default(),
322            );
323
324        assert_eq!(provider.name(), "openai");
325    }
326
327    #[test]
328    fn unified_provider_creation_matches_factory_behavior() {
329        let shim = create_provider_unified(
330            "ollama",
331            None,
332            Some("gpt-oss:20b".to_string()),
333            Some("http://localhost:11434".to_string()),
334            None,
335            Some(TimeoutsConfig::default()),
336        )
337        .expect("shim provider should build");
338
339        let factory = factory::create_provider_with_config(
340            "ollama",
341            FactoryProviderConfig {
342                api_key: None,
343                openai_chatgpt_auth: None,
344                copilot_auth: None,
345                base_url: Some("http://localhost:11434".to_string()),
346                model: Some("gpt-oss:20b".to_string()),
347                prompt_cache: None,
348                timeouts: Some(TimeoutsConfig::default()),
349                openai: None,
350                anthropic: None,
351                model_behavior: None,
352                workspace_root: None,
353            },
354        )
355        .expect("factory provider should build");
356
357        assert_eq!(shim.name(), factory.name());
358        assert_eq!(shim.supported_models(), factory.supported_models());
359    }
360}