mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix provider pool quota status handling
This commit is contained in:
@@ -395,17 +395,38 @@ pub fn admin_pool_is_oauth_invalid(key: &StoredProviderCatalogKey, now_unix_secs
|
||||
if key.auth_type.trim() != "oauth" {
|
||||
return false;
|
||||
}
|
||||
if key
|
||||
.oauth_invalid_reason
|
||||
.as_deref()
|
||||
.is_some_and(|value| !value.trim().is_empty())
|
||||
{
|
||||
return true;
|
||||
if let Some(reason) = key.oauth_invalid_reason.as_deref().map(str::trim) {
|
||||
let account_state = provider_status::resolve_pool_account_state(
|
||||
None,
|
||||
key.upstream_metadata.as_ref(),
|
||||
Some(reason),
|
||||
);
|
||||
if account_state.blocked && !account_state.recoverable {
|
||||
return true;
|
||||
}
|
||||
if admin_pool_reason_has_tag(reason, "[REFRESH_FAILED]") {
|
||||
return key
|
||||
.expires_at_unix_secs
|
||||
.is_none_or(|value| value == 0 || value <= now_unix_secs);
|
||||
}
|
||||
if admin_pool_reason_has_tag(reason, "[REQUEST_FAILED]") {
|
||||
return false;
|
||||
}
|
||||
if !reason.is_empty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
key.expires_at_unix_secs
|
||||
.is_some_and(|value| value > 0 && value <= now_unix_secs)
|
||||
}
|
||||
|
||||
fn admin_pool_reason_has_tag(reason: &str, tag: &str) -> bool {
|
||||
reason
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.any(|line| line.starts_with(tag))
|
||||
}
|
||||
|
||||
pub fn admin_pool_matches_quick_selector(
|
||||
key: &StoredProviderCatalogKey,
|
||||
selector: &str,
|
||||
|
||||
@@ -396,7 +396,8 @@ fn codex_merge_invalid_reason(current: &str, candidate_reason: &str) -> String {
|
||||
return current.to_string();
|
||||
}
|
||||
if current.starts_with(OAUTH_EXPIRED_PREFIX)
|
||||
&& candidate_reason.starts_with(OAUTH_REQUEST_FAILED_PREFIX)
|
||||
&& (candidate_reason.starts_with(OAUTH_REQUEST_FAILED_PREFIX)
|
||||
|| candidate_reason.starts_with(OAUTH_REFRESH_FAILED_PREFIX))
|
||||
{
|
||||
return current.to_string();
|
||||
}
|
||||
@@ -872,9 +873,11 @@ pub fn parse_chatgpt_web_conversation_init_response(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
codex_runtime_invalid_reason, parse_chatgpt_web_conversation_init_response,
|
||||
OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
|
||||
codex_build_invalid_state, codex_runtime_invalid_reason,
|
||||
parse_chatgpt_web_conversation_init_response, OAUTH_ACCOUNT_BLOCK_PREFIX,
|
||||
OAUTH_EXPIRED_PREFIX, OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
|
||||
};
|
||||
use aether_data_contracts::repository::provider_catalog::StoredProviderCatalogKey;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
@@ -900,6 +903,73 @@ mod tests {
|
||||
assert_eq!(codex_runtime_invalid_reason(403, Some("forbidden")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_invalid_state_keeps_oauth_expired_over_refresh_failure() {
|
||||
let mut key = StoredProviderCatalogKey::new(
|
||||
"key-1".to_string(),
|
||||
"provider-1".to_string(),
|
||||
"key-1".to_string(),
|
||||
"oauth".to_string(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.expect("key should build");
|
||||
key.oauth_invalid_at_unix_secs = Some(100);
|
||||
key.oauth_invalid_reason = Some(format!("{OAUTH_EXPIRED_PREFIX}session expired"));
|
||||
|
||||
assert_eq!(
|
||||
codex_build_invalid_state(
|
||||
&key,
|
||||
format!("{OAUTH_REFRESH_FAILED_PREFIX}Token 续期失败"),
|
||||
200,
|
||||
),
|
||||
(
|
||||
Some(100),
|
||||
Some(format!("{OAUTH_EXPIRED_PREFIX}session expired"))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
codex_build_invalid_state(
|
||||
&key,
|
||||
format!("{OAUTH_REQUEST_FAILED_PREFIX}账号状态检查失败"),
|
||||
200,
|
||||
),
|
||||
(
|
||||
Some(100),
|
||||
Some(format!("{OAUTH_EXPIRED_PREFIX}session expired"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_invalid_state_allows_account_block_to_override_oauth_expired() {
|
||||
let mut key = StoredProviderCatalogKey::new(
|
||||
"key-1".to_string(),
|
||||
"provider-1".to_string(),
|
||||
"key-1".to_string(),
|
||||
"oauth".to_string(),
|
||||
None,
|
||||
true,
|
||||
)
|
||||
.expect("key should build");
|
||||
key.oauth_invalid_at_unix_secs = Some(100);
|
||||
key.oauth_invalid_reason = Some(format!("{OAUTH_EXPIRED_PREFIX}session expired"));
|
||||
|
||||
assert_eq!(
|
||||
codex_build_invalid_state(
|
||||
&key,
|
||||
format!("{OAUTH_ACCOUNT_BLOCK_PREFIX}account has been deactivated"),
|
||||
200,
|
||||
),
|
||||
(
|
||||
Some(200),
|
||||
Some(format!(
|
||||
"{OAUTH_ACCOUNT_BLOCK_PREFIX}account has been deactivated"
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_chatgpt_web_image_quota_from_conversation_init() {
|
||||
let parsed = parse_chatgpt_web_conversation_init_response(
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
const OAUTH_ACCOUNT_BLOCK_PREFIX: &str = "[ACCOUNT_BLOCK] ";
|
||||
const OAUTH_REFRESH_FAILED_PREFIX: &str = "[REFRESH_FAILED] ";
|
||||
const OAUTH_EXPIRED_PREFIX: &str = "[OAUTH_EXPIRED] ";
|
||||
const OAUTH_REQUEST_FAILED_PREFIX: &str = "[REQUEST_FAILED] ";
|
||||
|
||||
const ACCOUNT_BLOCK_REASON_KEYWORDS: &[&str] = &[
|
||||
"suspended",
|
||||
"banned",
|
||||
@@ -326,55 +321,55 @@ fn resolve_from_metadata(
|
||||
fn resolve_from_oauth_invalid_reason(reason: Option<&str>) -> Option<PoolAccountState> {
|
||||
let text = clean_text(reason)?;
|
||||
|
||||
if text.starts_with(OAUTH_ACCOUNT_BLOCK_PREFIX) {
|
||||
let cleaned = clean_text(Some(text.trim_start_matches(OAUTH_ACCOUNT_BLOCK_PREFIX)))
|
||||
.unwrap_or_else(|| "账号异常".to_string());
|
||||
let (code, label) = classify_block_reason(&cleaned);
|
||||
if let Some(cleaned) = tagged_reason(&text, "ACCOUNT_BLOCK") {
|
||||
let reason = if cleaned.is_empty() {
|
||||
"账号异常".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
};
|
||||
let (code, label) = classify_block_reason(&reason);
|
||||
return Some(PoolAccountState {
|
||||
blocked: true,
|
||||
code: Some(code.to_string()),
|
||||
label: Some(label.to_string()),
|
||||
reason: Some(cleaned),
|
||||
reason: Some(reason),
|
||||
source: Some("oauth_invalid".to_string()),
|
||||
recoverable: false,
|
||||
});
|
||||
}
|
||||
if text.starts_with(OAUTH_EXPIRED_PREFIX) {
|
||||
let cleaned = clean_text(Some(text.trim_start_matches(OAUTH_EXPIRED_PREFIX)))
|
||||
.unwrap_or_else(|| "OAuth Token 已过期且无法续期".to_string());
|
||||
if let Some(cleaned) = tagged_reason(&text, "OAUTH_EXPIRED") {
|
||||
let reason = if cleaned.is_empty() {
|
||||
"OAuth Token 已过期且无法续期".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
};
|
||||
return Some(PoolAccountState {
|
||||
blocked: true,
|
||||
code: Some("oauth_token_invalid".to_string()),
|
||||
label: Some("Token 失效".to_string()),
|
||||
reason: Some(cleaned),
|
||||
reason: Some(reason),
|
||||
source: Some("oauth_invalid".to_string()),
|
||||
recoverable: false,
|
||||
});
|
||||
}
|
||||
if text.starts_with(OAUTH_REFRESH_FAILED_PREFIX) {
|
||||
let cleaned = clean_text(Some(text.trim_start_matches(OAUTH_REFRESH_FAILED_PREFIX)))
|
||||
.unwrap_or_else(|| "OAuth Token 续期失败".to_string());
|
||||
return Some(PoolAccountState {
|
||||
blocked: true,
|
||||
code: Some("oauth_token_invalid".to_string()),
|
||||
label: Some("Token 失效".to_string()),
|
||||
reason: Some(cleaned),
|
||||
source: Some("oauth_refresh".to_string()),
|
||||
recoverable: false,
|
||||
});
|
||||
}
|
||||
if text.starts_with(OAUTH_REQUEST_FAILED_PREFIX) {
|
||||
let cleaned = clean_text(Some(text.trim_start_matches(OAUTH_REQUEST_FAILED_PREFIX)))
|
||||
.unwrap_or_else(|| "账号状态检查失败".to_string());
|
||||
if let Some(cleaned) = tagged_reason(&text, "REQUEST_FAILED") {
|
||||
let reason = if cleaned.is_empty() {
|
||||
"账号状态检查失败".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
};
|
||||
return Some(PoolAccountState {
|
||||
blocked: false,
|
||||
code: Some("oauth_request_failed".to_string()),
|
||||
label: Some("请求失败".to_string()),
|
||||
reason: Some(cleaned),
|
||||
reason: Some(reason),
|
||||
source: Some("oauth_request".to_string()),
|
||||
recoverable: true,
|
||||
});
|
||||
}
|
||||
if tagged_reason(&text, "REFRESH_FAILED").is_some() {
|
||||
return None;
|
||||
}
|
||||
if text.starts_with('[') {
|
||||
return None;
|
||||
}
|
||||
@@ -462,20 +457,8 @@ pub fn resolve_account_status_snapshot(
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(cleaned) = tagged_reason(&text, "REFRESH_FAILED") {
|
||||
let reason = if cleaned.is_empty() {
|
||||
"OAuth Token 续期失败".to_string()
|
||||
} else {
|
||||
cleaned
|
||||
};
|
||||
return AccountStatusSnapshot {
|
||||
code: "oauth_token_invalid".to_string(),
|
||||
label: Some("Token 失效".to_string()),
|
||||
reason: Some(reason),
|
||||
blocked: true,
|
||||
source: Some("oauth_refresh".to_string()),
|
||||
recoverable: false,
|
||||
};
|
||||
if tagged_reason(&text, "REFRESH_FAILED").is_some() {
|
||||
return AccountStatusSnapshot::default();
|
||||
}
|
||||
|
||||
if text.starts_with('[') {
|
||||
@@ -578,31 +561,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_refresh_failed_as_token_invalid_pool_state() {
|
||||
fn ignores_refresh_failed_as_pool_account_block() {
|
||||
let state = resolve_pool_account_state(
|
||||
Some("codex"),
|
||||
None,
|
||||
Some("[REFRESH_FAILED] Token 续期失败 (401): refresh_token 已失效"),
|
||||
);
|
||||
|
||||
assert!(state.blocked);
|
||||
assert!(!state.recoverable);
|
||||
assert_eq!(state.code.as_deref(), Some("oauth_token_invalid"));
|
||||
assert_eq!(state.label.as_deref(), Some("Token 失效"));
|
||||
assert!(!state.blocked);
|
||||
assert_eq!(state.code.as_deref(), None);
|
||||
assert_eq!(state.label.as_deref(), None);
|
||||
assert!(!should_auto_remove_account_state(&state));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_snapshot_marks_refresh_failed_as_token_invalid() {
|
||||
fn account_snapshot_ignores_refresh_failed_as_account_block() {
|
||||
let snapshot = resolve_account_status_snapshot(
|
||||
Some("codex"),
|
||||
None,
|
||||
Some("[REFRESH_FAILED] Token 续期失败 (401): refresh_token 已失效"),
|
||||
);
|
||||
|
||||
assert_eq!(snapshot.code, "oauth_token_invalid");
|
||||
assert_eq!(snapshot.label.as_deref(), Some("Token 失效"));
|
||||
assert!(snapshot.blocked);
|
||||
assert_eq!(snapshot.code, "ok");
|
||||
assert_eq!(snapshot.label.as_deref(), None);
|
||||
assert!(!snapshot.blocked);
|
||||
assert!(!snapshot.recoverable);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user