use std::pin::Pin;
use std::sync::Arc;
use oxi_agent::tools::todo::{TodoOp, TodoPhase, TodoUpdateResult};
use oxi_agent::tools::{TodoStateProvider, ToolError};
use parking_lot::RwLock;
#[derive(Debug)]
pub struct TodoState {
phases: RwLock<Vec<TodoPhase>>,
}
impl TodoState {
pub fn new() -> Self {
Self {
phases: RwLock::new(Vec::new()),
}
}
pub fn with_phases(phases: Vec<TodoPhase>) -> Self {
Self {
phases: RwLock::new(phases),
}
}
pub fn get_phases(&self) -> Vec<TodoPhase> {
self.phases.read().clone()
}
pub fn apply(&self, ops: Vec<TodoOp>) -> TodoUpdateResult {
let mut phases = self.phases.write();
let result = oxi_agent::tools::todo::apply_ops(&mut phases, &ops);
drop(phases);
result
}
#[allow(dead_code)]
pub fn clear(&self) {
self.phases.write().clear();
}
}
impl Default for TodoState {
fn default() -> Self {
Self::new()
}
}
impl TodoStateProvider for TodoState {
fn get_phases(&self) -> Vec<TodoPhase> {
TodoState::get_phases(self)
}
fn apply_ops<'a>(
&'a self,
ops: Vec<TodoOp>,
) -> Pin<Box<dyn Future<Output = Result<TodoUpdateResult, ToolError>> + Send + 'a>> {
Box::pin(async move {
let result = {
let mut phases = self.phases.write();
let r = oxi_agent::tools::todo::apply_ops(&mut phases, &ops);
r
};
Ok(result)
})
}
}
pub fn provider_from_state(state: Arc<TodoState>) -> Arc<dyn TodoStateProvider> {
state as Arc<dyn TodoStateProvider>
}
#[cfg(test)]
mod tests {
use super::*;
use oxi_agent::tools::todo::TodoItem;
#[test]
fn empty_state() {
let state = TodoState::new();
assert!(state.get_phases().is_empty());
}
#[test]
fn apply_ops_via_provider() {
let state = TodoState::new();
let provider: Arc<dyn TodoStateProvider> = provider_from_state(Arc::new(state));
let phases = provider.get_phases();
assert!(phases.is_empty());
let state_arc = Arc::new(TodoState::new());
let result = state_arc.apply(vec![TodoOp::Init {
list: None,
items: Some(vec!["task1".into(), "task2".into()]),
}]);
assert!(result.errors.is_empty());
let phases = state_arc.get_phases();
assert_eq!(phases.len(), 1);
assert_eq!(phases[0].tasks.len(), 2);
}
#[test]
fn provider_share_state() {
let state = Arc::new(TodoState::with_phases(vec![]));
let p1: Arc<dyn TodoStateProvider> = state.clone();
let p2: Arc<dyn TodoStateProvider> = state.clone();
let _ = p1.get_phases(); let _ = p2.get_phases();
assert!(state.get_phases().is_empty());
}
#[test]
fn with_phases_initial() {
let initial = vec![TodoPhase {
name: "P".into(),
tasks: vec![TodoItem {
content: "t".into(),
status: oxi_agent::tools::todo::TodoStatus::Pending,
notes: None,
}],
}];
let state = TodoState::with_phases(initial);
assert_eq!(state.get_phases().len(), 1);
assert_eq!(state.get_phases()[0].name, "P");
}
}