malvin 0.2.5

Non-interactive research and coding agent
use serde_json::Value;

use crate::tool_summary::{TOOL_DISPLAY_MAX_WIDTH, shorten_middle};
pub(super) fn tool_summary_from_pi(name: Option<&str>, args: Option<&Value>) -> Option<String> {
    let label = name.unwrap_or("tool").trim();
    if label.is_empty() {
        return None;
    }
    let n = label.to_ascii_lowercase();
    let args = args.and_then(Value::as_object);
    if n == "bash" || n == "shell" {
        return Some(bash_summary(args));
    }
    if let Some(path) = path_arg(args)
        && let Some(summary) = path_tool_summary(&n, &path)
    {
        return Some(summary);
    }
    Some(label.to_string())
}

fn bash_summary(args: Option<&serde_json::Map<String, Value>>) -> String {
    let cmd = args
        .and_then(|a| a.get("command").or_else(|| a.get("cmd")))
        .and_then(Value::as_str)
        .map(flatten_ws)
        .filter(|s| !s.is_empty());
    cmd.map_or_else(
        || "Run".into(),
        |c| format!("Run {}", shorten_middle(&c, TOOL_DISPLAY_MAX_WIDTH)),
    )
}

fn path_arg(args: Option<&serde_json::Map<String, Value>>) -> Option<String> {
    args.and_then(|a| {
        a.get("path")
            .or_else(|| a.get("file_path"))
            .or_else(|| a.get("filePath"))
    })
    .and_then(Value::as_str)
    .map(flatten_ws)
    .filter(|s| !s.is_empty())
}

fn path_tool_summary(n: &str, path: &str) -> Option<String> {
    let short = shorten_middle(path, TOOL_DISPLAY_MAX_WIDTH);
    if n == "read" || n.starts_with("read_") {
        return Some(format!("Read {short}"));
    }
    if n == "write" || n == "edit" || n.starts_with("write_") || n.starts_with("edit_") {
        return Some(format!("Edit {short}"));
    }
    None
}

pub(super) fn flatten_ws(s: &str) -> String {
    s.split_whitespace().collect::<Vec<_>>().join(" ")
}

#[cfg(test)]
mod flatten_ws_tests {
    use super::flatten_ws;

    #[test]
    fn flatten_ws_collapses_runs_of_whitespace() {
        assert_eq!(flatten_ws("echo   \t a\t\tb\n c"), "echo a b c");
    }

    #[test]
    fn flatten_ws_trims_and_handles_degenerate_inputs() {
        assert_eq!(flatten_ws("  padded  "), "padded");
        assert_eq!(flatten_ws("token"), "token");
        assert_eq!(flatten_ws(""), "");
        assert_eq!(flatten_ws(" \t\n "), "");
    }
}