systemprompt-api 0.5.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
mod auth;
mod dispatch;
mod extract;
mod rejection;

use axum::body::Body;
use axum::extract::Request;
use axum::http::StatusCode;
use axum::response::Response;
use std::sync::Arc;
use systemprompt_config::ProfileBootstrap;
use systemprompt_identifiers::AiRequestId;
use systemprompt_models::Profile;
use systemprompt_runtime::AppContext;

use crate::services::middleware::JwtContextExtractor;

use dispatch::{build_error_response, dispatch_to_provider};
use extract::{RejectionPartial, extract_request_context};
use rejection::persist_rejection;

pub(super) struct RequestContext<'a> {
    pub jwt_extractor: &'a JwtContextExtractor,
    pub ctx: &'a AppContext,
    pub profile: &'a Profile,
    pub ai_request_id: &'a AiRequestId,
}

pub async fn handle(
    jwt_extractor: Arc<JwtContextExtractor>,
    ctx: AppContext,
    request: Request<Body>,
) -> Response<Body> {
    let ai_request_id = AiRequestId::generate();
    let mut partial = RejectionPartial::default();
    match handle_inner(&jwt_extractor, &ctx, request, &ai_request_id, &mut partial).await {
        Ok(resp) => resp,
        Err((status, message)) => {
            tracing::warn!(
                status = %status,
                message = %message,
                ai_request_id = %ai_request_id,
                "Gateway request rejected",
            );
            persist_rejection(&ctx, &ai_request_id, &partial, status, &message).await;
            build_error_response(status, &message)
        },
    }
}

async fn handle_inner(
    jwt_extractor: &JwtContextExtractor,
    ctx: &AppContext,
    request: Request<Body>,
    ai_request_id: &AiRequestId,
    partial: &mut RejectionPartial,
) -> Result<Response<Body>, (StatusCode, String)> {
    let profile = ProfileBootstrap::get().map_err(|e| {
        (
            StatusCode::SERVICE_UNAVAILABLE,
            format!("Profile not ready: {e}"),
        )
    })?;
    let request_ctx = RequestContext {
        jwt_extractor,
        ctx,
        profile,
        ai_request_id,
    };
    let prepared = extract_request_context(&request_ctx, request, partial).await?;
    dispatch_to_provider(&request_ctx, prepared).await
}