Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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/")]
Expand Down Expand Up @@ -615,7 +618,7 @@ pub struct Turn {
#[error("{message}")]
pub struct TurnError {
pub message: String,
pub codex_error_code: Option<CodexErrorInfo>,
pub codex_error_info: Option<CodexErrorInfo>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
Expand Down Expand Up @@ -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),
};
Expand Down
23 changes: 23 additions & 0 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,29 @@ 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, codexErrorInfo? } }`.
Comment thread
celia-oai marked this conversation as resolved.

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.

#### 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
- `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 `codexErrorInfo` variant.

#### Thread items

`ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items:
Expand Down
22 changes: 11 additions & 11 deletions codex-rs/app-server/src/bespoke_event_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
)
Expand All @@ -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(())
Expand Down Expand Up @@ -956,7 +956,7 @@ mod tests {
conversation_id,
TurnError {
message: "oops".to_string(),
codex_error_code: None,
codex_error_info: None,
},
&turn_summary_store,
)
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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),
}
}
);
Expand Down Expand Up @@ -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,
)
Expand All @@ -1098,7 +1098,7 @@ mod tests {
conversation_b,
TurnError {
message: "b1".to_string(),
codex_error_code: None,
codex_error_info: None,
},
&turn_summary_store,
)
Expand Down Expand Up @@ -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),
}
}
);
Expand All @@ -1155,7 +1155,7 @@ mod tests {
TurnStatus::Failed {
error: TurnError {
message: "b1".to_string(),
codex_error_code: None,
codex_error_info: None,
}
}
);
Expand Down
6 changes: 3 additions & 3 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1191,12 +1191,12 @@ impl Session {
message: impl Into<String>,
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;
}
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
}
}

Expand Down Expand Up @@ -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)
})
Expand Down
6 changes: 3 additions & 3 deletions codex-rs/exec/tests/event_processor_with_json_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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!(
Expand All @@ -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!(
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/protocol/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ pub struct ExitedReviewModeEvent {
pub struct ErrorEvent {
pub message: String,
#[serde(default)]
pub codex_error_code: Option<CodexErrorInfo>,
pub codex_error_info: Option<CodexErrorInfo>,
}

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
Expand Down Expand Up @@ -1395,7 +1395,7 @@ pub struct UndoCompletedEvent {
pub struct StreamErrorEvent {
pub message: String,
#[serde(default)]
pub codex_error_code: Option<CodexErrorInfo>,
pub codex_error_info: Option<CodexErrorInfo>,
}

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}),
});

Expand Down
Loading