magi-code 0.103.0

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

pub(super) fn bash_card(input: &ToolCardInput<'_>) -> CardDraft {
    let detail = input.detail;
    let mut status = input_status(input);
    if status == CardStatus::Failed && tool_was_canceled(detail) {
        status = CardStatus::Canceled;
    }
    let timed_out = detail
        .and_then(|detail| detail.metadata.get("timed_out"))
        .and_then(serde_json::Value::as_bool)
        .unwrap_or(false);
    let exit_code = detail
        .and_then(|detail| detail.metadata.get("exit_code"))
        .and_then(serde_json::Value::as_i64);
    let command = input
        .bash_command
        .map(sanitize_display_text)
        .unwrap_or_default();
    let mut title = bash_title(status, timed_out, exit_code);
    if detail.is_some_and(|detail| {
        detail
            .params
            .get("background")
            .and_then(serde_json::Value::as_bool)
            == Some(true)
            || detail
                .metadata
                .get("job_id")
                .is_some_and(serde_json::Value::is_string)
    }) {
        title.push_str(" • background");
    }
    let intent = detail
        .and_then(|detail| detail.params.get(crate::tool_contract::arg_key::INTENT))
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .map(sanitized_single_line)
        .filter(|intent| !intent.is_empty());
    let mut body_rows = intent.iter().cloned().collect::<Vec<_>>();
    if !command.is_empty() {
        body_rows.push(command);
    }
    let mut job_rows = Vec::new();
    append_job_status(&mut job_rows, detail);
    body_rows.extend(job_rows.iter().cloned());
    let mut display_lines = job_rows
        .into_iter()
        .map(TextLine::plain)
        .collect::<Vec<_>>();
    let notices = super::super::bash_notices::bash_execution_notices(detail);
    body_rows.extend(notices.iter().map(|notice| {
        format!(
            "{} • {}",
            notice.label,
            notice.value.as_deref().unwrap_or("not reported")
        )
    }));
    let status_row = match status {
        CardStatus::Canceled => Some("canceled"),
        CardStatus::Failed if notices.iter().all(|notice| notice.value.is_none()) => {
            Some("command failed")
        }
        _ => None,
    };
    if let Some(status_row) = status_row {
        display_lines.push(TextLine::plain(status_row));
        body_rows.push(status_row.to_owned());
    }
    CardDraft {
        role: ToolCardRole::Bash,
        status,
        title,
        metadata: bash_jev_metadata(detail),
        body: body_rows.join("\n"),
        body_lines: display_lines,
        activity_rows: Vec::new(),
    }
}

fn bash_title(status: CardStatus, timed_out: bool, exit_code: Option<i64>) -> String {
    if timed_out {
        return "Bash • timed out".to_string();
    }
    if matches!(status, CardStatus::Success | CardStatus::Failed)
        && let Some(exit_code) = exit_code
    {
        format!("Bash • exit {exit_code}")
    } else if status == CardStatus::Canceled {
        "Bash • canceled".to_string()
    } else {
        "Bash".to_string()
    }
}

fn append_job_status(rows: &mut Vec<String>, detail: Option<&ToolActivityDetail>) {
    let Some(detail) = detail else {
        return;
    };
    if detail.metadata["job_id"].as_str().is_none() && detail.params["job_id"].as_str().is_none() {
        return;
    }
    for (key, label) in [
        ("job_id", "Job ID"),
        ("pid", "Process ID"),
        ("pgid", "Process group ID"),
        ("status", "Status"),
        ("exit_code", "Exit code"),
        ("stdout_bytes_dropped", "Standard output bytes dropped"),
        ("stderr_bytes_dropped", "Standard error bytes dropped"),
        ("cleanup_warning", "Cleanup warning"),
    ] {
        let value = detail.metadata.get(key).filter(|value| !value.is_null());
        let value = value.or_else(|| {
            if key == "job_id" {
                detail.params.get(key)
            } else {
                None
            }
        });
        if key.ends_with("_bytes_dropped") && value.and_then(serde_json::Value::as_u64) == Some(0) {
            continue;
        }
        if let Some(value) = value {
            let text = value
                .as_str()
                .map(str::to_owned)
                .unwrap_or_else(|| value.to_string());
            rows.push(sanitized_single_line(&format!("{label}: {text}")));
        }
    }
}

pub(super) fn job_card(input: &ToolCardInput<'_>) -> CardDraft {
    let action = input
        .detail
        .and_then(|detail| detail.params["action"].as_str());
    let title = match action {
        Some("output") => "Bash Job • Collecting job output",
        Some("status") => "Bash Job • Checking job status",
        Some("stop") => "Bash Job • Stopping job",
        Some("list") => "Bash Job • Listing jobs",
        _ => "Bash Job",
    };
    let mut rows = Vec::new();
    append_job_status(&mut rows, input.detail);
    if let Some(detail) = input.detail {
        if matches!(action, Some("output" | "stop")) {
            let mut has_output = false;
            for (key, label) in [("stdout", "Standard output"), ("stderr", "Standard error")] {
                if let Some(output) = detail.metadata[key]
                    .as_str()
                    .filter(|text| !text.is_empty())
                {
                    rows.push(format!("{label}:"));
                    rows.extend(bounded_preview_lines(output).lines().map(str::to_owned));
                    has_output = true;
                }
            }
            if !has_output && detail.metadata["stdout"].is_string() {
                rows.push("No new output".into());
            }
            if detail.metadata["output_truncated"] == true {
                rows.push("… job output truncated".into());
            }
            if detail.metadata["has_more_output"] == true {
                rows.push("More job output available".into());
            }
        }
        if input_status(input) == CardStatus::Failed {
            rows.push(bounded_error_line(&detail.output));
        }
    }
    let mut card = basic_card(ToolCardRole::Bash, title, &rows.join("\n"));
    card.body_lines = rows.into_iter().map(TextLine::plain).collect();
    card.status = input_status(input);
    card
}