magi-code 0.96.2

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

pub(super) fn append_metadata_lines(lines: &mut Vec<String>, call: &ToolCall, result: &ToolResult) {
    match ToolCapability::from_dispatch_name(&call.name) {
        Some(ToolCapability::Read) => {
            push_read_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            push_read_arg_or_meta_string_array(
                lines,
                label::PATHS,
                &call.arguments,
                &result.metadata,
                arg_key::PATHS,
            );
            push_arg_or_meta_usize(
                lines,
                label::OFFSET,
                &call.arguments,
                &result.metadata,
                meta::OFFSET,
            );
            push_arg_or_meta_usize(
                lines,
                label::LIMIT,
                &call.arguments,
                &result.metadata,
                meta::LIMIT,
            );
        }
        Some(ToolCapability::ViewImage) => {
            push_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            push_meta_value(lines, label::PROVIDER, &result.metadata, meta::PROVIDER);
            push_meta_value(lines, label::MODEL, &result.metadata, meta::MODEL);
            push_meta_value(lines, label::MEDIA_TYPE, &result.metadata, meta::MEDIA_TYPE);
            push_meta_value(
                lines,
                label::IMAGE_BYTES,
                &result.metadata,
                meta::IMAGE_BYTES,
            );
            push_meta_value(lines, label::TRUNCATED, &result.metadata, meta::TRUNCATED);
        }
        Some(ToolCapability::Bash) => {
            for key in crate::tools::contract::BASH_COMMAND_ARG_KEYS {
                push_arg_string(lines, label::COMMAND, &call.arguments, key);
            }
            push_arg_or_meta_u64(
                lines,
                label::TIMEOUT,
                &call.arguments,
                &result.metadata,
                arg_key::TIMEOUT,
            );
            push_meta_value(lines, label::EXIT_CODE, &result.metadata, meta::EXIT_CODE);
            push_meta_value(lines, label::TIMED_OUT, &result.metadata, meta::TIMED_OUT);
            append_bash_protection_detail(lines, &result.metadata);
            if let Some(summary) = bash_file_tracking_summary(&result.metadata) {
                lines.push(format!("JEV: {summary}"));
            }
            push_meta_value(
                lines,
                label::CLEANUP_WARNING,
                &result.metadata,
                meta::CLEANUP_WARNING,
            );
        }
        Some(ToolCapability::HashEdit) => {
            if let Some(input) = call.arguments.get(arg_key::INPUT).and_then(Value::as_str) {
                lines.push(format!("{}: {}", label::CONTENT_BYTES, input.len()));
            } else {
                push_meta_value(lines, label::CONTENT_BYTES, &result.metadata, "input_bytes");
            }
        }
        Some(ToolCapability::Write) => {
            push_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            if let Some(content) = call.arguments.get(arg_key::CONTENT).and_then(Value::as_str) {
                lines.push(format!("{}: {}", label::CONTENT_BYTES, content.len()));
            } else {
                push_meta_value(lines, label::CONTENT_BYTES, &result.metadata, meta::BYTES);
            }
        }
        Some(ToolCapability::Grep) => {
            push_grep_patterns(lines, &call.arguments);
            push_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            push_arg_or_meta_usize(
                lines,
                label::LIMIT,
                &call.arguments,
                &result.metadata,
                meta::LIMIT,
            );
            push_arg_or_meta_usize(
                lines,
                label::CONTEXT,
                &call.arguments,
                &result.metadata,
                arg_key::CONTEXT,
            );
            push_meta_value(lines, label::ENGINE, &result.metadata, meta::ENGINE);
            push_meta_value(lines, label::EXIT_CODE, &result.metadata, meta::EXIT_CODE);
        }
        Some(ToolCapability::AstGrep) => {
            push_arg_string(lines, label::PATTERN, &call.arguments, arg_key::PATTERN);
            push_arg_string(lines, label::LANGUAGE, &call.arguments, arg_key::LANGUAGE);
            push_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            push_arg_or_meta_string(
                lines,
                label::REWRITE,
                &call.arguments,
                &result.metadata,
                meta::REWRITE,
            );
            push_arg_or_meta_usize(
                lines,
                label::LIMIT,
                &call.arguments,
                &result.metadata,
                meta::LIMIT,
            );
            push_meta_value(lines, label::EXIT_CODE, &result.metadata, meta::EXIT_CODE);
        }
        Some(ToolCapability::Find) => {
            push_arg_string(lines, label::QUERY, &call.arguments, arg_key::QUERY);
            push_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            push_arg_or_meta_usize(
                lines,
                label::LIMIT,
                &call.arguments,
                &result.metadata,
                meta::LIMIT,
            );
            push_arg_or_meta_usize(
                lines,
                label::OFFSET,
                &call.arguments,
                &result.metadata,
                meta::OFFSET,
            );
            push_arg_or_meta_string(
                lines,
                label::KIND,
                &call.arguments,
                &result.metadata,
                meta::KIND,
            );
            push_meta_value(lines, label::ENGINE, &result.metadata, meta::ENGINE);
            push_meta_value(
                lines,
                label::TOTAL_MATCHED,
                &result.metadata,
                meta::TOTAL_MATCHED,
            );
            push_meta_value(lines, label::TRUNCATED, &result.metadata, meta::TRUNCATED);
            push_meta_value(
                lines,
                label::INDEX_READY,
                &result.metadata,
                meta::INDEX_READY,
            );
        }
        Some(ToolCapability::ListFiles) => {
            push_arg_string(lines, label::PATH, &call.arguments, arg_key::PATH);
            push_meta_value(lines, label::FILES, &result.metadata, meta::FILES);
            push_meta_value(
                lines,
                label::DIRECTORIES,
                &result.metadata,
                meta::DIRECTORIES,
            );
            push_meta_value(lines, label::TRUNCATED, &result.metadata, meta::TRUNCATED);
        }
        Some(ToolCapability::Subagents) => {
            if let Some(tasks) = call.arguments.get(arg_key::TASKS).and_then(Value::as_array) {
                lines.push(format!("{}: {}", label::TASK_COUNT, tasks.len()));
                let ids = (1..=tasks.len())
                    .map(|index| format!("g{index}"))
                    .collect::<Vec<_>>()
                    .join(", ");
                if !ids.is_empty() {
                    lines.push(format!("{}: {ids}", label::TASK_IDS));
                }
                if let Some(identities) = subagent_identity_list(tasks) {
                    lines.push(format!("{}: {identities}", label::IDENTITIES));
                }
            }
            push_arg_or_meta_usize(
                lines,
                label::CONCURRENCY,
                &call.arguments,
                &result.metadata,
                meta::CONCURRENCY,
            );
            if let Some(summary) = result.metadata.get(meta::SUMMARY) {
                push_meta_value(lines, label::TOTAL_TOKENS, summary, meta::TOTAL_TOKENS);
            }
        }
        Some(ToolCapability::Web) => {
            push_arg_string(lines, "OPERATION", &call.arguments, "operation");
            push_arg_string(lines, "URL", &call.arguments, "url");
            push_arg_string(lines, "REFERENCE", &call.arguments, "reference");
            push_arg_string(lines, label::QUERY, &call.arguments, arg_key::QUERY);
            push_meta_value(lines, label::RESULTS, &result.metadata, meta::RESULTS);
            push_meta_value(lines, label::TRUNCATED, &result.metadata, meta::TRUNCATED);
        }
        Some(ToolCapability::MagiControl) => {
            push_arg_string(lines, "ACTION", &call.arguments, arg_key::ACTION);
        }
        None => append_safe_scalar_arguments(lines, &call.arguments),
    }
}

