Skip to main content

aurum_core/provider_platform/
builtin.rs

1//! Built-in factories for local STT/TTS and OpenRouter STT (JOE-1933).
2
3use super::context::ProviderBuildContext;
4use super::descriptor::{
5    NetworkRequirement, ProviderDescriptor, ProviderOperations, ProviderStability,
6};
7use super::factory::TranscriptionProviderFactory;
8use super::id::ProviderId;
9use super::registry::{ProviderRegistry, ProviderRegistryBuilder};
10use crate::capabilities::{local_whisper_capabilities, ProviderCapabilities};
11use crate::error::{Result, UserError};
12use crate::providers::local::LocalWhisperProvider;
13use crate::providers::openrouter::OpenRouterProvider;
14use crate::providers::TranscriptionProvider;
15use crate::remote::RemotePolicy;
16use crate::runtime::ResourceGovernor;
17use std::sync::Arc;
18
19#[cfg(feature = "tts")]
20use super::factory::SynthesisProviderFactory;
21use crate::capabilities::openai_stt_capabilities;
22use crate::capabilities::xai_stt_capabilities;
23#[cfg(feature = "tts")]
24use crate::capabilities::{
25    elevenlabs_tts_capabilities, local_tts_capabilities, openai_tts_capabilities,
26    openrouter_tts_capabilities, xai_tts_capabilities,
27};
28#[cfg(feature = "tts")]
29use crate::providers::elevenlabs_tts::ElevenLabsTtsProvider;
30use crate::providers::openai_stt::OpenAiSttProvider;
31#[cfg(feature = "tts")]
32use crate::providers::openai_tts::OpenAiTtsProvider;
33#[cfg(feature = "tts")]
34use crate::providers::openrouter_tts::OpenRouterTtsProvider;
35use crate::providers::xai_stt::XaiSttProvider;
36#[cfg(feature = "tts")]
37use crate::providers::xai_tts::XaiTtsProvider;
38#[cfg(feature = "tts")]
39use crate::tts::local::LocalTtsProvider;
40#[cfg(feature = "tts")]
41use crate::tts::provider::SynthesisProvider;
42
43fn engine_or_global_governor(ctx: &ProviderBuildContext) -> Arc<ResourceGovernor> {
44    ctx.governor()
45        .cloned()
46        .unwrap_or_else(ResourceGovernor::process_global)
47}
48
49// ── Local STT ───────────────────────────────────────────────────────────────
50
51pub struct LocalSttFactory {
52    descriptor: ProviderDescriptor,
53}
54
55impl LocalSttFactory {
56    pub fn new() -> Self {
57        Self {
58            descriptor: ProviderDescriptor::new(
59                ProviderId::local(),
60                "Local Whisper",
61                ProviderOperations::STT_ONLY,
62                NetworkRequirement::LocalOnly,
63                ProviderStability::Stable,
64            ),
65        }
66    }
67}
68
69impl Default for LocalSttFactory {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl TranscriptionProviderFactory for LocalSttFactory {
76    fn descriptor(&self) -> &ProviderDescriptor {
77        &self.descriptor
78    }
79
80    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
81        Ok(local_whisper_capabilities(model))
82    }
83
84    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn TranscriptionProvider>> {
85        let provider = match (ctx.stt_pool(), ctx.governor()) {
86            (Some(pool), Some(gov)) => LocalWhisperProvider::with_runtime(
87                ctx.cache_dir().to_path_buf(),
88                Arc::clone(pool),
89                Arc::clone(gov),
90            ),
91            _ => LocalWhisperProvider::new(ctx.cache_dir().to_path_buf()),
92        }
93        .with_progress(ctx.show_progress())
94        .with_local_only(ctx.local_only());
95        Ok(Arc::new(provider))
96    }
97}
98
99// ── OpenRouter STT ──────────────────────────────────────────────────────────
100
101pub struct OpenRouterSttFactory {
102    descriptor: ProviderDescriptor,
103}
104
105impl OpenRouterSttFactory {
106    pub fn new() -> Self {
107        Self {
108            descriptor: ProviderDescriptor::new(
109                ProviderId::openrouter(),
110                "OpenRouter",
111                ProviderOperations::STT_ONLY,
112                NetworkRequirement::RequiresNetwork,
113                ProviderStability::Stable,
114            ),
115        }
116    }
117}
118
119impl Default for OpenRouterSttFactory {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125impl TranscriptionProviderFactory for OpenRouterSttFactory {
126    fn descriptor(&self) -> &ProviderDescriptor {
127        &self.descriptor
128    }
129
130    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
131        // Route-aware capability (JOE-1979): Auto resolves reviewed models to the
132        // same path as execution. Unknown models fail closed (no false ASR claim).
133        use crate::capabilities::{openrouter_stt_capabilities, resolve_openrouter_stt_path};
134        use crate::providers::OpenRouterSttMode;
135        let path = resolve_openrouter_stt_path(OpenRouterSttMode::Auto, model)?;
136        Ok(openrouter_stt_capabilities(model, path))
137    }
138
139    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn TranscriptionProvider>> {
140        if ctx.local_only() {
141            return Err(UserError::UnsupportedCapability {
142                provider: "openrouter".into(),
143                model: "*".into(),
144                reason: "remote STT is disabled under local_only".into(),
145                hint: "unset local_only or use provider=local".into(),
146            }
147            .into());
148        }
149        let key = ctx.api_key_cloned();
150        let policy = RemotePolicy {
151            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
152            use_system_proxy: ctx.use_system_proxy(),
153            allow_loopback_http: ctx
154                .base_url()
155                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
156            ..RemotePolicy::default()
157        };
158        let provider = OpenRouterProvider::with_policy(
159            key,
160            ctx.base_url().map(|s| s.to_string()),
161            policy,
162            ctx.stt_mode(),
163        )?
164        .with_governor(engine_or_global_governor(ctx));
165        Ok(Arc::new(provider))
166    }
167}
168
169// ── Local TTS ───────────────────────────────────────────────────────────────
170
171#[cfg(feature = "tts")]
172pub struct LocalTtsFactory {
173    descriptor: ProviderDescriptor,
174}
175
176#[cfg(feature = "tts")]
177impl LocalTtsFactory {
178    pub fn new() -> Self {
179        Self {
180            descriptor: ProviderDescriptor::new(
181                ProviderId::local(),
182                "Local TTS",
183                ProviderOperations::TTS_ONLY,
184                NetworkRequirement::LocalOnly,
185                ProviderStability::Stable,
186            ),
187        }
188    }
189}
190
191#[cfg(feature = "tts")]
192impl Default for LocalTtsFactory {
193    fn default() -> Self {
194        Self::new()
195    }
196}
197
198#[cfg(feature = "tts")]
199impl SynthesisProviderFactory for LocalTtsFactory {
200    fn descriptor(&self) -> &ProviderDescriptor {
201        &self.descriptor
202    }
203
204    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
205        Ok(local_tts_capabilities(model))
206    }
207
208    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn SynthesisProvider>> {
209        let mut provider = match (ctx.tts_pool(), ctx.governor()) {
210            (Some(pool), Some(gov)) => LocalTtsProvider::with_runtime(
211                ctx.cache_dir().to_path_buf(),
212                Arc::clone(pool),
213                Arc::clone(gov),
214            ),
215            _ => LocalTtsProvider::new(ctx.cache_dir().to_path_buf()),
216        }
217        .with_progress(ctx.show_progress())
218        .with_local_only(ctx.local_only());
219        if let Some(n) = ctx.tts_max_chars() {
220            provider = provider.with_max_chars(n);
221        }
222        Ok(Arc::new(provider))
223    }
224}
225
226// ── OpenRouter TTS ──────────────────────────────────────────────────────────
227
228#[cfg(feature = "tts")]
229pub struct OpenRouterTtsFactory {
230    descriptor: ProviderDescriptor,
231}
232
233#[cfg(feature = "tts")]
234impl OpenRouterTtsFactory {
235    pub fn new() -> Self {
236        Self {
237            descriptor: ProviderDescriptor::new(
238                ProviderId::openrouter(),
239                "OpenRouter TTS",
240                ProviderOperations::TTS_ONLY,
241                NetworkRequirement::RequiresNetwork,
242                ProviderStability::Stable,
243            ),
244        }
245    }
246}
247
248#[cfg(feature = "tts")]
249impl Default for OpenRouterTtsFactory {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255#[cfg(feature = "tts")]
256impl SynthesisProviderFactory for OpenRouterTtsFactory {
257    fn descriptor(&self) -> &ProviderDescriptor {
258        &self.descriptor
259    }
260
261    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
262        openrouter_tts_capabilities(model)
263    }
264
265    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn SynthesisProvider>> {
266        if ctx.local_only() {
267            return Err(UserError::UnsupportedCapability {
268                provider: "openrouter".into(),
269                model: "*".into(),
270                reason: "remote TTS is disabled under local_only".into(),
271                hint: "unset local_only or use provider=local".into(),
272            }
273            .into());
274        }
275        let key = ctx.api_key_cloned();
276        let policy = RemotePolicy {
277            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
278            use_system_proxy: ctx.use_system_proxy(),
279            allow_loopback_http: ctx
280                .base_url()
281                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
282            ..RemotePolicy::default()
283        };
284        let provider =
285            OpenRouterTtsProvider::with_policy(key, ctx.base_url().map(|s| s.to_string()), policy)?
286                .with_governor(engine_or_global_governor(ctx));
287        Ok(Arc::new(provider))
288    }
289}
290
291// ── OpenAI STT ──────────────────────────────────────────────────────────────
292
293pub struct OpenAiSttFactory {
294    descriptor: ProviderDescriptor,
295}
296
297impl OpenAiSttFactory {
298    pub fn new() -> Self {
299        Self {
300            descriptor: ProviderDescriptor::new(
301                ProviderId::must("openai"),
302                "OpenAI STT",
303                ProviderOperations::STT_ONLY,
304                NetworkRequirement::RequiresNetwork,
305                ProviderStability::Stable,
306            ),
307        }
308    }
309}
310
311impl Default for OpenAiSttFactory {
312    fn default() -> Self {
313        Self::new()
314    }
315}
316
317impl TranscriptionProviderFactory for OpenAiSttFactory {
318    fn descriptor(&self) -> &ProviderDescriptor {
319        &self.descriptor
320    }
321
322    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
323        openai_stt_capabilities(model)
324    }
325
326    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn TranscriptionProvider>> {
327        if ctx.local_only() {
328            return Err(UserError::UnsupportedCapability {
329                provider: "openai".into(),
330                model: "*".into(),
331                reason: "remote STT is disabled under local_only".into(),
332                hint: "unset local_only or use provider=local".into(),
333            }
334            .into());
335        }
336        let key = ctx.api_key_cloned();
337        let policy = RemotePolicy {
338            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
339            use_system_proxy: ctx.use_system_proxy(),
340            allow_loopback_http: ctx
341                .base_url()
342                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
343            ..RemotePolicy::default()
344        };
345        let provider =
346            OpenAiSttProvider::with_policy(key, ctx.base_url().map(|s| s.to_string()), policy)?
347                .with_governor(engine_or_global_governor(ctx));
348        Ok(Arc::new(provider))
349    }
350}
351
352// ── OpenAI TTS ──────────────────────────────────────────────────────────────
353
354#[cfg(feature = "tts")]
355pub struct OpenAiTtsFactory {
356    descriptor: ProviderDescriptor,
357}
358
359#[cfg(feature = "tts")]
360impl OpenAiTtsFactory {
361    pub fn new() -> Self {
362        Self {
363            descriptor: ProviderDescriptor::new(
364                ProviderId::must("openai"),
365                "OpenAI TTS",
366                ProviderOperations::TTS_ONLY,
367                NetworkRequirement::RequiresNetwork,
368                ProviderStability::Stable,
369            ),
370        }
371    }
372}
373
374#[cfg(feature = "tts")]
375impl Default for OpenAiTtsFactory {
376    fn default() -> Self {
377        Self::new()
378    }
379}
380
381#[cfg(feature = "tts")]
382impl SynthesisProviderFactory for OpenAiTtsFactory {
383    fn descriptor(&self) -> &ProviderDescriptor {
384        &self.descriptor
385    }
386
387    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
388        openai_tts_capabilities(model)
389    }
390
391    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn SynthesisProvider>> {
392        if ctx.local_only() {
393            return Err(UserError::UnsupportedCapability {
394                provider: "openai".into(),
395                model: "*".into(),
396                reason: "remote TTS is disabled under local_only".into(),
397                hint: "unset local_only or use provider=local".into(),
398            }
399            .into());
400        }
401        let key = ctx.api_key_cloned();
402        let policy = RemotePolicy {
403            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
404            use_system_proxy: ctx.use_system_proxy(),
405            allow_loopback_http: ctx
406                .base_url()
407                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
408            ..RemotePolicy::default()
409        };
410        let provider =
411            OpenAiTtsProvider::with_policy(key, ctx.base_url().map(|s| s.to_string()), policy)?
412                .with_governor(engine_or_global_governor(ctx));
413        Ok(Arc::new(provider))
414    }
415}
416
417// ── ElevenLabs TTS ──────────────────────────────────────────────────────────
418
419#[cfg(feature = "tts")]
420pub struct ElevenLabsTtsFactory {
421    descriptor: ProviderDescriptor,
422}
423
424#[cfg(feature = "tts")]
425impl ElevenLabsTtsFactory {
426    pub fn new() -> Self {
427        Self {
428            descriptor: ProviderDescriptor::new(
429                ProviderId::must("elevenlabs"),
430                "ElevenLabs TTS",
431                ProviderOperations::TTS_ONLY,
432                NetworkRequirement::RequiresNetwork,
433                ProviderStability::Stable,
434            ),
435        }
436    }
437}
438
439#[cfg(feature = "tts")]
440impl Default for ElevenLabsTtsFactory {
441    fn default() -> Self {
442        Self::new()
443    }
444}
445
446#[cfg(feature = "tts")]
447impl SynthesisProviderFactory for ElevenLabsTtsFactory {
448    fn descriptor(&self) -> &ProviderDescriptor {
449        &self.descriptor
450    }
451
452    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
453        elevenlabs_tts_capabilities(model)
454    }
455
456    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn SynthesisProvider>> {
457        if ctx.local_only() {
458            return Err(UserError::UnsupportedCapability {
459                provider: "elevenlabs".into(),
460                model: "*".into(),
461                reason: "remote TTS is disabled under local_only".into(),
462                hint: "unset local_only or use provider=local".into(),
463            }
464            .into());
465        }
466        let key = ctx.api_key_cloned();
467        let policy = RemotePolicy {
468            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
469            use_system_proxy: ctx.use_system_proxy(),
470            allow_loopback_http: ctx
471                .base_url()
472                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
473            ..RemotePolicy::default()
474        };
475        let provider =
476            ElevenLabsTtsProvider::with_policy(key, ctx.base_url().map(|s| s.to_string()), policy)?
477                .with_governor(engine_or_global_governor(ctx));
478        Ok(Arc::new(provider))
479    }
480}
481
482// ── xAI STT / TTS ───────────────────────────────────────────────────────────
483
484pub struct XaiSttFactory {
485    descriptor: ProviderDescriptor,
486}
487
488impl XaiSttFactory {
489    pub fn new() -> Self {
490        Self {
491            descriptor: ProviderDescriptor::new(
492                ProviderId::must("xai"),
493                "xAI STT",
494                ProviderOperations::STT_ONLY,
495                NetworkRequirement::RequiresNetwork,
496                ProviderStability::Experimental,
497            ),
498        }
499    }
500}
501
502impl Default for XaiSttFactory {
503    fn default() -> Self {
504        Self::new()
505    }
506}
507
508impl TranscriptionProviderFactory for XaiSttFactory {
509    fn descriptor(&self) -> &ProviderDescriptor {
510        &self.descriptor
511    }
512
513    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
514        xai_stt_capabilities(model)
515    }
516
517    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn TranscriptionProvider>> {
518        if ctx.local_only() {
519            return Err(UserError::UnsupportedCapability {
520                provider: "xai".into(),
521                model: "*".into(),
522                reason: "remote STT is disabled under local_only".into(),
523                hint: "unset local_only or use provider=local".into(),
524            }
525            .into());
526        }
527        let key = ctx.api_key_cloned();
528        let policy = RemotePolicy {
529            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
530            use_system_proxy: ctx.use_system_proxy(),
531            allow_loopback_http: ctx
532                .base_url()
533                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
534            ..RemotePolicy::default()
535        };
536        let provider =
537            XaiSttProvider::with_policy(key, ctx.base_url().map(|s| s.to_string()), policy)?
538                .with_governor(engine_or_global_governor(ctx));
539        Ok(Arc::new(provider))
540    }
541}
542
543#[cfg(feature = "tts")]
544pub struct XaiTtsFactory {
545    descriptor: ProviderDescriptor,
546}
547
548#[cfg(feature = "tts")]
549impl XaiTtsFactory {
550    pub fn new() -> Self {
551        Self {
552            descriptor: ProviderDescriptor::new(
553                ProviderId::must("xai"),
554                "xAI TTS",
555                ProviderOperations::TTS_ONLY,
556                NetworkRequirement::RequiresNetwork,
557                ProviderStability::Experimental,
558            ),
559        }
560    }
561}
562
563#[cfg(feature = "tts")]
564impl Default for XaiTtsFactory {
565    fn default() -> Self {
566        Self::new()
567    }
568}
569
570#[cfg(feature = "tts")]
571impl SynthesisProviderFactory for XaiTtsFactory {
572    fn descriptor(&self) -> &ProviderDescriptor {
573        &self.descriptor
574    }
575
576    fn capabilities(&self, model: &str) -> Result<ProviderCapabilities> {
577        xai_tts_capabilities(model)
578    }
579
580    fn build(&self, ctx: &ProviderBuildContext) -> Result<Arc<dyn SynthesisProvider>> {
581        if ctx.local_only() {
582            return Err(UserError::UnsupportedCapability {
583                provider: "xai".into(),
584                model: "*".into(),
585                reason: "remote TTS is disabled under local_only".into(),
586                hint: "unset local_only or use provider=local".into(),
587            }
588            .into());
589        }
590        let key = ctx.api_key_cloned();
591        let policy = RemotePolicy {
592            allow_custom_credentialed_endpoint: ctx.allow_custom_endpoint(),
593            use_system_proxy: ctx.use_system_proxy(),
594            allow_loopback_http: ctx
595                .base_url()
596                .is_some_and(|u| u.contains("127.0.0.1") || u.contains("localhost")),
597            ..RemotePolicy::default()
598        };
599        let provider =
600            XaiTtsProvider::with_policy(key, ctx.base_url().map(|s| s.to_string()), policy)?
601                .with_governor(engine_or_global_governor(ctx));
602        Ok(Arc::new(provider))
603    }
604}
605
606/// Product built-in registry: local, OpenRouter, OpenAI, ElevenLabs, xAI.
607pub fn build_builtin_registry() -> Result<ProviderRegistry> {
608    let b = ProviderRegistryBuilder::default()
609        .register_stt(Arc::new(LocalSttFactory::new()))?
610        .register_stt(Arc::new(OpenRouterSttFactory::new()))?
611        .register_stt(Arc::new(OpenAiSttFactory::new()))?
612        .register_stt(Arc::new(XaiSttFactory::new()))?;
613    #[cfg(feature = "tts")]
614    let b = b
615        .register_tts(Arc::new(LocalTtsFactory::new()))?
616        .register_tts(Arc::new(OpenRouterTtsFactory::new()))?
617        .register_tts(Arc::new(OpenAiTtsFactory::new()))?
618        .register_tts(Arc::new(ElevenLabsTtsFactory::new()))?
619        .register_tts(Arc::new(XaiTtsFactory::new()))?;
620    Ok(b.build())
621}