oxicode_sdk/ports/inmem/
todo_state.rs1use std::pin::Pin;
25use std::sync::Arc;
26
27use oxicode_agent::tools::todo::{TodoOp, TodoPhase, TodoUpdateResult, apply_ops};
28use oxicode_agent::tools::{TodoStateProvider, ToolError};
29use parking_lot::RwLock;
30
31#[derive(Debug, Clone)]
39pub struct InMemoryTodoState {
40 phases: Arc<RwLock<Vec<TodoPhase>>>,
41}
42
43impl InMemoryTodoState {
44 pub fn new() -> Self {
46 Self {
47 phases: Arc::new(RwLock::new(Vec::new())),
48 }
49 }
50
51 pub fn with_phases(phases: Vec<TodoPhase>) -> Self {
53 Self {
54 phases: Arc::new(RwLock::new(phases)),
55 }
56 }
57
58 pub fn get_phases(&self) -> Vec<TodoPhase> {
60 self.phases.read().clone()
61 }
62}
63
64impl Default for InMemoryTodoState {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl TodoStateProvider for InMemoryTodoState {
71 fn get_phases(&self) -> Vec<TodoPhase> {
72 self.get_phases()
73 }
74
75 fn apply_ops<'a>(
76 &'a self,
77 ops: Vec<TodoOp>,
78 ) -> Pin<Box<dyn std::future::Future<Output = Result<TodoUpdateResult, ToolError>> + Send + 'a>>
79 {
80 Box::pin(async move {
81 let result = {
83 let mut phases = self.phases.write();
84 apply_ops(&mut phases, &ops)
85 };
86 Ok(result)
87 })
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use oxicode_agent::tools::todo::{TodoItem, TodoStatus};
95
96 #[test]
97 fn empty_state() {
98 let state = InMemoryTodoState::new();
99 assert!(state.get_phases().is_empty());
100 }
101
102 #[test]
103 fn init_and_read() {
104 let state = InMemoryTodoState::new();
105 let phases = state.phases.write();
106 drop(phases);
108 assert!(state.get_phases().is_empty());
109 }
110
111 #[test]
112 fn with_phases_preserves_data() {
113 let initial = vec![TodoPhase {
114 name: "Test".into(),
115 tasks: vec![TodoItem {
116 content: "do thing".into(),
117 status: TodoStatus::Pending,
118 notes: None,
119 block_reason: None,
120 }],
121 }];
122 let state = InMemoryTodoState::with_phases(initial);
123 let snapshot = state.get_phases();
124 assert_eq!(snapshot.len(), 1);
125 assert_eq!(snapshot[0].name, "Test");
126 assert_eq!(snapshot[0].tasks.len(), 1);
127 assert_eq!(snapshot[0].tasks[0].content, "do thing");
128 }
129
130 #[test]
131 fn clone_shares_state() {
132 let state = InMemoryTodoState::with_phases(vec![TodoPhase {
133 name: "Shared".into(),
134 tasks: vec![],
135 }]);
136 let clone = state.clone();
137 assert_eq!(clone.get_phases().len(), 1);
138 }
139}