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 set_phases_sync(&self, phases: Vec<TodoPhase>) {
76 *self.phases.write() = phases;
77 }
78
79 fn apply_ops<'a>(
80 &'a self,
81 ops: Vec<TodoOp>,
82 ) -> Pin<Box<dyn std::future::Future<Output = Result<TodoUpdateResult, ToolError>> + Send + 'a>>
83 {
84 Box::pin(async move {
85 let result = {
87 let mut phases = self.phases.write();
88 apply_ops(&mut phases, &ops)
89 };
90 Ok(result)
91 })
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use oxicode_agent::tools::todo::{TodoItem, TodoStatus};
99
100 #[test]
101 fn empty_state() {
102 let state = InMemoryTodoState::new();
103 assert!(state.get_phases().is_empty());
104 }
105
106 #[test]
107 fn init_and_read() {
108 let state = InMemoryTodoState::new();
109 let phases = state.phases.write();
110 drop(phases);
112 assert!(state.get_phases().is_empty());
113 }
114
115 #[test]
116 fn with_phases_preserves_data() {
117 let initial = vec![TodoPhase {
118 name: "Test".into(),
119 tasks: vec![TodoItem {
120 content: "do thing".into(),
121 status: TodoStatus::Pending,
122 notes: None,
123 block_reason: None,
124 }],
125 }];
126 let state = InMemoryTodoState::with_phases(initial);
127 let snapshot = state.get_phases();
128 assert_eq!(snapshot.len(), 1);
129 assert_eq!(snapshot[0].name, "Test");
130 assert_eq!(snapshot[0].tasks.len(), 1);
131 assert_eq!(snapshot[0].tasks[0].content, "do thing");
132 }
133
134 #[test]
135 fn clone_shares_state() {
136 let state = InMemoryTodoState::with_phases(vec![TodoPhase {
137 name: "Shared".into(),
138 tasks: vec![],
139 }]);
140 let clone = state.clone();
141 assert_eq!(clone.get_phases().len(), 1);
142 }
143}