miyabi_a2a/storage/mod.rs
1//! Task storage abstraction
2
3use crate::task::{A2ATask, TaskStatus};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8pub mod cursor;
9pub mod github;
10
11pub use cursor::PaginatedResult;
12
13/// Task storage backend trait
14///
15/// This trait defines the interface for persisting and retrieving
16/// A2A tasks. Implementations can use different backends (GitHub Issues,
17/// databases, etc.)
18#[async_trait]
19pub trait TaskStorage: Send + Sync {
20 /// Save a new task
21 ///
22 /// # Returns
23 /// The assigned task ID
24 async fn save_task(&self, task: A2ATask) -> Result<u64, StorageError>;
25
26 /// Get a task by ID
27 ///
28 /// # Returns
29 /// `Some(task)` if found, `None` if not found
30 async fn get_task(&self, id: u64) -> Result<Option<A2ATask>, StorageError>;
31
32 /// List tasks with optional filters
33 ///
34 /// # Arguments
35 /// * `filter` - Optional filtering criteria
36 ///
37 /// # Returns
38 /// List of tasks matching the filter
39 async fn list_tasks(&self, filter: TaskFilter) -> Result<Vec<A2ATask>, StorageError>;
40
41 /// List tasks with cursor-based pagination
42 ///
43 /// This method provides efficient pagination for large task lists using
44 /// opaque cursors. Cursors are stable across requests.
45 ///
46 /// # Arguments
47 /// * `filter` - Optional filtering criteria (including cursor)
48 ///
49 /// # Returns
50 /// Paginated result with items, next_cursor, previous_cursor, and has_more flag
51 ///
52 /// # Examples
53 ///
54 /// ```no_run
55 /// use miyabi_a2a::{GitHubTaskStorage, TaskStorage, TaskFilter};
56 ///
57 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
58 /// let storage = GitHubTaskStorage::new("token".into(), "owner".into(), "repo".into())?;
59 ///
60 /// // First page
61 /// let filter = TaskFilter { limit: Some(50), ..Default::default() };
62 /// let page1 = storage.list_tasks_paginated(filter).await?;
63 ///
64 /// // Next page
65 /// if let Some(cursor) = page1.next_cursor {
66 /// let filter = TaskFilter { cursor: Some(cursor), ..Default::default() };
67 /// let page2 = storage.list_tasks_paginated(filter).await?;
68 /// }
69 /// # Ok(())
70 /// # }
71 /// ```
72 async fn list_tasks_paginated(
73 &self,
74 filter: TaskFilter,
75 ) -> Result<PaginatedResult<A2ATask>, StorageError>;
76
77 /// Update an existing task
78 ///
79 /// # Arguments
80 /// * `id` - Task ID to update
81 /// * `update` - Fields to update (only provided fields are updated)
82 ///
83 /// # Returns
84 /// Ok(()) on success
85 async fn update_task(&self, id: u64, update: TaskUpdate) -> Result<(), StorageError>;
86
87 /// Delete a task
88 ///
89 /// # Arguments
90 /// * `id` - Task ID to delete
91 ///
92 /// # Returns
93 /// Ok(()) on success
94 async fn delete_task(&self, id: u64) -> Result<(), StorageError>;
95}
96
97/// Task filter for list_tasks()
98#[derive(Default, Debug, Clone)]
99pub struct TaskFilter {
100 /// Filter by context ID
101 pub context_id: Option<String>,
102
103 /// Filter by status
104 pub status: Option<TaskStatus>,
105
106 /// Filter by agent
107 pub agent: Option<String>,
108
109 /// Filter by tasks updated after this timestamp
110 pub last_updated_after: Option<DateTime<Utc>>,
111
112 /// Maximum number of results
113 pub limit: Option<usize>,
114
115 /// Pagination cursor
116 pub cursor: Option<String>,
117}
118
119/// Task update for update_task()
120#[derive(Default, Debug, Clone, Serialize, Deserialize)]
121pub struct TaskUpdate {
122 /// Update status
123 pub status: Option<TaskStatus>,
124
125 /// Update description
126 pub description: Option<String>,
127
128 /// Update assigned agent
129 pub agent: Option<String>,
130
131 /// Update priority
132 pub priority: Option<u8>,
133
134 /// Update retry count
135 pub retry_count: Option<u32>,
136}
137
138/// Storage error types
139#[derive(Debug, thiserror::Error)]
140pub enum StorageError {
141 #[error("Task not found: {0}")]
142 NotFound(u64),
143
144 #[error("Network error: {0}")]
145 Network(String),
146
147 #[error("Authentication error: {0}")]
148 Auth(String),
149
150 #[error("Serialization error: {0}")]
151 Serialization(String),
152
153 #[error("Storage error: {0}")]
154 Other(String),
155}
156
157impl From<octocrab::Error> for StorageError {
158 fn from(err: octocrab::Error) -> Self {
159 StorageError::Network(err.to_string())
160 }
161}
162
163impl From<serde_json::Error> for StorageError {
164 fn from(err: serde_json::Error) -> Self {
165 StorageError::Serialization(err.to_string())
166 }
167}