use agent_client_protocol_schema::ToolCallId;
use dashmap::DashMap;
use tokio::sync::oneshot;
use tokio_util::sync::CancellationToken;
use crate::event::PermissionResolution;
#[derive(Default)]
pub struct PermissionGate {
waiters: DashMap<ToolCallId, oneshot::Sender<PermissionResolution>>,
}
impl PermissionGate {
pub fn new() -> Self {
Self::default()
}
pub async fn wait(&self, id: ToolCallId, cancel: CancellationToken) -> PermissionResolution {
let (tx, rx) = oneshot::channel();
if let Some(prev) = self.waiters.insert(id.clone(), tx) {
tracing::warn!(
tool_call_id = %id,
"PermissionGate::wait called twice for same id; cancelling previous waiter"
);
let _ = prev.send(PermissionResolution::Cancelled);
}
tokio::select! {
biased;
() = cancel.cancelled() => {
self.waiters.remove(&id);
PermissionResolution::Cancelled
}
recv = rx => match recv {
Ok(outcome) => outcome,
Err(_) => PermissionResolution::Cancelled,
}
}
}
pub fn resolve(&self, id: &ToolCallId, outcome: PermissionResolution) {
if let Some((_, tx)) = self.waiters.remove(id) {
let _ = tx.send(outcome);
}
}
}
#[cfg(test)]
mod tests;