Skip to main content

vv_agent/app_server/
thread_state.rs

1use std::collections::{HashMap, HashSet, VecDeque};
2use std::sync::{Arc, Mutex as StdMutex};
3
4use tokio::sync::Mutex;
5
6use crate::app_server::protocol::{AppTurn, UserInput};
7use crate::app_server::transport::ConnectionId;
8use crate::runtime::state_v2::CheckpointStoreV2;
9use crate::RunHandle;
10
11pub type SteeringQueue = Arc<StdMutex<VecDeque<Vec<UserInput>>>>;
12
13#[derive(Clone, Default)]
14pub struct ThreadStateManager {
15    inner: Arc<Mutex<ThreadStateInner>>,
16}
17
18#[derive(Default)]
19struct ThreadStateInner {
20    subscribers: HashMap<String, HashSet<ConnectionId>>,
21    active_turns: HashMap<String, ActiveTurn>,
22    durable_resumes: HashMap<String, String>,
23    pending_approvals: HashMap<String, PendingApproval>,
24    follow_ups: HashMap<String, VecDeque<Vec<UserInput>>>,
25    closed_threads: HashSet<String>,
26}
27
28#[derive(Clone)]
29pub struct ActiveTurn {
30    pub turn: AppTurn,
31    pub handle: RunHandle,
32    pub steering: SteeringQueue,
33    pub owner_connection_id: ConnectionId,
34    pub checkpoint_store: Option<Arc<dyn CheckpointStoreV2>>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct PendingApproval {
39    pub connection_id: ConnectionId,
40    pub turn_id: String,
41    pub request_id: String,
42}
43
44impl ThreadStateManager {
45    pub async fn subscribe(&self, thread_id: impl Into<String>, connection_id: ConnectionId) {
46        let thread_id = thread_id.into();
47        let mut inner = self.inner.lock().await;
48        inner.closed_threads.remove(&thread_id);
49        inner
50            .subscribers
51            .entry(thread_id)
52            .or_default()
53            .insert(connection_id);
54    }
55
56    pub async fn subscribe_and_snapshot<T, E>(
57        &self,
58        thread_id: impl Into<String>,
59        connection_id: ConnectionId,
60        snapshot: impl FnOnce() -> Result<T, E>,
61    ) -> Result<T, E> {
62        let thread_id = thread_id.into();
63        let mut inner = self.inner.lock().await;
64        let was_closed = inner.closed_threads.remove(&thread_id);
65        let inserted = inner
66            .subscribers
67            .entry(thread_id.clone())
68            .or_default()
69            .insert(connection_id);
70        let result = snapshot();
71        if result.is_err() {
72            if inserted {
73                let remove_entry =
74                    inner
75                        .subscribers
76                        .get_mut(&thread_id)
77                        .is_some_and(|subscribers| {
78                            subscribers.remove(&connection_id);
79                            subscribers.is_empty()
80                        });
81                if remove_entry {
82                    inner.subscribers.remove(&thread_id);
83                }
84            }
85            if was_closed {
86                inner.closed_threads.insert(thread_id);
87            }
88        }
89        result
90    }
91
92    pub async fn unsubscribe(&self, thread_id: &str, connection_id: ConnectionId) -> bool {
93        let mut inner = self.inner.lock().await;
94        if let Some(subscribers) = inner.subscribers.get_mut(thread_id) {
95            subscribers.remove(&connection_id);
96            if subscribers.is_empty() {
97                inner.subscribers.remove(thread_id);
98            }
99        }
100        let closed = !inner.subscribers.contains_key(thread_id)
101            && !inner.active_turns.contains_key(thread_id)
102            && !inner.durable_resumes.contains_key(thread_id);
103        if closed {
104            inner.closed_threads.insert(thread_id.to_string());
105        }
106        closed
107    }
108
109    pub async fn unsubscribe_connection(&self, connection_id: ConnectionId) {
110        let mut inner = self.inner.lock().await;
111        inner.subscribers.retain(|_thread_id, subscribers| {
112            subscribers.remove(&connection_id);
113            !subscribers.is_empty()
114        });
115    }
116
117    pub async fn subscribers(&self, thread_id: &str) -> Vec<ConnectionId> {
118        self.inner
119            .lock()
120            .await
121            .subscribers
122            .get(thread_id)
123            .map(|subscribers| subscribers.iter().copied().collect())
124            .unwrap_or_default()
125    }
126
127    pub async fn is_subscribed(&self, thread_id: &str, connection_id: ConnectionId) -> bool {
128        self.inner
129            .lock()
130            .await
131            .subscribers
132            .get(thread_id)
133            .is_some_and(|subscribers| subscribers.contains(&connection_id))
134    }
135
136    pub async fn is_closed(&self, thread_id: &str) -> bool {
137        self.inner.lock().await.closed_threads.contains(thread_id)
138    }
139
140    pub async fn reopen(&self, thread_id: &str) {
141        self.inner.lock().await.closed_threads.remove(thread_id);
142    }
143
144    pub async fn set_active_turn(&self, thread_id: impl Into<String>, active_turn: ActiveTurn) {
145        let thread_id = thread_id.into();
146        let mut inner = self.inner.lock().await;
147        inner.closed_threads.remove(&thread_id);
148        inner.active_turns.insert(thread_id, active_turn);
149    }
150
151    pub async fn active_turn(&self, thread_id: &str) -> Option<ActiveTurn> {
152        self.inner.lock().await.active_turns.get(thread_id).cloned()
153    }
154
155    pub async fn active_turn_id(&self, thread_id: &str) -> Option<String> {
156        let inner = self.inner.lock().await;
157        inner
158            .active_turns
159            .get(thread_id)
160            .map(|active| active.turn.turn_id.clone())
161            .or_else(|| inner.durable_resumes.get(thread_id).cloned())
162    }
163
164    pub async fn has_active_turn(&self, thread_id: &str) -> bool {
165        let inner = self.inner.lock().await;
166        inner.active_turns.contains_key(thread_id) || inner.durable_resumes.contains_key(thread_id)
167    }
168
169    pub async fn set_durable_resume(
170        &self,
171        thread_id: impl Into<String>,
172        turn_id: impl Into<String>,
173    ) {
174        let thread_id = thread_id.into();
175        let mut inner = self.inner.lock().await;
176        inner.closed_threads.remove(&thread_id);
177        inner.durable_resumes.insert(thread_id, turn_id.into());
178    }
179
180    pub async fn clear_durable_resume(&self, thread_id: &str, turn_id: &str) {
181        let mut inner = self.inner.lock().await;
182        if inner
183            .durable_resumes
184            .get(thread_id)
185            .is_some_and(|active_turn_id| active_turn_id == turn_id)
186        {
187            inner.durable_resumes.remove(thread_id);
188        }
189    }
190
191    pub async fn clear_active_turn(&self, thread_id: &str, turn_id: &str) {
192        let mut inner = self.inner.lock().await;
193        if inner
194            .active_turns
195            .get(thread_id)
196            .is_some_and(|active| active.turn.turn_id == turn_id)
197        {
198            inner.active_turns.remove(thread_id);
199        }
200    }
201
202    pub async fn queue_follow_up(&self, thread_id: &str, input: Vec<UserInput>) {
203        self.inner
204            .lock()
205            .await
206            .follow_ups
207            .entry(thread_id.to_string())
208            .or_default()
209            .push_back(input);
210    }
211
212    pub async fn pop_follow_up(&self, thread_id: &str) -> Option<Vec<UserInput>> {
213        let mut inner = self.inner.lock().await;
214        let next = inner
215            .follow_ups
216            .get_mut(thread_id)
217            .and_then(VecDeque::pop_front);
218        if inner
219            .follow_ups
220            .get(thread_id)
221            .is_some_and(VecDeque::is_empty)
222        {
223            inner.follow_ups.remove(thread_id);
224        }
225        next
226    }
227
228    pub async fn set_pending_approval(
229        &self,
230        thread_id: impl Into<String>,
231        turn_id: impl Into<String>,
232        request_id: impl Into<String>,
233        connection_id: ConnectionId,
234    ) {
235        self.inner.lock().await.pending_approvals.insert(
236            thread_id.into(),
237            PendingApproval {
238                connection_id,
239                turn_id: turn_id.into(),
240                request_id: request_id.into(),
241            },
242        );
243    }
244
245    pub async fn pending_approval(&self, thread_id: &str) -> Option<PendingApproval> {
246        self.inner
247            .lock()
248            .await
249            .pending_approvals
250            .get(thread_id)
251            .cloned()
252    }
253
254    pub async fn clear_pending_approval(&self, thread_id: &str, request_id: &str) {
255        let mut inner = self.inner.lock().await;
256        if inner
257            .pending_approvals
258            .get(thread_id)
259            .is_some_and(|pending| pending.request_id == request_id)
260        {
261            inner.pending_approvals.remove(thread_id);
262        }
263    }
264}