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}