use af_context::InputId;
use std::collections::VecDeque;
use af_agent_session::{ContentBlock, DeliveryMode};
#[derive(Debug, Clone, PartialEq)]
pub struct InboxItem {
pub input_id: InputId,
pub mode: DeliveryMode,
pub content: Vec<ContentBlock>,
}
#[derive(Debug, Default)]
pub struct Inbox {
queued: VecDeque<InboxItem>,
}
impl Inbox {
pub fn push(&mut self, item: InboxItem, run_active: bool) -> Result<(), InboxError> {
if !run_active && item.mode != DeliveryMode::Followup {
return Err(InboxError::NoActiveRun);
}
match item.mode {
DeliveryMode::Followup => self.queued.push_back(item),
DeliveryMode::Steer => self.queued.push_front(item),
DeliveryMode::Inject => {
while self
.queued
.front()
.is_some_and(|queued| queued.mode == DeliveryMode::Inject)
{
self.queued.pop_front();
}
self.queued.push_front(item);
}
}
Ok(())
}
pub fn pop(&mut self) -> Option<InboxItem> {
self.queued.pop_front()
}
pub fn is_empty(&self) -> bool {
self.queued.is_empty()
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum InboxError {
#[error("steer and inject require an active run")]
NoActiveRun,
}
#[cfg(test)]
mod tests {
use super::*;
use af_agent_session::text;
fn item(id: &str, mode: DeliveryMode) -> InboxItem {
InboxItem {
input_id: id.parse().unwrap(),
mode,
content: text(id),
}
}
#[test]
fn inject_preempts_and_coalesces() {
let mut inbox = Inbox::default();
inbox
.push(item("later", DeliveryMode::Followup), true)
.unwrap();
inbox.push(item("old", DeliveryMode::Inject), true).unwrap();
inbox.push(item("new", DeliveryMode::Inject), true).unwrap();
assert_eq!(inbox.pop().unwrap().input_id, "new");
assert_eq!(inbox.pop().unwrap().input_id, "later");
}
}