Classify streaming rate-limit errors (#40931)

## What changed

- Classify `response.failed` events with the `rate_limit_exceeded` code as a distinct retryable error while preserving any parsed retry delay.
- Expose the error as `rateLimitExceeded` through the core protocol and app-server schemas after stream retries are exhausted.
- Preserve the upstream message for TUI display while keeping it out of telemetry summaries.

## Testing

- Cover SSE classification, retry metadata, protocol conversion and serialization, exhausted stream retries, telemetry redaction, and TUI rendering.

GitOrigin-RevId: 02dab4d3477dcd7653a58c49c4bd38687a616579
This commit is contained in:
Steve Coffey
2026-08-26 17:45:42 +00:00
committed by copyberry
parent 7625bd5665
commit e0c727de04
38 changed files with 198 additions and 26 deletions
@@ -78,6 +78,7 @@ pub enum CodexErrorInfo {
ContextWindowExceeded,
SessionBudgetExceeded,
UsageLimitExceeded,
RateLimitExceeded,
ServerOverloaded,
CyberPolicy,
MisalignmentPolicyViolation,
@@ -125,6 +126,7 @@ impl From<CoreCodexErrorInfo> for CodexErrorInfo {
CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
CoreCodexErrorInfo::SessionBudgetExceeded => CodexErrorInfo::SessionBudgetExceeded,
CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded,
CoreCodexErrorInfo::RateLimitExceeded => CodexErrorInfo::RateLimitExceeded,
CoreCodexErrorInfo::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
CoreCodexErrorInfo::CyberPolicy => CodexErrorInfo::CyberPolicy,
CoreCodexErrorInfo::MisalignmentPolicyViolation => {
@@ -38,6 +38,7 @@ use codex_protocol::permissions::FileSystemSandboxEntry as CoreFileSystemSandbox
use codex_protocol::permissions::FileSystemSpecialPath as CoreFileSystemSpecialPath;
use codex_protocol::protocol::AgentStatus as CoreAgentStatus;
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
use codex_protocol::protocol::ConversationTextRole;
use codex_protocol::protocol::ExecCommandSource as CoreExecCommandSource;
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
@@ -4488,11 +4489,19 @@ fn codex_error_info_serializes_http_status_code_in_camel_case() {
}
#[test]
fn codex_error_info_serializes_cyber_policy_in_camel_case() {
assert_eq!(
serde_json::to_value(CodexErrorInfo::CyberPolicy).unwrap(),
json!("cyberPolicy")
);
fn core_error_info_converts_to_camel_case() {
for (core, expected) in [
(CoreCodexErrorInfo::CyberPolicy, json!("cyberPolicy")),
(
CoreCodexErrorInfo::RateLimitExceeded,
json!("rateLimitExceeded"),
),
] {
assert_eq!(
serde_json::to_value(CodexErrorInfo::from(core)).unwrap(),
expected
);
}
}
#[test]