use std::future::Future;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookPermissionOverride {
Allow,
}
tokio::task_local! {
static HOOK_PERMISSION_OVERRIDE: Option<ScopedHookPermissionOverride>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ScopedHookPermissionOverride {
tool_call_id: String,
decision: HookPermissionOverride,
}
pub async fn with_hook_permission_override<F, T>(
permission_override: Option<HookPermissionOverride>,
tool_call_id: &str,
future: F,
) -> T
where
F: Future<Output = T>,
{
let scoped_override = permission_override.map(|decision| ScopedHookPermissionOverride {
tool_call_id: tool_call_id.to_string(),
decision,
});
HOOK_PERMISSION_OVERRIDE
.scope(scoped_override, future)
.await
}
pub fn current_hook_permission_override(tool_call_id: &str) -> Option<HookPermissionOverride> {
HOOK_PERMISSION_OVERRIDE
.try_with(|value| {
value
.as_ref()
.and_then(|scoped| (scoped.tool_call_id == tool_call_id).then_some(scoped.decision))
})
.ok()
.flatten()
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn override_is_scoped_to_one_future() {
assert_eq!(current_hook_permission_override("call-1"), None);
with_hook_permission_override(Some(HookPermissionOverride::Allow), "call-1", async {
assert_eq!(
current_hook_permission_override("call-1"),
Some(HookPermissionOverride::Allow)
);
assert_eq!(
current_hook_permission_override("nested-call"),
None,
"a nested or unrelated call must not inherit the override"
);
})
.await;
assert_eq!(current_hook_permission_override("call-1"), None);
}
}