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