Files
codex/codex-rs/protocol/src/codex_error_info.rs
T
Tom Leach (@toml) 6a6e2fb1f0 Distinguish policy-restricted capacity failures from server overloads (#57452)
## What changed

- Recognize `policy_capacity_unavailable` in HTTP and streaming API errors and expose it as `policyCapacityUnavailable` through the app-server protocol and generated bindings.
- Display “Policy-restricted capacity is unavailable. Please try again later.” while preserving overload retry behavior based on `Retry-After`.
- Handle the new error in TUI overload rendering, reviewer retries, and error telemetry.

## Testing

Add coverage for HTTP and streaming error classification, retry advice, WebSocket terminal error attribution, protocol serialization, and TUI rendering with and without an upstream message.

GitOrigin-RevId: 1b9536fba1e11cc891dcf689aee35410d82f5412
2026-10-27 13:31:38 +00:00

89 lines
2.5 KiB
Rust

//! Decodes unknown error classifications as `Other` so enclosing records stay readable.
//! The wire definition keeps serialization exhaustive and known payload validation strict.
use crate::protocol::CodexErrorInfo;
use crate::protocol::NonSteerableTurnKind;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use serde::de::Error as _;
use serde_json::Value;
#[derive(Serialize, Deserialize)]
#[serde(
remote = "CodexErrorInfo",
rename = "CodexErrorInfo",
rename_all = "snake_case"
)]
enum CodexErrorInfoWire {
ContextWindowExceeded,
SessionBudgetExceeded,
UsageLimitExceeded,
RateLimitExceeded,
FlexUnavailable,
ServerOverloaded,
ModelNotFound,
/// Capacity is unavailable on a route restricted by server policy.
PolicyCapacityUnavailable,
CyberPolicy,
BioPolicy,
MisalignmentPolicyViolation,
TooManyDenials,
HttpConnectionFailed {
http_status_code: Option<u16>,
},
ResponseStreamConnectionFailed {
http_status_code: Option<u16>,
},
InternalServerError,
Unauthorized,
BadRequest,
InvalidPrompt,
SandboxError,
ResponseStreamDisconnected {
http_status_code: Option<u16>,
},
ResponseTooManyFailedAttempts {
http_status_code: Option<u16>,
},
ActiveTurnNotSteerable {
turn_kind: NonSteerableTurnKind,
},
ThreadRollbackFailed,
#[serde(other)]
Other,
}
impl Serialize for CodexErrorInfo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
CodexErrorInfoWire::serialize(self, serializer)
}
}
impl<'de> Deserialize<'de> for CodexErrorInfo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
// Probe the tag first so only unknown payloads are discarded: `serde(other)`
// recognizes unknown tags, but its unit variant rejects their payloads.
if let Value::Object(fields) = &value
&& fields.len() == 1
&& let Some(kind) = fields.keys().next()
&& kind != "other"
&& matches!(
CodexErrorInfoWire::deserialize(Value::String(kind.clone())),
Ok(Self::Other)
)
{
return Ok(Self::Other);
}
CodexErrorInfoWire::deserialize(value).map_err(D::Error::custom)
}
}