pub(crate) mod in_memory;
use std::future::Future;
use std::pin::Pin;
use a2a_protocol_types::error::A2aResult;
use a2a_protocol_types::events::StreamResponse;
use a2a_protocol_types::message::{Message, MessageId};
use a2a_protocol_types::params::ListTasksParams;
use a2a_protocol_types::responses::TaskListResponse;
use a2a_protocol_types::task::{Task, TaskId};
pub use in_memory::InMemoryTaskStore;
mod config;
mod records;
pub use config::{
DEFAULT_IDEMPOTENCY_KEY_TTL, DEFAULT_MAX_EVENTS_PER_TASK, DEFAULT_MAX_PAGE_SIZE,
TaskStoreConfig,
};
pub use records::{ArtifactDelta, IdempotencyClaim, RecordedEvent};
pub trait TaskStore: Send + Sync + 'static {
fn save<'a>(
&'a self,
task: &'a Task,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>>;
fn get<'a>(
&'a self,
id: &'a TaskId,
) -> Pin<Box<dyn Future<Output = A2aResult<Option<Task>>> + Send + 'a>>;
fn list<'a>(
&'a self,
params: &'a ListTasksParams,
) -> Pin<Box<dyn Future<Output = A2aResult<TaskListResponse>> + Send + 'a>>;
fn insert_if_absent<'a>(
&'a self,
task: &'a Task,
) -> Pin<Box<dyn Future<Output = A2aResult<bool>> + Send + 'a>>;
fn delete<'a>(
&'a self,
id: &'a TaskId,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>>;
fn count<'a>(&'a self) -> Pin<Box<dyn Future<Output = A2aResult<u64>> + Send + 'a>> {
Box::pin(async { Ok(0) })
}
fn supports_idempotency(&self) -> bool {
false
}
fn claim_idempotency_key<'a>(
&'a self,
key: &'a str,
message_id: &'a MessageId,
task_id: &'a TaskId,
) -> Pin<Box<dyn Future<Output = A2aResult<IdempotencyClaim>> + Send + 'a>> {
let _ = (key, message_id, task_id);
Box::pin(async {
Err(a2a_protocol_types::error::A2aError::unsupported_operation(
"this task store does not implement idempotency keys",
))
})
}
fn release_idempotency_key<'a>(
&'a self,
key: &'a str,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
let _ = key;
Box::pin(async {
Err(a2a_protocol_types::error::A2aError::unsupported_operation(
"this task store does not implement idempotency keys",
))
})
}
fn save_artifact_delta<'a>(
&'a self,
task: &'a Task,
delta: ArtifactDelta,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
let _ = delta;
self.save(task)
}
fn save_status_delta<'a>(
&'a self,
task: &'a Task,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
self.save(task)
}
fn save_appending_history<'a>(
&'a self,
task: &'a Task,
messages: &'a [Message],
max_history: usize,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
Box::pin(async move {
let stored = self.get(&task.id).await?;
let mut history = stored.and_then(|s| s.history).unwrap_or_default();
history.extend_from_slice(messages);
let excess = history.len().saturating_sub(max_history);
history.drain(..excess);
let mut merged = task.clone();
merged.history = Some(history);
self.save(&merged).await
})
}
fn supports_event_log(&self) -> bool {
false
}
fn append_event<'a>(
&'a self,
task_id: &'a TaskId,
seq: u64,
event: &'a StreamResponse,
) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>> {
let _ = (task_id, seq, event);
Box::pin(async {
Err(a2a_protocol_types::error::A2aError::unsupported_operation(
"this task store keeps no event log",
))
})
}
fn last_event_seq<'a>(
&'a self,
task_id: &'a TaskId,
) -> Pin<Box<dyn Future<Output = A2aResult<u64>> + Send + 'a>> {
let _ = task_id;
Box::pin(async {
Err(a2a_protocol_types::error::A2aError::unsupported_operation(
"this task store keeps no event log",
))
})
}
fn read_events<'a>(
&'a self,
task_id: &'a TaskId,
after_seq: u64,
limit: usize,
) -> Pin<Box<dyn Future<Output = A2aResult<Vec<RecordedEvent>>> + Send + 'a>> {
let _ = (task_id, after_seq, limit);
Box::pin(async {
Err(a2a_protocol_types::error::A2aError::unsupported_operation(
"this task store keeps no event log",
))
})
}
fn earliest_event_seq<'a>(
&'a self,
task_id: &'a TaskId,
) -> Pin<Box<dyn Future<Output = A2aResult<Option<u64>>> + Send + 'a>> {
let _ = task_id;
Box::pin(async { Ok(None) })
}
fn event_log_covers<'a>(
&'a self,
task_id: &'a TaskId,
after_seq: u64,
) -> Pin<Box<dyn Future<Output = A2aResult<bool>> + Send + 'a>> {
Box::pin(async move {
Ok(self
.earliest_event_seq(task_id)
.await?
.is_none_or(|earliest| earliest <= after_seq.saturating_add(1)))
})
}
}
#[cfg(test)]
mod tests;