Merge origin/main into fix/gemini-cli-v1internal

This commit is contained in:
Mas0nShi
2026-05-28 11:58:00 +08:00
410 changed files with 38026 additions and 6621 deletions
+21 -1
View File
@@ -440,7 +440,21 @@ pub fn admin_provider_ops_verify_headers(
)?;
}
}
"nekocode" | "done_hub" => {
"nekocode" => {
if let Some(session_cookie) = credentials
.get("session_cookie")
.and_then(Value::as_str)
.filter(|value| !value.trim().is_empty())
{
insert_header(
&mut headers,
"Cookie",
&admin_provider_ops_session_cookie_header(session_cookie),
)?;
}
}
"done_hub" => {
insert_header(&mut headers, "User-Agent", ADMIN_PROVIDER_OPS_USER_AGENT)?;
if let Some(session_cookie) = credentials
.get("session_cookie")
.and_then(Value::as_str)
@@ -1005,6 +1019,12 @@ mod tests {
headers.get(COOKIE).and_then(|value| value.to_str().ok()),
Some("session=abc")
);
assert_eq!(
headers
.get(USER_AGENT)
.and_then(|value| value.to_str().ok()),
Some(ADMIN_PROVIDER_OPS_USER_AGENT)
);
}
#[test]
+23 -2
View File
@@ -157,18 +157,39 @@ fn admin_pool_health_score(key: &StoredProviderCatalogKey) -> f64 {
}
fn admin_pool_circuit_breaker_open(key: &StoredProviderCatalogKey) -> bool {
let now_unix_secs = Utc::now().timestamp().max(0) as u64;
key.circuit_breaker_by_format
.as_ref()
.and_then(Value::as_object)
.map(|formats| {
formats
.values()
.filter_map(Value::as_object)
.any(|item| item.get("open").and_then(Value::as_bool).unwrap_or(false))
.any(|item| admin_pool_circuit_payload_active_open_at(item, now_unix_secs))
})
.unwrap_or(false)
}
fn admin_pool_circuit_payload_active_open_at(value: &Value, now_unix_secs: u64) -> bool {
let Some(item) = value.as_object() else {
return false;
};
if !item.get("open").and_then(Value::as_bool).unwrap_or(false) {
return false;
}
if let Some(next_probe_at) = item.get("next_probe_at_unix_secs").and_then(Value::as_u64) {
return now_unix_secs < next_probe_at;
}
if let Some(next_probe_at) = item
.get("next_probe_at")
.and_then(Value::as_str)
.and_then(|value| chrono::DateTime::parse_from_rfc3339(value).ok())
.and_then(|value| u64::try_from(value.timestamp()).ok())
{
return now_unix_secs < next_probe_at;
}
true
}
fn unix_secs_to_rfc3339(unix_secs: u64) -> Option<String> {
Utc.timestamp_opt(unix_secs as i64, 0)
.single()
+35 -1
View File
@@ -199,7 +199,7 @@ pub fn quota_refresh_success_invalid_state(
.as_deref()
.map(str::trim)
.unwrap_or_default();
if current_reason.starts_with(OAUTH_REFRESH_FAILED_PREFIX) {
if current_reason.starts_with(OAUTH_ACCOUNT_BLOCK_PREFIX) {
return (
key.oauth_invalid_at_unix_secs,
(!current_reason.is_empty()).then_some(current_reason.to_string()),
@@ -1724,6 +1724,7 @@ mod tests {
parse_codex_wham_usage_response, parse_gemini_cli_retrieve_user_quota_response,
parse_gemini_cli_v1internal_credits_response, parse_windsurf_model_configs_response,
parse_windsurf_rate_limit_response, parse_windsurf_user_status_response,
quota_refresh_success_invalid_state, should_auto_remove_structured_reason,
OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX, OAUTH_REFRESH_FAILED_PREFIX,
OAUTH_REQUEST_FAILED_PREFIX,
};
@@ -1846,6 +1847,13 @@ mod tests {
);
}
#[test]
fn auto_remove_structured_reason_keeps_oauth_expired_token_invalid() {
assert!(!should_auto_remove_structured_reason(Some(
"[OAUTH_EXPIRED] token invalidated"
)));
}
#[test]
fn auto_remove_refresh_failed_after_access_token_expiry() {
let mut key = StoredProviderCatalogKey::new(
@@ -1996,6 +2004,32 @@ mod tests {
));
}
#[test]
fn quota_refresh_success_clears_refresh_failed_marker() {
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_reason = Some("[REFRESH_FAILED] Token 续期失败".to_string());
assert_eq!(quota_refresh_success_invalid_state(&key), (None, None));
}
#[test]
fn auto_remove_structured_reason_keeps_request_and_refresh_failures() {
assert!(!should_auto_remove_structured_reason(Some(
"[REQUEST_FAILED] 账号状态检查失败"
)));
assert!(!should_auto_remove_structured_reason(Some(
"[REFRESH_FAILED] Token 续期失败 (401): refresh_token 已失效"
)));
}
#[test]
fn parses_codex_spark_quota_from_additional_rate_limits() {
let parsed = parse_codex_wham_usage_response(
@@ -644,6 +644,19 @@ mod tests {
assert!(!snapshot.recoverable);
}
#[test]
fn oauth_expired_state_is_not_auto_removed() {
let state = resolve_pool_account_state(
Some("codex"),
None,
Some("[OAUTH_EXPIRED] token invalidated"),
);
assert!(state.blocked);
assert_eq!(state.code.as_deref(), Some("oauth_token_invalid"));
assert!(!should_auto_remove_account_state(&state));
}
#[test]
fn account_snapshot_detects_account_block_and_verification() {
let snapshot = resolve_account_status_snapshot(