From 8c238bb8a207d3b05534a36d0351e5272ade72f0 Mon Sep 17 00:00:00 2001 From: celia-oai Date: Thu, 20 Nov 2025 15:54:34 -0800 Subject: [PATCH 1/2] changes --- codex-rs/app-server/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 35653099aad4..0c62f17abd3e 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -339,6 +339,26 @@ Event notifications are the server-initiated event stream for thread lifecycles, The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with `turn/started` (initial `turn`) and ends with `turn/completed` (final `turn` plus token `usage`), and clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`. +- `turn/started` — `{ turn }` with the turn id, empty `items`, and `status: "inProgress"`. +- `turn/completed` — `{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorCode? } }`. + +- `error` — emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorCode? } }` payload as `turn.status: "failed"` and may precede that terminal notification. + + `codexErrorCode` maps to the `CodexErrorInfo` enum. Common values: + - `ContextWindowExceeded` + - `UsageLimitExceeded` + - `HttpConnectionFailed { httpStatusCode? }`: upstream HTTP failures including 4xx/5xx + - `ResponseStreamConnectionFailed { httpStatusCode? }`: failure to connect to the response SSE stream + - `ResponseStreamDisconnected { httpStatusCode? }`: disconnect of the response SSE stream in the middle of a turn before completion + - `ResponseTooManyFailedAttempts { httpStatusCode? }` + - `BadRequest` + - `Unauthorized` + - `SandboxError` + - `InternalServerError` + - `Other`: all unclassified errors + + When an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorCode` variant; otherwise it is `null`. + #### Thread items `ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items: From 7528212311a7a33e70bfe3ed7d5eb88bdf37b20e Mon Sep 17 00:00:00 2001 From: celia-oai Date: Thu, 20 Nov 2025 16:09:23 -0800 Subject: [PATCH 2/2] name fix --- .../app-server-protocol/src/protocol/v2.rs | 7 ++++-- codex-rs/app-server/README.md | 11 ++++++---- .../app-server/src/bespoke_event_handling.rs | 22 +++++++++---------- codex-rs/core/src/codex.rs | 6 ++--- codex-rs/core/src/error.rs | 4 ++-- .../tests/event_processor_with_json_output.rs | 6 ++--- codex-rs/protocol/src/protocol.rs | 4 ++-- codex-rs/tui/src/chatwidget/tests.rs | 2 +- 8 files changed, 34 insertions(+), 28 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 6ecf0f925658..1f8463a60a41 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -50,6 +50,9 @@ macro_rules! v2_enum_from_core { } /// This translation layer make sure that we expose codex error code in camel case. +/// +/// When an upstream HTTP status is available (for example, from the Responses API or a provider), +/// it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -615,7 +618,7 @@ pub struct Turn { #[error("{message}")] pub struct TurnError { pub message: String, - pub codex_error_code: Option, + pub codex_error_info: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] @@ -1253,7 +1256,7 @@ mod tests { } #[test] - fn codex_error_code_serializes_http_status_code_in_camel_case() { + fn codex_error_info_serializes_http_status_code_in_camel_case() { let value = CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code: Some(401), }; diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 0c62f17abd3e..d5ac8a57d08a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -340,11 +340,14 @@ Event notifications are the server-initiated event stream for thread lifecycles, The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with `turn/started` (initial `turn`) and ends with `turn/completed` (final `turn` plus token `usage`), and clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`. - `turn/started` — `{ turn }` with the turn id, empty `items`, and `status: "inProgress"`. -- `turn/completed` — `{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorCode? } }`. +- `turn/completed` — `{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo? } }`. -- `error` — emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorCode? } }` payload as `turn.status: "failed"` and may precede that terminal notification. +Today both notifications carry an empty `items` array even when item events were streamed; rely on `item/*` notifications for the canonical item list until this is fixed. - `codexErrorCode` maps to the `CodexErrorInfo` enum. Common values: +#### Errors +`error` event is emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorInfo? } }` payload as `turn.status: "failed"` and may precede that terminal notification. + + `codexErrorInfo` maps to the `CodexErrorInfo` enum. Common values: - `ContextWindowExceeded` - `UsageLimitExceeded` - `HttpConnectionFailed { httpStatusCode? }`: upstream HTTP failures including 4xx/5xx @@ -357,7 +360,7 @@ The app-server streams JSON-RPC notifications while a turn is running. Each turn - `InternalServerError` - `Other`: all unclassified errors - When an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorCode` variant; otherwise it is `null`. +When an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant. #### Thread items diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 69346d2e31c9..387aabc32df7 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -264,7 +264,7 @@ pub(crate) async fn apply_bespoke_event_handling( EventMsg::Error(ev) => { let turn_error = TurnError { message: ev.message, - codex_error_code: ev.codex_error_code.map(V2CodexErrorInfo::from), + codex_error_info: ev.codex_error_info.map(V2CodexErrorInfo::from), }; handle_error(conversation_id, turn_error.clone(), &turn_summary_store).await; outgoing @@ -278,7 +278,7 @@ pub(crate) async fn apply_bespoke_event_handling( // but we notify the client. let turn_error = TurnError { message: ev.message, - codex_error_code: ev.codex_error_code.map(V2CodexErrorInfo::from), + codex_error_info: ev.codex_error_info.map(V2CodexErrorInfo::from), }; outgoing .send_server_notification(ServerNotification::Error(ErrorNotification { @@ -899,7 +899,7 @@ mod tests { conversation_id, TurnError { message: "boom".to_string(), - codex_error_code: Some(V2CodexErrorInfo::InternalServerError), + codex_error_info: Some(V2CodexErrorInfo::InternalServerError), }, &turn_summary_store, ) @@ -910,7 +910,7 @@ mod tests { turn_summary.last_error, Some(TurnError { message: "boom".to_string(), - codex_error_code: Some(V2CodexErrorInfo::InternalServerError), + codex_error_info: Some(V2CodexErrorInfo::InternalServerError), }) ); Ok(()) @@ -956,7 +956,7 @@ mod tests { conversation_id, TurnError { message: "oops".to_string(), - codex_error_code: None, + codex_error_info: None, }, &turn_summary_store, ) @@ -996,7 +996,7 @@ mod tests { conversation_id, TurnError { message: "bad".to_string(), - codex_error_code: Some(V2CodexErrorInfo::Other), + codex_error_info: Some(V2CodexErrorInfo::Other), }, &turn_summary_store, ) @@ -1024,7 +1024,7 @@ mod tests { TurnStatus::Failed { error: TurnError { message: "bad".to_string(), - codex_error_code: Some(V2CodexErrorInfo::Other), + codex_error_info: Some(V2CodexErrorInfo::Other), } } ); @@ -1079,7 +1079,7 @@ mod tests { conversation_a, TurnError { message: "a1".to_string(), - codex_error_code: Some(V2CodexErrorInfo::BadRequest), + codex_error_info: Some(V2CodexErrorInfo::BadRequest), }, &turn_summary_store, ) @@ -1098,7 +1098,7 @@ mod tests { conversation_b, TurnError { message: "b1".to_string(), - codex_error_code: None, + codex_error_info: None, }, &turn_summary_store, ) @@ -1134,7 +1134,7 @@ mod tests { TurnStatus::Failed { error: TurnError { message: "a1".to_string(), - codex_error_code: Some(V2CodexErrorInfo::BadRequest), + codex_error_info: Some(V2CodexErrorInfo::BadRequest), } } ); @@ -1155,7 +1155,7 @@ mod tests { TurnStatus::Failed { error: TurnError { message: "b1".to_string(), - codex_error_code: None, + codex_error_info: None, } } ); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 8b8292cd1663..7e93cc9d0a46 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1191,12 +1191,12 @@ impl Session { message: impl Into, codex_error: CodexErr, ) { - let codex_error_code = CodexErrorInfo::ResponseStreamDisconnected { + let codex_error_info = CodexErrorInfo::ResponseStreamDisconnected { http_status_code: codex_error.http_status_code_value(), }; let event = EventMsg::StreamError(StreamErrorEvent { message: message.into(), - codex_error_code: Some(codex_error_code), + codex_error_info: Some(codex_error_info), }); self.send_event(turn_context, event).await; } @@ -1689,7 +1689,7 @@ mod handlers { id: sub_id.clone(), msg: EventMsg::Error(ErrorEvent { message: "Failed to shutdown rollout recorder".to_string(), - codex_error_code: Some(CodexErrorInfo::Other), + codex_error_info: Some(CodexErrorInfo::Other), }), }; sess.send_event_raw(event).await; diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index ee729304997d..9130b40e1e7d 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -469,7 +469,7 @@ impl CodexErr { }; ErrorEvent { message, - codex_error_code: Some(self.to_codex_protocol_error()), + codex_error_info: Some(self.to_codex_protocol_error()), } } @@ -650,7 +650,7 @@ mod tests { "prefix: Error while reading the server response: HTTP status client error (429 Too Many Requests) for url (http://example.com/), request id: req-123" ); assert_eq!( - event.codex_error_code, + event.codex_error_info, Some(CodexErrorInfo::ResponseStreamConnectionFailed { http_status_code: Some(429) }) diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index f716f0f0f4c9..06f99b96b105 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -540,7 +540,7 @@ fn error_event_produces_error() { "e1", EventMsg::Error(codex_core::protocol::ErrorEvent { message: "boom".to_string(), - codex_error_code: Some(CodexErrorInfo::Other), + codex_error_info: Some(CodexErrorInfo::Other), }), )); assert_eq!( @@ -580,7 +580,7 @@ fn stream_error_event_produces_error() { "e1", EventMsg::StreamError(codex_core::protocol::StreamErrorEvent { message: "retrying".to_string(), - codex_error_code: Some(CodexErrorInfo::Other), + codex_error_info: Some(CodexErrorInfo::Other), }), )); assert_eq!( @@ -599,7 +599,7 @@ fn error_followed_by_task_complete_produces_turn_failed() { "e1", EventMsg::Error(ErrorEvent { message: "boom".to_string(), - codex_error_code: Some(CodexErrorInfo::Other), + codex_error_info: Some(CodexErrorInfo::Other), }), ); assert_eq!( diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 5806e1a6f031..e0c2e4df3fff 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -716,7 +716,7 @@ pub struct ExitedReviewModeEvent { pub struct ErrorEvent { pub message: String, #[serde(default)] - pub codex_error_code: Option, + pub codex_error_info: Option, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] @@ -1395,7 +1395,7 @@ pub struct UndoCompletedEvent { pub struct StreamErrorEvent { pub message: String, #[serde(default)] - pub codex_error_code: Option, + pub codex_error_info: Option, } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 45a9a8df218f..61e23fb3fcc0 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -2648,7 +2648,7 @@ fn stream_error_updates_status_indicator() { id: "sub-1".into(), msg: EventMsg::StreamError(StreamErrorEvent { message: msg.to_string(), - codex_error_code: Some(CodexErrorInfo::Other), + codex_error_info: Some(CodexErrorInfo::Other), }), });