Skip to main content

af_agent/
inbox.rs

1use af_context::InputId;
2use std::collections::VecDeque;
3
4use af_agent_session::{ContentBlock, DeliveryMode};
5
6/// Queued input awaiting a Run.
7#[derive(Debug, Clone, PartialEq)]
8pub struct InboxItem {
9    /// Queued input this record refers to.
10    pub input_id: InputId,
11    /// Execution mode this record was produced under.
12    pub mode: DeliveryMode,
13    /// Content blocks carried by this record.
14    pub content: Vec<ContentBlock>,
15}
16
17/// Ordered pending inputs for one Session with delivery-mode rules.
18#[derive(Debug, Default)]
19pub struct Inbox {
20    queued: VecDeque<InboxItem>,
21}
22
23impl Inbox {
24    /// Queue an item; `steer` and `inject` require an active Run.
25    pub fn push(&mut self, item: InboxItem, run_active: bool) -> Result<(), InboxError> {
26        if !run_active && item.mode != DeliveryMode::Followup {
27            return Err(InboxError::NoActiveRun);
28        }
29        match item.mode {
30            DeliveryMode::Followup => self.queued.push_back(item),
31            DeliveryMode::Steer => self.queued.push_front(item),
32            DeliveryMode::Inject => {
33                while self
34                    .queued
35                    .front()
36                    .is_some_and(|queued| queued.mode == DeliveryMode::Inject)
37                {
38                    self.queued.pop_front();
39                }
40                self.queued.push_front(item);
41            }
42        }
43        Ok(())
44    }
45
46    /// Take the next item in delivery order.
47    pub fn pop(&mut self) -> Option<InboxItem> {
48        self.queued.pop_front()
49    }
50    /// Whether nothing is queued.
51    pub fn is_empty(&self) -> bool {
52        self.queued.is_empty()
53    }
54}
55
56/// Rejected inbox operation.
57#[derive(Debug, thiserror::Error, PartialEq, Eq)]
58pub enum InboxError {
59    /// Steer and inject require an active run.
60    #[error("steer and inject require an active run")]
61    NoActiveRun,
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use af_agent_session::text;
68
69    fn item(id: &str, mode: DeliveryMode) -> InboxItem {
70        InboxItem {
71            input_id: id.parse().unwrap(),
72            mode,
73            content: text(id),
74        }
75    }
76
77    #[test]
78    fn inject_preempts_and_coalesces() {
79        let mut inbox = Inbox::default();
80        inbox
81            .push(item("later", DeliveryMode::Followup), true)
82            .unwrap();
83        inbox.push(item("old", DeliveryMode::Inject), true).unwrap();
84        inbox.push(item("new", DeliveryMode::Inject), true).unwrap();
85        assert_eq!(inbox.pop().unwrap().input_id, "new");
86        assert_eq!(inbox.pop().unwrap().input_id, "later");
87    }
88}