systemprompt_models/profile/gateway/config/
runtime.rs1use std::borrow::Cow;
7use std::collections::HashMap;
8
9use systemprompt_identifiers::{ProviderId, RouteId};
10
11use crate::profile::gateway::config::{
12 BridgeReleasesSpec, DEFAULT_ROUTE_PATTERN, GatewayConfigSpec, default_auth_scheme,
13 default_inference_path_prefix,
14};
15use crate::profile::gateway::override_rule::SystemPromptRule;
16use crate::profile::gateway::route::GatewayRoute;
17use crate::profile::providers::ProviderRegistry;
18use crate::wire::canonical::CanonicalRequest;
19
20#[derive(Debug, Clone)]
27pub struct GatewayConfig {
28 pub enabled: bool,
29 pub routes: Vec<GatewayRoute>,
30 pub default_provider: Option<ProviderId>,
31 pub allow_unlisted_models: bool,
32 pub auth_scheme: String,
33 pub inference_path_prefix: String,
34 pub system_prompt_overrides: Vec<SystemPromptRule>,
35 pub bridge_releases: Option<BridgeReleasesSpec>,
36}
37
38impl Default for GatewayConfig {
39 fn default() -> Self {
40 Self {
41 enabled: false,
42 routes: Vec::new(),
43 default_provider: None,
44 allow_unlisted_models: false,
45 auth_scheme: default_auth_scheme(),
46 inference_path_prefix: default_inference_path_prefix(),
47 system_prompt_overrides: Vec::new(),
48 bridge_releases: None,
49 }
50 }
51}
52
53impl GatewayConfig {
54 pub fn find_route(&self, model: &str) -> Option<&GatewayRoute> {
55 self.routes.iter().find(|route| route.matches(model))
56 }
57
58 pub fn candidate_routes<'a>(
59 &'a self,
60 registry: &ProviderRegistry,
61 ) -> impl Iterator<Item = Cow<'a, GatewayRoute>> {
62 self.routes
63 .iter()
64 .map(Cow::Borrowed)
65 .chain(self.synthesize_default_route(registry).map(Cow::Owned))
66 }
67
68 #[must_use]
69 pub fn resolve_route<'a>(
70 &'a self,
71 registry: &ProviderRegistry,
72 request: &CanonicalRequest,
73 ) -> Option<Cow<'a, GatewayRoute>> {
74 self.candidate_routes(registry)
75 .find(|route| route.matches_request(request))
76 }
77
78 #[must_use]
79 pub fn dispatchable_route_ids(&self, registry: &ProviderRegistry) -> Vec<RouteId> {
80 let mut ids: Vec<RouteId> = Vec::new();
81 let mut seen: std::collections::HashSet<RouteId> = std::collections::HashSet::new();
82 for route in self.candidate_routes(registry) {
83 let mut route = route.into_owned();
84 route.ensure_id();
85 if seen.insert(route.id.clone()) {
86 ids.push(route.id);
87 }
88 }
89 ids
90 }
91
92 fn synthesize_default_route(&self, registry: &ProviderRegistry) -> Option<GatewayRoute> {
93 let provider = self.default_provider.as_ref()?;
94 registry.find_provider(provider.as_str())?;
95 let mut route = GatewayRoute {
96 id: RouteId::new(""),
97 model_pattern: DEFAULT_ROUTE_PATTERN.to_owned(),
98 provider: provider.clone(),
99 upstream_model: None,
100 extra_headers: HashMap::new(),
101 pricing: None,
102 when: None,
103 requires: None,
104 };
105 route.ensure_id();
106 Some(route)
107 }
108
109 #[must_use]
110 pub fn is_model_exposed(&self, registry: &ProviderRegistry, model: &str) -> bool {
111 if self.find_route(model).is_some() || registry.contains_model(model) {
112 return true;
113 }
114 if self.default_provider.is_some() && self.allow_unlisted_models {
115 tracing::warn!(
116 model,
117 "gateway forwarding an unlisted model to default_provider \
118 (allow_unlisted_models=true): open allowlist posture"
119 );
120 return true;
121 }
122 false
123 }
124
125 #[must_use]
126 pub fn to_spec(&self) -> GatewayConfigSpec {
127 GatewayConfigSpec {
128 enabled: self.enabled,
129 routes: self.routes.clone(),
130 default_provider: self.default_provider.clone(),
131 allow_unlisted_models: self.allow_unlisted_models,
132 auth_scheme: self.auth_scheme.clone(),
133 inference_path_prefix: self.inference_path_prefix.clone(),
134 system_prompt_overrides: self.system_prompt_overrides.clone(),
135 bridge_releases: self.bridge_releases.clone(),
136 }
137 }
138}