Skip to main content

routa_server/api/
acp_docker.rs

1//! Docker-based ACP agent API routes.
2//!
3//! Provides endpoints for Docker environment status, image management,
4//! and Docker-based agent execution.
5
6use axum::{
7    extract::{Query, State},
8    routing::{get, post},
9    Json, Router,
10};
11use serde::{Deserialize, Serialize};
12
13use crate::error::ServerError;
14use crate::state::AppState;
15use routa_core::acp::docker::{DockerContainerConfig, DockerStatus};
16
17pub fn router() -> Router<AppState> {
18    Router::new()
19        .route("/status", get(docker_status))
20        .route("/pull", post(docker_pull))
21        .route("/containers", get(list_containers))
22        .route("/container/start", post(start_container))
23        .route("/container/stop", post(stop_container))
24}
25
26/// Query params for Docker status.
27#[derive(Debug, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct DockerStatusQuery {
30    #[serde(default)]
31    force_refresh: bool,
32}
33
34/// GET /api/acp/docker/status — Check Docker availability.
35async fn docker_status(
36    State(state): State<AppState>,
37    Query(query): Query<DockerStatusQuery>,
38) -> Result<Json<DockerStatus>, ServerError> {
39    let status = state
40        .docker_state
41        .detector
42        .check_availability(query.force_refresh)
43        .await;
44
45    Ok(Json(status))
46}
47
48/// Request body for Docker image pull.
49#[derive(Debug, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct DockerPullRequest {
52    pub image: String,
53}
54
55/// POST /api/acp/docker/pull — Pull a Docker image.
56async fn docker_pull(
57    State(state): State<AppState>,
58    Json(body): Json<DockerPullRequest>,
59) -> Result<Json<serde_json::Value>, ServerError> {
60    let result = state.docker_state.detector.pull_image(&body.image).await;
61
62    Ok(Json(serde_json::json!({
63        "ok": result.ok,
64        "image": result.image,
65        "output": result.output,
66        "error": result.error,
67    })))
68}
69
70/// GET /api/acp/docker/containers — List running Docker containers.
71async fn list_containers(
72    State(state): State<AppState>,
73) -> Result<Json<serde_json::Value>, ServerError> {
74    let containers = state.docker_state.process_manager.list_containers().await;
75
76    Ok(Json(serde_json::json!({
77        "containers": containers,
78    })))
79}
80
81/// Request body for starting a Docker container.
82#[derive(Debug, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct StartContainerRequest {
85    pub session_id: String,
86    pub image: String,
87    pub workspace_path: String,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub auth_json: Option<String>,
90}
91
92/// POST /api/acp/docker/container/start — Start a Docker container for an agent session.
93async fn start_container(
94    State(state): State<AppState>,
95    Json(body): Json<StartContainerRequest>,
96) -> Result<Json<serde_json::Value>, ServerError> {
97    let config = DockerContainerConfig {
98        session_id: body.session_id.clone(),
99        image: body.image,
100        workspace_path: body.workspace_path,
101        env: None,
102        additional_volumes: None,
103        labels: None,
104        container_port: None,
105        auth_json: body.auth_json,
106    };
107
108    match state
109        .docker_state
110        .process_manager
111        .start_container(config)
112        .await
113    {
114        Ok(info) => Ok(Json(serde_json::json!({
115            "ok": true,
116            "container": info,
117        }))),
118        Err(e) => Ok(Json(serde_json::json!({
119            "ok": false,
120            "error": e,
121        }))),
122    }
123}
124
125/// Request body for stopping a Docker container.
126#[derive(Debug, Deserialize, Serialize)]
127#[serde(rename_all = "camelCase")]
128pub struct StopContainerRequest {
129    pub session_id: String,
130}
131
132/// POST /api/acp/docker/container/stop — Stop a Docker container.
133async fn stop_container(
134    State(state): State<AppState>,
135    Json(body): Json<StopContainerRequest>,
136) -> Result<Json<serde_json::Value>, ServerError> {
137    match state
138        .docker_state
139        .process_manager
140        .stop_container(&body.session_id)
141        .await
142    {
143        Ok(()) => Ok(Json(serde_json::json!({ "ok": true }))),
144        Err(e) => Ok(Json(serde_json::json!({ "ok": false, "error": e }))),
145    }
146}