use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PublicProtectionDecision {
NotReported,
Allowed,
Blocked,
ApprovalRequired,
Denied,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PublicProtection {
pub(crate) decision: PublicProtectionDecision,
pub(crate) summary: Option<String>,
}
pub(super) fn public_decision(
detail: Option<&ToolActivityDetail>,
key: &str,
) -> Option<(PublicProtectionDecision, &'static str)> {
use PublicProtectionDecision::*;
Some(match detail?.metadata[key]["decision"].as_str()? {
"allowed" => (Allowed, "allowed"),
"allowed automatically" => (Allowed, "allowed automatically"),
"allowed by user" => (Allowed, "allowed by user"),
"allowed by API failure policy" => (Allowed, "allowed by API failure policy"),
"denied by API failure policy" => (Denied, "denied by API failure policy"),
"denied by user" => (Denied, "denied by user"),
"denied; approval unavailable" => (Denied, "denied; approval unavailable"),
"denied; needs humanizing" => (Denied, "denied; needs humanizing"),
"approval required" => (ApprovalRequired, "approval required"),
"no added prose" => (Allowed, "no added prose"),
"no comments or documentation" => (Allowed, "no comments or documentation"),
_ => return None,
})
}
pub(super) fn public_protection(
input: &ToolCardInput<'_>,
summaries: &[String],
) -> PublicProtection {
use PublicProtectionDecision::*;
let mut decision = NotReported;
if ToolCapability::from_dispatch_name(input.tool_name).is_some()
|| input.tool_name == crate::code_mode::TOOL_NAME
{
decision = ["bash_protection", "humanize_protection"]
.into_iter()
.filter_map(|key| public_decision(input.detail, key))
.map(|(decision, _)| decision)
.max_by_key(|decision| match decision {
NotReported => 0,
Allowed => 1,
ApprovalRequired => 2,
Blocked => 3,
Denied => 4,
})
.unwrap_or(NotReported);
if let Some(detail) = input.detail
&& detail.metadata["prompt_injection_protection"]["enforced"].as_bool() == Some(true)
&& detail.metadata["prompt_injection_protection"]["action"].as_str()
== Some("quarantine")
&& decision != Denied
{
decision = Blocked;
}
}
let summary = summaries
.iter()
.filter(|line| line.starts_with("Jev: ") && !line.starts_with("Jev: file tracking"))
.cloned()
.collect::<Vec<_>>()
.join(" • ");
PublicProtection {
decision,
summary: (!summary.is_empty()).then_some(summary),
}
}