use super::{ToolDispatchContext, ToolDispatchResult, ToolMiddleware, ToolPipeline};
use crate::tool::PermissionCheck;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
pub type PermissionCheckFn = Arc<dyn Fn(&ToolDispatchContext) -> PermissionCheck + Send + Sync>;
pub type AskResolverFn =
Arc<dyn Fn(&str, &str) -> Pin<Box<dyn Future<Output = bool> + Send>> + Send + Sync>;
pub struct PermissionMiddleware {
check_fn: Option<PermissionCheckFn>,
ask_resolver: Option<AskResolverFn>,
}
impl PermissionMiddleware {
#[must_use]
pub fn deny_all() -> Self {
Self {
check_fn: Some(Arc::new(|_| PermissionCheck::Deny {
reason: "blocked by policy".into(),
})),
ask_resolver: None,
}
}
#[must_use]
pub fn allow_all() -> Self {
Self {
check_fn: Some(Arc::new(|_| PermissionCheck::Allow)),
ask_resolver: None,
}
}
#[must_use]
pub fn with_check(
mut self,
f: impl Fn(&ToolDispatchContext) -> PermissionCheck + Send + Sync + 'static,
) -> Self {
self.check_fn = Some(Arc::new(f));
self
}
#[must_use]
pub fn from_context() -> Self {
Self {
check_fn: None,
ask_resolver: None,
}
}
#[must_use]
pub fn with_ask_resolver(mut self, resolver: AskResolverFn) -> Self {
self.ask_resolver = Some(resolver);
self
}
fn resolve_permission(&self, ctx: &ToolDispatchContext) -> PermissionCheck {
match &self.check_fn {
Some(f) => f(ctx),
None => ctx.permission.clone(),
}
}
}
impl ToolMiddleware for PermissionMiddleware {
fn name(&self) -> &'static str {
"permission"
}
fn dispatch<'a>(
&'a self,
ctx: &'a mut ToolDispatchContext,
next: &'a ToolPipeline,
) -> Pin<Box<dyn Future<Output = ToolDispatchResult> + Send + 'a>> {
let permission = self.resolve_permission(ctx);
match permission {
PermissionCheck::Allow => next.dispatch(ctx),
PermissionCheck::Modify { modified_input } => {
ctx.input = modified_input;
next.dispatch(ctx)
}
PermissionCheck::Deny { reason } => Self::deny(ctx, &reason),
PermissionCheck::Ask { prompt } => {
if let Some(resolver) = &self.ask_resolver {
let resolver = Arc::clone(resolver);
Box::pin(async move {
let tool_name = ctx.tool_name.clone();
let approved = resolver(&prompt, &tool_name);
if approved.await {
next.dispatch(ctx).await
} else {
ToolDispatchResult::err(
&tool_name,
format!("Permission denied by user for tool '{tool_name}'"),
Duration::ZERO,
)
}
})
} else {
tracing::warn!(
tool = %ctx.tool_name,
prompt = %prompt,
"permission Ask denied: no resolver configured"
);
Self::deny(ctx, &format!("permission required: {prompt}"))
}
}
}
}
}
impl PermissionMiddleware {
fn deny<'a>(
ctx: &'a mut ToolDispatchContext,
reason: &str,
) -> Pin<Box<dyn Future<Output = ToolDispatchResult> + Send + 'a>> {
let tool_name = ctx.tool_name.clone();
let reason = reason.to_string();
tracing::warn!(
tool = %tool_name,
permission = %reason,
"tool call blocked by permission middleware"
);
Box::pin(std::future::ready(ToolDispatchResult::err(
&tool_name,
format!("Permission {reason} for tool '{tool_name}'"),
Duration::ZERO,
)))
}
}