crabmate 0.4.0

Rust AI agent: OpenAI-compatible chat/completions, function calling, HTTP serve, ops CLI
Documentation
//! 调试辅助工具:Rust panic/backtrace 解析

use std::collections::BTreeMap;

use crate::cm_tools::tools::tool_param_types::BacktraceAnalyzeArgs;

fn collect_backtrace_frame(
    line: &str,
    crate_hint: Option<&str>,
    frame_hits: &mut Vec<String>,
    module_count: &mut BTreeMap<String, usize>,
) {
    if !line.contains("::") {
        return;
    }
    if let Some(hint) = crate_hint {
        if !line.contains(hint) {
            return;
        }
    } else if is_noise_frame(line) {
        return;
    }
    frame_hits.push(line.to_string());
    let module = line
        .split("::")
        .take(2)
        .collect::<Vec<_>>()
        .join("::")
        .trim()
        .to_string();
    if !module.is_empty() {
        *module_count.entry(module).or_insert(0) += 1;
    }
}

fn format_backtrace_analysis(
    frame_hits: &[String],
    module_count: BTreeMap<String, usize>,
) -> String {
    if frame_hits.is_empty() {
        return "未识别到可分析的业务调用栈帧。可尝试传入 crate_hint(如你的 crate 名)。"
            .to_string();
    }

    let first = frame_hits.first().cloned().unwrap_or_default();
    let mut top_modules = module_count.into_iter().collect::<Vec<_>>();
    top_modules.sort_by_key(|b| std::cmp::Reverse(b.1));

    let mut out = String::new();
    out.push_str("backtrace 分析结果:\n");
    out.push_str(&format!("- 首个可疑业务帧: {}\n", first));
    out.push_str("- 主要模块命中:\n");
    for (name, count) in top_modules.into_iter().take(5) {
        out.push_str(&format!("  - {}: {}\n", name, count));
    }
    out.push_str(
        "- 建议: 优先从首个可疑业务帧对应函数开始排查参数、unwrap、索引越界和并发共享状态。",
    );
    out
}

pub fn rust_backtrace_analyze(args_json: &str) -> String {
    let parsed = match crate::cm_tools::tools::parse_args_json(args_json) {
        Ok(v) => v,
        Err(e) => return e,
    };
    let args: BacktraceAnalyzeArgs = match serde_json::from_value(parsed) {
        Ok(a) => a,
        Err(e) => return format!("参数解析错误: {e}"),
    };
    let text = match args.backtrace.trim() {
        s if !s.is_empty() => s,
        _ => return "错误:缺少 backtrace 参数".to_string(),
    };
    let crate_hint = args
        .crate_hint
        .as_deref()
        .map(str::trim)
        .filter(|s| !s.is_empty());

    let mut frame_hits: Vec<String> = Vec::new();
    let mut module_count: BTreeMap<String, usize> = BTreeMap::new();
    for line in text.lines() {
        let l = line.trim();
        if l.is_empty() {
            continue;
        }
        collect_backtrace_frame(l, crate_hint, &mut frame_hits, &mut module_count);
    }

    format_backtrace_analysis(&frame_hits, module_count)
}

fn is_noise_frame(line: &str) -> bool {
    let noise = [
        "std::",
        "core::",
        "tokio::",
        "alloc::",
        "panic_unwind",
        "backtrace",
    ];
    noise.iter().any(|n| line.contains(n))
}