magi-code 0.102.0

Repository-aware CLI coding agent for terminal work
Documentation
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 }
}

// These spans describe already-sanitized adapter text, not source previews.
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)
        {
            // Keep combining sequences intact rather than splitting them between styles.
            return TextLine::plain(text);
        }
    }
    line
}