Merge pull request #690 from zhefox/main

fix(provider): classify deleted Codex agent runtime as invalid
This commit is contained in:
ZheFox
2026-07-23 11:22:30 +08:00
committed by GitHub
5 changed files with 138 additions and 0 deletions
+11
View File
@@ -1311,6 +1311,7 @@ pub fn codex_looks_like_token_invalidated(message: Option<&str>) -> bool {
|| lowered.contains("authentication token has been invalidated")
|| lowered.contains("token has been invalidated")
|| lowered.contains("token invalidated")
|| lowered.contains("agent runtime has been deleted")
|| lowered.contains("personal access token owner is inactive")
|| lowered.contains("biscuit_baker_service_auth_credential_error_status")
|| lowered.contains("auth_credential")
@@ -2223,6 +2224,16 @@ mod tests {
);
}
#[test]
fn codex_runtime_invalid_reason_marks_deleted_agent_runtime_as_invalid() {
assert_eq!(
codex_runtime_invalid_reason(403, Some("Agent runtime has been deleted.")),
Some(format!(
"{OAUTH_EXPIRED_PREFIX}Agent runtime has been deleted."
))
);
}
#[test]
fn codex_runtime_invalid_reason_marks_402_as_account_blocked() {
assert_eq!(
@@ -451,6 +451,16 @@ fn resolve_from_oauth_invalid_reason(reason: Option<&str>) -> Option<PoolAccount
} else {
cleaned
};
if oauth_request_failure_indicates_invalid_identity(&reason) {
return Some(PoolAccountState {
blocked: true,
code: Some("oauth_token_invalid".to_string()),
label: Some("Token 失效".to_string()),
reason: Some(reason),
source: Some("oauth_invalid".to_string()),
recoverable: false,
});
}
return Some(PoolAccountState {
blocked: false,
code: Some("oauth_request_failed".to_string()),
@@ -485,6 +495,13 @@ fn resolve_from_oauth_invalid_reason(reason: Option<&str>) -> Option<PoolAccount
None
}
fn oauth_request_failure_indicates_invalid_identity(reason: &str) -> bool {
reason
.trim()
.to_ascii_lowercase()
.contains("agent runtime has been deleted")
}
pub fn resolve_pool_account_state(
provider_type: Option<&str>,
upstream_metadata: Option<&Value>,
@@ -551,6 +568,24 @@ pub fn resolve_account_status_snapshot(
};
}
if let Some(cleaned) = tagged_reason(&text, "REQUEST_FAILED") {
let reason = if cleaned.is_empty() {
"账号状态检查失败".to_string()
} else {
cleaned
};
if oauth_request_failure_indicates_invalid_identity(&reason) {
return AccountStatusSnapshot {
code: "oauth_token_invalid".to_string(),
label: Some("Token 失效".to_string()),
reason: Some(reason),
blocked: true,
source: Some("oauth_invalid".to_string()),
recoverable: false,
};
}
}
if tagged_reason(&text, "REFRESH_FAILED").is_some() {
return AccountStatusSnapshot::default();
}
@@ -739,6 +774,34 @@ mod tests {
assert!(!snapshot.recoverable);
}
#[test]
fn deleted_agent_runtime_request_failure_is_hard_invalid() {
let reason = "[REQUEST_FAILED] Agent runtime has been deleted.";
let state = resolve_pool_account_state(Some("codex"), None, Some(reason));
let snapshot = resolve_account_status_snapshot(Some("codex"), None, Some(reason));
assert!(state.blocked);
assert_eq!(state.code.as_deref(), Some("oauth_token_invalid"));
assert!(!state.recoverable);
assert_eq!(snapshot.code, "oauth_token_invalid");
assert_eq!(snapshot.label.as_deref(), Some("Token 失效"));
assert!(snapshot.blocked);
assert!(!snapshot.recoverable);
}
#[test]
fn transient_request_failure_remains_recoverable() {
let state = resolve_pool_account_state(
Some("codex"),
None,
Some("[REQUEST_FAILED] upstream request timed out"),
);
assert!(!state.blocked);
assert_eq!(state.code.as_deref(), Some("oauth_request_failed"));
assert!(state.recoverable);
}
#[test]
fn account_snapshot_marks_oauth_expired_as_token_expired() {
let snapshot = resolve_account_status_snapshot(