aurum_core/provider_platform/
mod.rs1mod builtin;
12mod conformance;
13mod context;
14mod descriptor;
15mod evidence;
16mod factory;
17mod id;
18mod listing;
19mod lookup;
20mod qualification;
21mod registry;
22
23pub use builtin::{LocalSttFactory, OpenRouterSttFactory};
24#[cfg(feature = "tts")]
25pub use builtin::{LocalTtsFactory, OpenRouterTtsFactory};
26pub use conformance::{
27 check_builtin_conformance, check_descriptor_capabilities, check_network_claim,
28 check_unique_descriptor_identities, ConformanceFailure,
29};
30pub use context::ProviderBuildContext;
31pub use descriptor::{
32 NetworkRequirement, ProviderDescriptor, ProviderOperations, ProviderStability,
33};
34pub use evidence::{
35 detect_catalogue_drift, effective_provider_tier, evaluate_supported_evidence_gate,
36 load_evidence_dir, local_stt_evidence, local_tts_evidence, provider_tier_matrix,
37 tier_from_registry_stability, CatalogueDriftReport, EvidenceFailureCategory,
38 EvidenceGateFinding, EvidenceGateReport, EvidenceOperation, ProviderEvidenceIndex,
39 ProviderEvidenceRecord, SupportTier, SupportedRouteClaim, PROVIDER_EVIDENCE_SCHEMA_VERSION,
40 SUPPORTED_EVIDENCE_MAX_AGE_SECS,
41};
42
43#[derive(Debug, Clone, Default)]
45pub struct ProviderResolveOptions {
46 pub show_progress: bool,
48 pub stt_mode: Option<crate::providers::OpenRouterSttMode>,
50 pub local_only: Option<bool>,
52}
53#[cfg(feature = "tts")]
54pub use factory::SynthesisProviderFactory;
55pub use factory::TranscriptionProviderFactory;
56pub use id::{ProviderId, MAX_PROVIDER_ID_LEN};
57pub use listing::{
58 list_provider_summaries, merge_provider_summaries, provider_list, ProviderList,
59 ProviderSummary, PROVIDER_LIST_SCHEMA_VERSION,
60};
61pub use lookup::{capabilities_for, preflight_stt_with_registry, preflight_tts_with_registry};
62pub use registry::{ProviderRegistry, ProviderRegistryBuilder};
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::capabilities::{CapabilityOperation, ProviderCapabilities};
68 use crate::secret::SecretString;
69 use std::sync::Arc;
70
71 struct FakeStt {
72 desc: ProviderDescriptor,
73 }
74
75 impl TranscriptionProviderFactory for FakeStt {
76 fn descriptor(&self) -> &ProviderDescriptor {
77 &self.desc
78 }
79
80 fn capabilities(&self, model: &str) -> crate::error::Result<ProviderCapabilities> {
81 let mut caps = ProviderCapabilities::with_core(
82 self.desc.id.as_str(),
83 model,
84 CapabilityOperation::Stt,
85 );
86 caps.stt_backend = Some(crate::capabilities::SttBackendClass::Asr);
87 caps.timestamps_reliable = true;
88 caps.languages = vec!["en".into()];
89 caps.max_duration_secs = Some(60.0);
90 caps.supports_cancellation = true;
91 caps.requires_network = false;
92 caps.local_only_ok = true;
93 caps.output_formats = vec!["txt".into()];
94 Ok(caps)
95 }
96
97 fn build(
98 &self,
99 _ctx: &ProviderBuildContext,
100 ) -> crate::error::Result<Arc<dyn crate::providers::TranscriptionProvider>> {
101 Err(crate::error::UserError::Other {
102 message: "fake stt does not implement inference".into(),
103 }
104 .into())
105 }
106 }
107
108 #[test]
109 fn rejects_duplicate_stt_registration() {
110 let f1: Arc<dyn TranscriptionProviderFactory> = Arc::new(FakeStt {
111 desc: ProviderDescriptor::new(
112 ProviderId::must("fake"),
113 "Fake",
114 ProviderOperations::STT_ONLY,
115 NetworkRequirement::LocalOnly,
116 ProviderStability::TestOnly,
117 ),
118 });
119 let f2: Arc<dyn TranscriptionProviderFactory> = Arc::new(FakeStt {
120 desc: ProviderDescriptor::new(
121 ProviderId::must("fake"),
122 "Fake2",
123 ProviderOperations::STT_ONLY,
124 NetworkRequirement::LocalOnly,
125 ProviderStability::TestOnly,
126 ),
127 });
128 let err = match ProviderRegistry::builder()
129 .register_stt(f1)
130 .unwrap()
131 .register_stt(f2)
132 {
133 Ok(_) => panic!("expected duplicate registration error"),
134 Err(e) => e,
135 };
136 let msg = err.to_string();
137 assert!(msg.contains("duplicate"), "{msg}");
138 }
139
140 #[test]
141 fn builtin_registers_local_and_openrouter_stt() {
142 let reg = ProviderRegistry::builtin().unwrap();
143 let local = ProviderId::local();
144 let or = ProviderId::openrouter();
145 assert!(reg.stt_factory(&local).is_ok());
146 assert!(reg.stt_factory(&or).is_ok());
147 let caps = reg
148 .stt_factory(&local)
149 .unwrap()
150 .capabilities("tiny-q5_1")
151 .unwrap();
152 assert_eq!(caps.provider, "local");
153 assert!(!caps.requires_network || caps.local_only_ok);
154
155 assert!(reg.stt_factory(&ProviderId::must("openai")).is_ok());
156 assert!(reg.stt_factory(&ProviderId::must("elevenlabs")).is_err());
157 }
158
159 #[test]
160 #[cfg(feature = "tts")]
161 fn builtin_registers_local_and_openrouter_tts() {
162 let reg = ProviderRegistry::builtin().unwrap();
163 let local = ProviderId::local();
164 assert!(reg.tts_factory(&local).is_ok());
165 assert!(reg.tts_factory(&ProviderId::openrouter()).is_ok());
167 assert!(reg.tts_factory(&ProviderId::must("openai")).is_ok());
168 assert!(reg.stt_factory(&ProviderId::must("openai")).is_ok());
169 }
170
171 #[test]
172 fn descriptors_are_deterministic() {
173 let a = ProviderRegistry::builtin().unwrap();
174 let b = ProviderRegistry::builtin().unwrap();
175 let da: Vec<_> = a.descriptors().iter().map(|d| d.id.as_str()).collect();
176 let db: Vec<_> = b.descriptors().iter().map(|d| d.id.as_str()).collect();
177 assert_eq!(da, db);
178 assert!(da.contains(&"local"));
179 assert!(da.contains(&"openrouter"));
180 }
181
182 #[test]
183 fn openrouter_factory_rejects_local_only() {
184 let reg = ProviderRegistry::builtin().unwrap();
185 let f = reg.stt_factory(&ProviderId::openrouter()).unwrap();
186 let ctx = ProviderBuildContext::new("/tmp/aurum-reg-test")
187 .with_local_only(true)
188 .with_api_key(Some(SecretString::new("sk-test")));
189 let err = match f.build(&ctx) {
190 Ok(_) => panic!("expected local_only rejection"),
191 Err(e) => e,
192 };
193 let msg = err.to_string();
194 assert!(
195 msg.contains("local_only") || msg.contains("remote"),
196 "{msg}"
197 );
198 }
199
200 #[test]
201 fn local_stt_builds_without_secret() {
202 let reg = ProviderRegistry::builtin().unwrap();
203 let f = reg.stt_factory(&ProviderId::local()).unwrap();
204 let ctx = ProviderBuildContext::new(std::env::temp_dir().join("aurum-reg-local"));
205 let p = f.build(&ctx).unwrap();
206 assert_eq!(p.name(), "local");
207 }
208
209 #[test]
210 fn secret_scoping_does_not_leak_via_debug() {
211 let ctx = ProviderBuildContext::new("/tmp/x")
212 .with_api_key(Some(SecretString::new("sk-must-not-appear-in-debug")));
213 assert!(!format!("{ctx:?}").contains("sk-must-not-appear"));
214 }
215
216 #[test]
217 fn capabilities_for_routes_through_registry() {
218 let reg = ProviderRegistry::builtin().unwrap();
219 let caps =
220 capabilities_for(®, &ProviderId::local(), CapabilityOperation::Stt, "base").unwrap();
221 assert_eq!(caps.provider, "local");
222 assert_eq!(
223 caps.schema_version,
224 crate::capabilities::CAPABILITY_SCHEMA_VERSION
225 );
226 }
227}