use std::collections::VecDeque;
use rho_sdk::SessionId;
use tokio::sync::mpsc::Receiver;
use crate::app::{
subagent_host_input::SubagentHostInputRequest,
subagent_messaging::{NoticePermits, NoticeRebind, SubagentNotice},
};
#[cfg(test)]
use crate::app::subagent_messaging::SubagentNoticeBridge;
#[derive(Default)]
pub(super) struct SubagentInbox {
questionnaires: Option<Receiver<SubagentHostInputRequest>>,
notices: Option<Receiver<SubagentNotice>>,
notice_permits: Option<NoticePermits>,
queued_questionnaires: VecDeque<SubagentHostInputRequest>,
queued_notices: VecDeque<QueuedNotice>,
pending_delivery_permits: Vec<Option<NoticePermits>>,
}
struct QueuedNotice {
notice: SubagentNotice,
permits: Option<NoticePermits>,
}
enum Incoming {
Questionnaire(SubagentHostInputRequest),
Notice(SubagentNotice),
QuestionnairesClosed,
NoticesClosed,
}
impl SubagentInbox {
pub(super) fn bind(&mut self, manager: &crate::tools::agent::SubagentManager) {
self.questionnaires = Some(manager.bind_host_input());
let rebind = manager.rebind_notices(self.notices.take());
self.install_notice_rebind(rebind);
}
pub(super) async fn recv(&mut self) {
let incoming = {
let Self {
questionnaires,
notices,
..
} = self;
tokio::select! {
request = recv_next(questionnaires) => match request {
Some(request) => Incoming::Questionnaire(request),
None => Incoming::QuestionnairesClosed,
},
notice = recv_next(notices) => match notice {
Some(notice) => Incoming::Notice(notice),
None => Incoming::NoticesClosed,
},
}
};
match incoming {
Incoming::Questionnaire(request) => self.queued_questionnaires.push_back(request),
Incoming::Notice(notice) => {
self.push_accepted_notice(notice, self.notice_permits.clone())
}
Incoming::QuestionnairesClosed => self.questionnaires = None,
Incoming::NoticesClosed => self.notices = None,
}
}
pub(super) fn drain(&mut self) -> bool {
let mut changed = false;
if let Some(receiver) = self.questionnaires.as_mut() {
while let Ok(request) = receiver.try_recv() {
self.queued_questionnaires.push_back(request);
changed = true;
}
}
let mut drained_notices = Vec::new();
if let Some(receiver) = self.notices.as_mut() {
while let Ok(notice) = receiver.try_recv() {
drained_notices.push(notice);
}
}
if !drained_notices.is_empty() {
let current_permits = self.notice_permits.clone();
for notice in drained_notices {
self.push_accepted_notice(notice, current_permits.clone());
}
changed = true;
}
changed
}
pub(super) fn discard_stale(&mut self, session_id: &SessionId) -> bool {
let mut changed = false;
for pending in std::mem::take(&mut self.queued_questionnaires) {
if pending.response.is_closed() {
changed = true;
continue;
}
if &pending.parent_session_id != session_id {
let _ = pending.response.send(Err(rho_sdk::Error::Interrupted {
message: "parent session changed before the delegated questionnaire was shown"
.into(),
}));
changed = true;
continue;
}
self.queued_questionnaires.push_back(pending);
}
let mut kept = VecDeque::new();
for queued in self.queued_notices.drain(..) {
if &queued.notice.parent_session_id == session_id {
kept.push_back(queued);
} else {
release_one(queued.permits);
changed = true;
}
}
self.queued_notices = kept;
changed
}
pub(super) fn take_notices(&mut self, session_id: &SessionId) -> Vec<SubagentNotice> {
debug_assert!(
self.pending_delivery_permits.is_empty(),
"previous take_notices was neither committed nor returned"
);
self.pending_delivery_permits.clear();
let mut kept = Vec::new();
for queued in self.queued_notices.drain(..) {
if &queued.notice.parent_session_id == session_id {
self.pending_delivery_permits.push(queued.permits);
kept.push(queued.notice);
} else {
release_one(queued.permits);
}
}
kept
}
pub(super) fn return_notices(&mut self, notices: impl IntoIterator<Item = SubagentNotice>) {
let notices = notices.into_iter().collect::<Vec<_>>();
let mut permits = std::mem::take(&mut self.pending_delivery_permits);
if permits.len() != notices.len() {
permits.resize_with(notices.len(), || None);
}
for (notice, permits) in notices.into_iter().zip(permits).rev() {
self.queued_notices
.push_front(QueuedNotice { notice, permits });
}
}
pub(super) fn commit_delivered_notices(&mut self, count: usize) {
debug_assert_eq!(
self.pending_delivery_permits.len(),
count,
"commit count must match the last take_notices batch"
);
let _ = count;
for permits in self.pending_delivery_permits.drain(..) {
release_one(permits);
}
}
pub(super) fn take_questionnaires(
&mut self,
) -> impl Iterator<Item = SubagentHostInputRequest> + '_ {
self.queued_questionnaires.drain(..)
}
pub(super) fn return_questionnaires(
&mut self,
requests: impl IntoIterator<Item = SubagentHostInputRequest>,
) {
self.queued_questionnaires.extend(requests);
}
pub(super) fn next_questionnaire(&mut self) -> Option<SubagentHostInputRequest> {
self.queued_questionnaires.pop_front()
}
pub(super) fn has_queued_questionnaires(&self) -> bool {
!self.queued_questionnaires.is_empty()
}
pub(super) fn has_pending_notices(&self) -> bool {
!self.queued_notices.is_empty()
}
#[cfg(test)]
pub(super) fn push_notice_for_test(&mut self, notice: SubagentNotice) {
self.push_accepted_notice(notice, None);
}
#[cfg(test)]
pub(super) fn bind_notices_for_test(&mut self, bridge: &SubagentNoticeBridge) {
let rebind = bridge.rebind_parent(self.notices.take());
self.install_notice_rebind(rebind);
}
#[cfg(test)]
pub(super) fn queued_notice_count(&self) -> usize {
self.queued_notices.len()
}
#[cfg(test)]
pub(super) fn push_questionnaire_for_test(&mut self, request: SubagentHostInputRequest) {
self.queued_questionnaires.push_back(request);
}
#[cfg(test)]
pub(super) fn queued_questionnaire_count(&self) -> usize {
self.queued_questionnaires.len()
}
fn install_notice_rebind(&mut self, rebind: NoticeRebind) {
for notice in rebind.retained {
self.push_accepted_notice(notice, rebind.retired_permits.clone());
}
self.notices = Some(rebind.receiver);
self.notice_permits = Some(rebind.permits);
}
fn push_accepted_notice(&mut self, notice: SubagentNotice, permits: Option<NoticePermits>) {
self.queued_notices
.push_back(QueuedNotice { notice, permits });
}
}
fn release_one(permits: Option<NoticePermits>) {
if let Some(permits) = permits {
permits.release(1);
}
}
async fn recv_next<T>(slot: &mut Option<Receiver<T>>) -> Option<T> {
match slot {
Some(receiver) => receiver.recv().await,
None => std::future::pending().await,
}
}
#[cfg(test)]
#[path = "subagent_inbox_tests.rs"]
mod tests;