fn push_arg_string(lines: &mut Vec<String>, label: &str, args: &Value, key: &str) {
    if let Some(value) = args.get(key).and_then(Value::as_str) {
        lines.push(format!("{label}: {}", redact_sensitive_text(value)));
    }
}

fn push_read_arg_string(lines: &mut Vec<String>, label: &str, args: &Value, key: &str) {
    if let Some(value) = args.get(key).and_then(Value::as_str) {
        lines.push(format!("{label}: {}", sanitize_read_target(value)));
    }
}

fn push_read_arg_or_meta_string_array(
    lines: &mut Vec<String>,
    label: &str,
    args: &Value,
    meta: &Value,
    key: &str,
) {
    let values = args
        .get(key)
        .or_else(|| meta.get(key))
        .and_then(Value::as_array)
        .map(|values| {
            values
                .iter()
                .filter_map(Value::as_str)
                .map(sanitize_read_target)
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    if !values.is_empty() {
        lines.push(format!("{label}: {}", values.join(", ")));
    }
}

fn push_grep_patterns(lines: &mut Vec<String>, args: &Value) {
    let patterns = args
        .get(arg_key::PATTERNS)
        .and_then(Value::as_array)
        .map(|values| {
            values
                .iter()
                .filter_map(Value::as_str)
                .map(redact_sensitive_text)
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    if !patterns.is_empty() {
        lines.push(format!("{}: {}", label::PATTERN, patterns.join(", ")));
    } else {
        push_arg_string(lines, label::PATTERN, args, arg_key::PATTERN);
    }
}

pub(super) fn grep_pattern_label(call: &ToolCall) -> String {
    let patterns = call
        .arguments
        .get(arg_key::PATTERNS)
        .and_then(Value::as_array)
        .map(|values| values.iter().filter_map(Value::as_str).collect::<Vec<_>>())
        .filter(|patterns| !patterns.is_empty())
        .map(|patterns| patterns.join(", "))
        .or_else(|| string_arg(&call.arguments, arg_key::PATTERN));
    patterns
        .map(|patterns| format!("{} {patterns}", call.name))
        .unwrap_or_else(|| call.name.clone())
}

fn push_arg_or_meta_usize(
    lines: &mut Vec<String>,
    label: &str,
    args: &Value,
    meta: &Value,
    key: &str,
) {
    if let Some(value) = args.get(key).and_then(Value::as_u64) {
        lines.push(format!("{label}: {value}"));
    } else {
        push_meta_value(lines, label, meta, key);
    }
}

fn push_arg_or_meta_u64(
    lines: &mut Vec<String>,
    label: &str,
    args: &Value,
    meta: &Value,
    key: &str,
) {
    push_arg_or_meta_usize(lines, label, args, meta, key);
}

fn push_arg_or_meta_string(
    lines: &mut Vec<String>,
    label: &str,
    args: &Value,
    meta: &Value,
    key: &str,
) {
    if let Some(value) = args.get(key).and_then(Value::as_str) {
        lines.push(format!("{label}: {}", redact_sensitive_text(value)));
    } else {
        push_meta_value(lines, label, meta, key);
    }
}

fn push_meta_value(lines: &mut Vec<String>, label: &str, meta: &Value, key: &str) {
    let Some(value) = meta.get(key) else {
        return;
    };
    if value.is_null() {
        return;
    }
    match value {
        Value::String(text) => lines.push(format!("{label}: {}", redact_sensitive_text(text))),
        Value::Bool(value) => lines.push(format!("{label}: {value}")),
        Value::Number(value) => lines.push(format!("{label}: {value}")),
        _ => {}
    }
}

fn append_safe_scalar_arguments(lines: &mut Vec<String>, args: &Value) {
    let Some(object) = args.as_object() else {
        return;
    };
    for (key, value) in object {
        if is_redacted_metadata_key(key) {
            continue;
        }
        match value {
            Value::String(text) => lines.push(format!(
                "{}: {}",
                key.to_uppercase(),
                redact_sensitive_text(text)
            )),
            Value::Bool(value) => lines.push(format!("{}: {value}", key.to_uppercase())),
            Value::Number(value) => lines.push(format!("{}: {value}", key.to_uppercase())),
            _ => {}
        }
    }
}

fn is_redacted_metadata_key(key: &str) -> bool {
    let normalized = key
        .chars()
        .filter(|character| {
            character.is_ascii_alphanumeric() || *character == '_' || *character == '-'
        })
        .flat_map(char::to_lowercase)
        .collect::<String>();
    matches!(
        normalized.as_str(),
        key if REDACTED_METADATA_KEYS.contains(&key)
    ) || is_credential_like_key(&normalized)
}