magi-code 0.103.0

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;
pub(super) fn list_files_card(input: &ToolCardInput<'_>) -> CardDraft {
    use crate::tool_contract::{arg_key, metadata_key};
    let detail = input.detail;
    let body = input.body;
    let mut status = input_status(input);
    if status == CardStatus::Failed && tool_was_canceled(detail) {
        status = CardStatus::Canceled;
    }
    let metadata = detail.map(|detail| &detail.metadata);
    let params = detail.map(|detail| &detail.params);
    let path = if status == CardStatus::Success {
        metadata
            .and_then(|value| value.get(metadata_key::PATH))
            .and_then(serde_json::Value::as_str)
            .or_else(|| {
                params
                    .and_then(|value| value.get(arg_key::PATH))
                    .and_then(serde_json::Value::as_str)
            })
    } else {
        params
            .and_then(|value| value.get(arg_key::PATH))
            .and_then(serde_json::Value::as_str)
            .or_else(|| {
                metadata
                    .and_then(|value| value.get(metadata_key::PATH))
                    .and_then(serde_json::Value::as_str)
            })
    }
    .unwrap_or(".");
    let path = truncate_preview_line(&sanitized_single_line(path), PREVIEW_LINE_MAX_CHARS);
    let files = list_files_count(metadata, metadata_key::FILES);
    let directories = list_files_count(metadata, metadata_key::DIRECTORIES);
    let include_directories = params
        .and_then(|value| value.get(arg_key::INCLUDE_DIRECTORIES))
        .and_then(serde_json::Value::as_bool)
        .unwrap_or(false);
    let includes_directories = include_directories || directories.is_some_and(|count| count > 0);
    let truncated = metadata
        .and_then(|value| value.get(metadata_key::TRUNCATED))
        .and_then(serde_json::Value::as_bool)
        .unwrap_or(false);
    let returned = files
        .zip(directories)
        .map(|(files, directories)| files.saturating_add(directories));
    let title = match status {
        CardStatus::Success if truncated => returned.map_or_else(
            || "List Files • entries returned".to_string(),
            |returned| format!("List Files • {returned} entries returned"),
        ),
        CardStatus::Success if includes_directories => returned.map_or_else(
            || "List Files".to_string(),
            |returned| format!("List Files • {returned} entries"),
        ),
        CardStatus::Success => files.map_or_else(
            || "List Files".to_string(),
            |files| format!("List Files • {files} {}", plural(files, "file", "files")),
        ),
        CardStatus::Canceled => "List Files • canceled".to_string(),
        _ => "List Files".to_string(),
    };
    let mut lines = vec![path];
    match status {
        CardStatus::Running => {
            if include_directories {
                lines.push("include directories".to_string());
            }
        }
        CardStatus::Success => {
            let output_lines = detail
                .map(|detail| {
                    detail
                        .output
                        .lines()
                        .filter(|line| !line.is_empty())
                        .collect::<Vec<_>>()
                })
                .unwrap_or_default();
            lines.extend(output_lines.iter().take(TOOL_PREVIEW_LINES).map(|line| {
                truncate_preview_line(&sanitized_single_line(line), PREVIEW_LINE_MAX_CHARS)
            }));
            if output_lines.len() > TOOL_PREVIEW_LINES {
                lines.push(format!("… preview limited to {TOOL_PREVIEW_LINES} lines"));
            }
            if truncated {
                lines.push("… output capped at 1000 entries".to_string());
            }
            if output_lines.is_empty() && returned == Some(0) {
                lines.push("0 entries".to_string());
            }
        }
        CardStatus::Failed => lines.push(bounded_error_line(
            detail.map_or(body, |detail| detail.output.as_ref()),
        )),
        CardStatus::Canceled => lines.push("Canceled before completion".to_string()),
        CardStatus::None => {}
    }
    CardDraft {
        role: ToolCardRole::ListFiles,
        status,
        title,
        metadata: Vec::new(),
        body: lines.join("\n"),
        body_lines: Vec::new(),
        activity_rows: Vec::new(),
    }
}

fn list_files_count(metadata: Option<&serde_json::Value>, key: &str) -> Option<usize> {
    metadata
        .and_then(|value| value.get(key))
        .and_then(serde_json::Value::as_u64)
        .and_then(|value| usize::try_from(value).ok())
}