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 GitHub Copilot provider.
208pub struct CopilotProviderBuild;
209
210impl ProviderBuildProvider<CopilotProviderConfig> for CopilotProviderBuild {
211    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
212        let FactoryProviderConfig { model, copilot_auth, workspace_root, .. } = config;
213
214        Box::new(CopilotProvider::from_config(model, copilot_auth, workspace_root))
215    }
216}
217
218macro_rules! impl_standard_provider_constructor {
219    ($($provider:ty),+ $(,)?) => {
220        $(
221            impl StandardProviderConstructor for $provider {
222                fn from_standard_config(
223                    api_key: Option<String>,
224                    model: Option<String>,
225                    base_url: Option<String>,
226                    prompt_cache: Option<PromptCachingConfig>,
227                    timeouts: Option<TimeoutsConfig>,
228                    anthropic: Option<AnthropicConfig>,
229                    model_behavior: Option<ModelConfig>,
230                ) -> Self {
231                    <$provider>::from_config(
232                        api_key,
233                        model,
234                        base_url,
235                        prompt_cache,
236                        timeouts,
237                        anthropic,
238                        model_behavior,
239                    )
240                }
241            }
242        )+
243    };
244}
245
246impl_standard_provider_constructor!(
247    GeminiProvider,
248    HuggingFaceProvider,
249    MiMoProvider,
250    MinimaxProvider,
251    DeepSeekProvider,
252    MetaProvider,
253    MistralProvider,
254    OpenRouterProvider,
255    OpenResponsesProvider,
256    MoonshotProvider,
257    OllamaProvider,
258    LlamaCppProvider,
259    LmStudioProvider,
260    ZAIProvider,
261    OpenCodeZenProvider,
262    OpenCodeGoProvider,
263    QwenProvider,
264    StepFunProvider,
265    EvolinkProvider,
266    PoolsideProvider,
267    XAIProvider,
268    NvidiaProvider,
269    MergeGatewayProvider,
270    VercelProvider,
271);
272
273crate::delegate_components!(GeminiProviderConfig {
274    ProviderMetadataComponent => GeminiProviderConfig,
275    ProviderBuildComponent => StandardProviderBuild<GeminiProvider>,
276});
277crate::delegate_components!(AnthropicProviderConfig {
278    ProviderMetadataComponent => AnthropicProviderConfig,
279    ProviderBuildComponent => AnthropicProviderBuild,
280});
281crate::delegate_components!(CopilotProviderConfig {
282    ProviderMetadataComponent => CopilotProviderConfig,
283    ProviderBuildComponent => CopilotProviderBuild,
284});
285crate::delegate_components!(OpenAIProviderConfig {
286    ProviderMetadataComponent => OpenAIProviderConfig,
287    ProviderBuildComponent => OpenAIProviderBuild,
288});
289crate::delegate_components!(HuggingFaceProviderConfig {
290    ProviderMetadataComponent => HuggingFaceProviderConfig,
291    ProviderBuildComponent => StandardProviderBuild<HuggingFaceProvider>,
292});
293crate::delegate_components!(DeepSeekProviderConfig {
294    ProviderMetadataComponent => DeepSeekProviderConfig,
295    ProviderBuildComponent => StandardProviderBuild<DeepSeekProvider>,
296});
297crate::delegate_components!(MetaProviderConfig {
298    ProviderMetadataComponent => MetaProviderConfig,
299    ProviderBuildComponent => StandardProviderBuild<MetaProvider>,
300});
301crate::delegate_components!(MiMoProviderConfig {
302    ProviderMetadataComponent => MiMoProviderConfig,
303    ProviderBuildComponent => StandardProviderBuild<MiMoProvider>,
304});
305crate::delegate_components!(MinimaxProviderConfig {
306    ProviderMetadataComponent => MinimaxProviderConfig,
307    ProviderBuildComponent => StandardProviderBuild<MinimaxProvider>,
308});
309crate::delegate_components!(OpenRouterProviderConfig {
310    ProviderMetadataComponent => OpenRouterProviderConfig,
311    ProviderBuildComponent => StandardProviderBuild<OpenRouterProvider>,
312});
313crate::delegate_components!(OpenResponsesProviderConfig {
314    ProviderMetadataComponent => OpenResponsesProviderConfig,
315    ProviderBuildComponent => StandardProviderBuild<OpenResponsesProvider>,
316});
317crate::delegate_components!(MoonshotProviderConfig {
318    ProviderMetadataComponent => MoonshotProviderConfig,
319    ProviderBuildComponent => StandardProviderBuild<MoonshotProvider>,
320});
321crate::delegate_components!(OllamaProviderConfig {
322    ProviderMetadataComponent => OllamaProviderConfig,
323    ProviderBuildComponent => StandardProviderBuild<OllamaProvider>,
324});
325crate::delegate_components!(OllamaCloudProviderConfig {
326    ProviderMetadataComponent => OllamaCloudProviderConfig,
327    ProviderBuildComponent => StandardProviderBuild<OllamaProvider>,
328});
329crate::delegate_components!(LmStudioProviderConfig {
330    ProviderMetadataComponent => LmStudioProviderConfig,
331    ProviderBuildComponent => StandardProviderBuild<LmStudioProvider>,
332});
333crate::delegate_components!(LlamaCppProviderConfig {
334    ProviderMetadataComponent => LlamaCppProviderConfig,
335    ProviderBuildComponent => StandardProviderBuild<LlamaCppProvider>,
336});
337crate::delegate_components!(ZAIProviderConfig {
338    ProviderMetadataComponent => ZAIProviderConfig,
339    ProviderBuildComponent => StandardProviderBuild<ZAIProvider>,
340});
341crate::delegate_components!(MistralProviderConfig {
342    ProviderMetadataComponent => MistralProviderConfig,
343    ProviderBuildComponent => StandardProviderBuild<MistralProvider>,
344});
345crate::delegate_components!(OpenCodeZenProviderConfig {
346    ProviderMetadataComponent => OpenCodeZenProviderConfig,
347    ProviderBuildComponent => StandardProviderBuild<OpenCodeZenProvider>,
348});
349crate::delegate_components!(OpenCodeGoProviderConfig {
350    ProviderMetadataComponent => OpenCodeGoProviderConfig,
351    ProviderBuildComponent => StandardProviderBuild<OpenCodeGoProvider>,
352});
353crate::delegate_components!(QwenProviderConfig {
354    ProviderMetadataComponent => QwenProviderConfig,
355    ProviderBuildComponent => StandardProviderBuild<QwenProvider>,
356});
357crate::delegate_components!(StepFunProviderConfig {
358    ProviderMetadataComponent => StepFunProviderConfig,
359    ProviderBuildComponent => StandardProviderBuild<StepFunProvider>,
360});
361crate::delegate_components!(EvolinkProviderConfig {
362    ProviderMetadataComponent => EvolinkProviderConfig,
363    ProviderBuildComponent => StandardProviderBuild<EvolinkProvider>,
364});
365crate::delegate_components!(PoolsideProviderConfig {
366    ProviderMetadataComponent => PoolsideProviderConfig,
367    ProviderBuildComponent => StandardProviderBuild<PoolsideProvider>,
368});
369crate::delegate_components!(XAIProviderConfig {
370    ProviderMetadataComponent => XAIProviderConfig,
371    ProviderBuildComponent => StandardProviderBuild<XAIProvider>,
372});
373crate::delegate_components!(NvidiaProviderConfig {
374    ProviderMetadataComponent => NvidiaProviderConfig,
375    ProviderBuildComponent => StandardProviderBuild<NvidiaProvider>,
376});
377crate::delegate_components!(MergeGatewayProviderConfig {
378    ProviderMetadataComponent => MergeGatewayProviderConfig,
379    ProviderBuildComponent => StandardProviderBuild<MergeGatewayProvider>,
380});
381crate::delegate_components!(VercelProviderConfig {
382    ProviderMetadataComponent => VercelProviderConfig,
383    ProviderBuildComponent => StandardProviderBuild<VercelProvider>,
384});
385
386/// Register all built-in provider contexts into the runtime factory.
387pub fn register_builtin_cgp_providers(factory: &mut LLMFactory) {
388    factory.register_cgp_provider::<GeminiProviderConfig>();
389    factory.register_cgp_provider::<OpenAIProviderConfig>();
390    factory.register_cgp_provider::<HuggingFaceProviderConfig>();
391    factory.register_cgp_provider::<AnthropicProviderConfig>();
392    factory.register_cgp_provider::<CopilotProviderConfig>();
393    factory.register_cgp_provider::<MinimaxProviderConfig>();
394    factory.register_cgp_provider::<MiMoProviderConfig>();
395    factory.register_cgp_provider::<DeepSeekProviderConfig>();
396    factory.register_cgp_provider::<MetaProviderConfig>();
397    factory.register_cgp_provider::<OpenRouterProviderConfig>();
398    factory.register_cgp_provider::<OpenResponsesProviderConfig>();
399    factory.register_cgp_provider::<MoonshotProviderConfig>();
400    factory.register_cgp_provider::<OllamaProviderConfig>();
401    factory.register_cgp_provider::<OllamaCloudProviderConfig>();
402    factory.register_cgp_provider::<LmStudioProviderConfig>();
403    factory.register_cgp_provider::<LlamaCppProviderConfig>();
404    factory.register_cgp_provider::<ZAIProviderConfig>();
405    factory.register_cgp_provider::<MistralProviderConfig>();
406    factory.register_cgp_provider::<OpenCodeZenProviderConfig>();
407    factory.register_cgp_provider::<OpenCodeGoProviderConfig>();
408    factory.register_cgp_provider::<QwenProviderConfig>();
409    factory.register_cgp_provider::<StepFunProviderConfig>();
410    factory.register_cgp_provider::<EvolinkProviderConfig>();
411    factory.register_cgp_provider::<PoolsideProviderConfig>();
412    factory.register_cgp_provider::<XAIProviderConfig>();
413    factory.register_cgp_provider::<NvidiaProviderConfig>();
414    factory.register_cgp_provider::<MergeGatewayProviderConfig>();
415    factory.register_cgp_provider::<VercelProviderConfig>();
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use vtcode_config::core::{AnthropicConfig, OpenAIConfig};
422
423    #[test]
424    fn provider_context_metadata_is_available_through_consumer_traits() {
425        assert_eq!(<GeminiProviderConfig as CanDescribeProvider>::PROVIDER_KEY, "gemini");
426        assert_eq!(<OpenAIProviderConfig as CanDescribeProvider>::DISPLAY_NAME, "OpenAI");
427        assert_eq!(
428            <AnthropicProviderConfig as CanDescribeProvider>::BASE_URL_ENV_VAR,
429            Some(vtcode_config::constants::env_vars::ANTHROPIC_BASE_URL)
430        );
431        assert_eq!(<MergeGatewayProviderConfig as CanDescribeProvider>::PROVIDER_KEY, "merge-gateway");
432        assert_eq!(
433            <MergeGatewayProviderConfig as CanDescribeProvider>::API_BASE_URL,
434            vtcode_config::constants::urls::MERGE_GATEWAY_NATIVE_API_BASE
435        );
436    }
437
438    #[test]
439    fn standard_build_consumer_builds_provider() {
440        let provider = <GeminiProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
441            api_key: Some("test-key".to_string()),
442            openai_chatgpt_auth: None,
443            copilot_auth: None,
444            base_url: None,
445            model: Some(vtcode_config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string()),
446            prompt_cache: None,
447            timeouts: None,
448            openai: None,
449            anthropic: None,
450            model_behavior: None,
451            workspace_root: None,
452        });
453
454        assert_eq!(provider.name(), "gemini");
455    }
456
457    #[test]
458    fn openai_build_consumer_accepts_provider_specific_config() {
459        let provider = <OpenAIProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
460            api_key: Some("test-key".to_string()),
461            openai_chatgpt_auth: None,
462            copilot_auth: None,
463            base_url: None,
464            model: Some(vtcode_config::constants::models::openai::DEFAULT_MODEL.to_string()),
465            prompt_cache: None,
466            timeouts: None,
467            openai: Some(OpenAIConfig { websocket_mode: true, ..OpenAIConfig::default() }),
468            anthropic: Some(AnthropicConfig::default()),
469            model_behavior: None,
470            workspace_root: None,
471        });
472
473        assert_eq!(provider.name(), "openai");
474    }
475
476    #[test]
477    fn anthropic_build_consumer_accepts_provider_specific_config() {
478        let provider = <AnthropicProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
479            api_key: Some("test-key".to_string()),
480            openai_chatgpt_auth: None,
481            copilot_auth: None,
482            base_url: None,
483            model: Some(vtcode_config::constants::models::anthropic::DEFAULT_MODEL.to_string()),
484            prompt_cache: None,
485            timeouts: None,
486            openai: None,
487            anthropic: Some(AnthropicConfig {
488                count_tokens_enabled: true,
489                ..AnthropicConfig::default()
490            }),
491            model_behavior: None,
492            workspace_root: None,
493        });
494
495        assert_eq!(provider.name(), "anthropic");
496    }
497
498    #[test]
499    fn builtin_registration_helper_registers_all_contexts() {
500        let mut factory = LLMFactory::new();
501        register_builtin_cgp_providers(&mut factory);
502
503        let mut providers = factory.list_providers();
504        providers.sort();
505
506        assert_eq!(
507            providers,
508            vec![
509                "anthropic",
510                "copilot",
511                "deepseek",
512                "evolink",
513                "gemini",
514                "huggingface",
515                "llamacpp",
516                "lmstudio",
517                "merge-gateway",
518                "meta",
519                "mimo",
520                "minimax",
521                "mistral",
522                "moonshot",
523                "nvidia",
524                "ollama",
525                "ollama-cloud",
526                "openai",
527                "opencode-go",
528                "opencode-zen",
529                "openresponses",
530                "openrouter",
531                "poolside",
532                "qwen",
533                "stepfun",
534                "vercel",
535                "xai",
536                "zai",
537            ]
538        );
539    }
540}