nanocodex_agent/agent/
turn.rs1use super::*;
2
3#[must_use = "a turn continues running when dropped; await result(), control it, or explicitly drop it"]
12pub struct Turn {
13 pub(super) control: TurnControl,
14 pub(super) events: AgentEvents,
15 pub(super) result: oneshot::Receiver<Result<TurnResult>>,
16}
17
18impl Turn {
19 #[must_use]
21 pub fn control(&self) -> TurnControl {
22 self.control.clone()
23 }
24
25 pub async fn steer(&self, prompt: impl Into<Prompt>) -> Result<()> {
32 self.control.steer(prompt).await
33 }
34
35 pub async fn cancel(&self) -> Result<()> {
47 self.control.cancel().await
48 }
49
50 pub async fn result(self) -> Result<TurnResult> {
61 self.await
62 }
63}
64
65impl Stream for Turn {
66 type Item = AgentEvent;
67
68 fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
69 Pin::new(&mut self.events).poll_next(context)
70 }
71}
72
73impl Future for Turn {
74 type Output = Result<TurnResult>;
75
76 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
77 Pin::new(&mut self.result)
78 .poll(context)
79 .map(|result| result.map_err(|_| NanocodexError::TurnStopped)?)
80 }
81}
82
83#[derive(Clone)]
85pub struct TurnControl {
86 pub(super) key: TurnKey,
87 pub(super) commands: mpsc::Sender<Command>,
88}
89
90impl TurnControl {
91 pub async fn steer(&self, prompt: impl Into<Prompt>) -> Result<()> {
98 let prompt = prompt.into();
99 if prompt.instruction.is_empty() {
100 return Err(NanocodexError::InvalidRequest(
101 "steer instruction must not be empty".to_owned(),
102 ));
103 }
104 request_command(&self.commands, |result| Command::Steer {
105 key: self.key,
106 prompt,
107 result,
108 })
109 .await
110 }
111
112 pub async fn cancel(&self) -> Result<()> {
119 request_command(&self.commands, |result| Command::Cancel {
120 key: self.key,
121 result,
122 })
123 .await
124 }
125}
126
127#[derive(Clone, Copy, Eq, PartialEq)]
128pub(super) struct TurnKey(pub(super) u64);
129
130#[derive(Clone)]
132#[non_exhaustive]
133pub struct TurnResult {
134 pub(super) final_message: String,
135 pub(super) usage: TurnUsage,
136 pub(super) checkpoint: Arc<CommittedSession>,
137}
138
139impl TurnResult {
140 #[must_use]
142 pub fn final_message(&self) -> &str {
143 &self.final_message
144 }
145
146 #[must_use]
148 pub fn into_final_message(self) -> String {
149 self.final_message
150 }
151
152 #[must_use]
154 pub const fn usage(&self) -> &TurnUsage {
155 &self.usage
156 }
157
158 #[must_use]
164 pub fn snapshot(&self) -> SessionSnapshot {
165 self.checkpoint.snapshot()
166 }
167}
168
169impl fmt::Debug for TurnResult {
170 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
171 formatter
172 .debug_struct("TurnResult")
173 .field("final_message", &self.final_message)
174 .finish_non_exhaustive()
175 }
176}
177
178pub(super) enum Command {
179 Prompt {
180 key: TurnKey,
181 prompt: Prompt,
182 thinking: Option<Thinking>,
183 fast_mode: Option<bool>,
184 parent: Option<tracing::Span>,
185 events: EventSink,
186 result: oneshot::Sender<Result<TurnResult>>,
187 },
188 Steer {
189 key: TurnKey,
190 prompt: Prompt,
191 result: oneshot::Sender<Result<()>>,
192 },
193 Cancel {
194 key: TurnKey,
195 result: oneshot::Sender<Result<()>>,
196 },
197 Fork {
198 checkpoint: Option<Arc<CommittedSession>>,
199 result: oneshot::Sender<Result<(Nanocodex, AgentEvents)>>,
200 },
201 Spawn {
202 result: oneshot::Sender<Result<(Nanocodex, AgentEvents)>>,
203 },
204 SetThinking {
205 thinking: Thinking,
206 result: oneshot::Sender<Result<()>>,
207 },
208 SetFastMode {
209 enabled: bool,
210 result: oneshot::Sender<Result<()>>,
211 },
212 Compact {
213 parent: Option<tracing::Span>,
214 result: oneshot::Sender<Result<()>>,
215 },
216 Shutdown,
217}
218
219pub(super) enum QueuedTurn {
220 Pending {
221 key: TurnKey,
222 prompt: Prompt,
223 thinking: Thinking,
224 fast_mode: bool,
225 parent: Option<tracing::Span>,
226 events: EventSink,
227 result: oneshot::Sender<Result<TurnResult>>,
228 },
229 Cancelled {
230 prompt: Prompt,
231 thinking: Thinking,
232 fast_mode: bool,
233 parent: Option<tracing::Span>,
234 events: EventSink,
235 result: oneshot::Sender<Result<TurnResult>>,
236 },
237}