use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex},
};
use futures_util::{
future::{BoxFuture, Shared},
FutureExt,
};
use super::subagent_messaging::{parent_message_prompt, ValidatedMessage};
type Receipt = Shared<BoxFuture<'static, Result<rho_sdk::SteeringId, String>>>;
struct PendingMessage {
receipt: Receipt,
message: ValidatedMessage,
}
#[derive(Default)]
struct SteeringState {
handle: Option<rho_sdk::SteeringHandle>,
pending: Vec<Arc<PendingMessage>>,
}
#[derive(Clone, Default)]
pub(crate) struct SteeringSlot {
state: Arc<Mutex<SteeringState>>,
}
impl std::fmt::Debug for SteeringSlot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SteeringSlot").finish_non_exhaustive()
}
}
impl SteeringSlot {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn publish(&self, handle: rho_sdk::SteeringHandle) {
self.state.lock().expect("parent steering state").handle = Some(handle);
}
pub(crate) async fn send(&self, message: &ValidatedMessage) -> anyhow::Result<()> {
let input = rho_sdk::UserInput::text(parent_message_prompt(message));
let pending = {
let mut state = self.state.lock().expect("parent steering state");
let Some(handle) = &state.handle else {
anyhow::bail!("delegated run is not accepting messages; wait until status is running, then message again");
};
let receipt = handle.request_steer_retractable(input)?;
let pending = Arc::new(PendingMessage {
receipt: receipt
.map(|result| result.map_err(|error| error.to_string()))
.boxed()
.shared(),
message: message.clone(),
});
state.pending.push(Arc::clone(&pending));
pending
};
let result = self.wait_receipt(pending.receipt.clone()).await;
if result.is_err() {
self.state
.lock()
.expect("parent steering state")
.pending
.retain(|message| !Arc::ptr_eq(message, &pending));
}
result.map(drop).map_err(anyhow::Error::msg)
}
async fn wait_receipt(&self, mut receipt: Receipt) -> Result<rho_sdk::SteeringId, String> {
futures_util::future::poll_fn(|cx| {
let _state = self.state.lock().expect("parent steering state");
Pin::new(&mut receipt).poll(cx)
})
.await
}
pub(crate) fn applied(&self, ids: &[rho_sdk::SteeringId]) -> Vec<ValidatedMessage> {
let mut state = self.state.lock().expect("parent steering state");
let mut messages = std::collections::HashMap::new();
state
.pending
.retain(|pending| match pending.receipt.clone().now_or_never() {
Some(Ok(id)) if ids.contains(&id) => {
messages.insert(id, pending.message.clone());
false
}
Some(Err(_)) => false,
Some(Ok(_)) | None => true,
});
ids.iter().filter_map(|id| messages.remove(id)).collect()
}
pub(crate) fn clear(&self) {
let mut state = self.state.lock().expect("parent steering state");
state.handle = None;
state.pending.clear();
}
}
#[cfg(test)]
#[path = "parent_steering_tests.rs"]
mod tests;