1use std::{
2 collections::VecDeque,
3 future::Future,
4 pin::Pin,
5 sync::{Arc, Mutex, MutexGuard},
6 task::{Context, Poll},
7};
8
9use futures_core::Stream;
10use runifold_core::Usage;
11use runifold_model::{ModelStreamEvent, ToolCall};
12use serde::{Deserialize, Serialize};
13
14use crate::{
15 AgentConversationError, AgentError, AgentFuture, AgentOutcome, ConversationVersion,
16 TerminalRequirementFailure, TerminalReviewVerdictKind,
17};
18
19#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
21#[non_exhaustive]
22pub enum CallableKind {
23 Tool,
25 Agent,
27}
28
29#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
31#[non_exhaustive]
32pub enum AgentStreamEvent {
33 Started {
35 agent: String,
37 },
38 TurnStarted {
40 turn: u32,
42 },
43 Model {
45 turn: u32,
47 event: ModelStreamEvent,
49 },
50 ContextRetrieved {
52 source: String,
54 documents: usize,
56 },
57 CallableStarted {
59 turn: u32,
61 kind: CallableKind,
63 call: ToolCall,
65 },
66 CallableCompleted {
68 turn: u32,
70 kind: CallableKind,
72 call_id: String,
74 name: String,
76 success: bool,
78 },
79 UsageUpdated {
81 usage: Usage,
83 },
84 Completed {
86 outcome: AgentOutcome,
88 },
89 ConversationSummaryStarted {
91 checkpoint_id: runifold_core::CheckpointId,
93 through_sequence: crate::ConversationSequence,
95 },
96 ConversationSummaryCommitted {
98 through_sequence: crate::ConversationSequence,
100 usage: Usage,
102 },
103 ConversationCommitted {
105 outcome: AgentOutcome,
107 conversation_version: ConversationVersion,
109 },
110 TerminalRepairScheduled {
112 attempt: u32,
114 failure: TerminalRequirementFailure,
116 },
117 TurnReviewStarted {
119 turn: u32,
121 },
122 TurnReviewCompleted {
124 turn: u32,
126 verdict: TerminalReviewVerdictKind,
128 },
129 TurnReviewRepairScheduled {
131 attempt: u32,
133 turn: u32,
135 },
136 TerminalReviewStarted {
138 attempt: u32,
140 },
141 TerminalReviewCompleted {
143 attempt: u32,
145 verdict: TerminalReviewVerdictKind,
147 },
148 TerminalReviewRepairScheduled {
150 attempt: u32,
152 },
153}
154
155pub(crate) trait AgentObserver: Send + Sync {
156 fn emit(&self, event: AgentStreamEvent);
157
158 fn backpressured(&self) -> bool {
159 false
160 }
161}
162
163#[derive(Debug)]
164pub(crate) struct NoopObserver;
165
166impl AgentObserver for NoopObserver {
167 fn emit(&self, _event: AgentStreamEvent) {}
168}
169
170#[derive(Clone, Debug, Default)]
171pub(crate) struct BufferedObserver {
172 suppress_completed: bool,
173 events: Arc<Mutex<VecDeque<AgentStreamEvent>>>,
174}
175
176impl BufferedObserver {
177 pub(crate) fn durable() -> Self {
178 Self {
179 suppress_completed: true,
180 ..Self::default()
181 }
182 }
183
184 pub(crate) fn events(&self) -> Arc<Mutex<VecDeque<AgentStreamEvent>>> {
185 self.events.clone()
186 }
187}
188
189impl AgentObserver for BufferedObserver {
190 fn emit(&self, event: AgentStreamEvent) {
191 if self.suppress_completed && matches!(event, AgentStreamEvent::Completed { .. }) {
192 return;
193 }
194 self.events
195 .lock()
196 .unwrap_or_else(std::sync::PoisonError::into_inner)
197 .push_back(event);
198 }
199
200 fn backpressured(&self) -> bool {
201 true
202 }
203}
204
205pub(crate) async fn emit_agent_event(observer: &dyn AgentObserver, event: AgentStreamEvent) {
206 observer.emit(event);
207 if observer.backpressured() {
208 YieldOnce::new().await;
209 }
210}
211
212struct YieldOnce {
213 yielded: bool,
214}
215
216impl YieldOnce {
217 const fn new() -> Self {
218 Self { yielded: false }
219 }
220}
221
222impl Future for YieldOnce {
223 type Output = ();
224
225 fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
226 if self.yielded {
227 Poll::Ready(())
228 } else {
229 self.yielded = true;
230 context.waker().wake_by_ref();
231 Poll::Pending
232 }
233 }
234}
235
236#[must_use = "streams do nothing unless polled"]
238pub struct AgentEventStream<'a, E = AgentError> {
239 execution: Option<AgentFuture<'a, Result<AgentOutcome, E>>>,
240 events: Arc<Mutex<VecDeque<AgentStreamEvent>>>,
241 failure: Option<E>,
242 finished: bool,
243}
244
245impl<'a, E> AgentEventStream<'a, E> {
246 pub(crate) fn new(
247 execution: AgentFuture<'a, Result<AgentOutcome, E>>,
248 events: Arc<Mutex<VecDeque<AgentStreamEvent>>>,
249 ) -> Self {
250 Self {
251 execution: Some(execution),
252 events,
253 failure: None,
254 finished: false,
255 }
256 }
257
258 fn events(&self) -> MutexGuard<'_, VecDeque<AgentStreamEvent>> {
259 self.events
260 .lock()
261 .unwrap_or_else(std::sync::PoisonError::into_inner)
262 }
263
264 fn pop_event(&self) -> Option<AgentStreamEvent> {
265 self.events().pop_front()
266 }
267}
268
269impl<E: Unpin> Stream for AgentEventStream<'_, E> {
270 type Item = Result<AgentStreamEvent, E>;
271
272 fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
273 let this = self.get_mut();
274 if let Some(event) = this.pop_event() {
275 return Poll::Ready(Some(Ok(event)));
276 }
277 if let Some(execution) = this.execution.as_mut() {
278 match execution.as_mut().poll(context) {
279 Poll::Pending => {
280 return this
281 .pop_event()
282 .map_or(Poll::Pending, |event| Poll::Ready(Some(Ok(event))));
283 }
284 Poll::Ready(Ok(_outcome)) => {
285 this.execution = None;
286 }
287 Poll::Ready(Err(error)) => {
288 this.execution = None;
289 this.failure = Some(error);
290 }
291 }
292 }
293 if let Some(event) = this.pop_event() {
294 return Poll::Ready(Some(Ok(event)));
295 }
296 if let Some(error) = this.failure.take() {
297 return Poll::Ready(Some(Err(error)));
298 }
299 if this.execution.is_none() {
300 this.finished = true;
301 }
302 if this.finished {
303 Poll::Ready(None)
304 } else {
305 Poll::Pending
306 }
307 }
308}
309
310impl<E> std::fmt::Debug for AgentEventStream<'_, E> {
311 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 formatter
313 .debug_struct("AgentEventStream")
314 .field("queued_events", &self.events().len())
315 .field("has_execution", &self.execution.is_some())
316 .field("has_failure", &self.failure.is_some())
317 .field("finished", &self.finished)
318 .finish()
319 }
320}
321
322pub type DurableConversationEventStream<'a> = AgentEventStream<'a, AgentConversationError>;