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 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
386pub 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}