1use std::pin::Pin;
2use std::sync::Arc;
3use std::time::Duration;
4
5use futures_core::Stream;
6use futures_util::StreamExt;
7use reqwest::header::ACCEPT;
8use reqwest::Method;
9use serde_json::Value;
10
11use crate::error::parse_api_error;
12use crate::sse::consume_sse_blocks;
13use crate::transport::{enc, Transport};
14use crate::{Error, JsonObject, StreamEvent};
15
16pub type EventStream = Pin<Box<dyn Stream<Item = crate::Result<StreamEvent>> + Send>>;
18
19pub trait StreamEventExt {
21 fn event_type(&self) -> &str;
23 fn run(&self) -> Option<&JsonObject>;
25 fn run_id(&self) -> Option<&str>;
27}
28
29impl StreamEventExt for StreamEvent {
30 fn event_type(&self) -> &str {
31 self.get("type").and_then(Value::as_str).unwrap_or("")
32 }
33
34 fn run(&self) -> Option<&JsonObject> {
35 self.get("run").and_then(Value::as_object)
36 }
37
38 fn run_id(&self) -> Option<&str> {
39 self.run()?.get("run_id")?.as_str()
40 }
41}
42
43pub(crate) fn iter_stream(
46 transport: Arc<Transport>,
47 method: Method,
48 path: String,
49 body: Option<Value>,
50 query: Vec<(String, String)>,
51) -> EventStream {
52 Box::pin(async_stream::stream! {
53 let mut builder = transport
54 .request_builder(method, &path)
55 .header(ACCEPT, "text/event-stream");
56 if !query.is_empty() {
57 builder = builder.query(&query);
58 }
59 if let Some(body) = &body {
60 builder = builder.json(body);
61 }
62
63 let response = match builder.send().await {
64 Ok(response) => response,
65 Err(error) => {
66 yield Err(Error::Http(error));
67 return;
68 }
69 };
70 let status = response.status();
71 if status.is_client_error() || status.is_server_error() {
72 let headers = response.headers().clone();
73 let text = response.text().await.unwrap_or_default();
74 yield Err(Error::Api(parse_api_error(status.as_u16(), &text, &headers)));
75 return;
76 }
77
78 let mut pending: Vec<u8> = Vec::new();
81 let mut buffer = String::new();
82 let mut chunks = response.bytes_stream();
83 while let Some(chunk) = chunks.next().await {
84 let chunk = match chunk {
85 Ok(chunk) => chunk,
86 Err(error) => {
87 yield Err(Error::Http(error));
88 return;
89 }
90 };
91 pending.extend_from_slice(&chunk);
92 let valid_len = match std::str::from_utf8(&pending) {
93 Ok(_) => pending.len(),
94 Err(error) => error.valid_up_to(),
95 };
96 buffer.push_str(std::str::from_utf8(&pending[..valid_len]).unwrap_or(""));
97 pending.drain(..valid_len);
98
99 let (events, rest) = consume_sse_blocks(&buffer);
100 buffer = rest;
101 for event in events {
102 yield Ok(event);
103 }
104 }
105
106 buffer.push_str("\n\n");
107 let (events, _) = consume_sse_blocks(&buffer);
108 for event in events {
109 yield Ok(event);
110 }
111 })
112}
113
114pub(crate) async fn wait_for_run_result(
115 transport: Arc<Transport>,
116 stream_id: &str,
117 event_id: &str,
118 timeout: Duration,
119 mut on_chunk: Option<&mut (dyn FnMut(&str) + Send)>,
120) -> crate::Result<JsonObject> {
121 let deadline = tokio::time::Instant::now() + timeout;
122 let mut current_run_id: Option<String> = None;
123 for attempts in 0..=5 {
124 let mut source = iter_stream(
125 transport.clone(),
126 Method::GET,
127 format!("/streams/{}/subscribe", enc(stream_id)),
128 None,
129 Vec::new(),
130 );
131 loop {
132 let item = match tokio::time::timeout_at(deadline, source.next()).await {
133 Ok(item) => item,
134 Err(_) => return Err(Error::Timeout),
135 };
136 let Some(item) = item else { break };
137 match item {
138 Ok(event) => {
139 if let Some(run) =
140 handle_run_event(&event, event_id, &mut current_run_id, &mut on_chunk)?
141 {
142 return Ok(run);
143 }
144 }
145 Err(_error) if attempts < 5 => break,
146 Err(error) => return Err(error),
147 }
148 }
149 if attempts == 5 {
150 return Err(Error::Protocol("stream ended before run completed".into()));
151 }
152 }
153 unreachable!()
154}
155
156pub(crate) async fn wait_for_call_result(
157 transport: Arc<Transport>,
158 body: JsonObject,
159 timeout: Duration,
160 mut on_chunk: Option<&mut (dyn FnMut(&str) + Send)>,
161) -> crate::Result<JsonObject> {
162 let deadline = tokio::time::Instant::now() + timeout;
163 let mut stream_id: Option<String> = None;
164 let mut event_id: Option<String> = None;
165 let mut current_run_id: Option<String> = None;
166
167 for attempts in 0..=5 {
168 let (method, path, request_body) = match stream_id.as_deref() {
169 None => (
170 Method::POST,
171 "/streams/subscribe-with-event".to_string(),
172 Some(Value::Object(body.clone())),
173 ),
174 Some(stream_id) => (
175 Method::GET,
176 format!("/streams/{}/subscribe", enc(stream_id)),
177 None,
178 ),
179 };
180 let mut source = iter_stream(transport.clone(), method, path, request_body, Vec::new());
181 loop {
182 let item = match tokio::time::timeout_at(deadline, source.next()).await {
183 Ok(item) => item,
184 Err(_) => return Err(Error::Timeout),
185 };
186 let Some(item) = item else { break };
187 let event = match item {
188 Ok(event) => event,
189 Err(_error) if stream_id.is_some() && attempts < 5 => break,
190 Err(error) => return Err(error),
191 };
192 if event.event_type() == "event:published" {
193 stream_id = event
194 .get("stream_id")
195 .and_then(Value::as_str)
196 .map(str::to_string);
197 event_id = event
198 .get("event_id")
199 .and_then(Value::as_str)
200 .map(str::to_string);
201 }
202 if let Some(event_id) = event_id.as_deref() {
203 if let Some(run) =
204 handle_run_event(&event, event_id, &mut current_run_id, &mut on_chunk)?
205 {
206 return Ok(run);
207 }
208 }
209 }
210 if stream_id.is_none() || event_id.is_none() {
211 return Err(Error::Protocol(
212 "stream ended before event:published was received".into(),
213 ));
214 }
215 if attempts == 5 {
216 return Err(Error::Protocol("stream ended before run completed".into()));
217 }
218 }
219 unreachable!()
220}
221
222pub(crate) async fn wait_for_task(
223 transport: Arc<Transport>,
224 task_id: &str,
225 timeout: Duration,
226 max_attempts: u32,
227) -> crate::Result<JsonObject> {
228 let deadline = tokio::time::Instant::now() + timeout;
229 for attempts in 0..=max_attempts {
230 let mut source = iter_stream(
231 transport.clone(),
232 Method::GET,
233 format!("/tasks/{}/subscribe", enc(task_id)),
234 None,
235 Vec::new(),
236 );
237 loop {
238 let item = match tokio::time::timeout_at(deadline, source.next()).await {
239 Ok(item) => item,
240 Err(_) => return Err(Error::TaskTimeout),
241 };
242 let Some(item) = item else { break };
243 let event = match item {
244 Ok(event) => event,
245 Err(_error) if attempts < max_attempts => break,
246 Err(error) => return Err(error),
247 };
248 if event.event_type() != "task:update" {
249 continue;
250 }
251 let Some(task) = event.get("task").and_then(Value::as_object) else {
252 continue;
253 };
254 match task.get("status").and_then(Value::as_str) {
255 Some("completed") => return Ok(task.clone()),
256 Some("failed" | "cancelled" | "timed_out") => {
257 let status = task
258 .get("status")
259 .and_then(Value::as_str)
260 .unwrap_or("failed");
261 let message = result_message(task.get("result"))
262 .map(str::to_string)
263 .unwrap_or_else(|| format!("task {status}"));
264 return Err(Error::TaskFailed {
265 message,
266 task: task.clone(),
267 });
268 }
269 _ => {}
270 }
271 }
272 if attempts == max_attempts {
273 return Err(Error::Protocol(
274 "task subscription ended before the task completed".into(),
275 ));
276 }
277 }
278 unreachable!()
279}
280
281pub(crate) async fn wait_for_run(
282 transport: Arc<Transport>,
283 run_id: &str,
284 timeout: Duration,
285 max_attempts: u32,
286) -> crate::Result<JsonObject> {
287 let deadline = tokio::time::Instant::now() + timeout;
288 for attempts in 0..=max_attempts {
289 let mut source = iter_stream(
290 transport.clone(),
291 Method::GET,
292 format!("/runs/{}/subscribe", enc(run_id)),
293 None,
294 Vec::new(),
295 );
296 loop {
297 let item = match tokio::time::timeout_at(deadline, source.next()).await {
298 Ok(item) => item,
299 Err(_) => return Err(Error::Timeout),
300 };
301 let Some(item) = item else { break };
302 let event = match item {
303 Ok(event) => event,
304 Err(_error) if attempts < max_attempts => break,
305 Err(error) => return Err(error),
306 };
307 if event.event_type() != "run:update" {
308 continue;
309 }
310 let Some(run) = event.get("run").and_then(Value::as_object) else {
311 continue;
312 };
313 match run.get("status").and_then(Value::as_str) {
314 Some("succeeded") => return Ok(run.clone()),
315 Some(status @ ("failed" | "cancelled")) => {
316 let message = run_result_message(run, &format!("run {status}"));
317 return Err(Error::RunWaitFailed {
318 message,
319 run: run.clone(),
320 });
321 }
322 _ => {}
323 }
324 }
325 if attempts == max_attempts {
326 return Err(Error::Protocol(
327 "run subscription ended before the run completed".into(),
328 ));
329 }
330 }
331 unreachable!()
332}
333
334#[allow(clippy::result_large_err)]
337fn handle_run_event(
338 event: &StreamEvent,
339 event_id: &str,
340 current_run_id: &mut Option<String>,
341 on_chunk: &mut Option<&mut (dyn FnMut(&str) + Send)>,
342) -> crate::Result<Option<JsonObject>> {
343 match event.event_type() {
344 "run:start" => {
345 if event_id_of_run(event.run()) == Some(event_id) {
346 *current_run_id = event.run_id().map(str::to_string);
347 }
348 }
349 "stream:data" => {
350 let matches_run = current_run_id.as_deref().is_some_and(|current| {
351 event.get("run_id").and_then(Value::as_str) == Some(current)
352 });
353 if matches_run {
354 if let Some(on_chunk) = on_chunk.as_deref_mut() {
355 if let Some(text) = event
356 .get("payload")
357 .and_then(Value::as_object)
358 .and_then(|payload| payload.get("text"))
359 .and_then(Value::as_str)
360 {
361 on_chunk(text);
362 }
363 }
364 }
365 }
366 event_type @ ("run:stop" | "run:fail" | "run:cancel") => {
367 let Some(run) = event.run() else {
368 return Ok(None);
369 };
370 if event_id_of_run(Some(run)) != Some(event_id) {
371 return Ok(None);
372 }
373 match event_type {
374 "run:fail" => return Err(Error::RunFailed(run_result_message(run, "run failed"))),
375 "run:cancel" => {
376 return Err(Error::RunFailed(run_result_message(run, "run cancelled")))
377 }
378 _ => return Ok(Some(run.clone())),
379 }
380 }
381 _ => {}
382 }
383 Ok(None)
384}
385
386pub(crate) fn event_id_of_run(run: Option<&JsonObject>) -> Option<&str> {
387 run?.get("event_id")?.as_str()
388}
389
390fn result_message(value: Option<&Value>) -> Option<&str> {
392 match value? {
393 Value::String(text) if !text.is_empty() => Some(text),
394 Value::Object(object) => ["$err", "$val", "error", "message", "msg", "reason", "err"]
395 .iter()
396 .find_map(|key| result_message(object.get(*key))),
397 _ => None,
398 }
399}
400
401pub(crate) fn run_result_message(run: &JsonObject, fallback: &str) -> String {
402 if let Some(message) = result_message(run.get("result")) {
403 return message.to_string();
404 }
405 match run.get("status").and_then(Value::as_str) {
406 Some(status) if !status.is_empty() => format!("{fallback} ({status})"),
407 _ => fallback.to_string(),
408 }
409}
410
411pub(crate) fn extract_run_result(result: Option<Value>) -> Value {
413 match result {
414 Some(Value::Object(mut object)) if object.contains_key("$ok") => {
415 object.remove("$ok").unwrap_or(Value::Null)
416 }
417 Some(other) => other,
418 None => Value::Null,
419 }
420}