mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
## Why Responses stopped by `content_filter` previously followed generic stream-error retry handling without guidance about the block. Retried requests need instructions to explain the limitation and offer a permitted alternative. ## What changed - Recognize content-filter failures as a distinct error while preserving retryability and the existing public error representation. - Append developer guidance to the conversation after each sampling block, retaining delivered output and using the existing retry handling. - Support a model catalog override through `model_messages.content_filter_guidance`. Missing, null, blank, or values exceeding 512 UTF-8 bytes fall back to bundled guidance. - The bundled guidance tells the model to avoid reproducing or working around blocked content, briefly explain the limitation, offer a permitted alternative, and continue unrelated authorized work. ## Testing Add a scenario and request-history snapshot covering repeated blocks, catalog guidance on each retry, retained partial output, and eventual completion. Add error-mapping assertions for retryability, public error classification, and error text. GitOrigin-RevId: ba0c201b3a5a3acf5cfa971332027240e0690225
66 lines
2.0 KiB
Rust
66 lines
2.0 KiB
Rust
use crate::rate_limits::RateLimitError;
|
|
use codex_client::TransportError;
|
|
use codex_http_client::RetryAfter;
|
|
use codex_protocol::protocol::MisalignmentErrorDetails;
|
|
use http::StatusCode;
|
|
use serde_json::Value;
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ApiError {
|
|
#[error(transparent)]
|
|
Transport(#[from] TransportError),
|
|
#[error("api error {status}: {message}")]
|
|
Api { status: StatusCode, message: String },
|
|
#[error("stream error: {0}")]
|
|
Stream(String),
|
|
#[error("stream error: Incomplete response returned, reason: content_filter")]
|
|
ContentFilter,
|
|
#[error("context window exceeded")]
|
|
ContextWindowExceeded,
|
|
#[error("quota exceeded")]
|
|
QuotaExceeded,
|
|
#[error("usage not included")]
|
|
UsageNotIncluded,
|
|
#[error("retryable error: {message}")]
|
|
Retryable {
|
|
message: String,
|
|
retry_after: Option<RetryAfter>,
|
|
},
|
|
#[error("rate limit exceeded: {message}")]
|
|
RateLimitExceeded {
|
|
message: String,
|
|
retry_after: Option<RetryAfter>,
|
|
},
|
|
#[error("rate limit: {0}")]
|
|
RateLimit(String),
|
|
#[error("invalid request: {message}")]
|
|
InvalidRequest { message: String },
|
|
#[error("invalid prompt: {message}")]
|
|
InvalidPrompt { message: String },
|
|
#[error("cyber policy: {message}")]
|
|
CyberPolicy { message: String },
|
|
#[error("bio policy: {message}")]
|
|
BioPolicy { message: String },
|
|
#[error("misalignment policy violation: {message}")]
|
|
MisalignmentPolicyViolation {
|
|
message: String,
|
|
misalignment: Option<MisalignmentErrorDetails>,
|
|
},
|
|
#[error("Flex capacity unavailable.")]
|
|
FlexUnavailable,
|
|
#[error("server overloaded")]
|
|
ServerOverloaded { retry_after: Option<RetryAfter> },
|
|
}
|
|
|
|
impl From<RateLimitError> for ApiError {
|
|
fn from(err: RateLimitError) -> Self {
|
|
Self::RateLimit(err.to_string())
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse_flex_unavailable(error: &Value) -> Option<ApiError> {
|
|
(error.get("code").and_then(Value::as_str) == Some("flex_unavailable"))
|
|
.then_some(ApiError::FlexUnavailable)
|
|
}
|