Skip to main content

vtcode_core/llm/
cgp.rs

1//! Context-generic provider wiring for VT Code's LLM factory.
2//!
3//! This keeps the runtime string-keyed registry intact while moving provider
4//! metadata and construction behind the same CGP substrate used by the tool
5//! runtime. Zero-sized provider config types act as the context, and the
6//! factory/builder layers consume blanket traits instead of hand-written
7//! per-provider registration macros.
8
9use std::marker::PhantomData;
10
11use super::factory::LLMFactory;
12use super::factory::ProviderConfig as FactoryProviderConfig;
13use super::provider::LLMProvider;
14use super::provider_config::{
15    AnthropicProviderConfig, CopilotProviderConfig, DeepSeekProviderConfig, EvolinkProviderConfig,
16    GeminiProviderConfig, HuggingFaceProviderConfig, LlamaCppProviderConfig, LmStudioProviderConfig,
17    MergeGatewayProviderConfig, MetaProviderConfig, MiMoProviderConfig, MinimaxProviderConfig, MistralProviderConfig,
18    MoonshotProviderConfig, NvidiaProviderConfig, OllamaCloudProviderConfig, OllamaProviderConfig,
19    OpenAIProviderConfig, OpenCodeGoProviderConfig, OpenCodeZenProviderConfig, OpenResponsesProviderConfig,
20    OpenRouterProviderConfig, PoolsideProviderConfig, QwenProviderConfig, StepFunProviderConfig, VercelProviderConfig,
21    XAIProviderConfig, ZAIProviderConfig,
22};
23use super::providers::{
24    AnthropicProvider, CopilotProvider, DeepSeekProvider, EvolinkProvider, GeminiProvider, HuggingFaceProvider,
25    LlamaCppProvider, LmStudioProvider, MergeGatewayProvider, MetaProvider, MiMoProvider, MinimaxProvider,
26    MistralProvider, MoonshotProvider, NvidiaProvider, OllamaProvider, OpenCodeGoProvider, OpenCodeZenProvider,
27    OpenResponsesProvider, OpenRouterProvider, PoolsideProvider, QwenProvider, StepFunProvider, VercelProvider,
28    XAIProvider, ZAIProvider,
29};
30use vtcode_commons::cgp::{ComponentProvider, HasComponent};
31use vtcode_config::TimeoutsConfig;
32use vtcode_config::core::{AnthropicConfig, ModelConfig, PromptCachingConfig};
33
34/// Marker component for static provider metadata.
35pub enum ProviderMetadataComponent {}
36
37/// Marker component for provider construction.
38pub enum ProviderBuildComponent {}
39
40/// Provider trait for provider metadata.
41pub trait ProviderMetadataProvider<Ctx> {
42    /// Registry key used to identify this provider in the factory.
43    const PROVIDER_KEY: &'static str;
44    /// Human-readable display name.
45    const DISPLAY_NAME: &'static str;
46    /// Default model identifier when none is specified.
47    const DEFAULT_MODEL: &'static str;
48    /// Base URL for the provider's API endpoint.
49    const API_BASE_URL: &'static str;
50    /// Optional environment variable that overrides the base URL.
51    const BASE_URL_ENV_VAR: Option<&'static str>;
52}
53
54/// Provider trait for constructing boxed providers from factory config.
55pub trait ProviderBuildProvider<Ctx>: Send + Sync {
56    /// Construct a boxed [`LLMProvider`] from the given factory configuration.
57    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider>;
58}
59
60/// Ergonomic blanket consumer over the metadata component.
61pub trait CanDescribeProvider {
62    /// Registry key used to identify this provider in the factory.
63    const PROVIDER_KEY: &'static str;
64    /// Human-readable display name.
65    const DISPLAY_NAME: &'static str;
66    /// Default model identifier when none is specified.
67    const DEFAULT_MODEL: &'static str;
68    /// Base URL for the provider's API endpoint.
69    const API_BASE_URL: &'static str;
70    /// Optional environment variable that overrides the base URL.
71    const BASE_URL_ENV_VAR: Option<&'static str>;
72}
73
74impl<Ctx> CanDescribeProvider for Ctx
75where
76    Ctx: HasComponent<ProviderMetadataComponent>,
77    ComponentProvider<Ctx, ProviderMetadataComponent>: ProviderMetadataProvider<Ctx>,
78{
79    const PROVIDER_KEY: &'static str =
80        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<Ctx>>::PROVIDER_KEY;
81    const DISPLAY_NAME: &'static str =
82        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<Ctx>>::DISPLAY_NAME;
83    const DEFAULT_MODEL: &'static str =
84        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<Ctx>>::DEFAULT_MODEL;
85    const API_BASE_URL: &'static str =
86        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<Ctx>>::API_BASE_URL;
87    const BASE_URL_ENV_VAR: Option<&'static str> =
88        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<Ctx>>::BASE_URL_ENV_VAR;
89}
90
91/// Ergonomic blanket consumer over the provider build component.
92pub trait CanBuildProvider {
93    /// Construct a boxed [`LLMProvider`] from the given factory configuration.
94    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider>;
95}
96
97impl<Ctx> CanBuildProvider for Ctx
98where
99    Ctx: HasComponent<ProviderBuildComponent>,
100    ComponentProvider<Ctx, ProviderBuildComponent>: ProviderBuildProvider<Ctx>,
101{
102    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
103        <ComponentProvider<Ctx, ProviderBuildComponent> as ProviderBuildProvider<Ctx>>::build_provider(config)
104    }
105}
106
107trait StandardProviderConstructor: LLMProvider + Send + Sync + 'static {
108    fn from_standard_config(
109        api_key: Option<String>,
110        model: Option<String>,
111        base_url: Option<String>,
112        prompt_cache: Option<PromptCachingConfig>,
113        timeouts: Option<TimeoutsConfig>,
114        anthropic: Option<AnthropicConfig>,
115        model_behavior: Option<ModelConfig>,
116    ) -> Self;
117}
118
119/// Blanket build implementation for providers that follow the standard
120/// `from_config` constructor pattern.
121pub struct StandardProviderBuild<P>(PhantomData<P>);
122
123impl<Ctx, P> ProviderBuildProvider<Ctx> for StandardProviderBuild<P>
124where
125    P: StandardProviderConstructor,
126{
127    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
128        let FactoryProviderConfig {
129            api_key,
130            openai_chatgpt_auth: _,
131            base_url,
132            model,
133            prompt_cache,
134            timeouts,
135            openai: _,
136            anthropic,
137            model_behavior,
138            ..
139        } = config;
140
141        Box::new(P::from_standard_config(api_key, model, base_url, prompt_cache, timeouts, anthropic, model_behavior))
142    }
143}
144
145/// Build implementation for the Anthropic provider with custom config handling.
146pub struct AnthropicProviderBuild;
147
148impl ProviderBuildProvider<AnthropicProviderConfig> for AnthropicProviderBuild {
149    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
150        let FactoryProviderConfig {
151            api_key,
152            openai_chatgpt_auth: _,
153            base_url,
154            model,
155            prompt_cache,
156            timeouts,
157            openai: _,
158            anthropic,
159            model_behavior,
160            ..
161        } = config;
162
163        Box::new(AnthropicProvider::from_config(
164            api_key,
165            model,
166            base_url,
167            prompt_cache,
168            timeouts,
169            anthropic,
170            model_behavior,
171        ))
172    }
173}
174
175/// Build implementation for the OpenAI provider with provider-specific config handling.
176pub struct OpenAIProviderBuild;
177
178impl ProviderBuildProvider<OpenAIProviderConfig> for OpenAIProviderBuild {
179    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
180        let FactoryProviderConfig {
181            api_key,
182            openai_chatgpt_auth,
183            base_url,
184            model,
185            prompt_cache,
186            timeouts,
187            openai,
188            anthropic,
189            model_behavior,
190            ..
191        } = config;
192
193        Box::new(vtcode_llm::providers::OpenAIProvider::from_config(
194            api_key,
195            openai_chatgpt_auth,
196            model,
197            base_url,
198            prompt_cache,
199            timeouts,
200            anthropic,
201            openai,
202            model_behavior,
203        ))
204    }
205}
206
207/// Build implementation for the OpenRouter provider with attribution config handling.
208pub struct OpenRouterProviderBuild;
209
210impl ProviderBuildProvider<OpenRouterProviderConfig> for OpenRouterProviderBuild {
211    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
212        let FactoryProviderConfig {
213            api_key,
214            openai_chatgpt_auth: _,
215            base_url,
216            model,
217            prompt_cache,
218            timeouts,
219            openai: _,
220            anthropic,
221            openrouter,
222            model_behavior,
223            ..
224        } = config;
225
226        Box::new(OpenRouterProvider::from_config_with_attribution(
227            api_key,
228            model,
229            base_url,
230            prompt_cache,
231            timeouts,
232            anthropic,
233            model_behavior,
234            openrouter,
235        ))
236    }
237}
238
239/// Build implementation for the GitHub Copilot provider.
240pub struct CopilotProviderBuild;
241
242impl ProviderBuildProvider<CopilotProviderConfig> for CopilotProviderBuild {
243    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
244        let FactoryProviderConfig { model, copilot_auth, workspace_root, .. } = config;
245
246        Box::new(CopilotProvider::from_config(model, copilot_auth, workspace_root))
247    }
248}
249
250macro_rules! impl_standard_provider_constructor {
251    ($($provider:ty),+ $(,)?) => {
252        $(
253            impl StandardProviderConstructor for $provider {
254                fn from_standard_config(
255                    api_key: Option<String>,
256                    model: Option<String>,
257                    base_url: Option<String>,
258                    prompt_cache: Option<PromptCachingConfig>,
259                    timeouts: Option<TimeoutsConfig>,
260                    anthropic: Option<AnthropicConfig>,
261                    model_behavior: Option<ModelConfig>,
262                ) -> Self {
263                    <$provider>::from_config(
264                        api_key,
265                        model,
266                        base_url,
267                        prompt_cache,
268                        timeouts,
269                        anthropic,
270                        model_behavior,
271                    )
272                }
273            }
274        )+
275    };
276}
277
278impl_standard_provider_constructor!(
279    GeminiProvider,
280    HuggingFaceProvider,
281    MiMoProvider,
282    MinimaxProvider,
283    DeepSeekProvider,
284    MetaProvider,
285    MistralProvider,
286    OpenResponsesProvider,
287    MoonshotProvider,
288    OllamaProvider,
289    LlamaCppProvider,
290    LmStudioProvider,
291    ZAIProvider,
292    OpenCodeZenProvider,
293    OpenCodeGoProvider,
294    QwenProvider,
295    StepFunProvider,
296    EvolinkProvider,
297    PoolsideProvider,
298    XAIProvider,
299    NvidiaProvider,
300    MergeGatewayProvider,
301    VercelProvider,
302);
303
304crate::delegate_components!(GeminiProviderConfig {
305    ProviderMetadataComponent => GeminiProviderConfig,
306    ProviderBuildComponent => StandardProviderBuild<GeminiProvider>,
307});
308crate::delegate_components!(AnthropicProviderConfig {
309    ProviderMetadataComponent => AnthropicProviderConfig,
310    ProviderBuildComponent => AnthropicProviderBuild,
311});
312crate::delegate_components!(CopilotProviderConfig {
313    ProviderMetadataComponent => CopilotProviderConfig,
314    ProviderBuildComponent => CopilotProviderBuild,
315});
316crate::delegate_components!(OpenAIProviderConfig {
317    ProviderMetadataComponent => OpenAIProviderConfig,
318    ProviderBuildComponent => OpenAIProviderBuild,
319});
320crate::delegate_components!(HuggingFaceProviderConfig {
321    ProviderMetadataComponent => HuggingFaceProviderConfig,
322    ProviderBuildComponent => StandardProviderBuild<HuggingFaceProvider>,
323});
324crate::delegate_components!(DeepSeekProviderConfig {
325    ProviderMetadataComponent => DeepSeekProviderConfig,
326    ProviderBuildComponent => StandardProviderBuild<DeepSeekProvider>,
327});
328crate::delegate_components!(MetaProviderConfig {
329    ProviderMetadataComponent => MetaProviderConfig,
330    ProviderBuildComponent => StandardProviderBuild<MetaProvider>,
331});
332crate::delegate_components!(MiMoProviderConfig {
333    ProviderMetadataComponent => MiMoProviderConfig,
334    ProviderBuildComponent => StandardProviderBuild<MiMoProvider>,
335});
336crate::delegate_components!(MinimaxProviderConfig {
337    ProviderMetadataComponent => MinimaxProviderConfig,
338    ProviderBuildComponent => StandardProviderBuild<MinimaxProvider>,
339});
340crate::delegate_components!(OpenRouterProviderConfig {
341    ProviderMetadataComponent => OpenRouterProviderConfig,
342    ProviderBuildComponent => OpenRouterProviderBuild,
343});
344crate::delegate_components!(OpenResponsesProviderConfig {
345    ProviderMetadataComponent => OpenResponsesProviderConfig,
346    ProviderBuildComponent => StandardProviderBuild<OpenResponsesProvider>,
347});
348crate::delegate_components!(MoonshotProviderConfig {
349    ProviderMetadataComponent => MoonshotProviderConfig,
350    ProviderBuildComponent => StandardProviderBuild<MoonshotProvider>,
351});
352crate::delegate_components!(OllamaProviderConfig {
353    ProviderMetadataComponent => OllamaProviderConfig,
354    ProviderBuildComponent => StandardProviderBuild<OllamaProvider>,
355});
356crate::delegate_components!(OllamaCloudProviderConfig {
357    ProviderMetadataComponent => OllamaCloudProviderConfig,
358    ProviderBuildComponent => StandardProviderBuild<OllamaProvider>,
359});
360crate::delegate_components!(LmStudioProviderConfig {
361    ProviderMetadataComponent => LmStudioProviderConfig,
362    ProviderBuildComponent => StandardProviderBuild<LmStudioProvider>,
363});
364crate::delegate_components!(LlamaCppProviderConfig {
365    ProviderMetadataComponent => LlamaCppProviderConfig,
366    ProviderBuildComponent => StandardProviderBuild<LlamaCppProvider>,
367});
368crate::delegate_components!(ZAIProviderConfig {
369    ProviderMetadataComponent => ZAIProviderConfig,
370    ProviderBuildComponent => StandardProviderBuild<ZAIProvider>,
371});
372crate::delegate_components!(MistralProviderConfig {
373    ProviderMetadataComponent => MistralProviderConfig,
374    ProviderBuildComponent => StandardProviderBuild<MistralProvider>,
375});
376crate::delegate_components!(OpenCodeZenProviderConfig {
377    ProviderMetadataComponent => OpenCodeZenProviderConfig,
378    ProviderBuildComponent => StandardProviderBuild<OpenCodeZenProvider>,
379});
380crate::delegate_components!(OpenCodeGoProviderConfig {
381    ProviderMetadataComponent => OpenCodeGoProviderConfig,
382    ProviderBuildComponent => StandardProviderBuild<OpenCodeGoProvider>,
383});
384crate::delegate_components!(QwenProviderConfig {
385    ProviderMetadataComponent => QwenProviderConfig,
386    ProviderBuildComponent => StandardProviderBuild<QwenProvider>,
387});
388crate::delegate_components!(StepFunProviderConfig {
389    ProviderMetadataComponent => StepFunProviderConfig,
390    ProviderBuildComponent => StandardProviderBuild<StepFunProvider>,
391});
392crate::delegate_components!(EvolinkProviderConfig {
393    ProviderMetadataComponent => EvolinkProviderConfig,
394    ProviderBuildComponent => StandardProviderBuild<EvolinkProvider>,
395});
396crate::delegate_components!(PoolsideProviderConfig {
397    ProviderMetadataComponent => PoolsideProviderConfig,
398    ProviderBuildComponent => StandardProviderBuild<PoolsideProvider>,
399});
400crate::delegate_components!(XAIProviderConfig {
401    ProviderMetadataComponent => XAIProviderConfig,
402    ProviderBuildComponent => StandardProviderBuild<XAIProvider>,
403});
404crate::delegate_components!(NvidiaProviderConfig {
405    ProviderMetadataComponent => NvidiaProviderConfig,
406    ProviderBuildComponent => StandardProviderBuild<NvidiaProvider>,
407});
408crate::delegate_components!(MergeGatewayProviderConfig {
409    ProviderMetadataComponent => MergeGatewayProviderConfig,
410    ProviderBuildComponent => StandardProviderBuild<MergeGatewayProvider>,
411});
412crate::delegate_components!(VercelProviderConfig {
413    ProviderMetadataComponent => VercelProviderConfig,
414    ProviderBuildComponent => StandardProviderBuild<VercelProvider>,
415});
416
417/// Register all built-in provider contexts into the runtime factory.
418pub fn register_builtin_cgp_providers(factory: &mut LLMFactory) {
419    factory.register_cgp_provider::<GeminiProviderConfig>();
420    factory.register_cgp_provider::<OpenAIProviderConfig>();
421    factory.register_cgp_provider::<HuggingFaceProviderConfig>();
422    factory.register_cgp_provider::<AnthropicProviderConfig>();
423    factory.register_cgp_provider::<CopilotProviderConfig>();
424    factory.register_cgp_provider::<MinimaxProviderConfig>();
425    factory.register_cgp_provider::<MiMoProviderConfig>();
426    factory.register_cgp_provider::<DeepSeekProviderConfig>();
427    factory.register_cgp_provider::<MetaProviderConfig>();
428    factory.register_cgp_provider::<OpenRouterProviderConfig>();
429    factory.register_cgp_provider::<OpenResponsesProviderConfig>();
430    factory.register_cgp_provider::<MoonshotProviderConfig>();
431    factory.register_cgp_provider::<OllamaProviderConfig>();
432    factory.register_cgp_provider::<OllamaCloudProviderConfig>();
433    factory.register_cgp_provider::<LmStudioProviderConfig>();
434    factory.register_cgp_provider::<LlamaCppProviderConfig>();
435    factory.register_cgp_provider::<ZAIProviderConfig>();
436    factory.register_cgp_provider::<MistralProviderConfig>();
437    factory.register_cgp_provider::<OpenCodeZenProviderConfig>();
438    factory.register_cgp_provider::<OpenCodeGoProviderConfig>();
439    factory.register_cgp_provider::<QwenProviderConfig>();
440    factory.register_cgp_provider::<StepFunProviderConfig>();
441    factory.register_cgp_provider::<EvolinkProviderConfig>();
442    factory.register_cgp_provider::<PoolsideProviderConfig>();
443    factory.register_cgp_provider::<XAIProviderConfig>();
444    factory.register_cgp_provider::<NvidiaProviderConfig>();
445    factory.register_cgp_provider::<MergeGatewayProviderConfig>();
446    factory.register_cgp_provider::<VercelProviderConfig>();
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use vtcode_config::core::{AnthropicConfig, OpenAIConfig};
453
454    #[test]
455    fn provider_context_metadata_is_available_through_consumer_traits() {
456        assert_eq!(<GeminiProviderConfig as CanDescribeProvider>::PROVIDER_KEY, "gemini");
457        assert_eq!(<OpenAIProviderConfig as CanDescribeProvider>::DISPLAY_NAME, "OpenAI");
458        assert_eq!(
459            <AnthropicProviderConfig as CanDescribeProvider>::BASE_URL_ENV_VAR,
460            Some(vtcode_config::constants::env_vars::ANTHROPIC_BASE_URL)
461        );
462        assert_eq!(<MergeGatewayProviderConfig as CanDescribeProvider>::PROVIDER_KEY, "merge-gateway");
463        assert_eq!(
464            <MergeGatewayProviderConfig as CanDescribeProvider>::API_BASE_URL,
465            vtcode_config::constants::urls::MERGE_GATEWAY_NATIVE_API_BASE
466        );
467    }
468
469    #[test]
470    fn standard_build_consumer_builds_provider() {
471        let provider = <GeminiProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
472            api_key: Some("test-key".to_string()),
473            openai_chatgpt_auth: None,
474            copilot_auth: None,
475            base_url: None,
476            model: Some(vtcode_config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string()),
477            prompt_cache: None,
478            timeouts: None,
479            openai: None,
480            anthropic: None,
481            openrouter: None,
482            model_behavior: None,
483            workspace_root: None,
484        });
485
486        assert_eq!(provider.name(), "gemini");
487    }
488
489    #[test]
490    fn openai_build_consumer_accepts_provider_specific_config() {
491        let provider = <OpenAIProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
492            api_key: Some("test-key".to_string()),
493            openai_chatgpt_auth: None,
494            copilot_auth: None,
495            base_url: None,
496            model: Some(vtcode_config::constants::models::openai::DEFAULT_MODEL.to_string()),
497            prompt_cache: None,
498            timeouts: None,
499            openai: Some(OpenAIConfig { websocket_mode: true, ..OpenAIConfig::default() }),
500            anthropic: Some(AnthropicConfig::default()),
501            openrouter: None,
502            model_behavior: None,
503            workspace_root: None,
504        });
505
506        assert_eq!(provider.name(), "openai");
507    }
508
509    #[test]
510    fn anthropic_build_consumer_accepts_provider_specific_config() {
511        let provider = <AnthropicProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
512            api_key: Some("test-key".to_string()),
513            openai_chatgpt_auth: None,
514            copilot_auth: None,
515            base_url: None,
516            model: Some(vtcode_config::constants::models::anthropic::DEFAULT_MODEL.to_string()),
517            prompt_cache: None,
518            timeouts: None,
519            openai: None,
520            anthropic: Some(AnthropicConfig {
521                count_tokens_enabled: true,
522                ..AnthropicConfig::default()
523            }),
524            openrouter: None,
525            model_behavior: None,
526            workspace_root: None,
527        });
528
529        assert_eq!(provider.name(), "anthropic");
530    }
531
532    #[test]
533    fn builtin_registration_helper_registers_all_contexts() {
534        let mut factory = LLMFactory::new();
535        register_builtin_cgp_providers(&mut factory);
536
537        let mut providers = factory.list_providers();
538        providers.sort();
539
540        assert_eq!(
541            providers,
542            vec![
543                "anthropic",
544                "copilot",
545                "deepseek",
546                "evolink",
547                "gemini",
548                "huggingface",
549                "llamacpp",
550                "lmstudio",
551                "merge-gateway",
552                "meta",
553                "mimo",
554                "minimax",
555                "mistral",
556                "moonshot",
557                "nvidia",
558                "ollama",
559                "ollama-cloud",
560                "openai",
561                "opencode-go",
562                "opencode-zen",
563                "openresponses",
564                "openrouter",
565                "poolside",
566                "qwen",
567                "stepfun",
568                "vercel",
569                "xai",
570                "zai",
571            ]
572        );
573    }
574}