Skip to main content

talos_session/todo/
model.rs

1//! Todo domain types and tool input schemas.
2
3use chrono::{DateTime, Utc};
4use rusqlite;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use uuid::Uuid;
9
10/// Errors that can occur while reading or writing session todos.
11#[derive(Debug, Error)]
12pub enum TodoError {
13    /// A database operation failed.
14    #[error("todo database error: {0}")]
15    Database(String),
16
17    /// JSON metadata could not be serialized or parsed.
18    #[error("todo metadata JSON error: {0}")]
19    Json(String),
20
21    /// A todo id did not exist in the target session.
22    #[error("todo item not found: {0}")]
23    NotFound(Uuid),
24
25    /// A dependency would create a cycle.
26    #[error("todo dependency would create a cycle: {parent_id} -> {child_id}")]
27    DependencyCycle {
28        /// Parent todo id from the attempted dependency edge.
29        parent_id: Uuid,
30        /// Child todo id from the attempted dependency edge.
31        child_id: Uuid,
32    },
33
34    /// A todo cannot depend on itself.
35    #[error("todo item cannot depend on itself: {0}")]
36    SelfDependency(Uuid),
37}
38
39impl From<rusqlite::Error> for TodoError {
40    fn from(err: rusqlite::Error) -> Self {
41        TodoError::Database(err.to_string())
42    }
43}
44
45impl From<serde_json::Error> for TodoError {
46    fn from(err: serde_json::Error) -> Self {
47        TodoError::Json(err.to_string())
48    }
49}
50
51/// Status for a session todo item.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
53#[serde(rename_all = "snake_case")]
54pub enum TodoStatus {
55    /// Not started.
56    Todo,
57    /// Currently being worked.
58    InProgress,
59    /// Completed.
60    Completed,
61    /// Blocked by an external condition.
62    Blocked,
63}
64
65impl TodoStatus {
66    /// Return the stable snake_case representation used in storage and prompts.
67    #[must_use]
68    pub fn as_str(self) -> &'static str {
69        match self {
70            TodoStatus::Todo => "todo",
71            TodoStatus::InProgress => "in_progress",
72            TodoStatus::Completed => "completed",
73            TodoStatus::Blocked => "blocked",
74        }
75    }
76
77    pub(super) fn from_str(value: &str) -> Self {
78        match value {
79            "in_progress" => TodoStatus::InProgress,
80            "completed" => TodoStatus::Completed,
81            "blocked" => TodoStatus::Blocked,
82            _ => TodoStatus::Todo,
83        }
84    }
85}
86
87/// Priority for a session todo item.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
89#[serde(rename_all = "snake_case")]
90pub enum TodoPriority {
91    /// Low priority.
92    Low,
93    /// Normal priority.
94    Medium,
95    /// High priority.
96    High,
97    /// Critical priority.
98    Critical,
99}
100
101impl TodoPriority {
102    /// Return the stable snake_case representation used in storage and prompts.
103    #[must_use]
104    pub fn as_str(self) -> &'static str {
105        match self {
106            TodoPriority::Low => "low",
107            TodoPriority::Medium => "medium",
108            TodoPriority::High => "high",
109            TodoPriority::Critical => "critical",
110        }
111    }
112
113    pub(super) fn from_str(value: &str) -> Self {
114        match value {
115            "low" => TodoPriority::Low,
116            "high" => TodoPriority::High,
117            "critical" => TodoPriority::Critical,
118            _ => TodoPriority::Medium,
119        }
120    }
121}
122
123/// A structured todo item owned by one session.
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct TodoItem {
126    /// Unique todo id.
127    pub id: Uuid,
128    /// Owning session id.
129    pub session_id: Uuid,
130    /// Short title.
131    pub title: String,
132    /// Optional longer description.
133    pub description: Option<String>,
134    /// Current status.
135    pub status: TodoStatus,
136    /// Planning priority.
137    pub priority: TodoPriority,
138    /// Creation timestamp.
139    pub created_at: DateTime<Utc>,
140    /// Completion timestamp, set when status is completed.
141    pub completed_at: Option<DateTime<Utc>>,
142    /// Optional turn id that owns or last selected this item.
143    pub assigned_to_turn: Option<String>,
144    /// User/model tags for filtering.
145    pub tags: Vec<String>,
146}
147
148/// A dependency edge between two todo items in one session.
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct TodoDependency {
151    /// Owning session id.
152    pub session_id: Uuid,
153    /// Parent todo that must be handled before the child.
154    pub parent_id: Uuid,
155    /// Child todo that depends on the parent.
156    pub child_id: Uuid,
157}
158
159/// Parameters for creating a todo item.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct CreateTodo {
162    /// Owning session id.
163    pub session_id: Uuid,
164    /// Short title.
165    pub title: String,
166    /// Optional longer description.
167    pub description: Option<String>,
168    /// Planning priority.
169    pub priority: TodoPriority,
170    /// Optional turn id assignment.
171    pub assigned_to_turn: Option<String>,
172    /// Tags for filtering.
173    pub tags: Vec<String>,
174}
175
176/// Parameters for updating todo item fields.
177#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178pub struct TodoUpdate {
179    /// New title.
180    pub title: Option<String>,
181    /// New description. `Some(None)` clears it.
182    pub description: Option<Option<String>>,
183    /// New priority.
184    pub priority: Option<TodoPriority>,
185    /// New turn assignment. `Some(None)` clears it.
186    pub assigned_to_turn: Option<Option<String>>,
187    /// New complete tag set.
188    pub tags: Option<Vec<String>>,
189}
190
191/// Filter for querying todos.
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct TodoQuery {
194    /// Restrict to one status.
195    pub status: Option<TodoStatus>,
196    /// Restrict to one priority.
197    pub priority: Option<TodoPriority>,
198    /// Require one tag.
199    pub tag: Option<String>,
200}
201
202/// Input for the `todo_create` tool.
203///
204/// `session_id` is intentionally absent: the owning tool resolves it from the
205/// active session at construction time so the model never has to track it.
206#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
207pub struct TodoCreateInput {
208    /// Short title.
209    pub title: String,
210    /// Optional longer description.
211    #[serde(default)]
212    pub description: Option<String>,
213    /// Planning priority. Defaults to medium when omitted.
214    #[serde(default = "default_priority")]
215    pub priority: TodoPriority,
216    /// Optional turn id assignment.
217    #[serde(default)]
218    pub assigned_to_turn: Option<String>,
219    /// Tags for filtering.
220    #[serde(default)]
221    pub tags: Vec<String>,
222}
223
224/// Input for the `todo_update_status` tool.
225#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
226pub struct TodoUpdateStatusInput {
227    /// Todo item id.
228    pub id: String,
229    /// New status.
230    pub status: TodoStatus,
231}
232
233/// Input for the `todo_update` tool.
234#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
235pub struct TodoUpdateInput {
236    /// Todo item id.
237    pub id: String,
238    /// New title.
239    #[serde(default)]
240    pub title: Option<String>,
241    /// New description.
242    #[serde(default)]
243    pub description: Option<String>,
244    /// Clear the existing description.
245    #[serde(default)]
246    pub clear_description: bool,
247    /// New priority.
248    #[serde(default)]
249    pub priority: Option<TodoPriority>,
250    /// New turn assignment.
251    #[serde(default)]
252    pub assigned_to_turn: Option<String>,
253    /// Clear the existing turn assignment.
254    #[serde(default)]
255    pub clear_assigned_to_turn: bool,
256    /// Replace tags with this complete set.
257    #[serde(default)]
258    pub tags: Option<Vec<String>>,
259}
260
261/// Input for the `todo_delete` tool.
262#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
263pub struct TodoDeleteInput {
264    /// Todo item id.
265    pub id: String,
266}
267
268/// Input for todo dependency mutation tools.
269#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
270pub struct TodoDependencyInput {
271    /// Parent todo that must be handled before the child.
272    pub parent_id: String,
273    /// Child todo that depends on the parent.
274    pub child_id: String,
275}
276
277/// Input for the `todo_query` tool.
278#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
279pub struct TodoQueryInput {
280    /// Restrict to one status.
281    #[serde(default)]
282    pub status: Option<TodoStatus>,
283    /// Restrict to one priority.
284    #[serde(default)]
285    pub priority: Option<TodoPriority>,
286    /// Require one tag.
287    #[serde(default)]
288    pub tag: Option<String>,
289}
290
291fn default_priority() -> TodoPriority {
292    TodoPriority::Medium
293}