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 crate::components::{ComponentProvider, HasComponent};
12use crate::config::TimeoutsConfig;
13use crate::config::core::{AnthropicConfig, ModelConfig, PromptCachingConfig};
14use crate::llm::factory::{LLMFactory, ProviderConfig as FactoryProviderConfig};
15use crate::llm::provider::LLMProvider;
16use crate::llm::provider_config::{
17    AnthropicProviderConfig, CopilotProviderConfig, DeepSeekProviderConfig, EvolinkProviderConfig,
18    GeminiProviderConfig, HuggingFaceProviderConfig, LlamaCppProviderConfig,
19    LmStudioProviderConfig, MiMoProviderConfig, MinimaxProviderConfig, MistralProviderConfig,
20    MoonshotProviderConfig, OllamaProviderConfig, OpenAIProviderConfig, OpenCodeGoProviderConfig,
21    OpenCodeZenProviderConfig, OpenResponsesProviderConfig, OpenRouterProviderConfig,
22    PoolsideProviderConfig, QwenProviderConfig, StepFunProviderConfig, ZAIProviderConfig,
23};
24use crate::llm::providers::{
25    AnthropicProvider, CopilotProvider, DeepSeekProvider, EvolinkProvider, GeminiProvider,
26    HuggingFaceProvider, LlamaCppProvider, LmStudioProvider, MiMoProvider, MinimaxProvider,
27    MistralProvider, MoonshotProvider, OllamaProvider, OpenAIProvider, OpenCodeGoProvider,
28    OpenCodeZenProvider, OpenResponsesProvider, OpenRouterProvider, PoolsideProvider, QwenProvider,
29    StepFunProvider, ZAIProvider,
30};
31
32/// Marker component for static provider metadata.
33pub enum ProviderMetadataComponent {}
34
35/// Marker component for provider construction.
36pub enum ProviderBuildComponent {}
37
38/// Provider trait for provider metadata.
39pub trait ProviderMetadataProvider<Ctx> {
40    const PROVIDER_KEY: &'static str;
41    const DISPLAY_NAME: &'static str;
42    const DEFAULT_MODEL: &'static str;
43    const API_BASE_URL: &'static str;
44    const BASE_URL_ENV_VAR: Option<&'static str>;
45}
46
47/// Provider trait for constructing boxed providers from factory config.
48pub trait ProviderBuildProvider<Ctx>: Send + Sync {
49    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider>;
50}
51
52/// Ergonomic blanket consumer over the metadata component.
53pub trait CanDescribeProvider {
54    const PROVIDER_KEY: &'static str;
55    const DISPLAY_NAME: &'static str;
56    const DEFAULT_MODEL: &'static str;
57    const API_BASE_URL: &'static str;
58    const BASE_URL_ENV_VAR: Option<&'static str>;
59}
60
61impl<Ctx> CanDescribeProvider for Ctx
62where
63    Ctx: HasComponent<ProviderMetadataComponent>,
64    ComponentProvider<Ctx, ProviderMetadataComponent>: ProviderMetadataProvider<Ctx>,
65{
66    const PROVIDER_KEY: &'static str =
67        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<
68            Ctx,
69        >>::PROVIDER_KEY;
70    const DISPLAY_NAME: &'static str =
71        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<
72            Ctx,
73        >>::DISPLAY_NAME;
74    const DEFAULT_MODEL: &'static str =
75        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<
76            Ctx,
77        >>::DEFAULT_MODEL;
78    const API_BASE_URL: &'static str =
79        <ComponentProvider<Ctx, ProviderMetadataComponent> as ProviderMetadataProvider<
80            Ctx,
81        >>::API_BASE_URL;
82    const BASE_URL_ENV_VAR: Option<&'static str> = <ComponentProvider<
83        Ctx,
84        ProviderMetadataComponent,
85    > as ProviderMetadataProvider<Ctx>>::BASE_URL_ENV_VAR;
86}
87
88/// Ergonomic blanket consumer over the provider build component.
89pub trait CanBuildProvider {
90    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider>;
91}
92
93impl<Ctx> CanBuildProvider for Ctx
94where
95    Ctx: HasComponent<ProviderBuildComponent>,
96    ComponentProvider<Ctx, ProviderBuildComponent>: ProviderBuildProvider<Ctx>,
97{
98    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
99        <ComponentProvider<Ctx, ProviderBuildComponent> as ProviderBuildProvider<Ctx>>::build_provider(
100            config,
101        )
102    }
103}
104
105trait StandardProviderConstructor: LLMProvider + Send + Sync + 'static {
106    fn from_standard_config(
107        api_key: Option<String>,
108        model: Option<String>,
109        base_url: Option<String>,
110        prompt_cache: Option<PromptCachingConfig>,
111        timeouts: Option<TimeoutsConfig>,
112        anthropic: Option<AnthropicConfig>,
113        model_behavior: Option<ModelConfig>,
114    ) -> Self;
115}
116
117pub struct StandardProviderBuild<P>(PhantomData<P>);
118
119impl<Ctx, P> ProviderBuildProvider<Ctx> for StandardProviderBuild<P>
120where
121    P: StandardProviderConstructor,
122{
123    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
124        let FactoryProviderConfig {
125            api_key,
126            openai_chatgpt_auth: _,
127            base_url,
128            model,
129            prompt_cache,
130            timeouts,
131            openai: _,
132            anthropic,
133            model_behavior,
134            ..
135        } = config;
136
137        Box::new(P::from_standard_config(
138            api_key,
139            model,
140            base_url,
141            prompt_cache,
142            timeouts,
143            anthropic,
144            model_behavior,
145        ))
146    }
147}
148
149pub struct AnthropicProviderBuild;
150
151impl ProviderBuildProvider<AnthropicProviderConfig> for AnthropicProviderBuild {
152    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
153        let FactoryProviderConfig {
154            api_key,
155            openai_chatgpt_auth: _,
156            base_url,
157            model,
158            prompt_cache,
159            timeouts,
160            openai: _,
161            anthropic,
162            model_behavior,
163            ..
164        } = config;
165
166        Box::new(AnthropicProvider::from_config(
167            api_key,
168            model,
169            base_url,
170            prompt_cache,
171            timeouts,
172            anthropic,
173            model_behavior,
174        ))
175    }
176}
177
178pub struct OpenAIProviderBuild;
179
180impl ProviderBuildProvider<OpenAIProviderConfig> for OpenAIProviderBuild {
181    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
182        let FactoryProviderConfig {
183            api_key,
184            openai_chatgpt_auth,
185            base_url,
186            model,
187            prompt_cache,
188            timeouts,
189            openai,
190            anthropic,
191            model_behavior,
192            ..
193        } = config;
194
195        Box::new(OpenAIProvider::from_config(
196            api_key,
197            openai_chatgpt_auth,
198            model,
199            base_url,
200            prompt_cache,
201            timeouts,
202            anthropic,
203            openai,
204            model_behavior,
205        ))
206    }
207}
208
209pub struct CopilotProviderBuild;
210
211impl ProviderBuildProvider<CopilotProviderConfig> for CopilotProviderBuild {
212    fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider> {
213        let FactoryProviderConfig {
214            model,
215            copilot_auth,
216            workspace_root,
217            ..
218        } = config;
219
220        Box::new(CopilotProvider::from_config(
221            model,
222            copilot_auth,
223            workspace_root,
224        ))
225    }
226}
227
228macro_rules! impl_standard_provider_constructor {
229    ($($provider:ty),+ $(,)?) => {
230        $(
231            impl StandardProviderConstructor for $provider {
232                fn from_standard_config(
233                    api_key: Option<String>,
234                    model: Option<String>,
235                    base_url: Option<String>,
236                    prompt_cache: Option<PromptCachingConfig>,
237                    timeouts: Option<TimeoutsConfig>,
238                    anthropic: Option<AnthropicConfig>,
239                    model_behavior: Option<ModelConfig>,
240                ) -> Self {
241                    <$provider>::from_config(
242                        api_key,
243                        model,
244                        base_url,
245                        prompt_cache,
246                        timeouts,
247                        anthropic,
248                        model_behavior,
249                    )
250                }
251            }
252        )+
253    };
254}
255
256impl_standard_provider_constructor!(
257    GeminiProvider,
258    HuggingFaceProvider,
259    MiMoProvider,
260    MinimaxProvider,
261    DeepSeekProvider,
262    MistralProvider,
263    OpenRouterProvider,
264    OpenResponsesProvider,
265    MoonshotProvider,
266    OllamaProvider,
267    LlamaCppProvider,
268    LmStudioProvider,
269    ZAIProvider,
270    OpenCodeZenProvider,
271    OpenCodeGoProvider,
272    QwenProvider,
273    StepFunProvider,
274    EvolinkProvider,
275    PoolsideProvider,
276);
277
278crate::delegate_components!(GeminiProviderConfig {
279    ProviderMetadataComponent => GeminiProviderConfig,
280    ProviderBuildComponent => StandardProviderBuild<GeminiProvider>,
281});
282crate::delegate_components!(AnthropicProviderConfig {
283    ProviderMetadataComponent => AnthropicProviderConfig,
284    ProviderBuildComponent => AnthropicProviderBuild,
285});
286crate::delegate_components!(CopilotProviderConfig {
287    ProviderMetadataComponent => CopilotProviderConfig,
288    ProviderBuildComponent => CopilotProviderBuild,
289});
290crate::delegate_components!(OpenAIProviderConfig {
291    ProviderMetadataComponent => OpenAIProviderConfig,
292    ProviderBuildComponent => OpenAIProviderBuild,
293});
294crate::delegate_components!(HuggingFaceProviderConfig {
295    ProviderMetadataComponent => HuggingFaceProviderConfig,
296    ProviderBuildComponent => StandardProviderBuild<HuggingFaceProvider>,
297});
298crate::delegate_components!(DeepSeekProviderConfig {
299    ProviderMetadataComponent => DeepSeekProviderConfig,
300    ProviderBuildComponent => StandardProviderBuild<DeepSeekProvider>,
301});
302crate::delegate_components!(MiMoProviderConfig {
303    ProviderMetadataComponent => MiMoProviderConfig,
304    ProviderBuildComponent => StandardProviderBuild<MiMoProvider>,
305});
306crate::delegate_components!(MinimaxProviderConfig {
307    ProviderMetadataComponent => MinimaxProviderConfig,
308    ProviderBuildComponent => StandardProviderBuild<MinimaxProvider>,
309});
310crate::delegate_components!(OpenRouterProviderConfig {
311    ProviderMetadataComponent => OpenRouterProviderConfig,
312    ProviderBuildComponent => StandardProviderBuild<OpenRouterProvider>,
313});
314crate::delegate_components!(OpenResponsesProviderConfig {
315    ProviderMetadataComponent => OpenResponsesProviderConfig,
316    ProviderBuildComponent => StandardProviderBuild<OpenResponsesProvider>,
317});
318crate::delegate_components!(MoonshotProviderConfig {
319    ProviderMetadataComponent => MoonshotProviderConfig,
320    ProviderBuildComponent => StandardProviderBuild<MoonshotProvider>,
321});
322crate::delegate_components!(OllamaProviderConfig {
323    ProviderMetadataComponent => OllamaProviderConfig,
324    ProviderBuildComponent => StandardProviderBuild<OllamaProvider>,
325});
326crate::delegate_components!(LmStudioProviderConfig {
327    ProviderMetadataComponent => LmStudioProviderConfig,
328    ProviderBuildComponent => StandardProviderBuild<LmStudioProvider>,
329});
330crate::delegate_components!(LlamaCppProviderConfig {
331    ProviderMetadataComponent => LlamaCppProviderConfig,
332    ProviderBuildComponent => StandardProviderBuild<LlamaCppProvider>,
333});
334crate::delegate_components!(ZAIProviderConfig {
335    ProviderMetadataComponent => ZAIProviderConfig,
336    ProviderBuildComponent => StandardProviderBuild<ZAIProvider>,
337});
338crate::delegate_components!(MistralProviderConfig {
339    ProviderMetadataComponent => MistralProviderConfig,
340    ProviderBuildComponent => StandardProviderBuild<MistralProvider>,
341});
342crate::delegate_components!(OpenCodeZenProviderConfig {
343    ProviderMetadataComponent => OpenCodeZenProviderConfig,
344    ProviderBuildComponent => StandardProviderBuild<OpenCodeZenProvider>,
345});
346crate::delegate_components!(OpenCodeGoProviderConfig {
347    ProviderMetadataComponent => OpenCodeGoProviderConfig,
348    ProviderBuildComponent => StandardProviderBuild<OpenCodeGoProvider>,
349});
350crate::delegate_components!(QwenProviderConfig {
351    ProviderMetadataComponent => QwenProviderConfig,
352    ProviderBuildComponent => StandardProviderBuild<QwenProvider>,
353});
354crate::delegate_components!(StepFunProviderConfig {
355    ProviderMetadataComponent => StepFunProviderConfig,
356    ProviderBuildComponent => StandardProviderBuild<StepFunProvider>,
357});
358crate::delegate_components!(EvolinkProviderConfig {
359    ProviderMetadataComponent => EvolinkProviderConfig,
360    ProviderBuildComponent => StandardProviderBuild<EvolinkProvider>,
361});
362crate::delegate_components!(PoolsideProviderConfig {
363    ProviderMetadataComponent => PoolsideProviderConfig,
364    ProviderBuildComponent => StandardProviderBuild<PoolsideProvider>,
365});
366
367/// Register all built-in provider contexts into the runtime factory.
368pub fn register_builtin_cgp_providers(factory: &mut LLMFactory) {
369    factory.register_cgp_provider::<GeminiProviderConfig>();
370    factory.register_cgp_provider::<OpenAIProviderConfig>();
371    factory.register_cgp_provider::<HuggingFaceProviderConfig>();
372    factory.register_cgp_provider::<AnthropicProviderConfig>();
373    factory.register_cgp_provider::<CopilotProviderConfig>();
374    factory.register_cgp_provider::<MinimaxProviderConfig>();
375    factory.register_cgp_provider::<MiMoProviderConfig>();
376    factory.register_cgp_provider::<DeepSeekProviderConfig>();
377    factory.register_cgp_provider::<OpenRouterProviderConfig>();
378    factory.register_cgp_provider::<OpenResponsesProviderConfig>();
379    factory.register_cgp_provider::<MoonshotProviderConfig>();
380    factory.register_cgp_provider::<OllamaProviderConfig>();
381    factory.register_cgp_provider::<LmStudioProviderConfig>();
382    factory.register_cgp_provider::<LlamaCppProviderConfig>();
383    factory.register_cgp_provider::<ZAIProviderConfig>();
384    factory.register_cgp_provider::<MistralProviderConfig>();
385    factory.register_cgp_provider::<OpenCodeZenProviderConfig>();
386    factory.register_cgp_provider::<OpenCodeGoProviderConfig>();
387    factory.register_cgp_provider::<QwenProviderConfig>();
388    factory.register_cgp_provider::<StepFunProviderConfig>();
389    factory.register_cgp_provider::<EvolinkProviderConfig>();
390    factory.register_cgp_provider::<PoolsideProviderConfig>();
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate::config::core::{AnthropicConfig, OpenAIConfig};
397
398    #[test]
399    fn provider_context_metadata_is_available_through_consumer_traits() {
400        assert_eq!(
401            <GeminiProviderConfig as CanDescribeProvider>::PROVIDER_KEY,
402            "gemini"
403        );
404        assert_eq!(
405            <OpenAIProviderConfig as CanDescribeProvider>::DISPLAY_NAME,
406            "OpenAI"
407        );
408        assert_eq!(
409            <AnthropicProviderConfig as CanDescribeProvider>::BASE_URL_ENV_VAR,
410            Some(crate::config::constants::env_vars::ANTHROPIC_BASE_URL)
411        );
412    }
413
414    #[test]
415    fn standard_build_consumer_builds_provider() {
416        let provider =
417            <GeminiProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
418                api_key: Some("test-key".to_string()),
419                openai_chatgpt_auth: None,
420                copilot_auth: None,
421                base_url: None,
422                model: Some(
423                    crate::config::constants::models::google::GEMINI_3_FLASH_PREVIEW.to_string(),
424                ),
425                prompt_cache: None,
426                timeouts: None,
427                openai: None,
428                anthropic: None,
429                model_behavior: None,
430                workspace_root: None,
431            });
432
433        assert_eq!(provider.name(), "gemini");
434    }
435
436    #[test]
437    fn openai_build_consumer_accepts_provider_specific_config() {
438        let provider =
439            <OpenAIProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
440                api_key: Some("test-key".to_string()),
441                openai_chatgpt_auth: None,
442                copilot_auth: None,
443                base_url: None,
444                model: Some(crate::config::constants::models::openai::DEFAULT_MODEL.to_string()),
445                prompt_cache: None,
446                timeouts: None,
447                openai: Some(OpenAIConfig {
448                    websocket_mode: true,
449                    ..OpenAIConfig::default()
450                }),
451                anthropic: Some(AnthropicConfig::default()),
452                model_behavior: None,
453                workspace_root: None,
454            });
455
456        assert_eq!(provider.name(), "openai");
457    }
458
459    #[test]
460    fn anthropic_build_consumer_accepts_provider_specific_config() {
461        let provider =
462            <AnthropicProviderConfig as CanBuildProvider>::build_provider(FactoryProviderConfig {
463                api_key: Some("test-key".to_string()),
464                openai_chatgpt_auth: None,
465                copilot_auth: None,
466                base_url: None,
467                model: Some(crate::config::constants::models::anthropic::DEFAULT_MODEL.to_string()),
468                prompt_cache: None,
469                timeouts: None,
470                openai: None,
471                anthropic: Some(AnthropicConfig {
472                    count_tokens_enabled: true,
473                    ..AnthropicConfig::default()
474                }),
475                model_behavior: None,
476                workspace_root: None,
477            });
478
479        assert_eq!(provider.name(), "anthropic");
480    }
481
482    #[test]
483    fn builtin_registration_helper_registers_all_contexts() {
484        let mut factory = LLMFactory::new();
485        register_builtin_cgp_providers(&mut factory);
486
487        let mut providers = factory.list_providers();
488        providers.sort();
489
490        assert_eq!(
491            providers,
492            vec![
493                "anthropic",
494                "copilot",
495                "deepseek",
496                "evolink",
497                "gemini",
498                "huggingface",
499                "llamacpp",
500                "lmstudio",
501                "mimo",
502                "minimax",
503                "mistral",
504                "moonshot",
505                "ollama",
506                "openai",
507                "opencode-go",
508                "opencode-zen",
509                "openresponses",
510                "openrouter",
511                "poolside",
512                "qwen",
513                "stepfun",
514                "zai",
515            ]
516        );
517    }
518}