oxios_kernel/tools/
pending_tool_approvals.rs1use parking_lot::Mutex;
10use std::collections::HashMap;
11use tokio::sync::oneshot;
12use uuid::Uuid;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ToolApprovalResult {
17 Approved,
19 Denied,
21}
22
23struct PendingEntry {
24 tool_name: String,
25 grant_key: String,
26 sender: oneshot::Sender<ToolApprovalResult>,
27}
28
29#[derive(Default)]
31pub struct PendingToolApprovals {
32 inner: Mutex<HashMap<Uuid, PendingEntry>>,
33}
34
35impl PendingToolApprovals {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn register(
44 &self,
45 tool_name: String,
46 grant_key: String,
47 ) -> (Uuid, oneshot::Receiver<ToolApprovalResult>) {
48 let id = Uuid::new_v4();
49 let (tx, rx) = oneshot::channel();
50 self.inner.lock().insert(
51 id,
52 PendingEntry {
53 tool_name,
54 grant_key,
55 sender: tx,
56 },
57 );
58 (id, rx)
59 }
60
61 pub fn resolve(&self, id: Uuid, result: ToolApprovalResult) -> Option<String> {
64 let entry = self.inner.lock().remove(&id)?;
65 let _ = entry.sender.send(result);
66 Some(entry.tool_name)
67 }
68 pub fn grant_key(&self, id: Uuid) -> Option<String> {
69 self.inner.lock().get(&id).map(|e| e.grant_key.clone())
70 }
71
72 pub fn cancel_all(&self) {
74 let mut guard = self.inner.lock();
75 for (_, entry) in guard.drain() {
76 let _ = entry.sender.send(ToolApprovalResult::Denied);
77 }
78 }
79}
80#[cfg(test)]
81mod tests {
82 use super::*;
83 #[test]
84 fn grant_key_round_trip() {
85 let registry = PendingToolApprovals::new();
86 let (id, rx) = registry.register("exec".to_string(), "exec:curl".to_string());
87 assert_eq!(registry.grant_key(id), Some("exec:curl".to_string()));
88 assert_eq!(
89 registry.resolve(id, ToolApprovalResult::Approved),
90 Some("exec".to_string())
91 );
92 let result = rx.blocking_recv().expect("oneshot did not deliver");
93 assert_eq!(result, ToolApprovalResult::Approved);
94 }
95}