magi-code 0.64.1

Repository-aware CLI coding agent for terminal work
Documentation
use crate::{
    output::{
        ActivityId, ActivityMetadata, pending_tool_display_summary, tool_display_label,
        tool_display_summary,
    },
    providers::ToolCall,
    tools::ToolResult,
};
use serde_json::json;

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(super) struct ToolCallFingerprint {
    id: String,
    name: String,
    arguments: String,
}

impl From<&ToolCall> for ToolCallFingerprint {
    fn from(call: &ToolCall) -> Self {
        Self {
            id: call.id.clone(),
            name: call.name.clone(),
            arguments: call.arguments.to_string(),
        }
    }
}

pub(super) fn function_call_item(call: &ToolCall) -> serde_json::Value {
    json!({
        "type": "function_call",
        "call_id": call.id.clone(),
        "name": call.name.clone(),
        "arguments": arguments_json_text(&call.arguments),
        "status": "completed",
    })
}

pub(super) fn arguments_json_text(arguments: &serde_json::Value) -> String {
    match arguments {
        serde_json::Value::String(text) => text.clone(),
        value => value.to_string(),
    }
}

pub(super) fn activity_id_for_call(
    iteration: usize,
    tool_index: usize,
    call: &ToolCall,
) -> ActivityId {
    if call.id.trim().is_empty() {
        ActivityId::new(format!("turn-{iteration}/tool-{tool_index}-{}", call.name))
    } else {
        ActivityId::new(call.id.clone())
    }
}

pub(super) fn tool_activity_metadata(
    call: &ToolCall,
    result: Option<&ToolResult>,
) -> ActivityMetadata {
    let summary = result
        .map(|result| tool_display_summary(call, result))
        .or_else(|| pending_tool_display_summary(call));
    let mut metadata = ActivityMetadata::new(
        summary
            .as_ref()
            .map(|summary| summary.label.clone())
            .unwrap_or_else(|| tool_display_label(call)),
    );
    if let Some(summary) = summary {
        metadata.fields = summary.metadata;
    }
    metadata
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fallback_activity_ids_include_per_call_index() {
        let first = ToolCall {
            id: String::new(),
            name: "read".to_string(),
            arguments: json!({}),
        };
        let second = ToolCall {
            id: String::new(),
            name: "read".to_string(),
            arguments: json!({}),
        };
        assert_eq!(
            activity_id_for_call(2, 0, &first).as_str(),
            "turn-2/tool-0-read"
        );
        assert_eq!(
            activity_id_for_call(2, 1, &second).as_str(),
            "turn-2/tool-1-read"
        );
    }

    #[test]
    fn running_write_activity_metadata_includes_content_bytes_without_content() {
        let call = ToolCall {
            id: "call_write".to_string(),
            name: "write".to_string(),
            arguments: json!({"path":"file.txt","content":"secret payload"}),
        };

        let metadata = tool_activity_metadata(&call, None);

        assert_eq!(metadata.label, "write file.txt");
        assert_eq!(
            metadata.fields,
            vec![("bytes".to_string(), "14".to_string())]
        );
        assert!(!format!("{metadata:?}").contains("secret payload"));
    }

    #[test]
    fn running_hash_edit_activity_metadata_includes_bytes_without_payloads() {
        let call = ToolCall {
            id: "call_hash_edit".to_string(),
            name: "hash_edit".to_string(),
            arguments: json!({"input":"[file.txt#ABCD]\nSWAP 1.=1:\n+other"}),
        };

        let metadata = tool_activity_metadata(&call, None);

        assert_eq!(metadata.label, "hash_edit");
        assert_eq!(
            metadata.fields,
            vec![("bytes".to_string(), "33".to_string())]
        );
        let debug = format!("{metadata:?}");
        assert!(!debug.contains("SWAP"));
        assert!(!debug.contains("secret"));
    }
}