systemprompt_models/profile/gateway/
config.rs1use std::borrow::Cow;
9use std::collections::HashMap;
10
11use serde::{Deserialize, Serialize};
12use systemprompt_identifiers::{ProviderId, RouteId};
13
14use super::super::providers::ProviderRegistry;
15use super::error::{GatewayProfileError, GatewayResult};
16use super::override_rule::SystemPromptRule;
17use super::route::GatewayRoute;
18use crate::wire::canonical::CanonicalRequest;
19
20pub(crate) const DEFAULT_ROUTE_PATTERN: &str = "*";
21
22#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
23#[serde(deny_unknown_fields)]
24pub struct GatewayConfigSpec {
25 #[serde(default)]
26 pub enabled: bool,
27 #[serde(default)]
28 pub routes: Vec<GatewayRoute>,
29 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub default_provider: Option<ProviderId>,
34 #[serde(default)]
39 pub allow_unlisted_models: bool,
40 #[serde(default = "default_auth_scheme")]
41 pub auth_scheme: String,
42 #[serde(default = "default_inference_path_prefix")]
43 pub inference_path_prefix: String,
44 #[serde(default, skip_serializing_if = "Vec::is_empty")]
45 pub system_prompt_overrides: Vec<SystemPromptRule>,
46}
47
48impl Default for GatewayConfigSpec {
49 fn default() -> Self {
50 Self {
51 enabled: false,
52 routes: Vec::new(),
53 default_provider: None,
54 allow_unlisted_models: false,
55 auth_scheme: default_auth_scheme(),
56 inference_path_prefix: default_inference_path_prefix(),
57 system_prompt_overrides: Vec::new(),
58 }
59 }
60}
61
62fn default_auth_scheme() -> String {
63 "bearer".to_owned()
64}
65
66fn default_inference_path_prefix() -> String {
67 "/v1".to_owned()
68}
69
70impl GatewayConfigSpec {
71 #[must_use]
72 pub fn resolve(self) -> GatewayConfig {
73 let Self {
74 enabled,
75 routes,
76 default_provider,
77 allow_unlisted_models,
78 auth_scheme,
79 inference_path_prefix,
80 system_prompt_overrides,
81 } = self;
82
83 GatewayConfig {
84 enabled,
85 routes,
86 default_provider,
87 allow_unlisted_models,
88 auth_scheme,
89 inference_path_prefix,
90 system_prompt_overrides,
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
102pub struct GatewayConfig {
103 pub enabled: bool,
104 pub routes: Vec<GatewayRoute>,
105 pub default_provider: Option<ProviderId>,
106 pub allow_unlisted_models: bool,
107 pub auth_scheme: String,
108 pub inference_path_prefix: String,
109 pub system_prompt_overrides: Vec<SystemPromptRule>,
110}
111
112impl Default for GatewayConfig {
113 fn default() -> Self {
114 Self {
115 enabled: false,
116 routes: Vec::new(),
117 default_provider: None,
118 allow_unlisted_models: false,
119 auth_scheme: default_auth_scheme(),
120 inference_path_prefix: default_inference_path_prefix(),
121 system_prompt_overrides: Vec::new(),
122 }
123 }
124}
125
126impl GatewayConfig {
127 pub fn find_route(&self, model: &str) -> Option<&GatewayRoute> {
128 self.routes.iter().find(|route| route.matches(model))
129 }
130
131 pub fn candidate_routes<'a>(
132 &'a self,
133 registry: &ProviderRegistry,
134 ) -> impl Iterator<Item = Cow<'a, GatewayRoute>> {
135 self.routes
136 .iter()
137 .map(Cow::Borrowed)
138 .chain(self.synthesize_default_route(registry).map(Cow::Owned))
139 }
140
141 #[must_use]
145 pub fn resolve_route<'a>(
146 &'a self,
147 registry: &ProviderRegistry,
148 request: &CanonicalRequest,
149 ) -> Option<Cow<'a, GatewayRoute>> {
150 self.candidate_routes(registry)
151 .find(|route| route.matches_request(request))
152 }
153
154 #[must_use]
155 pub fn dispatchable_route_ids(&self, registry: &ProviderRegistry) -> Vec<RouteId> {
156 let mut ids: Vec<RouteId> = Vec::new();
157 let mut seen: std::collections::HashSet<RouteId> = std::collections::HashSet::new();
158 for route in self.candidate_routes(registry) {
159 let mut route = route.into_owned();
160 route.ensure_id();
161 if seen.insert(route.id.clone()) {
162 ids.push(route.id);
163 }
164 }
165 ids
166 }
167
168 fn synthesize_default_route(&self, registry: &ProviderRegistry) -> Option<GatewayRoute> {
177 let provider = self.default_provider.as_ref()?;
178 registry.find_provider(provider.as_str())?;
179 let mut route = GatewayRoute {
180 id: RouteId::new(""),
181 model_pattern: DEFAULT_ROUTE_PATTERN.to_owned(),
182 provider: provider.clone(),
183 upstream_model: None,
184 extra_headers: HashMap::new(),
185 pricing: None,
186 when: None,
187 };
188 route.ensure_id();
189 Some(route)
190 }
191
192 #[must_use]
197 pub fn is_model_exposed(&self, registry: &ProviderRegistry, model: &str) -> bool {
198 if self.find_route(model).is_some() || registry.contains_model(model) {
199 return true;
200 }
201 if self.default_provider.is_some() && self.allow_unlisted_models {
202 tracing::warn!(
203 model,
204 "gateway forwarding an unlisted model to default_provider \
205 (allow_unlisted_models=true): open allowlist posture"
206 );
207 return true;
208 }
209 false
210 }
211
212 pub fn validate(&self, registry: &ProviderRegistry) -> GatewayResult<()> {
213 let mut route_ids: std::collections::HashSet<&str> =
214 std::collections::HashSet::with_capacity(self.routes.len());
215 for route in &self.routes {
216 if !route_ids.insert(route.id.as_str()) {
217 return Err(GatewayProfileError::DuplicateRouteId {
218 id: route.id.as_str().to_owned(),
219 });
220 }
221 }
222 if let Some(provider) = self.default_provider.as_ref()
223 && registry.find_provider(provider.as_str()).is_none()
224 {
225 return Err(GatewayProfileError::DefaultProviderNotInRegistry {
226 provider: provider.as_str().to_owned(),
227 });
228 }
229 for route in &self.routes {
230 if registry.find_provider(route.provider.as_str()).is_none() {
231 return Err(GatewayProfileError::RouteProviderNotInRegistry {
232 route: route.model_pattern.clone(),
233 provider: route.provider.as_str().to_owned(),
234 });
235 }
236 if let Some(when) = route.when.as_ref() {
237 when.validate()?;
238 }
239 }
240 for rule in &self.system_prompt_overrides {
241 rule.validate()?;
242 if let Some(provider) = rule.provider.as_ref()
243 && registry.find_provider(provider.as_str()).is_none()
244 {
245 return Err(GatewayProfileError::OverrideProviderNotInRegistry {
246 provider: provider.as_str().to_owned(),
247 });
248 }
249 }
250 Ok(())
251 }
252
253 #[must_use]
254 pub fn to_spec(&self) -> GatewayConfigSpec {
255 GatewayConfigSpec {
256 enabled: self.enabled,
257 routes: self.routes.clone(),
258 default_provider: self.default_provider.clone(),
259 allow_unlisted_models: self.allow_unlisted_models,
260 auth_scheme: self.auth_scheme.clone(),
261 inference_path_prefix: self.inference_path_prefix.clone(),
262 system_prompt_overrides: self.system_prompt_overrides.clone(),
263 }
264 }
265}