systemprompt-api 0.15.0

Axum-based HTTP server and API gateway for systemprompt.io AI governance infrastructure. Exposes governed agents, MCP, A2A, and admin endpoints with rate limiting and RBAC.
Documentation
use axum::Router;
use axum::routing::get;
use std::sync::Arc;
use systemprompt_runtime::AppContext;
use systemprompt_traits::{StartupEvent, StartupEventSender};

use crate::services::middleware::authz::AuthzPolicy;
use crate::services::middleware::{PublicContextMiddleware, RouterExt, site_auth_gate};
use crate::services::static_content::{
    StaticContentMatcher, StaticContentState, serve_homepage, smart_fallback_handler,
};

pub(super) fn build_static_router(
    ctx: &AppContext,
    public_middleware: PublicContextMiddleware,
    events: Option<&StartupEventSender>,
) -> Router {
    let path = ctx.app_paths().system().content_config().to_path_buf();
    let content_matcher = path.to_str().map_or_else(
        || {
            if let Some(tx) = events {
                if tx
                    .unbounded_send(StartupEvent::Warning {
                        message: "CONTENT_CONFIG_PATH contains invalid UTF-8".to_owned(),
                        context: None,
                    })
                    .is_err()
                {
                    tracing::debug!("Startup event receiver dropped");
                }
            }
            Arc::new(StaticContentMatcher::empty())
        },
        |path_str| match StaticContentMatcher::from_config(path_str) {
            Ok(matcher) => Arc::new(matcher),
            Err(e) => {
                if let Some(tx) = events {
                    if tx
                        .unbounded_send(StartupEvent::Warning {
                            message: format!("Failed to load content config: {e}"),
                            context: Some("Static content matching will be disabled".to_owned()),
                        })
                        .is_err()
                    {
                        tracing::debug!("Startup event receiver dropped");
                    }
                }
                Arc::new(StaticContentMatcher::empty())
            },
        },
    );

    let static_state = StaticContentState {
        ctx: Arc::new(ctx.clone()),
        matcher: content_matcher,
        route_classifier: Arc::clone(ctx.route_classifier()),
    };

    let static_router = Router::new()
        .route("/", get(serve_homepage))
        .fallback(smart_fallback_handler)
        .with_state(static_state)
        .with_auth(public_middleware, AuthzPolicy::public());

    let site_auth_config = ctx
        .extension_registry()
        .extensions()
        .iter()
        .find_map(|ext| ext.site_auth());

    if let Some(auth_config) = site_auth_config {
        static_router.layer(axum::middleware::from_fn(move |req, next| {
            let config = auth_config;
            async move { site_auth_gate(req, next, config).await }
        }))
    } else {
        static_router
    }
}