use std::{
collections::VecDeque,
sync::{Arc, Mutex, MutexGuard},
};
use crate::{ContentBlock, Message, error::RuntimeError, runtime::RunOptions};
use super::Agent;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum QueueMode {
All,
#[default]
OneAtATime,
}
#[derive(Default)]
struct SteeringQueues {
steer: VecDeque<Vec<ContentBlock>>,
follow_up: VecDeque<Vec<ContentBlock>>,
steer_mode: QueueMode,
follow_up_mode: QueueMode,
}
#[derive(Clone, Default)]
pub struct SteeringHandle {
queues: Arc<Mutex<SteeringQueues>>,
}
impl SteeringHandle {
pub(crate) fn new() -> Self {
Self::default()
}
pub fn steer(&self, content: impl Into<Vec<ContentBlock>>) {
let content = content.into();
if !content.is_empty() {
self.lock().steer.push_back(content);
}
}
pub fn follow_up(&self, content: impl Into<Vec<ContentBlock>>) {
let content = content.into();
if !content.is_empty() {
self.lock().follow_up.push_back(content);
}
}
pub fn clear_steer(&self) {
self.lock().steer.clear();
}
pub fn clear_follow_up(&self) {
self.lock().follow_up.clear();
}
pub fn has_pending(&self) -> bool {
let queues = self.lock();
!queues.steer.is_empty() || !queues.follow_up.is_empty()
}
pub fn set_steer_mode(&self, mode: QueueMode) {
self.lock().steer_mode = mode;
}
pub fn set_follow_up_mode(&self, mode: QueueMode) {
self.lock().follow_up_mode = mode;
}
pub(crate) fn has_steer(&self) -> bool {
!self.lock().steer.is_empty()
}
pub(crate) fn has_follow_up(&self) -> bool {
!self.lock().follow_up.is_empty()
}
fn drain_steer(&self) -> Vec<Vec<ContentBlock>> {
let mut queues = self.lock();
let mode = queues.steer_mode;
drain(&mut queues.steer, mode)
}
fn drain_follow_up(&self) -> Vec<Vec<ContentBlock>> {
let mut queues = self.lock();
let mode = queues.follow_up_mode;
drain(&mut queues.follow_up, mode)
}
fn prepend_steer(&self, entries: Vec<Vec<ContentBlock>>) {
prepend(&mut self.lock().steer, entries);
}
fn prepend_follow_up(&self, entries: Vec<Vec<ContentBlock>>) {
prepend(&mut self.lock().follow_up, entries);
}
fn lock(&self) -> MutexGuard<'_, SteeringQueues> {
self.queues
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
impl Agent {
pub fn steering_handle(&self) -> SteeringHandle {
self.steering.clone()
}
pub fn steer(&self, content: impl Into<Vec<ContentBlock>>) {
self.steering.steer(content);
}
pub fn follow_up(&self, content: impl Into<Vec<ContentBlock>>) {
self.steering.follow_up(content);
}
pub async fn run_queued(&mut self, options: RunOptions) -> Result<Message, RuntimeError> {
let Some(content) = self.drain_steer() else {
return Err(RuntimeError::OperationDenied(
"no queued steering input is available".to_string(),
));
};
let result = self.run(content, options).await;
if result.is_err() {
self.requeue_inflight_steering();
}
result
}
pub(super) fn has_pending_steer(&self) -> bool {
self.steering.has_steer()
}
pub(super) fn has_pending_follow_up(&self) -> bool {
self.steering.has_follow_up()
}
pub(super) fn drain_steer(&mut self) -> Option<Vec<ContentBlock>> {
let entries = self.steering.drain_steer();
if entries.is_empty() {
return None;
}
let content = flatten(&entries);
self.inflight_steer.extend(entries);
Some(content)
}
pub(super) fn drain_follow_up(&mut self) -> Option<Vec<ContentBlock>> {
let entries = self.steering.drain_follow_up();
if entries.is_empty() {
return None;
}
let content = flatten(&entries);
self.inflight_follow_up.extend(entries);
Some(content)
}
pub(super) fn clear_inflight_steering(&mut self) {
self.inflight_steer.clear();
self.inflight_follow_up.clear();
}
pub(super) fn requeue_inflight_steering(&mut self) {
self.steering
.prepend_steer(std::mem::take(&mut self.inflight_steer));
self.steering
.prepend_follow_up(std::mem::take(&mut self.inflight_follow_up));
}
}
fn drain(queue: &mut VecDeque<Vec<ContentBlock>>, mode: QueueMode) -> Vec<Vec<ContentBlock>> {
match mode {
QueueMode::All => queue.drain(..).collect(),
QueueMode::OneAtATime => queue.pop_front().into_iter().collect(),
}
}
fn prepend(queue: &mut VecDeque<Vec<ContentBlock>>, entries: Vec<Vec<ContentBlock>>) {
for entry in entries.into_iter().rev() {
queue.push_front(entry);
}
}
fn flatten(entries: &[Vec<ContentBlock>]) -> Vec<ContentBlock> {
entries.iter().flatten().cloned().collect()
}