use crate::agent_events::SideEffectCeilingDetails;
pub(super) fn denied_tool_result(tool_name: &str, reason: impl Into<String>) -> serde_json::Value {
let reason = reason.into();
let allowed: Vec<String> = crate::orchestration::current_allowed_tool_names()
.into_iter()
.filter(|name| name != tool_name)
.collect();
let available_clause = if allowed.is_empty() {
String::new()
} else {
format!(" Available tools: {}.", allowed.join(", "))
};
let next_step = format!(
"The `{tool_name}` tool is not permitted right now. Do not retry the same call. \
Make progress with the tools you are allowed to use, or if this capability is \
essential, briefly tell the user what you need permission for and why.\
{available_clause}"
);
serde_json::json!({
"error": "permission_denied",
"tool": tool_name,
"reason": reason,
"next_step": next_step,
})
}
pub(super) fn side_effect_ceiling_tool_result(
tool_name: &str,
reason: impl Into<String>,
details: &SideEffectCeilingDetails,
) -> serde_json::Value {
let reason = reason.into();
let next_step = format!(
"`{tool_name}` requires side-effect level `{}`, but this session permits only through `{}`. \
Do not retry the same call. Choose a non-mutating approach, or ask the operator to raise \
the session side-effect ceiling to `{}` before retrying.",
details.required_level.as_str(),
details.ceiling.as_str(),
details.required_level.as_str(),
);
serde_json::json!({
"error": "permission_denied",
"tool": tool_name,
"reason": reason,
"next_step": next_step,
})
}
#[cfg(test)]
mod tests {
use super::denied_tool_result;
use crate::orchestration::{pop_execution_policy, push_execution_policy, CapabilityPolicy};
#[test]
fn a_denied_tool_is_excluded_from_its_own_available_list() {
push_execution_policy(CapabilityPolicy {
tools: vec!["denied".into(), "usable".into()],
..Default::default()
});
let result = denied_tool_result("denied", "blocked by approval policy");
pop_execution_policy();
let available = result["next_step"]
.as_str()
.unwrap()
.split_once("Available tools:")
.unwrap()
.1;
assert!(!available.contains("denied"));
assert!(available.contains("usable"));
}
}