1use 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
32pub enum ProviderMetadataComponent {}
34
35pub enum ProviderBuildComponent {}
37
38pub 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
47pub trait ProviderBuildProvider<Ctx>: Send + Sync {
49 fn build_provider(config: FactoryProviderConfig) -> Box<dyn LLMProvider>;
50}
51
52pub 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
88pub 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
367pub 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}