magi-code 0.102.2

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;
use crate::output::tool_card::{
    self, CardStatus, ChildToolFacts, TextLine, TextMeaning, ToolBlock, ToolCardInput, ToolCardRole,
};
use crate::rendering::{DisplayRole, DisplaySpan, highlight::highlight_code};

pub(super) fn project_tool_card(
    state: &MissionControlState,
    id: TranscriptCardId,
    link: Option<TranscriptActivityLink>,
    body: &str,
) -> TranscriptCard {
    let node = link
        .as_ref()
        .and_then(|link| state.nodes.get(&link.activity_id));
    let tool_name = link
        .as_ref()
        .map(|link| link.tool_name.as_str())
        .or_else(|| transcript_tool_name(state, id.entry_index))
        .unwrap_or("Tool");
    let capability = ToolCapability::from_dispatch_name(tool_name);
    let children: Vec<_> = node
        .and_then(|node| state.children.get(&node.id))
        .into_iter()
        .flatten()
        .filter_map(|id| state.nodes.get(id))
        .filter(|node| node.kind == ActivityKind::Tool)
        .map(|child| ChildToolFacts {
            id: &child.id,
            status: child.status,
            tool_name: child.transcript_tool_name.as_deref(),
            detail: child.tool_detail.as_ref(),
            safe_target: (capability.is_none()
                && ToolCapability::from_dispatch_name(
                    child.transcript_tool_name.as_deref().unwrap_or("tool"),
                )
                .is_some())
            .then(|| {
                project_activity_card(state, child)
                    .body
                    .lines()
                    .next()
                    .unwrap_or_default()
                    .to_string()
            }),
        })
        .collect();
    let status = node.map(|node| node.status).or_else(|| {
        state
            .transcript
            .get(id.entry_index)
            .and_then(TranscriptEntry::tool_data)
            .and_then(|data| data.identity.status())
    });
    let bash_mode_response = is_bash_mode_response_card(state, id.entry_index, capability);
    let mut shared = tool_card::project_tool_card(&ToolCardInput {
        tool_name,
        detail: node.and_then(|node| node.tool_detail.as_ref()),
        status,
        interrupted: false,
        bash_command: node.and_then(|node| node.transcript_bash_command.as_deref()),
        bash_mode_response,
        preview: node
            .map(|node| node.preview.as_str())
            .filter(|preview| !preview.trim().is_empty())
            .unwrap_or(body),
        body,
        children: &children,
        children_complete: false,
    });
    if shared.role == ToolCardRole::ViewImage {
        let source = shared.blocks.iter().find_map(|block| match block {
            ToolBlock::Results { rows } => rows
                .iter()
                .find(|row| row.label == "Image source")
                .and_then(|row| row.value.as_deref()),
            _ => None,
        });
        if let Some(source) = source {
            let status = shared.title.strip_prefix("View Image").unwrap_or_default();
            shared.title = format!("View Image • {source}{status}");
        }
        shared.metadata.retain(|metadata| {
            metadata.starts_with("Jev:")
                || matches!(
                    metadata.as_str(),
                    "response truncated" | "preview truncated"
                )
        });
    }
    let role = match shared.role {
        ToolCardRole::Bash => TranscriptCardRole::Bash,
        ToolCardRole::CodeMode => TranscriptCardRole::CodeMode,
        ToolCardRole::MagiControl => TranscriptCardRole::MagiControl,
        ToolCardRole::Read => TranscriptCardRole::Read,
        ToolCardRole::Grep => TranscriptCardRole::Grep,
        ToolCardRole::AstGrep => TranscriptCardRole::AstGrep,
        ToolCardRole::Find => TranscriptCardRole::Find,
        ToolCardRole::ListFiles => TranscriptCardRole::ListFiles,
        ToolCardRole::Web => TranscriptCardRole::Web,
        ToolCardRole::Tool => TranscriptCardRole::Tool,
        ToolCardRole::Write => TranscriptCardRole::Write,
        ToolCardRole::HashEdit => TranscriptCardRole::HashEdit,
        ToolCardRole::SubagentBatch => TranscriptCardRole::SubagentBatch,
        ToolCardRole::ViewImage => TranscriptCardRole::ViewImage,
    };
    let children = if role == TranscriptCardRole::SubagentBatch {
        subagent_aggregation::subagent_children(
            state,
            link.as_ref().map(|link| &link.activity_id),
            id.entry_index,
        )
    } else {
        Vec::new()
    };
    let subagent_card = (role == TranscriptCardRole::SubagentBatch).then(|| {
        subagent_aggregation::subagent_card_summary_from_children(
            state,
            link.as_ref().map(|link| &link.activity_id),
            &children,
            node,
        )
    });
    let body_lines: Vec<_> = shared
        .blocks
        .iter()
        .flat_map(|block| render_block(block, shared.role))
        .collect();
    let body = if matches!(
        shared.role,
        ToolCardRole::Bash | ToolCardRole::CodeMode | ToolCardRole::Read
    ) {
        // Keep the plain-text fallback and copy source aligned with visible rows.
        body_lines
            .iter()
            .map(DisplayLine::plain_text)
            .collect::<Vec<_>>()
            .join("\n")
    } else {
        shared.body
    };
    let intent = (!bash_mode_response)
        .then(|| {
            node?.tool_detail.as_ref()?.params[crate::tools::contract::arg_key::INTENT].as_str()
        })
        .flatten()
        .map(str::trim)
        .filter(|intent| !intent.is_empty())
        .map(sanitized_single_line);
    TranscriptCard {
        id,
        role,
        status: terminal_status(shared.status),
        title: shared.title,
        metadata: shared.metadata,
        intent,
        body,
        body_lines,
        children,
        activity_rows: shared
            .activity_rows
            .into_iter()
            .map(|row| TranscriptCardActivityRow {
                label: row.label,
                status: terminal_status(row.status),
                activity_id: row.activity_id,
            })
            .collect(),
        subagent_card,
    }
}

