roboticus-api 0.11.3

HTTP routes, WebSocket, auth, rate limiting, and dashboard for the Roboticus agent runtime
Documentation
//! HTTP handler for the non-streaming agent message endpoint.
//!
//! Thin connector: parse → `run_pipeline()` → format JSON response.

use axum::extract::State;
use axum::response::IntoResponse;
use serde_json::json;

use super::pipeline::{
    PipelineConfig, PipelineError, PipelineOutcome, PipelineRequest, resolve_web_scope_hint,
    run_pipeline,
};
use super::{AgentMessageRequest, AppState};

pub async fn agent_message(
    State(state): State<AppState>,
    axum::Json(body): axum::Json<AgentMessageRequest>,
) -> Result<impl IntoResponse, PipelineError> {
    tracing::info!(channel = "api", session_id = ?body.session_id, "Processing agent message");

    let scope_hint = resolve_web_scope_hint(&state, &body).await?;

    let request = PipelineRequest {
        state: &state,
        config: PipelineConfig::api(),
        raw_content: &body.content,
        session_id_hint: body.session_id.as_deref(),
        scope_hint,
        is_correction_turn: false,
        channel_context: None,
        content_parts: None,
    };

    let outcome = run_pipeline(request).await?;

    let platform = body.channel.as_deref().unwrap_or("web").trim();
    let format_for_client =
        |text: &str| roboticus_channels::formatter::formatter_for(platform).format(text);

    // ── Format: convert PipelineOutcome to JSON ────────────────────
    match outcome {
        PipelineOutcome::Complete {
            session_id,
            user_message_id,
            result,
        } => Ok(axum::Json(json!({
            "session_id": session_id,
            "user_message_id": user_message_id,
            "assistant_message_id": result.assistant_message_id,
            "content": format_for_client(&result.content),
            "selected_model": result.selected_model,
            "model": result.model,
            "model_shift_from": result.model_shift_from,
            "cached": result.cached,
            "tokens_saved": result.tokens_saved,
            "tokens_in": result.tokens_in,
            "tokens_out": result.tokens_out,
            "cost": result.cost,
            "react_turns": result.react_turns,
        }))),
        PipelineOutcome::SpecialistProposal { session_id, prompt } => Ok(axum::Json(json!({
            "session_id": session_id,
            "content": format_for_client(&prompt),
            "decomposition": "requires_specialist_creation",
        }))),
        PipelineOutcome::StreamReady(_) => {
            // API endpoint should never get StreamReady — PipelineConfig::api()
            // uses InferenceMode::Standard. This is a programming error.
            Err(PipelineError::Internal(
                "unexpected streaming outcome on API endpoint".into(),
            ))
        }
    }
}