crabmate 0.5.0

Rust AI agent: OpenAI-compatible chat/completions, function calling, HTTP serve, ops CLI
Documentation
//! `TurnRouteDecision` v1 金样:`fixtures/turn_route_decision_golden.jsonl`(经 [`assess_turn_routing`])。

use crate::cm_agent::agent_turn::{
    AssessTurnRoutingParams, TurnRouteDecisionV1, TurnRouteDriver, TurnStartSnapshot,
    assess_turn_routing,
};
use serde::Deserialize;
use serde_json::Value;
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Deserialize)]
struct GoldenLine {
    id: String,
    cfg_mode: String,
    turn_start: Value,
    expect: GoldenExpect,
}

#[derive(Debug, Deserialize)]
struct GoldenExpect {
    orchestration_mode: String,
    #[serde(default, alias = "freeform_because")]
    react_because: Option<Value>,
    #[serde(default)]
    driver: Option<String>,
}

fn cfg_with(mode: &str) -> crate::cm_config::AgentConfig {
    use crate::cm_config::PlannerExecutorMode;
    let pem = PlannerExecutorMode::parse(mode).expect("planner mode");
    let mut c = crate::cm_config::load_config(None).expect("embed default config");
    c.per_plan_policy.planner_executor_mode = pem;
    c
}

fn parse_turn_start(v: &Value) -> TurnStartSnapshot {
    let outcome = v["outcome"].as_str().expect("turn_start.outcome");
    match outcome {
        "disabled" => TurnStartSnapshot::Disabled,
        "empty_task" => TurnStartSnapshot::EmptyTask,
        "act_heuristics" => TurnStartSnapshot::ActHeuristics {
            review_readonly: v
                .get("review_readonly")
                .and_then(|x| x.as_bool())
                .unwrap_or(false),
        },
        other => panic!("unknown turn_start outcome {other}"),
    }
}

fn assert_freeform_because(decision: &TurnRouteDecisionV1, expect: &Value, ctx: &str) {
    match expect {
        Value::Null => assert!(decision.freeform_because.is_none(), "{ctx}"),
        Value::String(s) => {
            assert_eq!(
                decision.freeform_because.as_deref(),
                Some(s.as_str()),
                "{ctx}"
            )
        }
        other => panic!("{ctx}: unexpected freeform_because expect {other}"),
    }
}

#[test]
fn golden_turn_route_decision() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let path = root.join("fixtures/turn_route_decision_golden.jsonl");
    let raw = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));

    for (line_no, line) in raw.lines().enumerate() {
        let t = line.trim();
        if t.is_empty() || t.starts_with('#') {
            continue;
        }
        let row: GoldenLine = serde_json::from_str(t).unwrap_or_else(|e| {
            panic!("{}:{}: invalid json: {e}\n{t}", path.display(), line_no + 1)
        });
        let ctx = format!("{}:{} ({})", path.display(), line_no + 1, row.id);
        let cfg = cfg_with(&row.cfg_mode);
        let turn_start = parse_turn_start(&row.turn_start);

        let assessed = assess_turn_routing(AssessTurnRoutingParams {
            cfg: &cfg,
            turn_start: turn_start.clone(),
        });
        let decision = &assessed.decision;

        assert_eq!(decision.version, 1, "{ctx}");
        assert_eq!(
            decision.orchestration_mode, row.expect.orchestration_mode,
            "{ctx}"
        );
        assert_freeform_because(
            decision,
            &row.expect.react_because.clone().unwrap_or(Value::Null),
            &ctx,
        );
        if let Some(driver) = &row.expect.driver {
            match driver.as_str() {
                "react" => assert!(matches!(assessed.driver, TurnRouteDriver::ReAct), "{ctx}"),
                other => panic!("{ctx}: unknown driver expect {other}"),
            }
        }
        assert!(decision.to_json().expect("json").contains("\"version\":1"));
    }
}