feat(codex): align Search and execution protocol

This commit is contained in:
MMEXA
2026-07-12 03:04:15 +08:00
parent 59d37ae1dd
commit 2316df5c9a
121 changed files with 5141 additions and 543 deletions
@@ -92,7 +92,10 @@ pub(crate) fn classify_local_failover(
return LocalFailoverClassification::RetryStatusCode;
}
if should_failover_local_upstream_status(input.status_code) {
if should_failover_local_upstream_status(
input.status_code,
policy.retry_client_errors_by_default,
) {
return LocalFailoverClassification::RetryUpstreamFailure;
}
@@ -109,8 +112,11 @@ pub(crate) fn local_failover_error_message(response_text: Option<&str>) -> Optio
.filter(|value| !value.is_empty())
}
fn should_failover_local_upstream_status(status_code: u16) -> bool {
status_code >= 400
fn should_failover_local_upstream_status(
status_code: u16,
retry_client_errors_by_default: bool,
) -> bool {
status_code >= 500 || status_code >= 400 && retry_client_errors_by_default
}
fn local_error_response_has_cyber_policy_code(response_text: Option<&str>) -> bool {
@@ -474,6 +480,39 @@ mod tests {
}
}
#[test]
fn classifier_passes_through_client_errors_when_protocol_default_disables_failover() {
let policy = LocalFailoverPolicy {
retry_client_errors_by_default: false,
..LocalFailoverPolicy::default()
};
for status_code in [400, 401, 429, 499] {
assert_eq!(
classify_local_failover(&policy, LocalFailoverInput::new(status_code, None)),
LocalFailoverClassification::UseDefault
);
}
assert_eq!(
classify_local_failover(&policy, LocalFailoverInput::new(500, None)),
LocalFailoverClassification::RetryUpstreamFailure
);
}
#[test]
fn classifier_explicit_continue_rule_overrides_protocol_client_error_default() {
let policy = LocalFailoverPolicy {
continue_status_codes: [429].into_iter().collect(),
retry_client_errors_by_default: false,
..LocalFailoverPolicy::default()
};
assert_eq!(
classify_local_failover(&policy, LocalFailoverInput::new(429, None)),
LocalFailoverClassification::RetryStatusCode
);
}
#[test]
fn classifier_keeps_embedded_rate_limit_error_in_success_response_on_default_path() {
assert_eq!(
@@ -243,7 +243,11 @@ fn local_client_session_affinity(report_context: Option<&Value>) -> Option<Clien
.get("original_request_body")
.filter(|value| !value.is_null());
crate::client_session_affinity::client_session_affinity_from_request(&headers, body_json)
crate::client_session_affinity::client_session_affinity_from_api_request(
report_context_string_field(Some(report_context), "client_api_format").unwrap_or_default(),
&headers,
body_json,
)
}
fn header_map_from_report_context(headers: Option<&Value>) -> http::HeaderMap {
@@ -7,7 +7,7 @@ use tracing::debug;
use crate::provider_transport::GatewayProviderTransportSnapshot;
use crate::AppState;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LocalFailoverPolicy {
pub(crate) max_retries: Option<u64>,
pub(crate) stop_status_codes: BTreeSet<u16>,
@@ -15,6 +15,21 @@ pub(crate) struct LocalFailoverPolicy {
pub(crate) success_failover_patterns: Vec<LocalFailoverRegexRule>,
pub(crate) error_stop_patterns: Vec<LocalFailoverRegexRule>,
pub(crate) stop_cyber_policy_errors: bool,
pub(crate) retry_client_errors_by_default: bool,
}
impl Default for LocalFailoverPolicy {
fn default() -> Self {
Self {
max_retries: None,
stop_status_codes: BTreeSet::new(),
continue_status_codes: BTreeSet::new(),
success_failover_patterns: Vec::new(),
error_stop_patterns: Vec::new(),
stop_cyber_policy_errors: false,
retry_client_errors_by_default: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -81,6 +96,10 @@ pub(crate) fn local_failover_policy_from_transport(
LocalFailoverPolicy {
max_retries,
retry_client_errors_by_default:
aether_ai_formats::api_format_defaults_to_client_error_failover(
&transport.endpoint.api_format,
),
stop_cyber_policy_errors: codex_cyber_flag_passthrough_enabled(
&transport.provider.provider_type,
transport.provider.config.as_ref(),
@@ -144,6 +163,10 @@ pub(crate) fn local_failover_policy_from_report_context(
.get("stop_cyber_policy_errors")
.and_then(Value::as_bool)
.unwrap_or(false),
retry_client_errors_by_default: object
.get("retry_client_errors_by_default")
.and_then(Value::as_bool)
.unwrap_or(true),
})
}
@@ -178,6 +201,7 @@ fn local_failover_policy_to_value(policy: &LocalFailoverPolicy) -> Value {
"success_failover_patterns": policy.success_failover_patterns.iter().map(local_failover_regex_rule_to_value).collect::<Vec<_>>(),
"error_stop_patterns": policy.error_stop_patterns.iter().map(local_failover_regex_rule_to_value).collect::<Vec<_>>(),
"stop_cyber_policy_errors": policy.stop_cyber_policy_errors,
"retry_client_errors_by_default": policy.retry_client_errors_by_default,
})
}
@@ -375,10 +399,26 @@ mod tests {
status_codes: [422].into_iter().collect(),
}],
stop_cyber_policy_errors: false,
retry_client_errors_by_default: true,
})
);
}
#[test]
fn search_transport_disables_default_client_error_failover() {
let mut transport = sample_transport(None, None, None);
transport.endpoint.api_format = "openai:search".to_string();
let policy = local_failover_policy_from_transport(&transport);
assert!(!policy.retry_client_errors_by_default);
let report_context = append_local_failover_policy_to_value(json!({}), &transport);
assert_eq!(
local_failover_policy_from_report_context(Some(&report_context))
.map(|policy| policy.retry_client_errors_by_default),
Some(false)
);
}
#[test]
fn codex_cyber_policy_passthrough_defaults_on_and_can_be_disabled() {
let mut transport = sample_transport(None, None, None);