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 let mut source = iter_stream(
124 transport,
125 Method::GET,
126 format!("/streams/{}/subscribe", enc(stream_id)),
127 None,
128 Vec::new(),
129 );
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 let event = item?;
138
139 match event.event_type() {
140 "run:start" => {
141 if event_id_of_run(event.run()) == Some(event_id) {
142 current_run_id = event.run_id().map(str::to_string);
143 }
144 }
145 "stream:data" => {
146 let matches_run = current_run_id.as_deref().is_some_and(|current| {
147 event.get("run_id").and_then(Value::as_str) == Some(current)
148 });
149 if matches_run {
150 if let Some(on_chunk) = on_chunk.as_deref_mut() {
151 if let Some(text) = event
152 .get("payload")
153 .and_then(Value::as_object)
154 .and_then(|payload| payload.get("text"))
155 .and_then(Value::as_str)
156 {
157 on_chunk(text);
158 }
159 }
160 }
161 }
162 event_type @ ("run:stop" | "run:fail" | "run:cancel") => {
163 let Some(run) = event.run() else { continue };
164 if event_id_of_run(Some(run)) != Some(event_id) {
165 continue;
166 }
167 match event_type {
168 "run:fail" => {
169 return Err(Error::RunFailed(run_result_message(run, "run failed")))
170 }
171 "run:cancel" => {
172 return Err(Error::RunFailed(run_result_message(run, "run cancelled")))
173 }
174 _ => return Ok(run.clone()),
175 }
176 }
177 _ => {}
178 }
179 }
180
181 Err(Error::Protocol("stream ended before run completed".into()))
182}
183
184pub(crate) fn event_id_of_run(run: Option<&JsonObject>) -> Option<&str> {
185 run?.get("event_id")?.as_str()
186}
187
188fn result_message(value: Option<&Value>) -> Option<&str> {
190 match value? {
191 Value::String(text) if !text.is_empty() => Some(text),
192 Value::Object(object) => ["$err", "$val", "error", "message", "msg", "reason", "err"]
193 .iter()
194 .find_map(|key| result_message(object.get(*key))),
195 _ => None,
196 }
197}
198
199pub(crate) fn run_result_message(run: &JsonObject, fallback: &str) -> String {
200 if let Some(message) = result_message(run.get("result")) {
201 return message.to_string();
202 }
203 match run.get("status").and_then(Value::as_str) {
204 Some(status) if !status.is_empty() => format!("{fallback} ({status})"),
205 _ => fallback.to_string(),
206 }
207}
208
209pub(crate) fn extract_run_result(result: Option<Value>) -> Value {
211 match result {
212 Some(Value::Object(mut object)) if object.contains_key("$ok") => {
213 object.remove("$ok").unwrap_or(Value::Null)
214 }
215 Some(other) => other,
216 None => Value::Null,
217 }
218}