pub(super) fn terminal_status(status: CardStatus) -> TranscriptCardStatus {
    match status {
        CardStatus::Running => TranscriptCardStatus::Running,
        CardStatus::Success => TranscriptCardStatus::Success,
        CardStatus::Failed => TranscriptCardStatus::Failed,
        CardStatus::Canceled => TranscriptCardStatus::Canceled,
        CardStatus::None => TranscriptCardStatus::None,
    }
}

fn render_block(block: &ToolBlock, role: ToolCardRole) -> Vec<DisplayLine> {
    match block {
        ToolBlock::Text { lines } | ToolBlock::Diff { lines, .. } => lines
            .iter()
            .filter(|line| {
                !line
                    .spans
                    .iter()
                    .any(|span| span.meaning == TextMeaning::Metadata)
            })
            .map(render_line)
            .collect(),
        ToolBlock::Results { .. }
            if matches!(
                role,
                ToolCardRole::Bash
                    | ToolCardRole::CodeMode
                    | ToolCardRole::Read
                    | ToolCardRole::Find
                    | ToolCardRole::Grep
                    | ToolCardRole::ListFiles
                    | ToolCardRole::ViewImage
            ) =>
        {
            Vec::new()
        }
        ToolBlock::Results { rows } => rows
            .iter()
            .filter(|row| {
                role != ToolCardRole::MagiControl
                    || !matches!(
                        row.label.as_str(),
                        "Control operation"
                            | "Context used tokens"
                            | "Context maximum tokens"
                            | "Context used percent"
                    )
            })
            .map(|row| {
                DisplayLine::plain(format!(
                    "{} • {}",
                    row.label,
                    row.value.as_deref().unwrap_or("not reported")
                ))
            })
            .collect(),
        ToolBlock::Resources { rows } => rows.iter().map(|row| render_line(&row.line)).collect(),
        ToolBlock::Command {
            command: Some(command),
            ..
        } => highlight_code(command, Some("bash")),
        ToolBlock::Command { command: None, .. } | ToolBlock::Progress { .. } => Vec::new(),
    }
}

fn render_line(line: &TextLine) -> DisplayLine {
    DisplayLine {
        spans: line
            .spans
            .iter()
            .map(|span| {
                DisplaySpan::new(
                    &span.text,
                    match span.meaning {
                        TextMeaning::Plain | TextMeaning::Metadata => DisplayRole::Plain,
                        TextMeaning::Resource => DisplayRole::InlineCode,
                        TextMeaning::Pattern => DisplayRole::String,
                        TextMeaning::Count => DisplayRole::Number,
                        TextMeaning::Separator => DisplayRole::Punctuation,
                        TextMeaning::OutcomeMark => DisplayRole::ListMarker,
                        TextMeaning::Added => DisplayRole::DiffInserted,
                        TextMeaning::Removed => DisplayRole::DiffRemoved,
                    },
                )
            })
            .collect(),
        table: None,
    }
}