systemprompt_api/services/gateway/
registry.rs1use std::collections::HashMap;
7use std::sync::{Arc, OnceLock};
8
9use super::protocol::outbound::anthropic::AnthropicOutbound;
10use super::protocol::outbound::gemini::GeminiOutbound;
11use super::protocol::outbound::openai_chat::OpenAiChatOutbound;
12use super::protocol::outbound::openai_responses::OpenAiResponsesOutbound;
13use super::protocol::outbound::{OutboundAdapter, OutboundAdapterRegistration};
14use systemprompt_ai::{NullScanner, SafetyScanner, SafetyScannerRegistration};
15use systemprompt_models::profile::WireProtocol;
16
17pub struct GatewayUpstreamRegistry {
18 entries: HashMap<String, Arc<dyn OutboundAdapter>>,
19}
20
21impl std::fmt::Debug for GatewayUpstreamRegistry {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("GatewayUpstreamRegistry")
24 .field("tags", &self.tags())
25 .finish()
26 }
27}
28
29impl GatewayUpstreamRegistry {
30 pub fn global() -> &'static Self {
31 static REGISTRY: OnceLock<GatewayUpstreamRegistry> = OnceLock::new();
32 REGISTRY.get_or_init(Self::build)
33 }
34
35 pub fn get(&self, tag: &str) -> Option<&Arc<dyn OutboundAdapter>> {
36 self.entries.get(tag)
37 }
38
39 pub fn tags(&self) -> Vec<&str> {
40 self.entries.keys().map(String::as_str).collect()
41 }
42
43 pub(super) fn build() -> Self {
44 let mut entries: HashMap<String, Arc<dyn OutboundAdapter>> = HashMap::new();
45
46 entries.insert(
47 WireProtocol::Anthropic.as_tag().to_owned(),
48 Arc::new(AnthropicOutbound),
49 );
50 entries.insert(
51 WireProtocol::OpenAiChat.as_tag().to_owned(),
52 Arc::new(OpenAiChatOutbound),
53 );
54 entries.insert(
55 WireProtocol::OpenAiResponses.as_tag().to_owned(),
56 Arc::new(OpenAiResponsesOutbound),
57 );
58 entries.insert(
59 WireProtocol::Gemini.as_tag().to_owned(),
60 Arc::new(GeminiOutbound),
61 );
62
63 for registration in inventory::iter::<OutboundAdapterRegistration> {
64 let tag = registration.tag.to_owned();
65 if entries.contains_key(&tag) {
66 tracing::warn!(
67 tag = %registration.tag,
68 "Extension-registered gateway upstream shadows a built-in"
69 );
70 }
71 entries.insert(tag, (registration.factory)());
72 }
73
74 Self { entries }
75 }
76}
77
78pub struct SafetyScannerRegistry {
79 entries: HashMap<String, Arc<dyn SafetyScanner>>,
80}
81
82impl std::fmt::Debug for SafetyScannerRegistry {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("SafetyScannerRegistry")
85 .field("names", &self.names())
86 .finish()
87 }
88}
89
90impl SafetyScannerRegistry {
91 pub fn global() -> &'static Self {
92 static REGISTRY: OnceLock<SafetyScannerRegistry> = OnceLock::new();
93 REGISTRY.get_or_init(Self::build)
94 }
95
96 pub fn get(&self, name: &str) -> Option<&Arc<dyn SafetyScanner>> {
97 self.entries.get(name)
98 }
99
100 pub fn names(&self) -> Vec<&str> {
101 self.entries.keys().map(String::as_str).collect()
102 }
103
104 pub(super) fn build() -> Self {
108 let mut entries: HashMap<String, Arc<dyn SafetyScanner>> = HashMap::new();
109 entries.insert("null".to_owned(), Arc::new(NullScanner));
110
111 for registration in inventory::iter::<SafetyScannerRegistration> {
112 let name = registration.name.to_owned();
113 if entries.contains_key(&name) || name == "heuristic" {
114 tracing::warn!(
115 name = %registration.name,
116 "Extension-registered safety scanner shadows a built-in"
117 );
118 }
119 entries.insert(name, (registration.factory)());
120 }
121
122 Self { entries }
123 }
124}