1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use futures_core::Stream;
6
7use crate::{AgentError, AgentEvent, Input, TurnOptions, TurnResult, Usage};
8
9pub trait Session: Send {
10 fn turn(
14 &mut self,
15 input: Input,
16 options: TurnOptions,
17 ) -> Pin<Box<dyn Future<Output = Result<TurnOutput, AgentError>> + Send + '_>> {
18 Box::pin(async move { self.turn_stream(input, options).await?.collect_turn().await })
19 }
20
21 fn turn_stream(
23 &mut self,
24 input: Input,
25 options: TurnOptions,
26 ) -> Pin<Box<dyn Future<Output = Result<EventStream, AgentError>> + Send + '_>>;
27
28 fn session_id(&self) -> Option<&str>;
30
31 fn interrupt(&mut self) -> Pin<Box<dyn Future<Output = Result<(), AgentError>> + Send + '_>>;
33}
34
35#[must_use = "streams do nothing unless polled"]
36pub struct EventStream {
37 inner: Pin<Box<dyn Stream<Item = Result<AgentEvent, AgentError>> + Send>>,
38}
39
40impl EventStream {
41 pub fn new(
42 stream: impl Stream<Item = Result<AgentEvent, AgentError>> + Send + 'static,
43 ) -> Self {
44 Self {
45 inner: Box::pin(stream),
46 }
47 }
48
49 pub fn from_receiver(rx: tokio::sync::mpsc::Receiver<Result<AgentEvent, AgentError>>) -> Self {
50 Self::new(ReceiverStream { inner: rx })
51 }
52
53 pub async fn collect_turn(mut self) -> Result<TurnOutput, AgentError> {
55 let mut events = Vec::new();
56 let mut response = None;
57 let mut response_deltas = String::new();
58 let mut saw_delta = false;
59 let mut usage = None;
60 let mut result = None;
61 let mut tool_calls: Vec<ToolCallOutput> = Vec::new();
62
63 loop {
64 match std::future::poll_fn(|cx| self.inner.as_mut().poll_next(cx)).await {
65 Some(Ok(event)) => {
66 match &event {
67 AgentEvent::Message { text, .. } => {
68 response = Some(text.clone());
69 }
70 AgentEvent::TextDelta { delta, .. } => {
71 response_deltas.push_str(delta);
72 saw_delta = true;
73 }
74 AgentEvent::TurnCompleted {
75 usage: u,
76 result: r,
77 } => {
78 usage.clone_from(u);
79 result.clone_from(r);
80 }
81 AgentEvent::TurnFailed { message } => {
82 return Err(AgentError::TurnFailed {
83 message: message.clone(),
84 });
85 }
86 AgentEvent::ToolCall {
87 id,
88 name,
89 arguments,
90 } => {
91 tool_calls.push(ToolCallOutput {
92 id: id.clone(),
93 name: name.clone(),
94 arguments: arguments.clone(),
95 });
96 }
97 _ => {}
98 }
99 events.push(event);
100 }
101 Some(Err(e)) => return Err(e),
102 None => break,
103 }
104 }
105
106 if response.is_none() && saw_delta {
107 response = Some(response_deltas);
108 }
109
110 Ok(TurnOutput {
111 events,
112 response,
113 tool_calls,
114 usage,
115 result,
116 })
117 }
118}
119
120impl Stream for EventStream {
121 type Item = Result<AgentEvent, AgentError>;
122
123 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
124 self.inner.as_mut().poll_next(cx)
125 }
126}
127
128struct ReceiverStream {
130 inner: tokio::sync::mpsc::Receiver<Result<AgentEvent, AgentError>>,
131}
132
133impl Stream for ReceiverStream {
134 type Item = Result<AgentEvent, AgentError>;
135
136 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
137 self.inner.poll_recv(cx)
138 }
139}
140
141#[derive(Debug, Clone)]
143pub struct ToolCallOutput {
144 pub id: String,
145 pub name: String,
146 pub arguments: String,
148}
149
150#[derive(Debug, Clone)]
151pub struct TurnOutput {
152 pub events: Vec<AgentEvent>,
153 pub response: Option<String>,
154 pub tool_calls: Vec<ToolCallOutput>,
156 pub usage: Option<Usage>,
157 pub result: Option<TurnResult>,
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[tokio::test]
165 async fn collect_turn_extracts_response() {
166 let (tx, rx) = tokio::sync::mpsc::channel(16);
167
168 tx.send(Ok(AgentEvent::TurnStarted)).await.unwrap();
169 tx.send(Ok(AgentEvent::Message {
170 id: "msg-1".into(),
171 text: "Hello world".into(),
172 }))
173 .await
174 .unwrap();
175 tx.send(Ok(AgentEvent::TurnCompleted {
176 usage: Some(Usage {
177 input_tokens: Some(10),
178 output_tokens: Some(5),
179 ..Default::default()
180 }),
181 result: None,
182 }))
183 .await
184 .unwrap();
185 drop(tx);
186
187 let stream = EventStream::from_receiver(rx);
188 let output = stream.collect_turn().await.unwrap();
189
190 assert_eq!(output.response.as_deref(), Some("Hello world"));
191 assert_eq!(output.events.len(), 3);
192 assert_eq!(output.usage.as_ref().unwrap().input_tokens, Some(10));
193 }
194
195 #[tokio::test]
196 async fn collect_turn_fails_on_turn_failed() {
197 let (tx, rx) = tokio::sync::mpsc::channel(16);
198
199 tx.send(Ok(AgentEvent::TurnStarted)).await.unwrap();
200 tx.send(Ok(AgentEvent::TurnFailed {
201 message: "something broke".into(),
202 }))
203 .await
204 .unwrap();
205 drop(tx);
206
207 let stream = EventStream::from_receiver(rx);
208 let err = stream.collect_turn().await.unwrap_err();
209
210 match err {
211 AgentError::TurnFailed { message } => assert_eq!(message, "something broke"),
212 _ => panic!("expected TurnFailed, got {err:?}"),
213 }
214 }
215
216 #[tokio::test]
217 async fn collect_turn_uses_last_message() {
218 let (tx, rx) = tokio::sync::mpsc::channel(16);
219
220 tx.send(Ok(AgentEvent::Message {
221 id: "1".into(),
222 text: "first".into(),
223 }))
224 .await
225 .unwrap();
226 tx.send(Ok(AgentEvent::Message {
227 id: "2".into(),
228 text: "second".into(),
229 }))
230 .await
231 .unwrap();
232 tx.send(Ok(AgentEvent::TurnCompleted {
233 usage: None,
234 result: None,
235 }))
236 .await
237 .unwrap();
238 drop(tx);
239
240 let stream = EventStream::from_receiver(rx);
241 let output = stream.collect_turn().await.unwrap();
242
243 assert_eq!(output.response.as_deref(), Some("second"));
244 }
245
246 #[tokio::test]
247 async fn collect_turn_uses_deltas_when_no_message() {
248 let (tx, rx) = tokio::sync::mpsc::channel(16);
249
250 tx.send(Ok(AgentEvent::TextDelta {
251 id: "block_0".into(),
252 delta: "Hello".into(),
253 }))
254 .await
255 .unwrap();
256 tx.send(Ok(AgentEvent::TextDelta {
257 id: "block_0".into(),
258 delta: " world".into(),
259 }))
260 .await
261 .unwrap();
262 tx.send(Ok(AgentEvent::TurnCompleted {
263 usage: None,
264 result: None,
265 }))
266 .await
267 .unwrap();
268 drop(tx);
269
270 let stream = EventStream::from_receiver(rx);
271 let output = stream.collect_turn().await.unwrap();
272
273 assert_eq!(output.response.as_deref(), Some("Hello world"));
274 }
275
276 #[tokio::test]
277 async fn collect_turn_propagates_stream_error() {
278 let (tx, rx) = tokio::sync::mpsc::channel(16);
279
280 tx.send(Ok(AgentEvent::TurnStarted)).await.unwrap();
281 tx.send(Err(AgentError::ProcessFailed {
282 code: 1,
283 stderr: "crash".into(),
284 }))
285 .await
286 .unwrap();
287 drop(tx);
288
289 let stream = EventStream::from_receiver(rx);
290 let err = stream.collect_turn().await.unwrap_err();
291
292 match err {
293 AgentError::ProcessFailed { code, .. } => assert_eq!(code, 1),
294 _ => panic!("expected ProcessFailed, got {err:?}"),
295 }
296 }
297
298 #[tokio::test]
299 async fn empty_stream_produces_empty_output() {
300 let (_tx, rx) = tokio::sync::mpsc::channel::<Result<AgentEvent, AgentError>>(1);
301 drop(_tx);
302
303 let stream = EventStream::from_receiver(rx);
304 let output = stream.collect_turn().await.unwrap();
305
306 assert!(output.events.is_empty());
307 assert!(output.response.is_none());
308 assert!(output.usage.is_none());
309 }
310}