Skip to main content

aurum_core/provider_platform/
registry.rs

1//! Immutable-after-build provider registry (JOE-1933).
2
3use super::descriptor::ProviderDescriptor;
4use super::factory::TranscriptionProviderFactory;
5use super::id::ProviderId;
6use crate::error::{Result, UserError};
7use std::collections::BTreeMap;
8use std::sync::Arc;
9
10#[cfg(feature = "tts")]
11use super::factory::SynthesisProviderFactory;
12
13/// Engine-owned registry of compiled provider factories.
14///
15/// Built via [`ProviderRegistryBuilder`], then frozen. No unsynchronized global
16/// mutable plugin table.
17#[derive(Clone)]
18pub struct ProviderRegistry {
19    stt: BTreeMap<String, Arc<dyn TranscriptionProviderFactory>>,
20    #[cfg(feature = "tts")]
21    tts: BTreeMap<String, Arc<dyn SynthesisProviderFactory>>,
22    /// Deterministic enumeration order (registration order).
23    order: Vec<ProviderId>,
24}
25
26impl std::fmt::Debug for ProviderRegistry {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        let mut d = f.debug_struct("ProviderRegistry");
29        d.field("stt", &self.stt.keys().collect::<Vec<_>>());
30        #[cfg(feature = "tts")]
31        d.field("tts", &self.tts.keys().collect::<Vec<_>>());
32        d.field("order", &self.order).finish()
33    }
34}
35
36impl ProviderRegistry {
37    pub fn builder() -> ProviderRegistryBuilder {
38        ProviderRegistryBuilder::default()
39    }
40
41    /// Built-in product registry (local STT/TTS + OpenRouter STT).
42    pub fn builtin() -> Result<Self> {
43        super::builtin::build_builtin_registry()
44    }
45
46    pub fn stt_factory(&self, id: &ProviderId) -> Result<&dyn TranscriptionProviderFactory> {
47        self.stt
48            .get(id.as_str())
49            .map(|f| f.as_ref())
50            .ok_or_else(|| unknown_or_unsupported(id, "stt"))
51    }
52
53    #[cfg(feature = "tts")]
54    pub fn tts_factory(&self, id: &ProviderId) -> Result<&dyn SynthesisProviderFactory> {
55        self.tts
56            .get(id.as_str())
57            .map(|f| f.as_ref())
58            .ok_or_else(|| unknown_or_unsupported(id, "tts"))
59    }
60
61    /// Descriptors in registration order (one entry per registered id).
62    pub fn descriptors(&self) -> Vec<&ProviderDescriptor> {
63        let mut out = Vec::new();
64        let mut seen = std::collections::HashSet::new();
65        for id in &self.order {
66            if !seen.insert(id.as_str()) {
67                continue;
68            }
69            if let Some(f) = self.stt.get(id.as_str()) {
70                out.push(f.descriptor());
71                continue;
72            }
73            #[cfg(feature = "tts")]
74            if let Some(f) = self.tts.get(id.as_str()) {
75                out.push(f.descriptor());
76            }
77        }
78        out
79    }
80
81    pub fn list_stt_ids(&self) -> Vec<ProviderId> {
82        self.stt.keys().map(|k| ProviderId::must(k)).collect()
83    }
84
85    #[cfg(feature = "tts")]
86    pub fn list_tts_ids(&self) -> Vec<ProviderId> {
87        self.tts.keys().map(|k| ProviderId::must(k)).collect()
88    }
89
90    pub fn known_provider_hint(&self) -> String {
91        let mut ids: Vec<&str> = self.order.iter().map(|p| p.as_str()).collect();
92        ids.sort_unstable();
93        ids.dedup();
94        ids.join(", ")
95    }
96}
97
98fn unknown_or_unsupported(id: &ProviderId, op: &str) -> crate::error::TranscriptionError {
99    UserError::InvalidProvider {
100        provider: format!(
101            "{id} (no {op} factory registered; known: use ProviderRegistry::descriptors)"
102        ),
103    }
104    .into()
105}
106
107/// Builder that rejects duplicate operation identities.
108#[derive(Default)]
109pub struct ProviderRegistryBuilder {
110    // maps hold trait objects — Debug is manual if needed
111    stt: BTreeMap<String, Arc<dyn TranscriptionProviderFactory>>,
112    #[cfg(feature = "tts")]
113    tts: BTreeMap<String, Arc<dyn SynthesisProviderFactory>>,
114    order: Vec<ProviderId>,
115}
116
117impl ProviderRegistryBuilder {
118    pub fn register_stt(mut self, factory: Arc<dyn TranscriptionProviderFactory>) -> Result<Self> {
119        let desc = factory.descriptor();
120        if !desc.operations.supports_stt() {
121            return Err(UserError::InvalidConfig {
122                reason: format!(
123                    "factory '{}' cannot register as STT: operations.stt is false",
124                    desc.id
125                ),
126            }
127            .into());
128        }
129        let key = desc.id.as_str().to_string();
130        if self.stt.contains_key(&key) {
131            return Err(UserError::InvalidConfig {
132                reason: format!("duplicate STT provider registration: {key}"),
133            }
134            .into());
135        }
136        if !self.order.iter().any(|p| p.as_str() == key) {
137            self.order.push(desc.id.clone());
138        }
139        self.stt.insert(key, factory);
140        Ok(self)
141    }
142
143    #[cfg(feature = "tts")]
144    pub fn register_tts(mut self, factory: Arc<dyn SynthesisProviderFactory>) -> Result<Self> {
145        let desc = factory.descriptor();
146        if !desc.operations.supports_tts() {
147            return Err(UserError::InvalidConfig {
148                reason: format!(
149                    "factory '{}' cannot register as TTS: operations.tts is false",
150                    desc.id
151                ),
152            }
153            .into());
154        }
155        let key = desc.id.as_str().to_string();
156        if self.tts.contains_key(&key) {
157            return Err(UserError::InvalidConfig {
158                reason: format!("duplicate TTS provider registration: {key}"),
159            }
160            .into());
161        }
162        if !self.order.iter().any(|p| p.as_str() == key) {
163            self.order.push(desc.id.clone());
164        }
165        self.tts.insert(key, factory);
166        Ok(self)
167    }
168
169    pub fn build(self) -> ProviderRegistry {
170        ProviderRegistry {
171            stt: self.stt,
172            #[cfg(feature = "tts")]
173            tts: self.tts,
174            order: self.order,
175        }
176    }
177}