use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::agent_cx::AgentCx;
use crate::error::Result;
use crate::model::AssistantMessage;
use super::{SessionControlHandle, control_error};
pub(super) type CancelWait = Pin<Box<dyn Future<Output = ()> + Send>>;
pub(super) fn cancellation(owner: AgentCx) -> CancelWait {
Box::pin(async move {
let (sender, mut receiver) = asupersync::channel::oneshot::channel::<()>();
let _ = receiver.recv(owner.cx()).await;
drop(sender);
})
}
pub(super) struct OwnedTurn<F> {
owner: Option<AgentCx>,
future: Option<Pin<Box<F>>>,
cancellation: Option<CancelWait>,
control: SessionControlHandle,
started: bool,
}
impl<F> OwnedTurn<F> {
pub(super) fn new(future: F, control: SessionControlHandle) -> Self {
let owner = asupersync::Cx::current().map(AgentCx::from_cx);
let cancellation = owner.clone().map(cancellation);
Self {
owner,
future: Some(Box::pin(future)),
cancellation,
control,
started: false,
}
}
fn retire(&mut self) {
drop(self.future.take());
drop(self.cancellation.take());
}
}
impl<F: Future<Output = Result<AssistantMessage>>> Future for OwnedTurn<F> {
type Output = Result<AssistantMessage>;
fn poll(self: Pin<&mut Self>, task: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
assert!(
this.future.is_some(),
"a completed owned turn cannot be polled again"
);
if this.owner.is_none() {
let owner = AgentCx::for_current_or_request();
this.cancellation = Some(cancellation(owner.clone()));
this.owner = Some(owner);
}
let owner = this.owner.as_ref().expect("turn owner bound").clone();
let _current = owner.cx().clone().set_current_restricted();
let owner_cancelled = owner.checkpoint().is_err()
|| this
.cancellation
.as_mut()
.is_some_and(|wait| wait.as_mut().poll(task).is_ready());
if owner_cancelled {
this.control.abort();
drop(this.cancellation.take());
}
if !this.started && !this.control.snapshot().accepting_input {
this.retire();
return Poll::Ready(Err(control_error(
"SESSION_CONTROL_CANCELLED",
"turn cancelled before execution; no prompt or continuation was dispatched",
)));
}
this.started = true;
let result = this
.future
.as_mut()
.expect("running turn retains its native future")
.as_mut()
.poll(task);
if result.is_ready() {
this.retire();
}
result
}
}
impl<F> Drop for OwnedTurn<F> {
fn drop(&mut self) {
let _current = self
.owner
.as_ref()
.map(|owner| owner.cx().clone().set_current_restricted());
self.retire();
}
}
#[cfg(test)]
mod tests;