mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
Treat model-not-found provider errors as terminal failures (#56445)
## Why Provider errors with code `model_not_found` should end the turn immediately and preserve the provider's message instead of triggering retries. ## What changed - Recognize `model_not_found` in HTTP 400, 403, and 404 responses and in streamed `error` and `response.failed` events. - Preserve the provider message and any available HTTP status, and expose `modelNotFound` through the app-server protocol and generated schemas and SDK types. ## Testing Add coverage for HTTP classification, statusless stream errors, and terminal failures without retries across HTTP, SSE, and WebSocket responses. Verify that `thread/read` preserves the error reported by the live notification. GitOrigin-RevId: 88f824ac03c8c9669915b1e40c67b76c08326fbe
This commit is contained in:
@@ -4,7 +4,7 @@ use crate::common::ResponseStream;
|
||||
use crate::common::SafetyBuffering;
|
||||
use crate::common::SafetyBufferingTreatment;
|
||||
use crate::error::ApiError;
|
||||
use crate::error::parse_flex_unavailable;
|
||||
use crate::error::parse_terminal_provider_error;
|
||||
use crate::rate_limits::parse_all_rate_limits;
|
||||
use crate::safety_buffering::treatment_from_headers;
|
||||
use crate::telemetry::SseTelemetry;
|
||||
@@ -347,7 +347,9 @@ pub fn process_responses_event(
|
||||
) -> std::result::Result<Option<ResponseEvent>, ResponsesEventError> {
|
||||
match event.kind.as_str() {
|
||||
"error" => {
|
||||
if let Some(error) = event.error.as_ref().and_then(parse_flex_unavailable) {
|
||||
if let Some(error) =
|
||||
parse_terminal_provider_error(event.error.as_ref(), /*status*/ None)
|
||||
{
|
||||
return Err(ResponsesEventError::Api(error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! Only rate-limit codes derive retry delays from plaintext messages.
|
||||
|
||||
use crate::error::ApiError;
|
||||
use crate::error::parse_flex_unavailable;
|
||||
use crate::error::parse_terminal_provider_error;
|
||||
use crate::responses_headers::json_headers_to_http_headers;
|
||||
use codex_http_client::RetryAfter;
|
||||
use codex_protocol::protocol::MisalignmentErrorDetails;
|
||||
@@ -29,7 +29,7 @@ pub(super) fn parse_failed_response(response: Option<Value>) -> ApiError {
|
||||
.and_then(|error| error.get("headers"))
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|headers| RetryAfter::from_headers(&json_headers_to_http_headers(headers)));
|
||||
if let Some(error) = error.and_then(parse_flex_unavailable) {
|
||||
if let Some(error) = parse_terminal_provider_error(error, /*status*/ None) {
|
||||
return error;
|
||||
}
|
||||
let Some(error) = error.and_then(|error| serde_json::from_value::<Error>(error.clone()).ok())
|
||||
|
||||
@@ -61,6 +61,25 @@ async fn flex_failure_with_retry_header_ends_stream_immediately() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statusless_model_errors_have_no_http_status() {
|
||||
let error = json!({"code": "model_not_found"});
|
||||
for event in [
|
||||
json!({"type": "error", "error": error}),
|
||||
json!({"type": "response.failed", "response": {"error": error}}),
|
||||
] {
|
||||
let event = serde_json::from_value(event).unwrap();
|
||||
let error = map_api_error(process_responses_event(event).unwrap_err().into_api_error());
|
||||
assert_eq!(
|
||||
(
|
||||
error.to_codex_protocol_error(),
|
||||
error.http_status_code_value()
|
||||
),
|
||||
(CodexErrorInfo::ModelNotFound, None)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Error headers beat rate-limit message advice; invalid ones fall back.
|
||||
#[tokio::test(start_paused = true)]
|
||||
async fn rate_limit_plaintext_loses_to_valid_error_header() {
|
||||
|
||||
Reference in New Issue
Block a user