use super::*;
#[must_use = "a turn continues running when dropped; await result(), control it, or explicitly drop it"]
pub struct Turn {
pub(super) control: TurnControl,
pub(super) events: AgentEvents,
pub(super) result: oneshot::Receiver<Result<TurnResult>>,
}
impl Turn {
#[must_use]
pub fn control(&self) -> TurnControl {
self.control.clone()
}
pub async fn steer(&self, prompt: impl Into<Prompt>) -> Result<()> {
self.control.steer(prompt).await
}
pub async fn cancel(&self) -> Result<()> {
self.control.cancel().await
}
pub async fn result(self) -> Result<TurnResult> {
self.await
}
}
impl Stream for Turn {
type Item = AgentEvent;
fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Pin::new(&mut self.events).poll_next(context)
}
}
impl Future for Turn {
type Output = Result<TurnResult>;
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.result)
.poll(context)
.map(|result| result.map_err(|_| NanocodexError::TurnStopped)?)
}
}
#[derive(Clone)]
pub struct TurnControl {
pub(super) key: TurnKey,
pub(super) commands: mpsc::Sender<Command>,
}
impl TurnControl {
pub async fn steer(&self, prompt: impl Into<Prompt>) -> Result<()> {
let prompt = prompt.into();
if prompt.instruction.is_empty() {
return Err(NanocodexError::InvalidRequest(
"steer instruction must not be empty".to_owned(),
));
}
request_command(&self.commands, |result| Command::Steer {
key: self.key,
prompt,
result,
})
.await
}
pub async fn cancel(&self) -> Result<()> {
request_command(&self.commands, |result| Command::Cancel {
key: self.key,
result,
})
.await
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub(super) struct TurnKey(pub(super) u64);
#[derive(Clone)]
#[non_exhaustive]
pub struct TurnResult {
pub(super) final_message: String,
pub(super) usage: TurnUsage,
pub(super) checkpoint: Arc<CommittedSession>,
}
impl TurnResult {
#[must_use]
pub fn final_message(&self) -> &str {
&self.final_message
}
#[must_use]
pub fn into_final_message(self) -> String {
self.final_message
}
#[must_use]
pub const fn usage(&self) -> &TurnUsage {
&self.usage
}
#[must_use]
pub fn snapshot(&self) -> SessionSnapshot {
self.checkpoint.snapshot()
}
}
impl fmt::Debug for TurnResult {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("TurnResult")
.field("final_message", &self.final_message)
.finish_non_exhaustive()
}
}
pub(super) enum Command {
Prompt {
key: TurnKey,
prompt: Prompt,
thinking: Option<Thinking>,
fast_mode: Option<bool>,
parent: Option<tracing::Span>,
events: EventSink,
result: oneshot::Sender<Result<TurnResult>>,
},
Steer {
key: TurnKey,
prompt: Prompt,
result: oneshot::Sender<Result<()>>,
},
Cancel {
key: TurnKey,
result: oneshot::Sender<Result<()>>,
},
Fork {
checkpoint: Option<Arc<CommittedSession>>,
result: oneshot::Sender<Result<(Nanocodex, AgentEvents)>>,
},
Spawn {
result: oneshot::Sender<Result<(Nanocodex, AgentEvents)>>,
},
SetThinking {
thinking: Thinking,
result: oneshot::Sender<Result<()>>,
},
SetFastMode {
enabled: bool,
result: oneshot::Sender<Result<()>>,
},
Compact {
parent: Option<tracing::Span>,
result: oneshot::Sender<Result<()>>,
},
Shutdown,
}
pub(super) enum QueuedTurn {
Pending {
key: TurnKey,
prompt: Prompt,
thinking: Thinking,
fast_mode: bool,
parent: Option<tracing::Span>,
events: EventSink,
result: oneshot::Sender<Result<TurnResult>>,
},
Cancelled {
prompt: Prompt,
thinking: Thinking,
fast_mode: bool,
parent: Option<tracing::Span>,
events: EventSink,
result: oneshot::Sender<Result<TurnResult>>,
},
}