use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use agent_client_protocol::schema::v1::SessionId;
use crate::mode::{ApprovalMode, SessionModes};
use basis::{PreparedRun, run::TurnOptions};
use mentra::runtime::CancellationToken;
#[derive(Clone)]
pub struct AcpSession {
run: Arc<tokio::sync::Mutex<PreparedRun>>,
cancel: Arc<Mutex<Option<CancellationToken>>>,
modes: SessionModes,
id: SessionId,
}
impl AcpSession {
pub fn new(run: PreparedRun, initial_mode: ApprovalMode) -> Self {
Self {
id: SessionId::new(run.agent_id().to_string()),
run: Arc::new(tokio::sync::Mutex::new(run)),
cancel: Arc::new(Mutex::new(None)),
modes: SessionModes::new(initial_mode),
}
}
pub fn id(&self) -> SessionId {
self.id.clone()
}
pub fn modes(&self) -> &SessionModes {
&self.modes
}
pub async fn lock_turn(&self) -> tokio::sync::MutexGuard<'_, PreparedRun> {
self.run.lock().await
}
pub fn begin_turn(&self) -> TurnOptions {
let (options, token) = TurnOptions::cancellable();
*self.cancel_slot() = Some(token);
options
}
pub fn end_turn(&self) {
*self.cancel_slot() = None;
}
pub fn cancel(&self) -> bool {
match self.cancel_slot().take() {
Some(token) => {
token.cancel();
true
}
None => false,
}
}
fn cancel_slot(&self) -> std::sync::MutexGuard<'_, Option<CancellationToken>> {
self.cancel
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
#[derive(Clone, Default)]
pub struct SessionRegistry {
sessions: Arc<Mutex<HashMap<SessionId, AcpSession>>>,
}
impl SessionRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&self, session: AcpSession) -> SessionId {
let id = session.id();
self.lock().insert(id.clone(), session);
id
}
pub fn get(&self, id: &SessionId) -> Option<AcpSession> {
self.lock().get(id).cloned()
}
pub fn remove(&self, id: &SessionId) -> Option<AcpSession> {
self.lock().remove(id)
}
pub fn len(&self) -> usize {
self.lock().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<SessionId, AcpSession>> {
self.sessions
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unknown_session_is_not_found() {
let registry = SessionRegistry::new();
assert!(registry.is_empty());
assert!(registry.get(&SessionId::new("nobody")).is_none());
assert!(registry.remove(&SessionId::new("nobody")).is_none());
}
#[test]
fn clones_share_one_map() {
let registry = SessionRegistry::new();
let clone = registry.clone();
assert_eq!(registry.len(), clone.len());
assert!(clone.is_empty());
}
}