mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
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:
@@ -30,6 +30,7 @@ pub fn map_api_error(err: ApiError) -> CodexErr {
|
||||
ApiError::Transport(_)
|
||||
| ApiError::Api { .. }
|
||||
| ApiError::Stream(_)
|
||||
| ApiError::WebsocketMessageTooLarge
|
||||
| ApiError::ContentFilter
|
||||
| ApiError::ResponseIncomplete(_)
|
||||
| ApiError::ContextWindowExceeded
|
||||
@@ -67,6 +68,9 @@ fn map_api_error_details(err: ApiError) -> CodexErr {
|
||||
CodexErr::new(CodexErrorDetails::RateLimitExceeded(message))
|
||||
}
|
||||
ApiError::Stream(msg) => CodexErr::Stream(msg),
|
||||
ApiError::WebsocketMessageTooLarge => {
|
||||
CodexErr::new(CodexErrorDetails::WebsocketMessageTooLarge)
|
||||
}
|
||||
ApiError::ContentFilter => CodexErr::new(CodexErrorDetails::ContentFilter),
|
||||
ApiError::ResponseIncomplete(incomplete) => {
|
||||
CodexErr::new(CodexErrorDetails::ResponseIncomplete(incomplete))
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -15,6 +15,8 @@ pub enum ApiError {
|
||||
Api { status: StatusCode, message: String },
|
||||
#[error("stream error: {0}")]
|
||||
Stream(String),
|
||||
#[error("websocket request exceeds the peer's message size limit (close code 1009)")]
|
||||
WebsocketMessageTooLarge,
|
||||
#[error("stream error: Incomplete response returned, reason: content_filter")]
|
||||
ContentFilter,
|
||||
#[error(transparent)]
|
||||
|
||||
Reference in New Issue
Block a user