use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use anyhow::Result;
use serde_json::{Value, json};
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use uuid::Uuid;
use crate::brain::agent::{AgentService, QueuedUserMessage};
use crate::cli::session_resolve::resolve_or_create_session;
use crate::services::SessionService;
use super::protocol::{self, ClientMessage};
use super::transport::{Transport, TransportHandle};
use super::turn;
pub type SteerMap = Arc<Mutex<HashMap<Uuid, VecDeque<QueuedUserMessage>>>>;
pub fn new_steer_map() -> SteerMap {
Arc::new(Mutex::new(HashMap::new()))
}
pub struct SessionState {
pub id: Uuid,
pub model: Mutex<Option<String>>,
pub active_cancel: Mutex<Option<CancellationToken>>,
}
pub struct ServerState {
pub handle: TransportHandle,
pub agent: Arc<AgentService>,
pub sessions: SessionService,
pub states: Mutex<HashMap<String, Arc<SessionState>>>,
pub steer: SteerMap,
pub default_model: Option<String>,
}
pub struct AcpServer {
state: Arc<ServerState>,
transport: Transport,
}
impl AcpServer {
pub fn new(
agent: Arc<AgentService>,
sessions: SessionService,
default_model: Option<String>,
steer: SteerMap,
) -> Self {
let transport = Transport::spawn();
let state = Arc::new(ServerState {
handle: transport.handle(),
agent,
sessions,
states: Mutex::new(HashMap::new()),
steer,
default_model,
});
Self { state, transport }
}
pub async fn run(mut self) -> Result<()> {
tracing::info!("acp server: ready");
while let Some(msg) = self.transport.next().await {
match msg {
ClientMessage::Request { id, method, params } => {
Self::dispatch_request(self.state.clone(), id, &method, params).await;
}
ClientMessage::Notification { method, params } => {
Self::dispatch_notification(&self.state, &method, params).await;
}
ClientMessage::Response { .. } => {}
}
}
tracing::info!("acp server: stdin closed, exiting");
Ok(())
}
async fn dispatch_request(state: Arc<ServerState>, id: Value, method: &str, params: Value) {
match method {
protocol::INITIALIZE => {
state.handle.respond(id, protocol::initialize_result());
}
protocol::SESSION_NEW => {
Self::session_new(state, id, None).await;
}
protocol::SESSION_LOAD => {
let session_id = params
.get("sessionId")
.and_then(Value::as_str)
.map(str::to_string);
match session_id {
Some(sid) => Self::session_new(state, id, Some(&sid)).await,
None => state.handle.respond_error(
id,
protocol::INVALID_PARAMS,
"session/load requires sessionId",
),
}
}
protocol::SESSION_SET_MODEL | protocol::SESSION_SET_MODE => {
Self::session_set_model(state, id, params).await;
}
protocol::SESSION_PROMPT => {
Self::session_prompt(state, id, params).await;
}
other => {
state.handle.respond_error(
id,
protocol::METHOD_NOT_FOUND,
format!("method not found: {other}"),
);
}
}
}
async fn dispatch_notification(state: &Arc<ServerState>, method: &str, params: Value) {
match method {
protocol::SESSION_CANCEL => {
if let Some(st) = Self::lookup(state, ¶ms).await
&& let Some(token) = st.active_cancel.lock().await.as_ref()
{
token.cancel();
}
}
protocol::SESSION_STEER => {
if let (Some(st), Some(text)) = (
Self::lookup(state, ¶ms).await,
protocol::prompt_text(¶ms),
) {
state
.steer
.lock()
.await
.entry(st.id)
.or_default()
.push_back(QueuedUserMessage::plain(text));
}
}
other => {
tracing::debug!("acp server: ignoring unknown notification {other}");
}
}
}
async fn lookup(state: &Arc<ServerState>, params: &Value) -> Option<Arc<SessionState>> {
let sid = params.get("sessionId").and_then(Value::as_str)?;
state.states.lock().await.get(sid).cloned()
}
async fn session_new(state: Arc<ServerState>, id: Value, resume: Option<&str>) {
match resolve_or_create_session(&state.sessions, resume, "ACP").await {
Ok(session) => {
let acp_id = session.id.to_string();
let st = Arc::new(SessionState {
id: session.id,
model: Mutex::new(state.default_model.clone()),
active_cancel: Mutex::new(None),
});
state.states.lock().await.insert(acp_id.clone(), st);
state.handle.respond(id, json!({ "sessionId": acp_id }));
}
Err(e) => {
state
.handle
.respond_error(id, protocol::INVALID_PARAMS, format!("session: {e}"));
}
}
}
async fn session_set_model(state: Arc<ServerState>, id: Value, params: Value) {
let model = params
.get("modelId")
.or_else(|| params.get("modeId"))
.and_then(Value::as_str);
match (Self::lookup(&state, ¶ms).await, model) {
(Some(st), Some(model)) => {
*st.model.lock().await = Some(model.to_string());
state.handle.respond(id, json!({}));
}
(None, _) => state.handle.respond_error(
id,
protocol::INVALID_PARAMS,
"session/set_model: unknown session",
),
(_, None) => state.handle.respond_error(
id,
protocol::INVALID_PARAMS,
"session/set_model requires modelId (modeId accepted)",
),
}
}
async fn session_prompt(state: Arc<ServerState>, id: Value, params: Value) {
let Some(st) = Self::lookup(&state, ¶ms).await else {
state.handle.respond_error(
id,
protocol::INVALID_PARAMS,
"session/prompt: unknown session — call session/new first",
);
return;
};
let Some(text) = protocol::prompt_text(¶ms) else {
state.handle.respond_error(
id,
protocol::INVALID_PARAMS,
"session/prompt: empty prompt",
);
return;
};
let cancel = CancellationToken::new();
let mut guard = st.active_cancel.lock().await;
if guard.is_some() {
state.handle.respond_error(
id,
protocol::INVALID_REQUEST,
"turn already in progress for this session",
);
return;
}
*guard = Some(cancel.clone());
drop(guard);
tokio::spawn(turn::run_turn(state, st, id, text, cancel));
}
}