1use 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
49pub 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
99pub 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 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#[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#[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
291pub 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#[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#[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
482pub 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
606pub 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}