fix(provider): fallback to sse on codex websocket no-response
Classify websocket idle/no-response conditions as transport-unavailable so Codex auto mode can fall back to SSE. Keep partial-output timeout cases as stream errors and add focused regression tests for classification behavior. Refs #2551
This commit is contained in:
parent
b21a1a91ac
commit
0eab71eb73
@ -74,6 +74,25 @@ impl std::fmt::Display for WebsocketRequestError {
|
||||
|
||||
impl std::error::Error for WebsocketRequestError {}
|
||||
|
||||
fn websocket_idle_timeout_error(has_partial_output: bool) -> WebsocketRequestError {
|
||||
if has_partial_output {
|
||||
WebsocketRequestError::stream(anyhow::anyhow!(
|
||||
"No response from OpenAI Codex websocket stream before timeout"
|
||||
))
|
||||
} else {
|
||||
WebsocketRequestError::transport_unavailable(anyhow::anyhow!(
|
||||
"OpenAI Codex websocket stream timed out after {}s waiting for events",
|
||||
CODEX_WS_READ_TIMEOUT.as_secs()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn websocket_no_response_error() -> WebsocketRequestError {
|
||||
WebsocketRequestError::transport_unavailable(anyhow::anyhow!(
|
||||
"No response from OpenAI Codex websocket stream"
|
||||
))
|
||||
}
|
||||
|
||||
pub struct OpenAiCodexProvider {
|
||||
auth: AuthService,
|
||||
auth_profile_override: Option<String>,
|
||||
@ -795,10 +814,7 @@ impl OpenAiCodexProvider {
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
return Err(WebsocketRequestError::stream(anyhow::anyhow!(
|
||||
"OpenAI Codex websocket stream timed out after {}s waiting for events",
|
||||
CODEX_WS_READ_TIMEOUT.as_secs()
|
||||
)));
|
||||
return Err(websocket_idle_timeout_error(false));
|
||||
}
|
||||
};
|
||||
|
||||
@ -881,14 +897,10 @@ impl OpenAiCodexProvider {
|
||||
return Ok(text);
|
||||
}
|
||||
if timed_out {
|
||||
return Err(WebsocketRequestError::stream(anyhow::anyhow!(
|
||||
"No response from OpenAI Codex websocket stream before timeout"
|
||||
)));
|
||||
return Err(websocket_idle_timeout_error(true));
|
||||
}
|
||||
|
||||
Err(WebsocketRequestError::stream(anyhow::anyhow!(
|
||||
"No response from OpenAI Codex websocket stream"
|
||||
)))
|
||||
Err(websocket_no_response_error())
|
||||
}
|
||||
|
||||
async fn send_responses_sse_request(
|
||||
@ -1611,4 +1623,44 @@ data: [DONE]
|
||||
assert!(!caps.native_tool_calling);
|
||||
assert!(caps.vision);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_idle_timeout_without_partial_output_is_transport_unavailable() {
|
||||
let error = websocket_idle_timeout_error(false);
|
||||
assert!(matches!(
|
||||
error,
|
||||
WebsocketRequestError::TransportUnavailable(_)
|
||||
));
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("timed out after 60s waiting for events"),
|
||||
"unexpected error message: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_idle_timeout_with_partial_output_is_stream_error() {
|
||||
let error = websocket_idle_timeout_error(true);
|
||||
assert!(matches!(error, WebsocketRequestError::Stream(_)));
|
||||
assert!(
|
||||
error.to_string().contains("before timeout"),
|
||||
"unexpected error message: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_no_response_maps_to_transport_unavailable() {
|
||||
let error = websocket_no_response_error();
|
||||
assert!(matches!(
|
||||
error,
|
||||
WebsocketRequestError::TransportUnavailable(_)
|
||||
));
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("No response from OpenAI Codex websocket stream"),
|
||||
"unexpected error message: {error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user