use super::*;
use super::super::protection::public_decision;
pub(super) fn humanize_protection_metadata(detail: Option<&ToolActivityDetail>) -> Vec<String> {
public_decision(detail, "humanize_protection")
.map(|(_, decision)| vec![format!("Jev: {decision}")])
.unwrap_or_default()
}
pub(super) fn prompt_injection_protection_metadata(
detail: Option<&ToolActivityDetail>,
) -> Option<String> {
let protection = &detail?.metadata["prompt_injection_protection"];
let action = match protection["action"].as_str()? {
"allow" => "allow",
"annotate" => "annotate",
"quarantine" => "quarantine",
"escalate" => "escalate",
_ => return None,
};
let mode = match protection["enforced"].as_bool() {
Some(true) => "enforced",
Some(false) => "observe-only",
None => "enforcement unavailable",
};
Some(format!("Jev: prompt injection: {action} • {mode}"))
}
pub(super) fn bash_jev_metadata(detail: Option<&ToolActivityDetail>) -> Vec<String> {
super::super::bash_notices(detail)
.rows
.into_iter()
.map(|notice| {
format!(
"Jev: {} • {}",
notice.label,
notice.value.as_deref().unwrap_or("not reported")
)
})
.collect()
}
pub(super) fn attempted_change_preview(value: Option<&serde_json::Value>) -> Vec<String> {
let Some(text) = value.and_then(serde_json::Value::as_str) else {
return Vec::new();
};
let mut lines = text
.lines()
.take(TOOL_PREVIEW_LINES)
.map(|line| truncate_preview_line(&sanitize_display_text(line), PREVIEW_LINE_MAX_CHARS))
.collect::<Vec<_>>();
if text.lines().count() > TOOL_PREVIEW_LINES {
lines.push(format!(
"… change preview limited to {TOOL_PREVIEW_LINES} lines"
));
}
lines
}
pub(super) fn bounded_preview_lines(text: &str) -> String {
let safe = sanitize_display_text(text);
let mut source = safe.lines();
let mut lines = source
.by_ref()
.take(TOOL_PREVIEW_LINES)
.map(|line| truncate_preview_line(line, PREVIEW_LINE_MAX_CHARS))
.collect::<Vec<_>>();
if source.next().is_some() {
lines.push(format!(
"… response preview limited to {TOOL_PREVIEW_LINES} lines"
));
}
lines.join("\n")
}
pub(in crate::output::tool_card) fn bounded_error_line(text: &str) -> String {
let line = text
.lines()
.find(|line| !line.trim().is_empty())
.unwrap_or("operation failed");
truncate_preview_line(&sanitize_display_text(line), PREVIEW_LINE_MAX_CHARS)
}
pub(super) fn tool_was_canceled(detail: Option<&crate::output::ToolActivityDetail>) -> bool {
detail.is_some_and(|detail| {
detail
.output
.lines()
.map(str::trim)
.any(|line| line == "prompt canceled")
})
}
pub(super) fn plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
if count == 1 { singular } else { plural }
}
pub(super) fn split_role_line(
text: &str,
split: usize,
prefix: TextMeaning,
suffix: TextMeaning,
) -> TextLine {
tool_display_line(vec![
TextSpan::new(&text[..split], prefix),
TextSpan::new(&text[split..], suffix),
])
}
pub(super) fn tool_display_line(spans: Vec<TextSpan>) -> TextLine {
use unicode_segmentation::UnicodeSegmentation;
let line = TextLine { spans };
let text = line.plain_text();
let mut boundary = 0;
for span in line.spans.iter().take(line.spans.len().saturating_sub(1)) {
boundary += span.text.len();
if boundary != text.len()
&& !text
.grapheme_indices(true)
.any(|(index, _)| index == boundary)
{
return TextLine::plain(text);
}
}
line
}