gproxy_protocol/adapt/responses_ws/
turn.rs1use super::*;
2use crate::{
3 codec,
4 connection::WsFrame,
5 transform::generate::stream::responses::ResponsesStreamCollector,
6 wire::{
7 DeclaredFields,
8 openai::responses::websocket::{
9 ClientEvent, ErrorMessage, RequestMessage, ServerEvent, ServerMessage,
10 },
11 },
12};
13use futures_core::Stream;
14use std::{
15 pin::Pin,
16 task::{Context, Poll},
17};
18
19pub struct ResponsesWsTurn<'a> {
20 session: &'a mut ResponsesWsSession,
21 socket: Option<WebSocket>,
22 send: SendState,
23 collector: Option<ResponsesStreamCollector>,
24 received_bytes: usize,
25 received_frames: usize,
26 sent_bytes: usize,
27 sent_frames: usize,
28 terminal: bool,
29 lane: Option<String>,
30 failed: bool,
31}
32
33impl Unpin for ResponsesWsTurn<'_> {}
34enum SendState {
35 Ready(WsFrame),
36 Flush,
37 Done,
38}
39
40impl<'a> ResponsesWsTurn<'a> {
41 pub(super) fn new(
42 session: &'a mut ResponsesWsSession,
43 mut request: RequestMessage,
44 ) -> Result<Self, TransformError> {
45 let ClientEvent::ResponseCreate(body) = &mut request.event else {
46 return Err(TransformError::unsupported(
47 "responses.websocket.control",
48 "steering and injection require a duplex continuation driver, not a single response turn",
49 ));
50 };
51 if body.background.flatten() == Some(true) {
52 return Err(TransformError::unsupported(
53 "responses.websocket.background",
54 "WebSocket mode has no background generation control",
55 ));
56 }
57 body.stream = None;
58 body.background = None;
59 let event = request.into_declared();
60 let lane = event.stream_id.clone();
61 let bytes = codec::encode_json(
62 &event,
63 codec_limits(session.bounds.send_event.min(session.bounds.send_bytes)),
64 )
65 .map_err(|e| codec_error(e, true))?;
66 let len = bytes.len();
67 let frame =
68 WsFrame::Text(String::from_utf8(bytes.to_vec()).expect("JSON serialization is UTF-8"));
69 let collector = ResponsesStreamCollector::new(session.bounds.collector);
70 let socket = session
71 .socket
72 .take()
73 .ok_or_else(|| invalid("socket unavailable"))?;
74 session.busy = true;
76 Ok(Self {
77 session,
78 socket: Some(socket),
79 send: SendState::Ready(frame),
80 collector: Some(collector),
81 received_bytes: 0,
82 received_frames: 0,
83 sent_bytes: len,
84 sent_frames: 1,
85 terminal: false,
86 lane,
87 failed: false,
88 })
89 }
90 pub fn received_frames(&self) -> usize {
91 self.received_frames
92 }
93 pub fn received_bytes(&self) -> usize {
94 self.received_bytes
95 }
96 pub fn reserved_send_frames(&self) -> usize {
98 self.sent_frames
99 }
100 pub fn reserved_send_bytes(&self) -> usize {
101 self.sent_bytes
102 }
103 pub fn failure_event(&self) -> Option<&StreamEvent> {
104 self.session.failure_event()
105 }
106 pub fn websocket_failure(&self) -> Option<&ErrorMessage> {
107 self.session.websocket_failure()
108 }
109 fn fail(&mut self, error: TransformError) -> TransformError {
110 self.failed = true;
111 self.socket.take();
112 self.session.poison();
113 error
114 }
115 fn account(&mut self, frame: &WsFrame) -> Result<(), TransformError> {
116 self.received_frames = self
117 .received_frames
118 .checked_add(1)
119 .ok_or_else(|| limit("responses.websocket.frames", "receive frame count overflow"))?;
120 let bytes = match frame {
121 WsFrame::Text(v) => v.len(),
122 WsFrame::Binary(v) | WsFrame::Ping(v) | WsFrame::Pong(v) => v.len(),
123 WsFrame::Close(Some(v)) => v
124 .reason
125 .len()
126 .checked_add(2)
127 .ok_or_else(|| limit("responses.websocket.frame", "close payload overflow"))?,
128 WsFrame::Close(None) => 0,
129 };
130 if bytes > self.session.bounds.receive_frame {
131 return Err(limit(
132 "responses.websocket.frame",
133 "receive frame byte cap exceeded",
134 ));
135 }
136 self.received_bytes = self
137 .received_bytes
138 .checked_add(bytes)
139 .filter(|v| *v <= self.session.bounds.receive_bytes)
140 .ok_or_else(|| {
141 limit(
142 "responses.websocket.bytes",
143 "aggregate receive byte cap exceeded",
144 )
145 })?;
146 if matches!(
147 frame,
148 WsFrame::Ping(_) | WsFrame::Pong(_) | WsFrame::Close(_)
149 ) && bytes > 125
150 {
151 return Err(invalid("invalid oversized WebSocket control frame"));
152 }
153 Ok(())
154 }
155 fn pong(&mut self, payload: bytes::Bytes) -> Result<(), TransformError> {
156 if payload.len() > self.session.bounds.send_frame {
157 return Err(limit(
158 "responses.websocket.pong",
159 "Pong exceeds host send frame cap",
160 ));
161 }
162 let bytes = self
163 .sent_bytes
164 .checked_add(payload.len())
165 .filter(|v| *v <= self.session.bounds.send_bytes)
166 .ok_or_else(|| {
167 limit(
168 "responses.websocket.pong",
169 "Pong exceeds aggregate send byte cap",
170 )
171 })?;
172 let frames = self
173 .sent_frames
174 .checked_add(1)
175 .ok_or_else(|| limit("responses.websocket.pong", "send frame count overflow"))?;
176 self.sent_bytes = bytes;
177 self.sent_frames = frames;
178 self.send = SendState::Ready(WsFrame::Pong(payload));
179 Ok(())
180 }
181 fn decode(&mut self, bytes: &[u8]) -> Result<StreamEvent, TransformError> {
182 if bytes.len() > self.session.bounds.receive_event {
183 return Err(limit(
184 "responses.websocket.event",
185 "JSON event cap exceeded",
186 ));
187 }
188 let limits = codec_limits(self.session.bounds.receive_event);
189 let probe: serde_json::Value =
192 codec::decode_json(bytes, limits).map_err(|e| codec_error(e, false))?;
193 if probe.get("type").and_then(serde_json::Value::as_str) == Some("error")
194 && probe.get("error").is_some()
195 {
196 let error = codec::decode_json::<ErrorMessage>(bytes, limits)
197 .map_err(|e| codec_error(e, false))?
198 .into_declared();
199 let lane_matches = error.stream_id.is_none() || error.stream_id == self.lane;
200 self.session.websocket_failure = Some(Box::new(error));
201 return Err(invalid(if lane_matches {
202 "native WebSocket error; see websocket_failure receipt"
203 } else {
204 "WebSocket error belongs to another lane"
205 }));
206 }
207 let message = codec::decode_json::<ServerMessage>(bytes, limits)
208 .map_err(|e| codec_error(e, false))?
209 .into_declared();
210 if message.stream_id != self.lane {
211 return Err(invalid("WebSocket event belongs to another lane"));
212 }
213 let ServerEvent::Response(event) = message.event else {
214 return Err(TransformError::unsupported(
215 "responses.websocket.control",
216 "steering and injection events require a duplex continuation driver",
217 ));
218 };
219 if matches!(event, StreamEvent::Failed(_) | StreamEvent::Error(_)) {
220 self.session.failure = Some(Box::new(event.clone()));
221 }
222 let terminal = matches!(
223 event,
224 StreamEvent::Completed(_) | StreamEvent::Incomplete(_)
225 );
226 self.collector
227 .as_mut()
228 .ok_or_else(|| invalid("collector already consumed"))?
229 .push(event.clone())?;
230 if terminal {
231 let complete = self
232 .collector
233 .take()
234 .ok_or_else(|| invalid("collector already consumed"))?
235 .finish()?;
236 let socket = self
237 .socket
238 .take()
239 .ok_or_else(|| invalid("terminal socket unavailable"))?;
240 self.session.last_response_id = Some(complete.value.id);
241 self.session.last_status = complete.value.status;
242 self.session.last_report = complete.report;
243 self.session.socket = Some(socket);
244 self.session.busy = false;
245 self.terminal = true;
246 }
247 Ok(event)
248 }
249}
250
251impl Stream for ResponsesWsTurn<'_> {
252 type Item = Result<StreamEvent, TransformError>;
253 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
254 let this = self.get_mut();
255 if this.terminal || this.failed {
256 return Poll::Ready(None);
257 }
258 let Some(socket) = this.socket.as_mut() else {
259 return Poll::Ready(Some(Err(this.fail(invalid("socket closed")))));
260 };
261 match poll_send(socket, &mut this.send, cx) {
262 Poll::Pending => return Poll::Pending,
263 Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(this.fail(e)))),
264 Poll::Ready(Ok(())) => {}
265 }
266 if this.received_bytes >= this.session.bounds.receive_bytes {
267 return Poll::Ready(Some(Err(this.fail(limit(
268 "responses.websocket.bytes",
269 "receive byte budget exhausted before terminal",
270 )))));
271 }
272 let frame = match socket.incoming.as_mut().poll_next(cx) {
273 Poll::Pending => return Poll::Pending,
274 Poll::Ready(None) => {
275 return Poll::Ready(Some(Err(
276 this.fail(invalid("socket ended before terminal response"))
277 )));
278 }
279 Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(this.fail(host_error(e))))),
280 Poll::Ready(Some(Ok(frame))) => frame,
281 };
282 if let Err(e) = this.account(&frame) {
283 return Poll::Ready(Some(Err(this.fail(e))));
284 }
285 match frame {
286 WsFrame::Ping(payload) => {
287 if let Err(e) = this.pong(payload) {
288 return Poll::Ready(Some(Err(this.fail(e))));
289 }
290 cx.waker().wake_by_ref();
291 Poll::Pending
292 }
293 WsFrame::Pong(_) => {
294 cx.waker().wake_by_ref();
295 Poll::Pending
296 }
297 WsFrame::Close(close) => {
298 this.session.peer_close = close;
299 Poll::Ready(Some(Err(
300 this.fail(invalid("socket closed before terminal response"))
301 )))
302 }
303 WsFrame::Binary(_) if !this.session.bounds.allow_binary => Poll::Ready(Some(Err(this
304 .fail(TransformError::unsupported(
305 "responses.websocket.frame",
306 "binary JSON server messages are disabled",
307 ))))),
308 WsFrame::Text(text) => {
309 Poll::Ready(Some(this.decode(text.as_bytes()).map_err(|e| this.fail(e))))
310 }
311 WsFrame::Binary(bytes) => {
312 Poll::Ready(Some(this.decode(&bytes).map_err(|e| this.fail(e))))
313 }
314 }
315 }
316}
317
318impl Drop for ResponsesWsTurn<'_> {
319 fn drop(&mut self) {
320 if !self.terminal {
321 self.socket.take();
322 self.session.poison();
323 }
324 }
325}
326
327fn poll_send(
328 socket: &mut WebSocket,
329 state: &mut SendState,
330 cx: &mut Context<'_>,
331) -> Poll<Result<(), TransformError>> {
332 loop {
333 match state {
334 SendState::Ready(_) => match socket.outgoing.as_mut().poll_ready(cx) {
335 Poll::Pending => return Poll::Pending,
336 Poll::Ready(Err(e)) => return Poll::Ready(Err(host_error(e))),
337 Poll::Ready(Ok(())) => {
338 let SendState::Ready(frame) = std::mem::replace(state, SendState::Flush) else {
341 unreachable!()
343 };
344 if let Err(e) = socket.outgoing.as_mut().start_send(frame) {
345 return Poll::Ready(Err(host_error(e)));
346 }
347 }
348 },
349 SendState::Flush => match socket.outgoing.as_mut().poll_flush(cx) {
350 Poll::Pending => return Poll::Pending,
351 Poll::Ready(Err(e)) => return Poll::Ready(Err(host_error(e))),
352 Poll::Ready(Ok(())) => *state = SendState::Done,
353 },
354 SendState::Done => return Poll::Ready(Ok(())),
355 }
356 }
357}