1use 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
34pub enum ProviderMetadataComponent {}
36
37pub enum ProviderBuildComponent {}
39
40pub trait ProviderMetadataProvider<Ctx> {
42 const PROVIDER_KEY: &'static str;
44 const DISPLAY_NAME: &'static str;
46 const DEFAULT_MODEL: &'static str;
48 const API_BASE_URL: &'static str;
50 const BASE_URL_ENV_VAR: Option<&'static str>;
52}
53
54pub trait ProviderBuildProvider<Ctx>: Send + Sync {
56 fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider>;
58}
59
60pub trait CanDescribeProvider {
62 const PROVIDER_KEY: &'static str;
64 const DISPLAY_NAME: &'static str;
66 const DEFAULT_MODEL: &'static str;
68 const API_BASE_URL: &'static str;
70 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
91pub trait CanBuildProvider {
93 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
119pub 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
145pub 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
175pub 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
207pub 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
239pub 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
417pub 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}