magi-code 0.96.5

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

pub(super) fn read_card(input: &ToolCardInput<'_>) -> CardDraft {
    let detail = input.detail;
    let status = input_status(input);
    let targets = detail
        .and_then(|detail| read_targets(&detail.params))
        .unwrap_or_default();
    let results = detail
        .and_then(|detail| detail.metadata.get("results"))
        .and_then(serde_json::Value::as_array);
    let total = read_usize(detail, "files")
        .unwrap_or_else(|| results.map_or(targets.len(), Vec::len).max(targets.len()));
    let succeeded = read_usize(detail, "succeeded").or_else(|| {
        results
            .filter(|items| items.iter().all(|item| item["success"].as_bool().is_some()))
            .map(|items| {
                items
                    .iter()
                    .filter(|item| item["success"].as_bool() == Some(true))
                    .count()
            })
    });
    let incomplete = read_usize(detail, "failed").is_some_and(|count| count > 0)
        || read_usize(detail, "omitted").is_some_and(|count| count > 0)
        || succeeded.is_some_and(|count| count < total);
    let multi = total > 1;
    let title = if multi {
        match status {
            CardStatus::Success if incomplete && succeeded.is_some() => {
                format!("Read • {}/{total} resources", succeeded.unwrap_or_default())
            }
            _ => format!("Read • {total} resources"),
        }
    } else {
        "Read".to_string()
    };
    let card_body = if status == CardStatus::Canceled {
        targets
            .iter()
            .cloned()
            .chain(std::iter::once("Canceled before completion".to_string()))
            .collect::<Vec<_>>()
            .join("\n")
    } else if status == CardStatus::Running {
        targets.join("\n")
    } else if multi {
        read_multi_body(&targets, results)
    } else {
        let target = targets.first().cloned().unwrap_or_default();
        if status == CardStatus::Failed {
            let error = detail
                .and_then(read_single_error)
                .unwrap_or_else(|| "read failed".to_string());
            format!("{target}\n{error}")
        } else if status == CardStatus::Success {
            format_read_resource(&target, read_line_count(detail))
        } else {
            target
        }
    };
    let card_body = sanitize_display_controls(&card_body);
    let body_lines = read_body_lines(&card_body, &targets, status, multi);
    CardDraft {
        role: ToolCardRole::Read,
        status,
        title,
        metadata: Vec::new(),
        body: card_body,
        body_lines,
        activity_rows: Vec::new(),
    }
}

fn read_targets(params: &serde_json::Value) -> Option<Vec<String>> {
    params
        .get("paths")
        .and_then(serde_json::Value::as_array)
        .map(|paths| {
            paths
                .iter()
                .filter_map(serde_json::Value::as_str)
                .map(sanitize_read_target)
                .collect()
        })
        .or_else(|| {
            params
                .get("path")
                .and_then(serde_json::Value::as_str)
                .map(|path| vec![sanitize_read_target(path)])
        })
}

fn sanitize_read_target(text: &str) -> String {
    sanitize_display_controls(text)
        .chars()
        .filter(|character| !character.is_control())
        .collect()
}

fn read_multi_body(targets: &[String], results: Option<&Vec<serde_json::Value>>) -> String {
    targets
        .iter()
        .enumerate()
        .map(|(index, target)| {
            let result = results.and_then(|items| items.get(index));
            match result.and_then(|item| item["success"].as_bool()) {
                Some(true) => format!(
                    "✓ {}",
                    format_read_resource(target, result.and_then(read_result_line_count))
                ),
                Some(false) => format!(
                    "✗ {target}\n  {}",
                    result
                        .and_then(|item| item["error"].as_str())
                        .and_then(read_first_non_empty_line)
                        .unwrap_or_else(|| "read failed".to_string())
                ),
                None => format!("○ {target}\n  result unavailable"),
            }
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn format_read_resource(target: &str, line_count: Option<usize>) -> String {
    line_count.map_or_else(
        || target.to_string(),
        |count| {
            format!(
                "{target} • {count} {}",
                if count == 1 { "line" } else { "lines" }
            )
        },
    )
}

fn read_result_line_count(result: &serde_json::Value) -> Option<usize> {
    result
        .get("hashline_seen_lines")
        .and_then(serde_json::Value::as_array)
        .map(Vec::len)
}

fn read_usize(detail: Option<&crate::output::ToolActivityDetail>, key: &str) -> Option<usize> {
    detail?
        .metadata
        .get(key)?
        .as_u64()
        .and_then(|value| usize::try_from(value).ok())
}

fn read_single_error(detail: &crate::output::ToolActivityDetail) -> Option<String> {
    detail
        .metadata
        .get("error")
        .and_then(serde_json::Value::as_str)
        .and_then(read_first_non_empty_line)
        .or_else(|| read_first_non_empty_line(detail.output.as_ref()))
}

fn read_first_non_empty_line(text: &str) -> Option<String> {
    sanitize_display_controls(text)
        .lines()
        .map(str::trim)
        .find(|line| !line.is_empty())
        .map(str::to_string)
}

fn read_line_count(detail: Option<&crate::output::ToolActivityDetail>) -> Option<usize> {
    detail
        .and_then(|detail| {
            detail.metadata.get("hashline_seen_lines").or_else(|| {
                detail
                    .metadata
                    .get("results")
                    .and_then(serde_json::Value::as_array)
                    .and_then(|results| results.first())
                    .and_then(|result| result.get("hashline_seen_lines"))
            })
        })
        .and_then(serde_json::Value::as_array)
        .map(Vec::len)
}

fn read_body_lines(
    body: &str,
    targets: &[String],
    status: CardStatus,
    multi: bool,
) -> Vec<TextLine> {
    body.lines()
        .enumerate()
        .map(|(index, line)| {
            let bare_target_row = index == 0
                || (matches!(status, CardStatus::Running | CardStatus::Canceled)
                    && index < targets.len());
            if bare_target_row
                && let Some(target) = targets
                    .iter()
                    .filter(|target| {
                        line == target.as_str() || line.starts_with(&format!("{target} • "))
                    })
                    .max_by_key(|target| target.len())
            {
                return read_resource_display_line(line, target, None);
            }
            if multi
                && let Some((marker, resource)) = line.split_once(' ')
                && matches!(marker, "✓" | "✗" | "○")
                && let Some(target) = targets
                    .iter()
                    .filter(|target| {
                        resource == target.as_str() || resource.starts_with(&format!("{target} • "))
                    })
                    .max_by_key(|target| target.len())
            {
                return read_resource_display_line(line, target, Some(marker));
            }
            TextLine::plain(line)
        })
        .collect()
}

fn read_resource_display_line(line: &str, target: &str, marker: Option<&str>) -> TextLine {
    let mut spans = Vec::new();
    let target_start = marker.map_or(0, |marker| {
        spans.push(TextSpan::new(
            format!("{marker} "),
            TextMeaning::OutcomeMark,
        ));
        marker.len() + 1
    });
    spans.push(TextSpan::new(target, TextMeaning::Resource));
    let suffix = &line[target_start + target.len()..];
    if let Some((prefix, count_and_unit)) = suffix.split_once('•') {
        spans.push(TextSpan::new(prefix, TextMeaning::Plain));
        spans.push(TextSpan::new("• ", TextMeaning::Separator));
        if let Some((count, unit)) = count_and_unit.trim_start().split_once(' ') {
            spans.push(TextSpan::new(count, TextMeaning::Count));
            spans.push(TextSpan::new(format!(" {unit}"), TextMeaning::Plain));
        }
    }
    tool_display_line(spans)
}