Skip to main content

trustee_api/
lib.rs

1//! Trustee API — REST + WebSocket server for the Trustee agent.
2//!
3//! Wraps a [`trustee_core::session::Session`] and exposes it over HTTP.
4//! Static frontend files are served from [`trustee_web`].
5
6mod routes;
7mod state;
8
9use std::net::SocketAddr;
10
11use anyhow::Result;
12use axum::routing::{get, post};
13use tower_http::cors::CorsLayer;
14
15pub use state::ServerState;
16
17/// Run the API server.
18///
19/// Creates a `Session` with the given config, starts a background task to
20/// drain workflow messages and broadcast them to WebSocket clients, then
21/// serves the REST + WebSocket + static files on `addr`.
22pub async fn run(
23    config_toml: String,
24    secrets: std::collections::HashMap<String, String>,
25    build_info: trustee_core::types::BuildInfo,
26    addr: SocketAddr,
27) -> Result<()> {
28    // Build the session
29    let (mut session, workflow_rx) = trustee_core::session::Session::new();
30    session.config_toml = Some(config_toml);
31    session.secrets = Some(secrets);
32    session.build_info = Some(build_info);
33    session.parse_auto_handoff_config();
34
35    // Create the broadcast channel for WebSocket fan-out
36    let (ws_tx, _ws_rx) = tokio::sync::broadcast::channel::<String>(256);
37
38    // Wrap session in shared state
39    let state = ServerState::new(session, ws_tx);
40
41    // Start background message drain task (owns workflow_rx directly — no deadlock)
42    state.clone().spawn_drain_task(workflow_rx);
43
44    // Build router
45    let app = axum::Router::new()
46        // API routes
47        .route("/api/v1/health", get(routes::health))
48        .route("/api/v1/session", get(routes::get_session))
49        .route("/api/v1/session/command", post(routes::post_command))
50        .route("/api/v1/session/cancel", post(routes::post_cancel))
51        .route("/api/v1/session/handoff", post(routes::post_handoff))
52        .route("/api/v1/session/stream", get(routes::ws_handler))
53        // Static files from trustee-web
54        .route("/", get(routes::serve_index))
55        .route("/{file}", get(routes::serve_static))
56        .layer(CorsLayer::permissive())
57        .layer(axum::extract::DefaultBodyLimit::max(10 * 1024 * 1024))
58        .with_state(state);
59
60    // Start server
61    let listener = tokio::net::TcpListener::bind(addr).await?;
62    tracing::info!("Trustee API listening on http://{}", addr);
63    axum::serve(listener, app).await?;
64
65    Ok(())
66}