Skip to main content

cookie_agent_engine/
actor.rs

1//! Long-lived per-session mailbox actors.
2//!
3//! The actor owns command ordering only.  Provider streams and tool futures
4//! are spawned by a command handler and report back through this mailbox; they
5//! must never be awaited by the actor itself.
6
7use std::{future::Future, thread};
8
9use tokio::sync::mpsc;
10
11/// Handle to a dedicated session actor.
12///
13/// The mailbox is bounded so producers apply backpressure instead of growing
14/// memory without limit. The actor has its own thread; callers on Tokio worker
15/// threads use [`Self::send`] and await capacity rather than blocking workers.
16#[derive(Debug)]
17pub struct SessionActor<M> {
18    sender: mpsc::Sender<M>,
19}
20
21impl<M: Send + 'static> Clone for SessionActor<M> {
22    fn clone(&self) -> Self {
23        Self {
24            sender: self.sender.clone(),
25        }
26    }
27}
28
29impl<M: Send + 'static> SessionActor<M> {
30    #[must_use]
31    pub fn spawn<F, Fut>(capacity: usize, mut handler: F) -> Self
32    where
33        F: FnMut(M) -> Fut + Send + 'static,
34        Fut: Future<Output = ()> + Send + 'static,
35    {
36        let (sender, mut receiver) = mpsc::channel(capacity);
37        thread::Builder::new()
38            .name("cookie_agent_session_actor".into())
39            .spawn(move || {
40                let runtime = tokio::runtime::Builder::new_current_thread()
41                    .enable_all()
42                    .build()
43                    .expect("create session actor runtime");
44                runtime.block_on(async move {
45                    while let Some(message) = receiver.recv().await {
46                        handler(message).await;
47                    }
48                });
49            })
50            .expect("spawn session actor thread");
51        Self { sender }
52    }
53
54    pub async fn send(&self, message: M) -> Result<(), mpsc::error::SendError<M>> {
55        self.sender.send(message).await
56    }
57
58    /// Used only by synchronous facade methods called outside a Tokio worker.
59    pub fn blocking_send(&self, message: M) -> Result<(), mpsc::error::SendError<M>> {
60        self.sender.blocking_send(message)
61    }
62}