use actix_web::{dev::HttpServiceFactory, web};
use crate::handlers::{agent, settings};
fn mcp_scope() -> impl HttpServiceFactory {
web::scope("/mcp")
.route("/servers", web::get().to(agent::mcp::list_servers))
.route("/servers", web::post().to(agent::mcp::add_server))
.route(
"/servers/import",
web::post().to(agent::mcp::import_servers),
)
.route("/servers/{id}", web::get().to(agent::mcp::get_server))
.route("/servers/{id}", web::put().to(agent::mcp::update_server))
.route("/servers/{id}", web::delete().to(agent::mcp::delete_server))
.route(
"/servers/{id}/connect",
web::post().to(agent::mcp::connect_server),
)
.route(
"/servers/{id}/disconnect",
web::post().to(agent::mcp::disconnect_server),
)
.route(
"/servers/{id}/refresh",
web::post().to(agent::mcp::refresh_tools),
)
.route(
"/servers/{id}/tools",
web::get().to(agent::mcp::get_server_tools),
)
.route("/tools", web::get().to(agent::mcp::list_tools))
}
pub fn agent_routes(cfg: &mut web::ServiceConfig) {
let mut scope = web::scope("/api/v1")
.wrap(actix_web::middleware::from_fn(
settings::enforce_access_password_middleware,
))
.route("/chat", web::post().to(agent::chat::handler))
.route(
"/prompt-presets",
web::get().to(agent::prompt_presets::list_prompt_presets),
)
.route(
"/prompt-presets",
web::post().to(agent::prompt_presets::create_prompt_preset),
)
.route(
"/prompt-presets/{preset_id}",
web::patch().to(agent::prompt_presets::patch_prompt_preset),
)
.route(
"/prompt-presets/{preset_id}",
web::delete().to(agent::prompt_presets::delete_prompt_preset),
)
.route(
"/runs/active",
web::get().to(agent::sessions::running_sessions_snapshot),
)
.route("/sessions", web::get().to(agent::sessions::list_sessions))
.route("/sessions", web::post().to(agent::sessions::create_session))
.route(
"/sessions/cleanup",
web::post().to(agent::sessions::cleanup_sessions),
)
.route(
"/sessions/{session_id}",
web::get().to(agent::sessions::get_session),
)
.route(
"/sessions/{session_id}/system-prompt",
web::get().to(agent::sessions::get_system_prompt_snapshot),
)
.route(
"/sessions/{session_id}/discoverable-tools",
web::get().to(agent::sessions::list_discoverable_tools),
)
.route(
"/sessions/{session_id}/discoverable-tools",
web::post().to(agent::sessions::activate_discoverable_tools),
)
.route(
"/sessions/{session_id}/discoverable-tools",
web::delete().to(agent::sessions::deactivate_discoverable_tools),
)
.route(
"/sessions/{session_id}",
web::patch().to(agent::sessions::patch_session),
)
.route(
"/sessions/{session_id}/regenerate-title",
web::post().to(agent::sessions::regenerate_session_title),
)
.route(
"/sessions/{session_id}/clear",
web::post().to(agent::sessions::clear_session),
)
.route(
"/sessions/{session_id}/project-dream/run",
web::post().to(agent::sessions::run_project_dream),
)
.route(
"/sessions/{session_id}/messages/truncate",
web::post().to(agent::messages::truncate_messages),
)
.route(
"/sessions/{session_id}/restore",
web::post().to(agent::messages::restore_session_state),
)
.route(
"/sessions/{session_id}/messages/{message_id}",
web::patch().to(agent::messages::patch_message),
)
.route(
"/sessions/{session_id}/messages/{message_id}",
web::delete().to(agent::messages::delete_message),
)
.route(
"/sessions/{session_id}/attachments/{attachment_id}",
web::get().to(agent::sessions::get_attachment),
)
.route(
"/schedules",
web::get().to(agent::schedules::list_schedules),
)
.route(
"/schedules",
web::post().to(agent::schedules::create_schedule),
)
.route(
"/schedules/{schedule_id}",
web::patch().to(agent::schedules::patch_schedule),
)
.route(
"/schedules/{schedule_id}",
web::delete().to(agent::schedules::delete_schedule),
)
.route(
"/schedules/{schedule_id}/run",
web::post().to(agent::schedules::run_now),
)
.route(
"/schedules/{schedule_id}/sessions",
web::get().to(agent::schedules::list_sessions_for_schedule),
)
.route(
"/schedules/{schedule_id}/runs",
web::get().to(agent::schedules::list_runs_for_schedule),
)
.route(
"/execute/{session_id}",
web::post().to(agent::execute::handler),
)
.route(
"/events/{session_id}",
web::get().to(agent::events::handler),
)
.route("/stream", web::get().to(agent::stream::handler))
.route("/stop/{session_id}", web::post().to(agent::stop::handler))
.route(
"/child-approval/{child_session_id}",
web::post().to(agent::child_approval::handler),
)
.route(
"/history/{session_id}",
web::get().to(agent::history::handler),
)
.route(
"/task/{session_id}",
web::get().to(agent::task::get_task_list),
)
.route(
"/task/{session_id}/exists",
web::get().to(agent::task::has_task_list),
)
.route(
"/respond/{session_id}",
web::post().to(agent::respond::submit_response),
)
.route(
"/respond/{session_id}/pending",
web::get().to(agent::respond::get_pending_question),
)
.route(
"/notifications/preferences",
web::get().to(agent::notifications::get_preferences),
)
.route(
"/notifications/preferences",
web::put().to(agent::notifications::update_preferences),
)
.route(
"/notifications/test",
web::post().to(agent::notifications::send_test_notification),
)
.route(
"/sessions/{session_id}",
web::delete().to(agent::delete::handler),
)
.route("/health", web::get().to(agent::health::handler))
.route("/metrics/summary", web::get().to(agent::metrics::summary))
.route("/metrics/by-model", web::get().to(agent::metrics::by_model))
.route("/metrics/sessions", web::get().to(agent::metrics::sessions))
.route(
"/metrics/sessions/{session_id}",
web::get().to(agent::metrics::session_detail),
)
.route("/metrics/daily", web::get().to(agent::metrics::daily))
.route(
"/metrics/usage-breakdown",
web::get().to(agent::metrics::usage_breakdown),
)
.route(
"/metrics/memory/summary",
web::get().to(agent::metrics::memory_summary),
)
.route(
"/metrics/memory/timeline",
web::get().to(agent::metrics::memory_timeline),
)
.route(
"/metrics/forward/summary",
web::get().to(agent::metrics::forward_summary),
)
.route(
"/metrics/forward/by-endpoint",
web::get().to(agent::metrics::forward_by_endpoint),
)
.route(
"/metrics/forward/requests",
web::get().to(agent::metrics::forward_requests),
)
.route(
"/metrics/v2/summary",
web::get().to(agent::metrics::v2_unified_summary),
)
.route(
"/metrics/v2/timeline",
web::get().to(agent::metrics::v2_unified_timeline),
)
.service(mcp_scope());
if dev_endpoints_enabled() {
scope = scope.route("/dev/reset", web::post().to(agent::dev::reset));
}
cfg.service(scope);
cfg.route("/healthz", web::get().to(agent::health::healthz));
cfg.route("/readyz", web::get().to(agent::health::readyz));
let v2_scope = web::scope("/v2")
.wrap(actix_web::middleware::from_fn(
settings::enforce_access_password_middleware,
))
.route("/pair", web::post().to(settings::pair_device))
.route("/pair/code", web::post().to(settings::create_pairing_code))
.route("/devices", web::get().to(settings::list_devices))
.route(
"/devices/{device_id}",
web::delete().to(settings::revoke_device),
)
.route(
"/devices/{device_id}/rotate",
web::post().to(settings::rotate_device),
)
.route("/stream", web::get().to(agent::ws_v2::handler));
cfg.service(v2_scope);
}
fn dev_endpoints_enabled() -> bool {
cfg!(debug_assertions) || dev_endpoints_env_enabled()
}
fn dev_endpoints_env_enabled() -> bool {
std::env::var("BAMBOO_ENABLE_DEV_ENDPOINTS")
.map(|v| {
matches!(
v.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
})
.unwrap_or(false)
}
#[cfg(test)]
mod dev_gate_tests {
use super::dev_endpoints_env_enabled;
const VAR: &str = "BAMBOO_ENABLE_DEV_ENDPOINTS";
#[test]
fn dev_endpoints_env_gate_is_off_by_default_and_only_on_for_truthy_values() {
let saved = std::env::var(VAR).ok();
std::env::remove_var(VAR);
assert!(
!dev_endpoints_env_enabled(),
"unset -> dev endpoints OFF (production default)"
);
for off in ["0", "false", "no", "off", "", " ", "maybe"] {
std::env::set_var(VAR, off);
assert!(
!dev_endpoints_env_enabled(),
"{off:?} must NOT enable dev endpoints"
);
}
for on in [
"1", "true", "TRUE", "True", "yes", "Yes", "on", "ON", " on ",
] {
std::env::set_var(VAR, on);
assert!(
dev_endpoints_env_enabled(),
"{on:?} must enable dev endpoints"
);
}
match saved {
Some(v) => std::env::set_var(VAR, v),
None => std::env::remove_var(VAR),
}
}
}