use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::event::SessionId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsyncTaskStatus {
Pending,
Completed,
Failed,
Cancelled,
}
impl AsyncTaskStatus {
pub fn as_str(&self) -> &'static str {
match self {
AsyncTaskStatus::Pending => "pending",
AsyncTaskStatus::Completed => "completed",
AsyncTaskStatus::Failed => "failed",
AsyncTaskStatus::Cancelled => "cancelled",
}
}
}
#[derive(Debug, Clone)]
pub struct AsyncTaskRecord {
pub task_id: String,
pub tool_name: String,
pub tool_call_id: String,
pub session_id: SessionId,
pub status: AsyncTaskStatus,
pub started_at: Instant,
pub result_summary: Option<String>,
}
#[derive(Clone, Default)]
pub struct TaskRegistry {
inner: Arc<Mutex<HashMap<String, AsyncTaskRecord>>>,
}
impl TaskRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, record: AsyncTaskRecord) {
let mut g = self.inner.lock().expect("task registry lock poisoned");
g.insert(record.task_id.clone(), record);
}
pub fn update(
&self,
task_id: &str,
status: AsyncTaskStatus,
result_summary: Option<String>,
) {
let mut g = self.inner.lock().expect("task registry lock poisoned");
if let Some(r) = g.get_mut(task_id) {
r.status = status;
if result_summary.is_some() {
r.result_summary = result_summary;
}
}
}
pub fn remove(&self, task_id: &str) {
let mut g = self.inner.lock().expect("task registry lock poisoned");
g.remove(task_id);
}
pub fn get(&self, task_id: &str) -> Option<AsyncTaskRecord> {
self.inner
.lock()
.expect("task registry lock poisoned")
.get(task_id)
.cloned()
}
pub fn list_pending(&self) -> Vec<AsyncTaskRecord> {
let g = self.inner.lock().expect("task registry lock poisoned");
g.values()
.filter(|r| r.status == AsyncTaskStatus::Pending)
.cloned()
.collect()
}
pub fn list_all(&self) -> Vec<AsyncTaskRecord> {
let g = self.inner.lock().expect("task registry lock poisoned");
g.values().cloned().collect()
}
pub fn len(&self) -> usize {
self.inner.lock().expect("task registry lock poisoned").len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn record(id: &str, status: AsyncTaskStatus) -> AsyncTaskRecord {
AsyncTaskRecord {
task_id: id.into(),
tool_name: "test_tool".into(),
tool_call_id: "tc".into(),
session_id: "sess".into(),
status,
started_at: Instant::now(),
result_summary: None,
}
}
#[test]
fn register_and_get() {
let reg = TaskRegistry::new();
reg.register(record("t1", AsyncTaskStatus::Pending));
assert_eq!(reg.len(), 1);
let r = reg.get("t1").unwrap();
assert_eq!(r.status, AsyncTaskStatus::Pending);
}
#[test]
fn update_changes_status_and_summary() {
let reg = TaskRegistry::new();
reg.register(record("t1", AsyncTaskStatus::Pending));
reg.update("t1", AsyncTaskStatus::Completed, Some("ok".into()));
let r = reg.get("t1").unwrap();
assert_eq!(r.status, AsyncTaskStatus::Completed);
assert_eq!(r.result_summary.as_deref(), Some("ok"));
}
#[test]
fn list_pending_filters_by_status() {
let reg = TaskRegistry::new();
reg.register(record("t1", AsyncTaskStatus::Pending));
reg.register(record("t2", AsyncTaskStatus::Completed));
reg.register(record("t3", AsyncTaskStatus::Pending));
let pending = reg.list_pending();
assert_eq!(pending.len(), 2);
assert!(pending.iter().all(|r| r.status == AsyncTaskStatus::Pending));
}
#[test]
fn remove_drops_record() {
let reg = TaskRegistry::new();
reg.register(record("t1", AsyncTaskStatus::Pending));
reg.remove("t1");
assert!(reg.get("t1").is_none());
assert!(reg.is_empty());
}
#[test]
fn clone_shares_state() {
let reg = TaskRegistry::new();
let clone = reg.clone();
reg.register(record("t1", AsyncTaskStatus::Pending));
assert!(clone.get("t1").is_some());
}
}