use parking_lot::Mutex;
use std::collections::HashMap;
use tokio::sync::oneshot;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolApprovalResult {
Approved,
Denied,
}
struct PendingEntry {
tool_name: String,
grant_key: String,
sender: oneshot::Sender<ToolApprovalResult>,
}
#[derive(Default)]
pub struct PendingToolApprovals {
inner: Mutex<HashMap<Uuid, PendingEntry>>,
}
impl PendingToolApprovals {
pub fn new() -> Self {
Self::default()
}
pub fn register(
&self,
tool_name: String,
grant_key: String,
) -> (Uuid, oneshot::Receiver<ToolApprovalResult>) {
let id = Uuid::new_v4();
let (tx, rx) = oneshot::channel();
self.inner.lock().insert(
id,
PendingEntry {
tool_name,
grant_key,
sender: tx,
},
);
(id, rx)
}
pub fn resolve(&self, id: Uuid, result: ToolApprovalResult) -> Option<String> {
let entry = self.inner.lock().remove(&id)?;
let _ = entry.sender.send(result);
Some(entry.tool_name)
}
pub fn grant_key(&self, id: Uuid) -> Option<String> {
self.inner.lock().get(&id).map(|e| e.grant_key.clone())
}
pub fn cancel_all(&self) {
let mut guard = self.inner.lock();
for (_, entry) in guard.drain() {
let _ = entry.sender.send(ToolApprovalResult::Denied);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grant_key_round_trip() {
let registry = PendingToolApprovals::new();
let (id, rx) = registry.register("exec".to_string(), "exec:curl".to_string());
assert_eq!(registry.grant_key(id), Some("exec:curl".to_string()));
assert_eq!(
registry.resolve(id, ToolApprovalResult::Approved),
Some("exec".to_string())
);
let result = rx.blocking_recv().expect("oneshot did not deliver");
assert_eq!(result, ToolApprovalResult::Approved);
}
}