1use crate::completion;
11use crate::completion::options::FinalBody;
12use crate::driver::Model;
13use crate::error::{EncodeError, ProviderError};
14use crate::http_client::{self, NoBody};
15use crate::json_utils::remove_preserving_order;
16use crate::operation::{Completion, ReplyFold};
17use crate::providers::openai::responses_api::streaming::{
18 ResponsesDecoder, ResponsesEvent, classify_responses_payload, document, is_lifecycle_event,
19};
20use crate::providers::openai::responses_api::wire::Responses;
21use crate::streaming::Item;
22use crate::wire::{Call, Flow, Mode, Operation, Reply, Shared, Wire, WireFrame};
23use crate::ws_client::{
24 BoxedWebSocketConnection, ConnectOptions, Frame, WebSocketClientExt, WebSocketConnection,
25};
26use serde::{Deserialize, Serialize};
27use serde_json::{Map, Value};
28use std::time::Duration;
29
30const WEBSOCKET_PATH: &str = "responses";
32
33const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(30);
34
35const REQUEST_ID_HEADER: Option<&'static str> =
37 crate::providers::openai::wire::OPENAI.request_id_header;
38
39#[derive(Debug, Clone, Default, Serialize, Deserialize)]
41pub struct ResponsesWebSocketCreateOptions {
42 #[serde(skip_serializing_if = "Option::is_none")]
46 pub generate: Option<bool>,
47}
48
49impl ResponsesWebSocketCreateOptions {
50 #[must_use]
52 pub fn warmup() -> Self {
53 Self {
54 generate: Some(false),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Serialize)]
60struct ResponsesWebSocketClientEvent {
61 #[serde(rename = "type")]
62 kind: ResponsesWebSocketClientEventKind,
63 #[serde(flatten)]
64 request: FinalBody,
65}
66
67#[derive(Debug, Clone, Serialize)]
68enum ResponsesWebSocketClientEventKind {
69 #[serde(rename = "response.create")]
70 ResponseCreate,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ResponsesWebSocketErrorEvent {
76 #[serde(rename = "type")]
78 pub kind: ResponsesWebSocketErrorEventKind,
79 pub error: ResponsesWebSocketErrorPayload,
81}
82
83impl std::fmt::Display for ResponsesWebSocketErrorEvent {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 self.error.fmt(f)
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub enum ResponsesWebSocketErrorEventKind {
92 #[serde(rename = "error")]
93 Error,
94}
95
96#[derive(Debug, Clone, Default, Serialize, Deserialize)]
98pub struct ResponsesWebSocketErrorPayload {
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub code: Option<String>,
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub message: Option<String>,
105 #[serde(flatten, default)]
107 pub extra: Map<String, Value>,
108}
109
110impl std::fmt::Display for ResponsesWebSocketErrorPayload {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match (&self.code, &self.message) {
113 (Some(code), Some(message)) => write!(f, "{code}: {message}"),
114 (None, Some(message)) => f.write_str(message),
115 (Some(code), None) => f.write_str(code),
116 (None, None) => f.write_str("OpenAI websocket error"),
117 }
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ResponsesWebSocketDoneEvent {
124 #[serde(rename = "type")]
126 pub kind: ResponsesWebSocketDoneEventKind,
127 pub response: Value,
129}
130
131impl ResponsesWebSocketDoneEvent {
132 #[must_use]
134 pub fn response_id(&self) -> Option<&str> {
135 self.response.get("id").and_then(Value::as_str)
136 }
137
138 fn status(&self) -> Option<&str> {
139 self.response.get("status").and_then(Value::as_str)
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub enum ResponsesWebSocketDoneEventKind {
146 #[serde(rename = "response.done")]
147 ResponseDone,
148}
149
150#[derive(Debug, Clone)]
152pub enum ResponsesWebSocketEvent {
153 Response {
156 kind: String,
158 response: Value,
160 },
161 Item(Value),
164 Error(ResponsesWebSocketErrorEvent),
166 Done(ResponsesWebSocketDoneEvent),
168 Unknown(crate::streaming::UnknownPayload),
170}
171
172impl ResponsesWebSocketEvent {
173 #[must_use]
175 pub fn response_id(&self) -> Option<&str> {
176 match self {
177 Self::Response { response, .. } => response.get("id").and_then(Value::as_str),
178 Self::Done(done) => done.response_id(),
179 Self::Item(_) | Self::Error(_) | Self::Unknown(_) => None,
180 }
181 }
182
183 #[must_use]
185 pub fn is_terminal(&self) -> bool {
186 match self {
187 Self::Response { kind, .. } => is_terminal_kind(kind),
188 Self::Error(_) | Self::Done(_) => true,
189 Self::Item(_) | Self::Unknown(_) => false,
190 }
191 }
192}
193
194fn is_terminal_kind(kind: &str) -> bool {
196 matches!(
197 kind,
198 "response.completed" | "response.failed" | "response.incomplete"
199 )
200}
201
202pub struct ResponsesWebSocketSessionBuilder {
207 wire: Responses,
208 connect_timeout: Option<Duration>,
209 event_timeout: Option<Duration>,
210}
211
212impl ResponsesWebSocketSessionBuilder {
213 pub(crate) fn new(wire: Responses) -> Self {
214 Self {
215 wire,
216 connect_timeout: Some(DEFAULT_CONNECT_TIMEOUT),
217 event_timeout: None,
218 }
219 }
220
221 #[must_use]
223 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
224 self.connect_timeout = Some(timeout);
225 self
226 }
227
228 #[must_use]
231 pub fn without_connect_timeout(mut self) -> Self {
232 self.connect_timeout = None;
233 self
234 }
235
236 #[must_use]
238 pub fn event_timeout(mut self, timeout: Duration) -> Self {
239 self.event_timeout = Some(timeout);
240 self
241 }
242
243 #[must_use]
245 pub fn without_event_timeout(mut self) -> Self {
246 self.event_timeout = None;
247 self
248 }
249}
250
251impl ResponsesWebSocketSessionBuilder {
252 #[cfg(all(feature = "tungstenite", not(target_family = "wasm")))]
255 #[cfg_attr(docsrs, doc(cfg(feature = "tungstenite")))]
256 pub async fn connect(self) -> Result<ResponsesWebSocketSession, ProviderError> {
257 self.connect_with(&rig_tungstenite::TungsteniteClient::new())
258 .await
259 }
260
261 pub async fn connect_with<W>(
264 self,
265 backend: &W,
266 ) -> Result<ResponsesWebSocketSession, ProviderError>
267 where
268 W: WebSocketClientExt,
269 {
270 ResponsesWebSocketSession::connect_with_timeouts(
271 backend,
272 self.wire,
273 self.connect_timeout,
274 self.event_timeout,
275 )
276 .await
277 }
278}
279
280pub struct ResponsesWebSocketSession {
284 wire: Responses,
285 previous_response_id: Option<String>,
286 pending_done_response_id: Option<String>,
287 socket: BoxedWebSocketConnection,
288 in_flight: bool,
289 turn: Option<ReplyFold>,
291 event_timeout: Option<Duration>,
292 closed: bool,
293 failed: bool,
294}
295
296impl ResponsesWebSocketSession {
297 async fn connect_with_timeouts<W>(
298 backend: &W,
299 wire: Responses,
300 connect_timeout: Option<Duration>,
301 event_timeout: Option<Duration>,
302 ) -> Result<Self, ProviderError>
303 where
304 W: WebSocketClientExt,
305 {
306 let request = websocket_request(&wire)?;
307 let socket = backend
308 .connect(request, ConnectOptions::new().with_timeout(connect_timeout))
309 .await
310 .map_err(websocket_provider_error)?;
311
312 Ok(Self::from_connection(wire, socket, event_timeout))
313 }
314
315 pub fn from_connection(
318 wire: Responses,
319 connection: BoxedWebSocketConnection,
320 event_timeout: Option<Duration>,
321 ) -> Self {
322 Self {
323 wire,
324 previous_response_id: None,
325 pending_done_response_id: None,
326 socket: connection,
327 in_flight: false,
328 turn: None,
329 event_timeout,
330 closed: false,
331 failed: false,
332 }
333 }
334
335 #[must_use]
337 pub fn previous_response_id(&self) -> Option<&str> {
338 self.previous_response_id.as_deref()
339 }
340
341 pub fn clear_previous_response_id(&mut self) {
343 self.previous_response_id = None;
344 }
345
346 pub async fn send(
348 &mut self,
349 completion_request: crate::completion::CompletionRequest,
350 ) -> Result<(), ProviderError> {
351 self.send_with_options(
352 completion_request,
353 ResponsesWebSocketCreateOptions::default(),
354 )
355 .await
356 }
357
358 pub async fn send_with_options(
360 &mut self,
361 completion_request: crate::completion::CompletionRequest,
362 options: ResponsesWebSocketCreateOptions,
363 ) -> Result<(), ProviderError> {
364 self.ensure_open()?;
365
366 if self.in_flight {
367 return Err(ProviderError::Provider(
368 "An OpenAI websocket response is already in flight on this session".to_string(),
369 ));
370 }
371
372 let mut completion_request = completion_request;
376 if let Some(generate) = options.generate {
377 let params = completion_request
378 .additional_params
379 .get_or_insert_with(|| Value::Object(Map::new()));
380 if params.is_null() {
381 *params = Value::Object(Map::new());
382 }
383 if let Some(params) = params.as_object_mut() {
384 params.insert("generate".to_owned(), Value::Bool(generate));
385 }
386 }
387 if let Some(previous) = &self.previous_response_id {
388 let params = completion_request
389 .additional_params
390 .get_or_insert_with(|| Value::Object(Map::new()));
391 if let Some(params) = params.as_object_mut()
392 && params
393 .get("previous_response_id")
394 .is_none_or(Value::is_null)
395 {
396 params.insert(
397 "previous_response_id".to_owned(),
398 Value::String(previous.clone()),
399 );
400 }
401 }
402 let describe = self.wire.describe();
405 let completion_request = Completion::prepare(completion_request, &describe)?;
406 let turn = Completion::fold(
407 &completion_request,
408 &mut Call::new(&describe, Mode::Streaming),
409 );
410 let payload = ResponsesWebSocketClientEvent {
411 kind: ResponsesWebSocketClientEventKind::ResponseCreate,
412 request: self.prepare_request(completion_request)?,
413 };
414
415 crate::providers::internal::trace_json(
416 crate::providers::internal::LogTarget::Completions,
417 "OpenAI websocket request",
418 &payload,
419 );
420
421 let payload = serde_json::to_string(&payload)?;
422
423 if let Err(error) = self.socket.send(Frame::Text(payload)).await {
424 return Err(self.fail_session(websocket_provider_error(error)));
425 }
426 self.in_flight = true;
427 self.turn = Some(turn);
428
429 Ok(())
430 }
431
432 pub async fn next_event(&mut self) -> Result<ResponsesWebSocketEvent, ProviderError> {
434 self.next_event_with_payload().await.map(|(event, _)| event)
435 }
436
437 async fn next_event_with_payload(
440 &mut self,
441 ) -> Result<(ResponsesWebSocketEvent, String), ProviderError> {
442 self.ensure_open()?;
443
444 if !self.in_flight {
445 return Err(ProviderError::Provider(
446 "No OpenAI websocket response is currently in flight on this session".to_string(),
447 ));
448 }
449
450 loop {
451 let message = match self.read_next_frame().await? {
452 Ok(message) => message,
453 Err(error) => return Err(self.fail_session(websocket_provider_error(error))),
454 };
455
456 let Some(message) = message else {
457 self.mark_closed();
458 return Err(ProviderError::Provider(
459 "The OpenAI websocket connection closed before the turn finished".to_string(),
460 ));
461 };
462
463 let payload = match websocket_frame_to_text(message) {
464 Ok(Some(payload)) => payload,
465 Ok(None) => continue,
466 Err(error) => return Err(self.fail_session(error)),
467 };
468 let event = match parse_server_event(&payload) {
469 Ok(Some(event)) => event,
470 Ok(None) => continue,
471 Err(error) => return Err(self.fail_session(error)),
472 };
473 if let ResponsesWebSocketEvent::Done(done) = &event {
474 if self.pending_done_response_id.as_deref() == done.response_id() {
477 self.pending_done_response_id = None;
478 continue;
479 }
480 }
481 self.update_state_for_event(&event);
482 return Ok((event, payload));
483 }
484 }
485
486 pub async fn warmup(
488 &mut self,
489 completion_request: crate::completion::CompletionRequest,
490 ) -> Result<String, ProviderError> {
491 self.send_with_options(
492 completion_request,
493 ResponsesWebSocketCreateOptions::warmup(),
494 )
495 .await?;
496 let response = self.wait_for_terminal_response().await?.0;
497 Ok(response
498 .get("id")
499 .and_then(Value::as_str)
500 .unwrap_or_default()
501 .to_owned())
502 }
503
504 pub async fn completion(
507 &mut self,
508 completion_request: crate::completion::CompletionRequest,
509 ) -> Result<completion::CompletionResponse, ProviderError> {
510 self.send(completion_request).await?;
511 Ok(self.wait_for_terminal_response().await?.1)
512 }
513
514 pub async fn close(&mut self) -> Result<(), ProviderError> {
519 if self.closed {
520 return Ok(());
521 }
522
523 let result = self
524 .socket
525 .close(None)
526 .await
527 .map_err(websocket_provider_error);
528 self.mark_closed();
529 result
530 }
531
532 fn prepare_request(
535 &self,
536 completion_request: crate::completion::CompletionRequest,
537 ) -> Result<FinalBody, ProviderError> {
538 Ok(self
539 .wire
540 .responses_request(&completion_request, super::Delivery::WebSocket)?)
541 }
542
543 async fn wait_for_terminal_response(
548 &mut self,
549 ) -> Result<(Value, completion::CompletionResponse), ProviderError> {
550 let provider = self.wire.describe().name.to_owned();
551 let Some(turn) = self.turn.take() else {
552 return Err(ProviderError::Provider(
553 "No OpenAI websocket response is currently in flight on this session".to_string(),
554 ));
555 };
556 let reply = std::sync::Mutex::new(Shared::new(turn));
558 let mut decoder = self.wire.decoder();
559 let mut document = self.wire.reassembler();
560 loop {
561 let (event, payload) = self.next_event_with_payload().await?;
562 match event {
563 ResponsesWebSocketEvent::Response { kind, response } => {
564 if !is_terminal_kind(&kind) {
565 feed(&mut decoder, &mut document, &reply, payload)?;
566 continue;
567 }
568 let response = terminal_response_result(response)?;
572 let ended = feed(&mut decoder, &mut document, &reply, payload)?;
573 let folded = fold_reply(reply, ended, &provider, document)?;
574 return Ok((response, folded));
575 }
576 ResponsesWebSocketEvent::Done(done) => {
577 if done.response.is_object() {
578 let response = terminal_response_result(done.response)?;
581 let body = serde_json::to_string(&response)?;
585 let ended = feed(&mut decoder, &mut document, &reply, body)?;
586 let folded = fold_reply(reply, ended, &provider, document)?;
587 return Ok((response, folded));
588 }
589
590 let message = if let Some(response_id) = done.response_id() {
591 format!(
592 "OpenAI websocket turn ended with response.done before a terminal response body was available (response_id={response_id})"
593 )
594 } else {
595 "OpenAI websocket turn ended with response.done before a terminal response body was available"
596 .to_string()
597 };
598
599 return Err(ProviderError::Provider(message));
600 }
601 ResponsesWebSocketEvent::Error(error) => {
602 return Err(provider_error_from_event(&error));
607 }
608 ResponsesWebSocketEvent::Item(_) | ResponsesWebSocketEvent::Unknown(_) => {
610 feed(&mut decoder, &mut document, &reply, payload)?;
611 }
612 }
613 }
614 }
615
616 fn update_state_for_event(&mut self, event: &ResponsesWebSocketEvent) {
617 match event {
618 ResponsesWebSocketEvent::Response { kind, response } => {
619 let response_id = response
620 .get("id")
621 .and_then(Value::as_str)
622 .map(str::to_owned);
623 match kind.as_str() {
624 "response.completed" | "response.incomplete" => {
628 self.previous_response_id.clone_from(&response_id);
629 self.pending_done_response_id = response_id;
630 self.in_flight = false;
631 }
632 "response.failed" => {
633 self.pending_done_response_id = response_id;
634 self.previous_response_id = None;
635 self.in_flight = false;
636 }
637 _ => {}
638 }
639 }
640 ResponsesWebSocketEvent::Done(done) => {
641 match done.status() {
642 Some("completed" | "incomplete") => {
643 if let Some(response_id) = done.response_id() {
644 self.previous_response_id = Some(response_id.to_string());
645 }
646 }
647 Some("in_progress" | "queued") | None => {}
648 Some(_) => {
649 self.previous_response_id = None;
650 }
651 }
652 self.pending_done_response_id = None;
653 self.in_flight = false;
654 }
655 ResponsesWebSocketEvent::Error(_) => {
656 self.previous_response_id = None;
657 self.pending_done_response_id = None;
658 self.in_flight = false;
659 }
660 ResponsesWebSocketEvent::Item(_) | ResponsesWebSocketEvent::Unknown(_) => {}
662 }
663 }
664
665 fn abort_turn(&mut self) {
666 self.turn = None;
667 self.previous_response_id = None;
668 self.pending_done_response_id = None;
669 self.in_flight = false;
670 }
671
672 fn mark_closed(&mut self) {
673 self.abort_turn();
674 self.closed = true;
675 self.failed = false;
676 }
677
678 fn mark_failed(&mut self) {
679 self.abort_turn();
680 self.failed = true;
681 }
682
683 fn ensure_open(&self) -> Result<(), ProviderError> {
684 if self.closed || self.failed {
685 return Err(ProviderError::Provider(
686 "The OpenAI websocket session is closed".to_string(),
687 ));
688 }
689
690 Ok(())
691 }
692
693 fn fail_session(&mut self, error: ProviderError) -> ProviderError {
694 self.mark_failed();
695 error
696 }
697
698 async fn read_next_frame(
701 &mut self,
702 ) -> Result<http_client::Result<Option<Frame>>, ProviderError> {
703 let Some(timeout_duration) = self.event_timeout else {
704 return Ok(self.socket.recv().await);
705 };
706
707 match crate::wasm_compat::timeout(timeout_duration, self.socket.recv()).await {
708 Ok(message) => Ok(message),
709 Err(_) => Err(self.fail_session(event_timeout_error(timeout_duration))),
710 }
711 }
712}
713
714impl Drop for ResponsesWebSocketSession {
715 fn drop(&mut self) {
716 if !self.closed {
717 tracing::warn!(
718 target: "rig::completions",
719 in_flight = self.in_flight,
720 "Dropping an OpenAI websocket session without calling close(); the connection will end without a close handshake"
721 );
722 }
723 }
724}
725
726fn feed(
729 decoder: &mut ResponsesDecoder,
730 document: &mut document::Response,
731 reply: &std::sync::Mutex<Shared<Completion>>,
732 payload: String,
733) -> Result<bool, ProviderError> {
734 crate::driver::step(
735 decoder,
736 Some(document),
737 reply,
738 WireFrame::Text(payload),
739 None,
740 )
741 .map(|step| matches!(step, Flow::Ended(_)))
742}
743
744fn fold_reply(
747 reply: std::sync::Mutex<Shared<Completion>>,
748 ended: bool,
749 provider: &str,
750 document: document::Response,
751) -> Result<completion::CompletionResponse, ProviderError> {
752 let fed = if ended {
753 Ok(())
754 } else {
755 Err(ProviderError::Truncated)
756 };
757 let reply_of = Reply {
758 provider: provider.to_owned(),
759 raw: crate::wire::document::Reassemble::finish(document),
760 provider_request_id: None,
762 };
763 crate::driver::settle(reply, fed, reply_of).outcome
764}
765
766fn terminal_response_result(response: Value) -> Result<Value, ProviderError> {
767 match response.get("status").and_then(Value::as_str) {
768 Some("completed" | "incomplete") | None => Ok(response),
772 Some("failed") => match response.get("error").filter(|error| !error.is_null()) {
775 Some(_) => Err(ProviderError::from_provider_body(response.to_string())),
776 None => Err(ProviderError::Provider(response_error_message(
777 "failed response",
778 ))),
779 },
780 Some(other) => Err(ProviderError::Provider(format!(
781 "OpenAI websocket response ended in state `{other}`"
782 ))),
783 }
784}
785
786fn response_error_message(fallback: &str) -> String {
787 format!("OpenAI websocket returned a {fallback}")
788}
789
790fn provider_error_from_event(error: &ResponsesWebSocketErrorEvent) -> ProviderError {
793 ProviderError::from_provider_body(
794 serde_json::to_string(&error).unwrap_or_else(|_| error.to_string()),
795 )
796}
797
798fn parse_server_event(payload: &str) -> Result<Option<ResponsesWebSocketEvent>, ProviderError> {
801 #[derive(Deserialize)]
802 struct EventType {
803 #[serde(rename = "type")]
804 kind: String,
805 }
806
807 let event_type = serde_json::from_str::<EventType>(payload)?;
808 match event_type.kind.as_str() {
809 "error" => serde_json::from_str(payload)
810 .map(|e| Some(ResponsesWebSocketEvent::Error(e)))
811 .map_err(ProviderError::from),
812 "response.done" => serde_json::from_str(payload)
813 .map(|d| Some(ResponsesWebSocketEvent::Done(d)))
814 .map_err(ProviderError::from),
815 _ => Ok(
816 match crate::driver::triage(classify_responses_payload(payload))? {
817 Item::Event(ResponsesEvent::Frame {
818 kind, mut frame, ..
819 }) => {
820 if is_lifecycle_event(&kind) {
821 let response = frame
822 .as_object_mut()
823 .and_then(|frame| remove_preserving_order(frame, "response"))
824 .unwrap_or_else(|| Value::Object(Map::new()));
825 Some(ResponsesWebSocketEvent::Response { kind, response })
826 } else {
827 Some(ResponsesWebSocketEvent::Item(frame))
828 }
829 }
830 Item::Event(ResponsesEvent::Whole(value)) => {
833 Some(ResponsesWebSocketEvent::Unknown(value.into()))
834 }
835 Item::Event(ResponsesEvent::Failure(body)) => {
836 return Err(ProviderError::from_provider_body(body));
837 }
838 Item::Event(ResponsesEvent::Sentinel) => None,
839 Item::Unknown(value) => Some(ResponsesWebSocketEvent::Unknown(value)),
840 },
841 ),
842 }
843}
844
845fn websocket_frame_to_text(frame: Frame) -> Result<Option<String>, ProviderError> {
850 match frame {
851 Frame::Text(text) => Ok(Some(text)),
852 Frame::Binary(bytes) => String::from_utf8(bytes.to_vec())
853 .map(Some)
854 .map_err(|error| ProviderError::Response(error.to_string())),
855 Frame::Ping(_) | Frame::Pong(_) => Ok(None),
856 Frame::Close(frame) => {
857 let reason = frame
858 .map(|frame| frame.reason)
859 .filter(|reason| !reason.is_empty())
860 .unwrap_or_else(|| "without a close reason".to_string());
861 Err(ProviderError::Provider(format!(
862 "The OpenAI websocket connection closed {reason}"
863 )))
864 }
865 }
866}
867
868fn websocket_request(wire: &Responses) -> Result<http_client::Request<NoBody>, EncodeError> {
874 let url = crate::ws_client::websocket_url(&wire.provider.base_url, WEBSOCKET_PATH)
875 .map_err(EncodeError::request)?;
876
877 let request = wire.provider.headers_in_session(
878 http_client::Request::builder()
879 .method(http::Method::GET)
880 .uri(url),
881 None,
882 );
883
884 request.body(NoBody).map_err(|error| {
885 EncodeError::request(format!("Failed to build OpenAI websocket request: {error}"))
886 })
887}
888
889fn event_timeout_error(timeout: Duration) -> ProviderError {
890 ProviderError::Provider(format!(
891 "Timed out waiting for the next OpenAI websocket event after {timeout:?}"
892 ))
893}
894
895fn websocket_provider_error(error: http_client::Error) -> ProviderError {
898 let provider_request_id = error.non_success_headers().and_then(|headers| {
899 crate::driver::http_transport::request_id_from(headers, REQUEST_ID_HEADER)
900 });
901 ProviderError::from_transport_error(error).with_provider_request_id(provider_request_id)
902}
903
904impl<T> Model<Responses, T> {
905 pub fn responses_websocket(&self) -> ResponsesWebSocketSessionBuilder {
910 ResponsesWebSocketSessionBuilder::new(self.wire.clone())
911 }
912}
913
914#[cfg(not(target_family = "wasm"))]
916const _: fn() = || {
917 fn assert_send_sync<T: Send + Sync>() {}
918 assert_send_sync::<ResponsesWebSocketSession>();
919 assert_send_sync::<ResponsesWebSocketSessionBuilder>();
920};
921
922#[cfg(test)]
923#[allow(clippy::expect_used, clippy::panic)]
924mod tests;