pub mod auth;
pub mod bridge;
pub mod bridge_data;
pub mod bridge_device;
pub mod bridge_error;
pub mod bridge_heartbeat;
pub mod bridge_manifest;
pub mod bridge_plugin_file;
pub mod bridge_profile_usage;
pub mod bridge_release;
pub mod bridge_resolved;
pub mod bridge_stream;
pub mod bridge_whoami;
pub mod messages;
pub mod models;
pub mod otel;
pub mod sessions;
pub mod access_log;
mod routers;
use axum::routing::get;
use axum::{Extension, Router};
use std::sync::Arc;
use systemprompt_runtime::AppContext;
use systemprompt_traits::AppContext as _;
use self::access_log::log_gateway_request;
use self::routers::{
bridge_auth_routes, bridge_profile_routes, bridge_release_routes, bridge_session_routes,
inference_routes, otel_routes,
};
use crate::services::middleware::{
JtiRevocationChecker, JwtContextExtractor, RateLimitState, RouterExt,
};
pub(crate) use self::access_log::GatewayLogIdentity;
fn build_jwt_extractor(ctx: &AppContext) -> anyhow::Result<Arc<JwtContextExtractor>> {
let sessions = ctx
.session_provider()
.ok_or_else(|| anyhow::anyhow!("gateway requires a session provider"))?;
let user_provider = ctx
.user_provider()
.ok_or_else(|| anyhow::anyhow!("gateway requires a user provider"))?;
let jti_revocation =
JtiRevocationChecker::from_repository(ctx.oauth_repositories().oauth.clone());
Ok(Arc::new(JwtContextExtractor::new(
sessions,
user_provider,
jti_revocation,
ctx.config().jwt_issuer.clone(),
)))
}
pub fn gateway_repositories(
ctx: &AppContext,
) -> anyhow::Result<systemprompt_gateway::GatewayRepositories> {
let journal = systemprompt_gateway::audit::journal::GatewayJournal::open(
ctx.app_paths().storage().data(),
systemprompt_config::SecretsBootstrap::get()?,
)?;
let payload_cap_bytes = systemprompt_config::ProfileBootstrap::get()?.payload_cap_bytes();
Ok(systemprompt_gateway::GatewayRepositories::new(
ctx.db_pool(),
journal,
ctx.context_materializer(),
ctx.background_tasks().clone(),
)
.with_artifact_ingest(ctx.artifact_ingest_arc())
.with_session_store(ctx.session_store())
.with_payload_cap(payload_cap_bytes))
}
struct GatewayParts {
traffic: Router,
bridge_auth: Router,
}
fn gateway_parts(ctx: &AppContext) -> anyhow::Result<GatewayParts> {
let jwt_extractor = build_jwt_extractor(ctx)?;
let gateway_repos = Arc::new(gateway_repositories(ctx)?);
Ok(GatewayParts {
traffic: Router::new()
.merge(inference_routes(ctx, &jwt_extractor, &gateway_repos))
.merge(bridge_profile_routes(ctx, &jwt_extractor))
.merge(bridge_session_routes(ctx, &jwt_extractor))
.merge(bridge_release_routes(&jwt_extractor))
.merge(otel_routes(ctx, &jwt_extractor))
.route("/models", get(models::list))
.route("/", get(models::root)),
bridge_auth: bridge_auth_routes(ctx, &jwt_extractor),
})
}
pub fn gateway_router(ctx: &AppContext) -> anyhow::Result<Router> {
let parts = gateway_parts(ctx)?;
Ok(common_layers(ctx, parts.traffic.merge(parts.bridge_auth)))
}
pub fn gateway_mount_router(ctx: &AppContext, limits: &RateLimitState) -> anyhow::Result<Router> {
let parts = gateway_parts(ctx)?;
let rate_config = &ctx.config().rate_limits;
let traffic =
parts
.traffic
.with_rate_limit(limits, rate_config.gateway_per_second, "gateway")?;
let bridge_auth = parts.bridge_auth.with_rate_limit(
limits,
rate_config.bridge_auth_per_second,
"bridge_auth",
)?;
Ok(common_layers(ctx, traffic.merge(bridge_auth)))
}
fn common_layers(ctx: &AppContext, router: Router) -> Router {
router
.layer(Extension(ctx.clone()))
.layer(axum::middleware::from_fn(log_gateway_request))
}