use super::action::ActionResult;
use super::state::AgentState;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DecisionContext {
pub agent_id: u32,
pub query: String,
pub phase: String,
pub tick: u64,
pub recent_results: Vec<ActionResult>,
pub state: AgentState,
pub active_files: Vec<String>,
pub remaining_work: Vec<String>,
#[serde(default)]
pub metadata: std::collections::HashMap<String, String>,
}
impl DecisionContext {
pub fn new(agent_id: u32, query: impl Into<String>) -> Self {
Self {
agent_id,
query: query.into(),
..Default::default()
}
}
pub fn with_phase(mut self, phase: impl Into<String>) -> Self {
self.phase = phase.into();
self
}
pub fn with_tick(mut self, tick: u64) -> Self {
self.tick = tick;
self
}
pub fn add_result(&mut self, result: ActionResult) {
self.recent_results.push(result);
if self.recent_results.len() > 10 {
self.recent_results.remove(0);
}
}
pub fn with_active_files(mut self, files: Vec<String>) -> Self {
self.active_files = files;
self
}
pub fn with_remaining_work(mut self, work: Vec<String>) -> Self {
self.remaining_work = work;
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn last_result(&self) -> Option<&ActionResult> {
self.recent_results.last()
}
pub fn last_succeeded(&self) -> bool {
self.last_result().map(|r| r.success).unwrap_or(true)
}
pub fn last_failed(&self) -> bool {
self.last_result().map(|r| !r.success).unwrap_or(false)
}
pub fn recent_failure_count(&self) -> usize {
self.recent_results.iter().filter(|r| !r.success).count()
}
pub fn recent_success_rate(&self) -> f64 {
if self.recent_results.is_empty() {
return 1.0;
}
let successes = self.recent_results.iter().filter(|r| r.success).count();
successes as f64 / self.recent_results.len() as f64
}
pub fn has_remaining_work(&self) -> bool {
!self.remaining_work.is_empty()
}
pub fn next_work_item(&self) -> Option<&String> {
self.remaining_work.first()
}
pub fn query_contains(&self, keyword: &str) -> bool {
self.query.to_lowercase().contains(&keyword.to_lowercase())
}
pub fn query_contains_any(&self, keywords: &[&str]) -> bool {
let query_lower = self.query.to_lowercase();
keywords
.iter()
.any(|k| query_lower.contains(&k.to_lowercase()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::decider::action::Action;
#[test]
fn test_context_creation() {
let ctx = DecisionContext::new(0, "rename foo to bar")
.with_phase("executing")
.with_tick(5);
assert_eq!(ctx.agent_id, 0);
assert_eq!(ctx.query, "rename foo to bar");
assert_eq!(ctx.phase, "executing");
assert_eq!(ctx.tick, 5);
}
#[test]
fn test_context_results() {
let mut ctx = DecisionContext::new(0, "test");
let success = super::super::action::ActionResult::success(Action::read("a.rs"));
let failure = super::super::action::ActionResult::failure(Action::read("b.rs"), "error");
ctx.add_result(success);
ctx.add_result(failure);
assert!(!ctx.last_succeeded());
assert!(ctx.last_failed());
assert_eq!(ctx.recent_failure_count(), 1);
assert_eq!(ctx.recent_success_rate(), 0.5);
}
#[test]
fn test_query_keywords() {
let ctx = DecisionContext::new(0, "Rename function foo to bar");
assert!(ctx.query_contains("rename"));
assert!(ctx.query_contains("RENAME")); assert!(ctx.query_contains_any(&["rename", "delete", "add"]));
assert!(!ctx.query_contains_any(&["compile", "test"]));
}
}