use crate::generate::{self, ActivePlannerConfig};
use car_engine::{ReplanCallback, ReplanContext};
use car_inference::InferenceEngine;
use car_ir::ActionProposal;
use car_planner::{Planner, PlannerConfig, ToolFeedback};
use std::collections::HashSet;
use std::sync::Arc;
pub struct ActiveReplanAdapter {
engine: Arc<InferenceEngine>,
planner: Planner,
config: ActivePlannerConfig,
feedback: Option<ToolFeedback>,
}
impl ActiveReplanAdapter {
pub fn new(
engine: Arc<InferenceEngine>,
config: ActivePlannerConfig,
planner_config: PlannerConfig,
) -> Self {
Self {
engine,
planner: Planner::new(planner_config),
config,
feedback: None,
}
}
pub fn with_feedback(mut self, feedback: ToolFeedback) -> Self {
self.feedback = Some(feedback);
self
}
}
#[async_trait::async_trait]
impl ReplanCallback for ActiveReplanAdapter {
async fn replan(&self, ctx: &ReplanContext) -> Result<ActionProposal, String> {
let failure_desc = ctx.failed_actions.iter()
.map(|f| {
let tool = f.tool.as_deref().unwrap_or("unknown");
format!("Action '{}' (tool: {}) failed: {}", f.action_id, tool, f.error)
})
.collect::<Vec<_>>()
.join("\n");
let failure_context = format!(
"{}\nAttempt {} of {}. State after rollback has {} keys.",
failure_desc,
ctx.attempt,
ctx.attempt + ctx.replans_remaining,
ctx.state_snapshot.len(),
);
let mut config = self.config.clone();
if ctx.replans_remaining == 0 {
config.strategies = vec![generate::Strategy::Conservative];
}
let goal = if let Some(goal_val) = ctx.original_context.get("goal") {
goal_val.as_str().unwrap_or("").to_string()
} else if let Some(desc) = ctx.original_context.get("description") {
desc.as_str().unwrap_or("").to_string()
} else {
format!(
"Replan for '{}' (originally {} actions). Accomplish the same objective with a different approach.",
ctx.original_source, ctx.original_action_count,
)
};
let candidates = generate::generate_candidates(
&self.engine, &goal, &config, Some(&failure_context),
).await;
if candidates.is_empty() {
return Err("all candidate generation attempts failed to produce valid proposals".into());
}
let tool_names: HashSet<String> = self.config.available_tools.clone();
let state = ctx.state_snapshot.clone();
let ranked = self.planner.rank_with_feedback(
&candidates,
Some(&state),
Some(&tool_names),
self.feedback.as_ref(),
);
ranked.into_iter()
.find(|s| s.valid)
.map(|s| candidates[s.index].clone())
.ok_or_else(|| "all generated candidates failed verification".into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adapter_constructible() {
let config = ActivePlannerConfig::default();
let planner_config = PlannerConfig::default();
assert_eq!(config.strategies.len(), 3);
assert_eq!(planner_config.cost_weight, 0.2);
}
}