#![warn(missing_docs)]
pub mod agents;
pub mod auth;
pub mod client_runs;
pub mod dispatch;
pub mod error;
pub mod executor;
pub mod graph;
pub mod json;
pub mod runs;
pub mod sse;
pub mod state;
pub mod tool_registry;
#[cfg(feature = "ui")]
pub mod ui;
use axum::Router;
use axum::middleware::from_fn_with_state;
use axum::routing::{get, post};
use tokio::net::TcpListener;
pub use dispatch::{Disposition, ResumeKind, classify};
pub use error::ApiError;
pub use executor::{LlmModelExecutor, ModelExecutor, ModelStream};
pub use state::{
AgentDefinition, AgentFactory, AppState, BuildFuture, BuiltAgent, ClientRunLease, DefFormat,
RegisteredAgent,
};
pub use tool_registry::ToolRegistry;
pub fn build_router(state: AppState) -> Router {
let api = Router::new()
.route("/v1/agents", post(agents::register).get(agents::list))
.route("/v1/agents/{hash}", get(agents::get))
.route("/v1/runs", post(runs::start).get(runs::list))
.route("/v1/runs/{id}", get(runs::get))
.route("/v1/runs/{id}/replay", get(runs::replay))
.route("/v1/runs/{id}/events", get(sse::stream))
.route("/v1/runs/{id}/resume", post(runs::resume))
.route("/v1/runs/{id}/resolve", post(runs::resolve))
.route("/v1/runs/{id}/abandon", post(runs::abandon))
.route("/v1/runs/{id}/graph", get(graph::projection))
.route("/v1/runs/{id}/fork", post(graph::fork))
.route("/v1/runs/{id}/forks", get(graph::forks))
.route("/v1/capabilities", get(capabilities))
.route("/v1/graphs", post(graph::submit).get(graph::list))
.route("/v1/graphs/validate", post(graph::validate_only))
.route("/v1/graphs/{hash}", get(graph::get))
.route("/v1/graph-runs", post(graph::start_run))
.route("/v1/client-runs", post(client_runs::open))
.route("/v1/client-runs/{id}/log", get(client_runs::get_log))
.route("/v1/client-runs/{id}/events", post(client_runs::append))
.route(
"/v1/client-runs/{id}/model-step",
post(client_runs::model_step),
)
.route(
"/v1/client-runs/{id}/tool-step",
post(client_runs::tool_step),
)
.route("/v1/client-runs/{id}/resolve", post(client_runs::resolve))
.layer(from_fn_with_state(state.clone(), auth::require_bearer));
#[cfg(feature = "ui")]
let api = api.fallback(ui::static_handler);
api.with_state(state)
}
async fn capabilities() -> axum::response::Response {
let mut server = serde_json::json!({ "version": env!("CARGO_PKG_VERSION") });
if let Some(commit) = option_env!("SALVOR_SERVER_GIT_COMMIT") {
server["commit"] = serde_json::Value::String(commit.to_owned());
}
axum::response::IntoResponse::into_response(axum::Json(serde_json::json!({
"capabilities": { "fork": true },
"server": server,
})))
}
pub async fn serve(listener: TcpListener, state: AppState) -> std::io::Result<()> {
axum::serve(listener, build_router(state)).await
}