fix: codex free accounts incorrectly marked as quota exhausted (#366)

* fix: codex free accounts incorrectly marked as quota exhausted when skip_exhausted_accounts enabled

- Cross-validate stored exhausted flag against window used_ratio in snapshot reader
- Guard has_credits:false check with window data presence in upstream_metadata fallback
- Add windows.is_empty() precondition to exhausted_by_credits in snapshot builder

Free codex accounts have has_credits:false (they use window-based quotas, not credits),
but the old logic treated this as credits depleted, skipping actual window usage checks.
This caused all free accounts to be incorrectly marked exhausted.

* test: cover codex free quota windows

---------

Co-authored-by: root <root@ser488556369540.local>
Co-authored-by: fawney19 <elky0401@gmail.com>
This commit is contained in:
jiuwovo-ai
2026-05-02 14:11:29 +08:00
committed by GitHub
parent c130d0e2c9
commit bfd1ea72b3
3 changed files with 113 additions and 10 deletions

View File

@@ -183,8 +183,28 @@ pub fn admin_pool_key_account_quota_exhausted(
key: &StoredProviderCatalogKey,
provider_type: &str,
) -> bool {
if let Some(exhausted) = admin_pool_key_quota_snapshot(key, provider_type)
.and_then(|quota_snapshot| admin_pool_json_bool(quota_snapshot.get("exhausted")))
if let Some(exhausted) =
admin_pool_key_quota_snapshot(key, provider_type).and_then(|quota_snapshot| {
let exhausted = admin_pool_json_bool(quota_snapshot.get("exhausted"))?;
if exhausted {
let windows_max_ratio = quota_snapshot
.get("windows")
.and_then(Value::as_array)
.filter(|w| !w.is_empty())
.and_then(|windows| {
windows
.iter()
.filter_map(Value::as_object)
.filter_map(|w| w.get("used_ratio"))
.filter_map(Value::as_f64)
.max_by(f64::total_cmp)
});
if windows_max_ratio.is_some_and(|ratio| ratio < 1.0 - 1e-6) {
return Some(false);
}
}
Some(exhausted)
})
{
return exhausted;
}
@@ -200,7 +220,9 @@ pub fn admin_pool_key_account_quota_exhausted(
if admin_pool_json_bool(bucket.get("credits_unlimited")) == Some(true) {
return false;
}
if admin_pool_json_bool(bucket.get("has_credits")) == Some(false) {
let has_window_data = admin_pool_json_f64(bucket.get("primary_used_percent")).is_some()
|| admin_pool_json_f64(bucket.get("secondary_used_percent")).is_some();
if !has_window_data && admin_pool_json_bool(bucket.get("has_credits")) == Some(false) {
return true;
}
admin_pool_json_f64(bucket.get("primary_used_percent"))
@@ -660,6 +682,17 @@ mod tests {
}))),
"codex",
));
assert!(!admin_pool_key_account_quota_exhausted(
&sample_key(Some(json!({
"codex": {
"has_credits": false,
"credits_unlimited": false,
"primary_used_percent": 64.0,
"secondary_used_percent": 3.0
}
}))),
"codex",
));
assert!(!admin_pool_key_account_quota_exhausted(
&sample_key(Some(json!({
"codex": {
@@ -708,6 +741,40 @@ mod tests {
assert!(!admin_pool_key_account_quota_exhausted(&key, "codex"));
}
#[test]
fn clears_stale_codex_exhausted_snapshot_when_windows_have_capacity() {
let mut key = sample_key(Some(json!({
"codex": {
"has_credits": false,
"primary_used_percent": 100.0
}
})));
key.status_snapshot = Some(json!({
"quota": {
"version": 2,
"provider_type": "codex",
"code": "exhausted",
"exhausted": true,
"usage_ratio": 0.0,
"updated_at": 1_776_395_200u64,
"windows": [
{
"code": "weekly",
"used_ratio": 0.0,
"remaining_ratio": 1.0
},
{
"code": "5h",
"used_ratio": 0.0,
"remaining_ratio": 1.0
}
]
}
}));
assert!(!admin_pool_key_account_quota_exhausted(&key, "codex"));
}
#[test]
fn detects_kiro_exhaustion_from_metadata() {
assert!(admin_pool_key_account_quota_exhausted(