Skip to main content

apollo/
agent_http.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
5use axum::extract::State;
6use axum::http::{Method, StatusCode};
7use axum::routing::{get, post};
8use axum::{Json, Router};
9use serde::{Deserialize, Serialize};
10use tokio::sync::mpsc;
11use tower_http::cors::{Any, CorsLayer};
12
13use crate::agent::stream::AgentStreamEvent;
14use crate::agent::AgentRunner;
15use crate::channels::http_inject::HttpInjectChannel;
16use crate::channels::IncomingMessage;
17
18#[derive(Debug, Deserialize)]
19pub struct ChatRequestBody {
20    pub message: String,
21    #[serde(default = "default_chat_id")]
22    pub chat_id: String,
23}
24
25fn default_chat_id() -> String {
26    "embed".into()
27}
28
29#[derive(Debug, Serialize)]
30pub struct ChatResponseBody {
31    pub response: String,
32}
33
34pub async fn chat_once(
35    runner: &AgentRunner,
36    message: &str,
37    chat_id: &str,
38) -> anyhow::Result<String> {
39    let msg = IncomingMessage {
40        id: uuid::Uuid::new_v4().to_string(),
41        sender_id: "http".into(),
42        sender_name: Some("HTTP".into()),
43        chat_id: chat_id.to_string(),
44        text: message.to_string(),
45        is_group: false,
46        reply_to: None,
47        timestamp: chrono::Utc::now(),
48    };
49    let channel = HttpInjectChannel::new();
50    runner.handle_message(&msg, &channel).await
51}
52
53pub fn http_listen_addr() -> SocketAddr {
54    let port = std::env::var("APOLLO_HTTP_PORT")
55        .ok()
56        .and_then(|p| p.parse().ok())
57        .unwrap_or(31338);
58    SocketAddr::from(([127, 0, 0, 1], port))
59}
60
61fn cors_layer() -> CorsLayer {
62    CorsLayer::new()
63        .allow_origin(Any)
64        .allow_methods([Method::GET, Method::POST, Method::OPTIONS])
65        .allow_headers(Any)
66}
67
68pub fn spawn_http_server(runner: Arc<AgentRunner>) {
69    let addr = http_listen_addr();
70    tokio::spawn(async move {
71        let app = Router::new()
72            .route("/health", get(|| async { "ok" }))
73            .route("/v1/chat", post(chat_handler))
74            .route("/v1/chat/stream", get(ws_chat_upgrade))
75            .layer(cors_layer())
76            .with_state(runner);
77        let listener = match tokio::net::TcpListener::bind(addr).await {
78            Ok(l) => l,
79            Err(e) => {
80                tracing::error!("apollo http bind {}: {}", addr, e);
81                return;
82            }
83        };
84        tracing::info!(
85            "apollo agent HTTP http://{}/v1/chat · WS /v1/chat/stream",
86            addr
87        );
88        if let Err(e) = axum::serve(listener, app).await {
89            tracing::error!("apollo http server: {}", e);
90        }
91    });
92}
93
94async fn chat_handler(
95    State(runner): State<Arc<AgentRunner>>,
96    Json(body): Json<ChatRequestBody>,
97) -> Result<Json<ChatResponseBody>, StatusCode> {
98    if body.message.trim().is_empty() {
99        return Err(StatusCode::BAD_REQUEST);
100    }
101    match chat_once(&runner, body.message.trim(), &body.chat_id).await {
102        Ok(response) => Ok(Json(ChatResponseBody { response })),
103        Err(e) => {
104            tracing::error!("http chat: {}", e);
105            Err(StatusCode::INTERNAL_SERVER_ERROR)
106        }
107    }
108}
109
110async fn ws_chat_upgrade(
111    ws: WebSocketUpgrade,
112    State(runner): State<Arc<AgentRunner>>,
113) -> impl axum::response::IntoResponse {
114    ws.on_upgrade(move |socket| handle_ws_chat(socket, runner))
115}
116
117async fn handle_ws_chat(mut socket: WebSocket, runner: Arc<AgentRunner>) {
118    let Some(Ok(Message::Text(text))) = socket.recv().await else {
119        return;
120    };
121    let Ok(body) = serde_json::from_str::<ChatRequestBody>(&text) else {
122        let _ = socket
123            .send(Message::Text(
124                serde_json::json!({"type":"error","message":"invalid JSON"}).to_string(),
125            ))
126            .await;
127        return;
128    };
129    if body.message.trim().is_empty() {
130        let _ = socket
131            .send(Message::Text(
132                serde_json::json!({"type":"error","message":"empty message"}).to_string(),
133            ))
134            .await;
135        return;
136    }
137
138    let (stream_tx, mut stream_rx) = mpsc::unbounded_channel::<AgentStreamEvent>();
139    let runner_bg = Arc::clone(&runner);
140    let message = body.message.trim().to_string();
141    let chat_id = body.chat_id.clone();
142    let mut chat_task = tokio::spawn(async move {
143        runner_bg.set_stream_sink(Some(stream_tx));
144        let result = chat_once(&runner_bg, &message, &chat_id).await;
145        runner_bg.set_stream_sink(None);
146        result
147    });
148
149    loop {
150        tokio::select! {
151            Some(ev) = stream_rx.recv() => {
152                if let Ok(json) = serde_json::to_string(&ev) {
153                    if socket.send(Message::Text(json)).await.is_err() {
154                        return;
155                    }
156                }
157            }
158            result = &mut chat_task => {
159                match result {
160                    Ok(Ok(response)) => {
161                        let payload = serde_json::to_string(&AgentStreamEvent::Done {
162                            response: response.clone(),
163                        })
164                        .unwrap_or_else(|_| {
165                            serde_json::json!({"type":"done","response": response}).to_string()
166                        });
167                        let _ = socket.send(Message::Text(payload)).await;
168                    }
169                    Ok(Err(e)) => {
170                        let payload = serde_json::to_string(&AgentStreamEvent::Error {
171                            message: e.to_string(),
172                        })
173                        .unwrap_or_else(|_| {
174                            serde_json::json!({"type":"error","message": e.to_string()}).to_string()
175                        });
176                        let _ = socket.send(Message::Text(payload)).await;
177                    }
178                    Err(e) => {
179                        let payload = serde_json::json!({"type":"error","message": e.to_string()});
180                        let _ = socket.send(Message::Text(payload.to_string())).await;
181                    }
182                }
183                break;
184            }
185        }
186    }
187
188    while let Ok(ev) = stream_rx.try_recv() {
189        if let Ok(json) = serde_json::to_string(&ev) {
190            let _ = socket.send(Message::Text(json)).await;
191        }
192    }
193}