1use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::{Arc, Mutex};
11use tokio::task::JoinHandle;
12
13#[derive(Default)]
14struct BufState {
15 data: String,
17 finished: bool,
18 result: Option<Value>,
19 error: Option<String>,
20}
21
22#[derive(Clone, Default)]
24pub struct TaskBuffer {
25 inner: Arc<Mutex<BufState>>,
26}
27
28impl TaskBuffer {
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn append(&self, chunk: &str) {
35 self.inner.lock().unwrap().data.push_str(chunk);
36 }
37
38 pub fn finish(&self, result: Result<Value, String>) {
40 let mut s = self.inner.lock().unwrap();
41 s.finished = true;
42 match result {
43 Ok(value) => s.result = Some(value),
44 Err(error) => s.error = Some(error),
45 }
46 }
47
48 pub fn drain_new(&self) -> String {
50 let mut s = self.inner.lock().unwrap();
51 std::mem::take(&mut s.data)
52 }
53
54 pub fn status_parts(&self) -> (bool, Option<Value>, Option<String>) {
56 let s = self.inner.lock().unwrap();
57 (s.finished, s.result.clone(), s.error.clone())
58 }
59}
60
61struct BgTask {
62 tool_name: String,
63 handle: JoinHandle<()>,
64 buffer: TaskBuffer,
65}
66
67pub struct TaskSnapshot {
69 pub new_output: String,
71 pub tool_name: String,
72 pub finished: bool,
73 pub result: Option<Value>,
74 pub error: Option<String>,
75}
76
77#[derive(Clone, Default)]
79pub struct TaskManager {
80 tasks: Arc<Mutex<HashMap<String, BgTask>>>,
81 counter: Arc<AtomicU64>,
82}
83
84impl TaskManager {
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn register(
91 &self,
92 tool_name: impl Into<String>,
93 handle: JoinHandle<()>,
94 buffer: TaskBuffer,
95 ) -> String {
96 let n = self.counter.fetch_add(1, Ordering::SeqCst);
97 let id = format!("task_{n}");
98 self.tasks.lock().unwrap().insert(
99 id.clone(),
100 BgTask {
101 tool_name: tool_name.into(),
102 handle,
103 buffer,
104 },
105 );
106 id
107 }
108
109 pub fn poll(&self, id: &str) -> Option<TaskSnapshot> {
112 let mut tasks = self.tasks.lock().unwrap();
113 let task = tasks.get(id)?;
114 let new_output = task.buffer.drain_new();
115 let (finished, result, error) = task.buffer.status_parts();
116 let snapshot = TaskSnapshot {
117 new_output,
118 tool_name: task.tool_name.clone(),
119 finished,
120 result,
121 error,
122 };
123 if snapshot.finished {
124 tasks.remove(id);
125 }
126 Some(snapshot)
127 }
128
129 pub fn kill(&self, id: &str) -> bool {
131 match self.tasks.lock().unwrap().remove(id) {
132 Some(task) => {
133 task.handle.abort();
134 true
135 }
136 None => false,
137 }
138 }
139}