Skip to main content

routa_server/api/
agents.rs

1use axum::{
2    extract::{Query, State},
3    routing::{get, post},
4    Json, Router,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use crate::error::ServerError;
10use crate::models::agent::{Agent, AgentRole, AgentStatus, ModelTier};
11use crate::state::AppState;
12
13pub fn router() -> Router<AppState> {
14    Router::new()
15        .route("/", get(list_agents).post(create_agent))
16        .route("/{id}", get(get_agent_by_path).delete(delete_agent))
17        .route("/{id}/status", post(update_agent_status))
18}
19
20#[derive(Debug, Deserialize)]
21#[serde(rename_all = "camelCase")]
22struct ListAgentsQuery {
23    /// If provided, returns a single agent (Next.js compatible: GET /api/agents?id=xxx)
24    id: Option<String>,
25    workspace_id: Option<String>,
26    role: Option<String>,
27    status: Option<String>,
28    parent_id: Option<String>,
29    #[allow(dead_code)]
30    summary: Option<String>,
31}
32
33async fn list_agents(
34    State(state): State<AppState>,
35    Query(query): Query<ListAgentsQuery>,
36) -> Result<Json<serde_json::Value>, ServerError> {
37    // Next.js compatible: GET /api/agents?id=xxx returns single agent
38    if let Some(id) = &query.id {
39        let agent = state.agent_store.get(id).await?;
40        return Ok(Json(serde_json::json!(agent)));
41    }
42
43    let workspace_id = query.workspace_id.as_deref().unwrap_or("default");
44
45    let agents = if let Some(parent_id) = &query.parent_id {
46        state.agent_store.list_by_parent(parent_id).await?
47    } else if let Some(role_str) = &query.role {
48        let role = AgentRole::from_str(role_str)
49            .ok_or_else(|| ServerError::BadRequest(format!("Invalid role: {role_str}")))?;
50        state.agent_store.list_by_role(workspace_id, &role).await?
51    } else if let Some(status_str) = &query.status {
52        let status = AgentStatus::from_str(status_str)
53            .ok_or_else(|| ServerError::BadRequest(format!("Invalid status: {status_str}")))?;
54        state
55            .agent_store
56            .list_by_status(workspace_id, &status)
57            .await?
58    } else {
59        state.agent_store.list_by_workspace(workspace_id).await?
60    };
61
62    Ok(Json(serde_json::json!({ "agents": agents })))
63}
64
65/// GET /api/agents/{id} — REST-style single agent lookup
66async fn get_agent_by_path(
67    State(state): State<AppState>,
68    axum::extract::Path(id): axum::extract::Path<String>,
69) -> Result<Json<Agent>, ServerError> {
70    state
71        .agent_store
72        .get(&id)
73        .await?
74        .map(Json)
75        .ok_or_else(|| ServerError::NotFound(format!("Agent {id} not found")))
76}
77
78#[derive(Debug, Deserialize)]
79#[serde(rename_all = "camelCase")]
80struct CreateAgentRequest {
81    name: String,
82    role: String,
83    workspace_id: Option<String>,
84    parent_id: Option<String>,
85    model_tier: Option<String>,
86    metadata: Option<HashMap<String, String>>,
87}
88
89#[derive(Debug, Serialize)]
90#[serde(rename_all = "camelCase")]
91struct CreateAgentResponse {
92    agent_id: String,
93    agent: Agent,
94}
95
96async fn create_agent(
97    State(state): State<AppState>,
98    Json(body): Json<CreateAgentRequest>,
99) -> Result<Json<CreateAgentResponse>, ServerError> {
100    let role = AgentRole::from_str(&body.role)
101        .ok_or_else(|| ServerError::BadRequest(format!("Invalid role: {}", body.role)))?;
102    let model_tier = body.model_tier.as_deref().and_then(ModelTier::from_str);
103    let workspace_id = body.workspace_id.unwrap_or_else(|| "default".to_string());
104
105    state.workspace_store.ensure_default().await?;
106
107    let agent = Agent::new(
108        uuid::Uuid::new_v4().to_string(),
109        body.name,
110        role,
111        workspace_id,
112        body.parent_id,
113        model_tier,
114        body.metadata,
115    );
116
117    state.agent_store.save(&agent).await?;
118
119    Ok(Json(CreateAgentResponse {
120        agent_id: agent.id.clone(),
121        agent,
122    }))
123}
124
125async fn delete_agent(
126    State(state): State<AppState>,
127    axum::extract::Path(id): axum::extract::Path<String>,
128) -> Result<Json<serde_json::Value>, ServerError> {
129    state.agent_store.delete(&id).await?;
130    Ok(Json(serde_json::json!({ "deleted": true })))
131}
132
133#[derive(Debug, Deserialize)]
134struct UpdateStatusRequest {
135    status: String,
136}
137
138async fn update_agent_status(
139    State(state): State<AppState>,
140    axum::extract::Path(id): axum::extract::Path<String>,
141    Json(body): Json<UpdateStatusRequest>,
142) -> Result<Json<serde_json::Value>, ServerError> {
143    let status = AgentStatus::from_str(&body.status)
144        .ok_or_else(|| ServerError::BadRequest(format!("Invalid status: {}", body.status)))?;
145    state.agent_store.update_status(&id, &status).await?;
146    Ok(Json(serde_json::json!({ "updated": true })))
147}