1use std::collections::{HashMap, VecDeque};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Condvar, Mutex, MutexGuard};
7
8use super::model::{self, Obj};
9use super::{Artifact, Message, Task, TaskState, TaskStatus, MAX_TASKS};
10use super::push::{self, PushConfig};
11use crate::sse::{SseEvent, SseSender};
12use std::sync::mpsc::SyncSender;
13use std::sync::OnceLock;
14
15pub(crate) const MAX_SUBSCRIBERS: usize = 16;
17
18pub(crate) struct Subscriber {
19 pub(crate) sender: SseSender,
20 pub(crate) rpc_id: String,
22}
23
24pub(crate) struct Stored {
25 pub(crate) owner: String,
26 pub(crate) task: Task,
27 pub(crate) subscribers: Vec<Subscriber>,
28 pub(crate) canceled: Arc<AtomicBool>,
29 pub(crate) push: Vec<PushConfig>,
30}
31
32#[derive(Default)]
33pub(crate) struct TaskStore {
34 pub(crate) tasks: HashMap<String, Stored>,
35 pub(crate) order: VecDeque<String>,
36}
37
38impl TaskStore {
39 pub(crate) fn put(&mut self, owner: &str, task: Task) {
42 match self.tasks.get_mut(&task.id) {
43 Some(stored) => stored.task = task,
44 None => {
45 self.order.push_back(task.id.clone());
46 self.tasks.insert(task.id.clone(), Stored { owner: owner.to_string(), task, subscribers: Vec::new(), canceled: Arc::new(AtomicBool::new(false)), push: Vec::new() });
47 }
48 }
49 while self.tasks.len() > MAX_TASKS {
50 let finished = self.order.iter().position(|id| self.tasks.get(id).is_none_or(|s| s.task.status.state.is_terminal())).unwrap_or(0);
51 match self.order.remove(finished) {
52 Some(id) => {
53 self.tasks.remove(&id);
54 }
55 None => break,
56 }
57 }
58 }
59
60 pub(crate) fn get(&self, owner: &str, id: &str) -> Option<&Task> {
61 self.tasks.get(id).filter(|s| s.owner == owner).map(|s| &s.task)
62 }
63
64 pub(crate) fn get_mut(&mut self, owner: &str, id: &str) -> Option<&mut Stored> {
65 self.tasks.get_mut(id).filter(|s| s.owner == owner)
66 }
67}
68
69#[derive(Default)]
71pub(crate) struct Shared {
72 pub(crate) store: Mutex<TaskStore>,
73 pub(crate) changed: Condvar,
74 pub(crate) push: OnceLock<SyncSender<push::Delivery>>,
76}
77
78impl Shared {
79 pub(crate) fn lock(&self) -> MutexGuard<'_, TaskStore> {
80 self.store.lock().unwrap_or_else(|e| e.into_inner())
81 }
82}
83
84pub(crate) fn event(rpc_id: &str, kind: &str, json: &str) -> SseEvent {
87 SseEvent::data(format!(r#"{{"jsonrpc":"2.0","id":{rpc_id},"result":{{"{kind}":{json}}}}}"#))
88}
89
90pub(crate) fn publish(stored: &mut Stored, queue: Option<&SyncSender<push::Delivery>>, kind: &str, json: &str) {
94 stored.subscribers.retain(|s| matches!(s.sender.try_send(event(&s.rpc_id, kind, json)), Ok(true)));
95 if let Some(queue) = queue {
96 for config in &stored.push {
97 push::enqueue(queue, config, kind, json);
98 }
99 }
100}
101
102pub(crate) fn set_state(shared: &Shared, owner: &str, id: &str, state: TaskState, message: Option<Message>) -> bool {
107 let mut store = shared.lock();
108 let Some(stored) = store.get_mut(owner, id) else { return false };
109 if stored.task.status.state.is_terminal() {
110 return false;
111 }
112 let message = message.map(|mut m| {
113 m.task_id = Some(stored.task.id.clone());
114 m.context_id = Some(stored.task.context_id.clone());
115 m
116 });
117 if let Some(m) = &message {
118 stored.task.history.push(m.clone());
119 }
120 stored.task.status = TaskStatus { state, message, timestamp: model::timestamp() };
121 let update = Obj::new().str("taskId", &stored.task.id).str("contextId", &stored.task.context_id).raw("status", &stored.task.status.to_json()).done();
122 publish(stored, shared.push.get(), "statusUpdate", &update);
123 if state.is_terminal() || state.is_interrupted() {
124 stored.subscribers.clear();
125 }
126 if state == TaskState::Canceled {
127 stored.canceled.store(true, Ordering::Relaxed);
128 }
129 drop(store);
130 shared.changed.notify_all();
131 true
132}
133
134#[derive(Clone)]
138pub struct TaskHandle {
139 pub(crate) shared: Arc<Shared>,
140 pub(crate) owner: String,
141 pub(crate) id: String,
142 pub(crate) canceled: Arc<AtomicBool>,
143}
144
145impl TaskHandle {
146 pub fn id(&self) -> &str {
147 &self.id
148 }
149
150 pub fn working(&self, note: Option<&str>) {
152 set_state(&self.shared, &self.owner, &self.id, TaskState::Working, note.map(Message::agent_text));
153 }
154
155 pub fn artifact(&self, artifact: Artifact) {
157 self.artifact_chunk(artifact, false, true);
158 }
159
160 pub fn artifact_chunk(&self, artifact: Artifact, append: bool, last_chunk: bool) {
163 let mut store = self.shared.lock();
164 let Some(stored) = store.get_mut(&self.owner, &self.id) else { return };
165 if stored.task.status.state.is_terminal() {
166 return;
167 }
168 let update = Obj::new()
169 .str("taskId", &stored.task.id)
170 .str("contextId", &stored.task.context_id)
171 .raw("artifact", &artifact.to_json())
172 .raw("append", if append { "true" } else { "false" })
173 .raw("lastChunk", if last_chunk { "true" } else { "false" })
174 .done();
175 match stored.task.artifacts.iter_mut().find(|a| a.artifact_id == artifact.artifact_id) {
176 Some(existing) if append => existing.parts.extend(artifact.parts),
177 Some(existing) => *existing = artifact,
178 None => stored.task.artifacts.push(artifact),
179 }
180 publish(stored, self.shared.push.get(), "artifactUpdate", &update);
181 }
182
183 pub fn complete(&self) {
185 set_state(&self.shared, &self.owner, &self.id, TaskState::Completed, None);
186 }
187
188 pub fn complete_with(&self, message: Message) {
190 set_state(&self.shared, &self.owner, &self.id, TaskState::Completed, Some(message));
191 }
192
193 pub fn fail(&self, reason: &str) {
195 set_state(&self.shared, &self.owner, &self.id, TaskState::Failed, Some(Message::agent_text(reason)));
196 }
197
198 pub fn input_required(&self, question: &str) {
201 set_state(&self.shared, &self.owner, &self.id, TaskState::InputRequired, Some(Message::agent_text(question)));
202 }
203
204 pub fn is_canceled(&self) -> bool {
206 self.canceled.load(Ordering::Relaxed)
207 }
208
209 pub fn snapshot(&self) -> Option<Task> {
211 self.shared.lock().get(&self.owner, &self.id).cloned()
212 }
213}