Fall back to HTTP immediately for oversized WebSocket requests (#60840)

## Why

A WebSocket close with code `1009` before any text arrives indicates that the request exceeds the peer's message size limit. Retrying the request over another WebSocket does not address that limit.

## What changed

- Classify these early size rejections as requiring HTTP fallback and switch transports without exhausting WebSocket retries, including during prewarming.
- Preserve ordinary stream retry behavior for size closes after any text frame and for other close codes.

## Testing

Add integration coverage for early size rejections during generation and prewarming, size closes after metadata, rate limits, or response creation, and ordinary closes.

GitOrigin-RevId: 4550d289bb414fec1faae2a195bcc635d394117b
This commit is contained in:
Charlie Marsh
2026-11-11 18:58:21 +00:00
committed by copyberry
parent 9e6d1c5278
commit bc3ccba6e1
13 changed files with 216 additions and 12 deletions
@@ -47,6 +47,7 @@ use tokio_tungstenite::tungstenite::handshake::client::Response;
use tokio_tungstenite::tungstenite::protocol::CloseFrame;
use tokio_tungstenite::tungstenite::protocol::frame::Fragment;
use tokio_tungstenite::tungstenite::protocol::frame::Frame;
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
use tokio_tungstenite::tungstenite::protocol::frame::coding::Data;
use tracing::Instrument;
use tracing::Span;
@@ -848,6 +849,7 @@ async fn run_websocket_response_stream(
let mut interrupt_request = interrupt.clone().fuse();
let mut interruption = None;
let mut response_id = None;
let mut received_text = false;
let mut awaiting_interrupt_ack = false;
loop {
let poll_start = Instant::now();
@@ -896,6 +898,7 @@ async fn run_websocket_response_stream(
match message {
Message::Text(text) => {
received_text = true;
let event = serde_json::from_str::<ResponsesStreamEvent>(&text);
let kind = event.as_ref().ok().map(ResponsesStreamEvent::kind);
if let Some(t) = telemetry.as_ref() {
@@ -1033,6 +1036,9 @@ async fn run_websocket_response_stream(
Message::Binary(_) => {
return Err(ApiError::Stream("unexpected binary websocket event".into()));
}
Message::Close(Some(frame)) if frame.code == CloseCode::Size && !received_text => {
return Err(ApiError::WebsocketMessageTooLarge);
}
Message::Close(_) => {
return Err(ApiError::Stream(
"websocket closed by server before response.completed".into(),