mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[core] add optional status_code to error events (#6865)
We want to better uncover error status code for clients. Add an optional
status_code to error events (thread error, error, stream error) so app
server could uncover the status code from the client side later.
in event log:
```
< {
< "method": "codex/event/stream_error",
< "params": {
< "conversationId": "019a9a32-f576-7292-9711-8e57e8063536",
< "id": "0",
< "msg": {
< "message": "Reconnecting... 5/5",
< "status_code": 401,
< "type": "stream_error"
< }
< }
< }
< {
< "method": "codex/event/error",
< "params": {
< "conversationId": "019a9a32-f576-7292-9711-8e57e8063536",
< "id": "0",
< "msg": {
< "message": "exceeded retry limit, last status: 401 Unauthorized, request id: 9a0cb03a485067f7-SJC",
< "status_code": 401,
< "type": "error"
< }
< }
< }
```
This commit is contained in:
committed by
GitHub
Unverified
parent
20982d5c6a
commit
c2ec477d93
@@ -66,6 +66,7 @@ use crate::context_manager::ContextManager;
|
||||
use crate::environment_context::EnvironmentContext;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::error::http_status_code_value;
|
||||
#[cfg(test)]
|
||||
use crate::exec::StreamOutput;
|
||||
use crate::mcp::auth::compute_auth_statuses;
|
||||
@@ -79,7 +80,6 @@ use crate::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::protocol::BackgroundEventEvent;
|
||||
use crate::protocol::DeprecationNoticeEvent;
|
||||
use crate::protocol::ErrorEvent;
|
||||
use crate::protocol::Event;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::ExecApprovalRequestEvent;
|
||||
@@ -133,6 +133,7 @@ use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_readiness::Readiness;
|
||||
use codex_utils_readiness::ReadinessFlag;
|
||||
use codex_utils_tokenizer::warm_model_cache;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
/// The high-level interface to the Codex system.
|
||||
/// It operates as a queue pair where you send submissions and receive events.
|
||||
@@ -1186,9 +1187,11 @@ impl Session {
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
message: impl Into<String>,
|
||||
http_status_code: Option<StatusCode>,
|
||||
) {
|
||||
let event = EventMsg::StreamError(StreamErrorEvent {
|
||||
message: message.into(),
|
||||
http_status_code: http_status_code_value(http_status_code),
|
||||
});
|
||||
self.send_event(turn_context, event).await;
|
||||
}
|
||||
@@ -1680,6 +1683,7 @@ mod handlers {
|
||||
id: sub_id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: "Failed to shutdown rollout recorder".to_string(),
|
||||
http_status_code: None,
|
||||
}),
|
||||
};
|
||||
sess.send_event_raw(event).await;
|
||||
@@ -1933,10 +1937,8 @@ pub(crate) async fn run_task(
|
||||
}
|
||||
Err(e) => {
|
||||
info!("Turn error: {e:#}");
|
||||
let event = EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
});
|
||||
sess.send_event(&turn_context, event).await;
|
||||
sess.send_event(&turn_context, EventMsg::Error(e.to_error_event(None)))
|
||||
.await;
|
||||
// let the user continue the conversation
|
||||
break;
|
||||
}
|
||||
@@ -2060,6 +2062,7 @@ async fn run_turn(
|
||||
sess.notify_stream_error(
|
||||
&turn_context,
|
||||
format!("Reconnecting... {retries}/{max_retries}"),
|
||||
e.http_status_code(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::error::Result as CodexResult;
|
||||
use crate::features::Feature;
|
||||
use crate::protocol::AgentMessageEvent;
|
||||
use crate::protocol::CompactedItem;
|
||||
use crate::protocol::ErrorEvent;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::TaskStartedEvent;
|
||||
use crate::protocol::TurnContextItem;
|
||||
@@ -128,10 +127,8 @@ async fn run_compact_task_inner(
|
||||
continue;
|
||||
}
|
||||
sess.set_total_tokens_full(turn_context.as_ref()).await;
|
||||
let event = EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
});
|
||||
sess.send_event(&turn_context, event).await;
|
||||
sess.send_event(&turn_context, EventMsg::Error(e.to_error_event(None)))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -141,15 +138,14 @@ async fn run_compact_task_inner(
|
||||
sess.notify_stream_error(
|
||||
turn_context.as_ref(),
|
||||
format!("Reconnecting... {retries}/{max_retries}"),
|
||||
e.http_status_code(),
|
||||
)
|
||||
.await;
|
||||
tokio::time::sleep(delay).await;
|
||||
continue;
|
||||
} else {
|
||||
let event = EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
});
|
||||
sess.send_event(&turn_context, event).await;
|
||||
sess.send_event(&turn_context, EventMsg::Error(e.to_error_event(None)))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use crate::codex::TurnContext;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::protocol::AgentMessageEvent;
|
||||
use crate::protocol::CompactedItem;
|
||||
use crate::protocol::ErrorEvent;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::RolloutItem;
|
||||
use crate::protocol::TaskStartedEvent;
|
||||
@@ -30,10 +29,8 @@ pub(crate) async fn run_remote_compact_task(sess: Arc<Session>, turn_context: Ar
|
||||
|
||||
async fn run_remote_compact_task_inner(sess: &Arc<Session>, turn_context: &Arc<TurnContext>) {
|
||||
if let Err(err) = run_remote_compact_task_inner_impl(sess, turn_context).await {
|
||||
let event = EventMsg::Error(ErrorEvent {
|
||||
message: format!("Error running remote compact task: {err}"),
|
||||
});
|
||||
sess.send_event(turn_context, event).await;
|
||||
let event = err.to_error_event(Some("Error running remote compact task".to_string()));
|
||||
sess.send_event(turn_context, EventMsg::Error(event)).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use codex_async_utils::CancelErr;
|
||||
use codex_protocol::ConversationId;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::RateLimitSnapshot;
|
||||
use reqwest::StatusCode;
|
||||
use serde_json;
|
||||
@@ -430,6 +431,37 @@ impl CodexErr {
|
||||
pub fn downcast_ref<T: std::any::Any>(&self) -> Option<&T> {
|
||||
(self as &dyn std::any::Any).downcast_ref::<T>()
|
||||
}
|
||||
|
||||
pub fn http_status_code(&self) -> Option<StatusCode> {
|
||||
match self {
|
||||
CodexErr::UnexpectedStatus(err) => Some(err.status),
|
||||
CodexErr::RetryLimit(err) => Some(err.status),
|
||||
CodexErr::UsageLimitReached(_) | CodexErr::UsageNotIncluded => {
|
||||
Some(StatusCode::TOO_MANY_REQUESTS)
|
||||
}
|
||||
CodexErr::InternalServerError => Some(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
CodexErr::ResponseStreamFailed(err) => err.source.status(),
|
||||
CodexErr::ConnectionFailed(err) => err.source.status(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_error_event(&self, message_prefix: Option<String>) -> ErrorEvent {
|
||||
let error_message = self.to_string();
|
||||
let message: String = match message_prefix {
|
||||
Some(prefix) => format!("{prefix}: {error_message}"),
|
||||
None => error_message,
|
||||
};
|
||||
|
||||
ErrorEvent {
|
||||
message,
|
||||
http_status_code: http_status_code_value(self.http_status_code()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn http_status_code_value(http_status_code: Option<StatusCode>) -> Option<u16> {
|
||||
http_status_code.as_ref().map(StatusCode::as_u16)
|
||||
}
|
||||
|
||||
pub fn get_error_message_ui(e: &CodexErr) -> String {
|
||||
@@ -775,4 +807,43 @@ mod tests {
|
||||
assert_eq!(err.to_string(), expected);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_event_includes_http_status_code_when_available() {
|
||||
let err = CodexErr::UnexpectedStatus(UnexpectedResponseError {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
body: "oops".to_string(),
|
||||
request_id: Some("req-1".to_string()),
|
||||
});
|
||||
let event = err.to_error_event(None);
|
||||
|
||||
assert_eq!(
|
||||
event.message,
|
||||
"unexpected status 400 Bad Request: oops, request id: req-1"
|
||||
);
|
||||
assert_eq!(
|
||||
event.http_status_code,
|
||||
Some(StatusCode::BAD_REQUEST.as_u16())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_event_omits_http_status_code_when_unknown() {
|
||||
let event = CodexErr::Fatal("boom".to_string()).to_error_event(None);
|
||||
|
||||
assert_eq!(event.message, "Fatal error: boom");
|
||||
assert_eq!(event.http_status_code, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_event_applies_message_wrapper() {
|
||||
let event = CodexErr::Fatal("boom".to_string())
|
||||
.to_error_event(Some("Error running remote compact task".to_string()));
|
||||
|
||||
assert_eq!(
|
||||
event.message,
|
||||
"Error running remote compact task: Fatal error: boom"
|
||||
);
|
||||
assert_eq!(event.http_status_code, None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user