use crate::runner::{invoke, InvokeOptions, InvokeResult};
use car_verify::goal::{run_goal_loop, GoalInputs, GoalRun, GoalSpec, IterationOutcome};
use std::future::Future;
pub fn invoke_result_to_outcome(r: &InvokeResult) -> IterationOutcome {
IterationOutcome {
cost_usd: r.total_cost_usd.unwrap_or(0.0),
made_progress: !r.is_error && r.tool_calls > 0,
}
}
pub async fn run_external_goal_loop<Gather, GF>(
adapter_id: &str,
base_opts: InvokeOptions,
spec: &GoalSpec,
gather: Gather,
) -> GoalRun
where
Gather: FnMut() -> GF,
GF: Future<Output = GoalInputs>,
{
let id = adapter_id.to_string();
run_goal_loop(
spec,
move |directive| {
let id = id.clone();
let opts = base_opts.clone();
async move {
match invoke(&id, &directive, opts).await {
Ok(r) => invoke_result_to_outcome(&r),
Err(_) => IterationOutcome {
cost_usd: 0.0,
made_progress: false,
},
}
}
},
gather,
)
.await
}
#[cfg(test)]
mod tests {
use super::*;
use car_verify::goal::{run_goal_loop, GoalCondition, GoalGovernor, GoalSpec, GoalStatus};
use std::cell::Cell;
#[test]
fn cost_and_progress_map_from_invoke_result() {
let mut r = InvokeResult::default();
r.total_cost_usd = Some(0.42);
r.tool_calls = 3;
let o = invoke_result_to_outcome(&r);
assert!((o.cost_usd - 0.42).abs() < 1e-9);
assert!(o.made_progress);
r.is_error = true;
assert!(!invoke_result_to_outcome(&r).made_progress);
}
#[tokio::test]
async fn external_loop_converges_under_cars_evaluator() {
let iters = Cell::new(0u32);
let spec = GoalSpec {
goal: "fix the build".into(),
condition: GoalCondition::Command {
id: "build".into(),
expect_exit: 0,
},
governor: GoalGovernor {
max_turns: Some(6),
..Default::default()
},
};
let run_iteration = |_directive: String| {
let n = iters.get() + 1;
iters.set(n);
async move {
invoke_result_to_outcome(&InvokeResult {
tool_calls: 2,
total_cost_usd: Some(0.01),
..Default::default()
})
}
};
let gather = || {
let passing = iters.get() >= 2;
async move {
let mut i = GoalInputs::default();
i.command_exits
.insert("build".into(), if passing { 0 } else { 1 });
i
}
};
let run = run_goal_loop(&spec, run_iteration, gather).await;
assert_eq!(run.status, GoalStatus::Achieved);
assert_eq!(run.iterations, 2);
assert!(run.grounded, "a command-check completion is grounded");
}
}