Merge remote-tracking branch 'origin/pr/498'

This commit is contained in:
fawney19
2026-05-21 22:56:43 +08:00
89 changed files with 18396 additions and 382 deletions

View File

@@ -9,6 +9,7 @@ use axum::{
response::{IntoResponse, Response},
Json,
};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
use serde_json::{json, Value};
use std::collections::BTreeMap;
@@ -548,16 +549,69 @@ fn admin_monitoring_trace_response_data(
return None;
}
let body = admin_monitoring_trace_response_body(headers, body);
Some(json!({
"source": source,
"status_code": status_code,
"headers": headers.cloned().unwrap_or(Value::Null),
"body": body.cloned().unwrap_or(Value::Null),
"body": body.unwrap_or(Value::Null),
"body_ref": body_ref,
"body_state": body_state.map(|state| state.as_str()),
}))
}
fn admin_monitoring_trace_response_body(
headers: Option<&Value>,
body: Option<&Value>,
) -> Option<Value> {
let body = body?;
admin_monitoring_decode_connect_json_error_body(headers, body).or_else(|| Some(body.clone()))
}
fn admin_monitoring_decode_connect_json_error_body(
headers: Option<&Value>,
body: &Value,
) -> Option<Value> {
if !admin_monitoring_headers_indicate_connect_json(headers) {
return None;
}
let body_base64 = match body {
Value::String(value) => Some(value.as_str()),
Value::Object(object) => object
.get("encoding")
.and_then(Value::as_str)
.is_some_and(|value| value.eq_ignore_ascii_case("base64"))
.then(|| object.get("data").and_then(Value::as_str))
.flatten(),
_ => None,
}?
.trim();
if body_base64.is_empty() {
return None;
}
let body_bytes = BASE64_STANDARD.decode(body_base64).ok()?;
aether_ai_formats::api::extract_provider_private_stream_error_body(None, &body_bytes)
}
fn admin_monitoring_headers_indicate_connect_json(headers: Option<&Value>) -> bool {
headers
.and_then(Value::as_object)
.and_then(|object| {
object.iter().find_map(|(key, value)| {
key.eq_ignore_ascii_case("content-type")
.then(|| value.as_str())
.flatten()
})
})
.map(str::trim)
.is_some_and(|value| {
let value = value.to_ascii_lowercase();
value.contains("application/connect+json") || value.contains("+connect+json")
})
}
fn merge_admin_monitoring_trace_response(
extra_object: &mut serde_json::Map<String, Value>,
key: &str,

View File

@@ -922,6 +922,317 @@ pub fn parse_kiro_usage_response(
Some(serde_json::Value::Object(result))
}
pub fn parse_windsurf_user_status_response(
value: &serde_json::Value,
updated_at_unix_secs: u64,
) -> Option<serde_json::Value> {
let user_status = value
.get("userStatus")
.or_else(|| value.get("user_status"))?;
let plan_status = user_status
.get("planStatus")
.or_else(|| user_status.get("plan_status"))?;
let plan_info = plan_status
.get("planInfo")
.or_else(|| plan_status.get("plan_info"));
let mut result = serde_json::Map::new();
result.insert("updated_at".to_string(), json!(updated_at_unix_secs));
if let Some(plan_name) = plan_info
.and_then(|value| {
coerce_json_string(value.get("planName").or_else(|| value.get("plan_name")))
})
.or_else(|| {
coerce_json_string(
plan_status
.get("planName")
.or_else(|| plan_status.get("plan_name")),
)
})
{
result.insert("plan_name".to_string(), json!(plan_name));
}
if let Some(email) = coerce_json_string(user_status.get("email")) {
result.insert("email".to_string(), json!(email));
}
if let Some(value) = plan_status
.get("dailyQuotaRemainingPercent")
.or_else(|| plan_status.get("daily_quota_remaining_percent"))
.and_then(coerce_json_f64)
{
result.insert("daily_remaining_percent".to_string(), json!(value));
}
if let Some(value) = plan_status
.get("weeklyQuotaRemainingPercent")
.or_else(|| plan_status.get("weekly_quota_remaining_percent"))
.and_then(coerce_json_f64)
{
result.insert("weekly_remaining_percent".to_string(), json!(value));
}
if let Some(value) = plan_status
.get("dailyQuotaResetAtUnix")
.or_else(|| plan_status.get("daily_quota_reset_at_unix"))
.and_then(coerce_json_u64)
{
result.insert("daily_reset_at".to_string(), json!(value));
}
if let Some(value) = plan_status
.get("weeklyQuotaResetAtUnix")
.or_else(|| plan_status.get("weekly_quota_reset_at_unix"))
.and_then(coerce_json_u64)
{
result.insert("weekly_reset_at".to_string(), json!(value));
}
if let Some(value) = plan_status
.get("overageBalanceMicros")
.or_else(|| plan_status.get("overage_balance_micros"))
.and_then(coerce_json_f64)
{
result.insert("overage_balance".to_string(), json!(value / 1_000_000.0));
}
let legacy_credit =
|value: Option<&serde_json::Value>| value.and_then(coerce_json_f64).map(|n| n / 100.0);
if let Some(value) = legacy_credit(
plan_status
.get("availablePromptCredits")
.or_else(|| plan_status.get("available_prompt_credits")),
) {
result.insert("prompt_remaining".to_string(), json!(value));
}
if let Some(value) = legacy_credit(
plan_status
.get("usedPromptCredits")
.or_else(|| plan_status.get("used_prompt_credits")),
) {
result.insert("prompt_used".to_string(), json!(value));
}
if let Some(value) = legacy_credit(plan_info.and_then(|plan_info| {
plan_info
.get("monthlyPromptCredits")
.or_else(|| plan_info.get("monthly_prompt_credits"))
})) {
result.insert("prompt_limit".to_string(), json!(value));
}
if let Some(value) = legacy_credit(
plan_status
.get("availableFlexCredits")
.or_else(|| plan_status.get("available_flex_credits")),
) {
result.insert("flex_remaining".to_string(), json!(value));
}
if let Some(value) = legacy_credit(
plan_status
.get("usedFlexCredits")
.or_else(|| plan_status.get("used_flex_credits")),
) {
result.insert("flex_used".to_string(), json!(value));
}
if let Some(value) = legacy_credit(plan_info.and_then(|plan_info| {
plan_info
.get("monthlyFlexCreditPurchaseAmount")
.or_else(|| plan_info.get("monthly_flex_credit_purchase_amount"))
})) {
result.insert("flex_limit".to_string(), json!(value));
}
let mut status_sources = vec![value, user_status, plan_status];
if let Some(plan_info) = plan_info {
status_sources.push(plan_info);
}
for (target, aliases) in [
(
"banned",
&[
"banned",
"isBanned",
"is_banned",
"accountBanned",
"account_banned",
][..],
),
(
"quarantined",
&[
"quarantined",
"isQuarantined",
"is_quarantined",
"accountQuarantined",
"account_quarantined",
][..],
),
(
"is_forbidden",
&[
"isForbidden",
"is_forbidden",
"forbidden",
"accountForbidden",
"account_forbidden",
][..],
),
] {
if let Some(found) = status_sources.iter().find_map(|source| {
aliases
.iter()
.find_map(|alias| source.get(*alias).and_then(coerce_json_bool))
}) {
result.insert(target.to_string(), json!(found));
}
}
for (target, aliases) in [
(
"ban_reason",
&[
"banReason",
"ban_reason",
"blockedReason",
"blocked_reason",
"reason",
"message",
][..],
),
(
"quarantine_reason",
&["quarantineReason", "quarantine_reason", "reason", "message"][..],
),
(
"forbidden_reason",
&["forbiddenReason", "forbidden_reason", "reason", "message"][..],
),
] {
if let Some(found) = status_sources.iter().find_map(|source| {
aliases
.iter()
.find_map(|alias| coerce_json_string(source.get(*alias)))
}) {
result.insert(target.to_string(), json!(found));
}
}
Some(serde_json::Value::Object(result))
}
pub fn parse_windsurf_model_configs_response(
value: &serde_json::Value,
updated_at_unix_secs: u64,
) -> Option<serde_json::Value> {
let configs = value
.get("clientModelConfigs")
.or_else(|| value.get("client_model_configs"))
.and_then(serde_json::Value::as_array)?;
let mut models = Vec::new();
for config in configs {
let Some(model_uid) = coerce_json_string(
config
.get("modelUid")
.or_else(|| config.get("model_uid"))
.or_else(|| config.get("id"))
.or_else(|| config.get("name")),
) else {
continue;
};
let mut model = serde_json::Map::new();
model.insert("model_uid".to_string(), json!(model_uid));
if let Some(label) = coerce_json_string(
config
.get("label")
.or_else(|| config.get("displayName"))
.or_else(|| config.get("display_name")),
) {
model.insert("label".to_string(), json!(label));
}
if let Some(provider) = coerce_json_string(config.get("provider")) {
model.insert("provider".to_string(), json!(provider));
}
if let Some(value) = config
.get("supportsImages")
.or_else(|| config.get("supports_images"))
.and_then(coerce_json_bool)
{
model.insert("supports_images".to_string(), json!(value));
}
if let Some(value) = config
.get("creditMultiplier")
.or_else(|| config.get("credit_multiplier"))
.and_then(coerce_json_f64)
{
model.insert("credit_multiplier".to_string(), json!(value));
}
models.push(serde_json::Value::Object(model));
}
let mut result = serde_json::Map::new();
result.insert("updated_at".to_string(), json!(updated_at_unix_secs));
result.insert(
"allowed_models_count".to_string(),
json!(models.len() as u64),
);
result.insert("models".to_string(), serde_json::Value::Array(models));
if let Some(default_model_uid) = value
.get("defaultOverrideModelConfig")
.or_else(|| value.get("default_override_model_config"))
.and_then(|default_config| {
coerce_json_string(
default_config
.get("modelUid")
.or_else(|| default_config.get("model_uid")),
)
})
{
result.insert("default_model_uid".to_string(), json!(default_model_uid));
}
Some(serde_json::Value::Object(result))
}
pub fn parse_windsurf_rate_limit_response(
value: &serde_json::Value,
updated_at_unix_secs: u64,
) -> Option<serde_json::Value> {
let root = value.as_object()?;
if root.is_empty() {
return None;
}
let has_capacity = value
.get("hasCapacity")
.or_else(|| value.get("has_capacity"))
.and_then(coerce_json_bool)
.unwrap_or(true);
let messages_remaining = value
.get("messagesRemaining")
.or_else(|| value.get("messages_remaining"))
.and_then(coerce_json_f64);
let max_messages = value
.get("maxMessages")
.or_else(|| value.get("max_messages"))
.and_then(coerce_json_f64);
let retry_after_ms = value
.get("retryAfterMs")
.or_else(|| value.get("retry_after_ms"))
.and_then(coerce_json_u64);
let limited = !has_capacity || messages_remaining.is_some_and(|value| value <= 0.0);
let mut rate_limit = serde_json::Map::new();
rate_limit.insert("limited".to_string(), json!(limited));
rate_limit.insert("has_capacity".to_string(), json!(has_capacity));
if let Some(value) = messages_remaining {
rate_limit.insert("messages_remaining".to_string(), json!(value));
}
if let Some(value) = max_messages {
rate_limit.insert("max_messages".to_string(), json!(value));
}
if let Some(value) = retry_after_ms {
rate_limit.insert("retry_after_ms".to_string(), json!(value));
}
Some(json!({
"updated_at": updated_at_unix_secs,
"rate_limit": rate_limit,
}))
}
fn chatgpt_web_quota_feature_name(value: &serde_json::Value) -> Option<String> {
coerce_json_string(
value
@@ -1141,8 +1452,10 @@ mod tests {
use super::{
codex_build_invalid_state, codex_runtime_invalid_reason,
parse_chatgpt_web_conversation_init_response, parse_codex_backend_me_response,
parse_codex_wham_usage_response, OAUTH_ACCOUNT_BLOCK_PREFIX, OAUTH_EXPIRED_PREFIX,
OAUTH_REFRESH_FAILED_PREFIX, OAUTH_REQUEST_FAILED_PREFIX,
parse_codex_wham_usage_response, parse_windsurf_model_configs_response,
parse_windsurf_rate_limit_response, parse_windsurf_user_status_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;
@@ -1504,6 +1817,118 @@ mod tests {
assert!(parsed.get("secondary_used_percent").is_none());
}
#[test]
fn parses_windsurf_user_status_response() {
let parsed = parse_windsurf_user_status_response(
&json!({
"userStatus": {
"email": "windsurf@example.com",
"isQuarantined": true,
"quarantineReason": "quota review",
"planStatus": {
"dailyQuotaRemainingPercent": 45.5,
"weeklyQuotaRemainingPercent": 80,
"dailyQuotaResetAtUnix": "1775553285",
"weeklyQuotaResetAtUnix": 1776158085u64,
"availablePromptCredits": 900,
"usedPromptCredits": 100,
"availableFlexCredits": 250,
"usedFlexCredits": 50,
"overageBalanceMicros": 1250000,
"planInfo": {
"planName": "Pro",
"monthlyPromptCredits": 1000,
"monthlyFlexCreditPurchaseAmount": 300
}
}
}
}),
1_770_000_000,
)
.expect("windsurf user status should parse");
assert_eq!(parsed.get("plan_name"), Some(&json!("Pro")));
assert_eq!(parsed.get("daily_remaining_percent"), Some(&json!(45.5)));
assert_eq!(parsed.get("weekly_remaining_percent"), Some(&json!(80.0)));
assert_eq!(parsed.get("daily_reset_at"), Some(&json!(1_775_553_285u64)));
assert_eq!(
parsed.get("weekly_reset_at"),
Some(&json!(1_776_158_085u64))
);
assert_eq!(parsed.get("prompt_remaining"), Some(&json!(9.0)));
assert_eq!(parsed.get("prompt_used"), Some(&json!(1.0)));
assert_eq!(parsed.get("prompt_limit"), Some(&json!(10.0)));
assert_eq!(parsed.get("flex_remaining"), Some(&json!(2.5)));
assert_eq!(parsed.get("flex_used"), Some(&json!(0.5)));
assert_eq!(parsed.get("flex_limit"), Some(&json!(3.0)));
assert_eq!(parsed.get("overage_balance"), Some(&json!(1.25)));
assert_eq!(parsed.get("email"), Some(&json!("windsurf@example.com")));
assert_eq!(parsed.get("quarantined"), Some(&json!(true)));
assert_eq!(
parsed.get("quarantine_reason"),
Some(&json!("quota review"))
);
assert_eq!(parsed.get("updated_at"), Some(&json!(1_770_000_000u64)));
}
#[test]
fn parses_windsurf_model_configs_response() {
let parsed = parse_windsurf_model_configs_response(
&json!({
"clientModelConfigs": [
{
"modelUid": "claude-sonnet-4-5",
"label": "Claude Sonnet 4.5",
"provider": "anthropic",
"supportsImages": true,
"creditMultiplier": 2
},
{
"modelUid": "gpt-5-mini",
"label": "GPT-5 mini"
}
],
"defaultOverrideModelConfig": {
"modelUid": "claude-sonnet-4-5"
}
}),
1_770_000_100,
)
.expect("windsurf model configs should parse");
assert_eq!(parsed.get("allowed_models_count"), Some(&json!(2u64)));
assert_eq!(
parsed.get("default_model_uid"),
Some(&json!("claude-sonnet-4-5"))
);
assert_eq!(parsed.get("updated_at"), Some(&json!(1_770_000_100u64)));
}
#[test]
fn parses_windsurf_rate_limit_response() {
let parsed = parse_windsurf_rate_limit_response(
&json!({
"hasCapacity": false,
"messagesRemaining": 0,
"maxMessages": 25,
"retryAfterMs": 45000
}),
1_770_000_200,
)
.expect("windsurf rate limit should parse");
assert_eq!(parsed.get("updated_at"), Some(&json!(1_770_000_200u64)));
assert_eq!(parsed.pointer("/rate_limit/limited"), Some(&json!(true)));
assert_eq!(
parsed.pointer("/rate_limit/messages_remaining"),
Some(&json!(0.0))
);
assert_eq!(
parsed.pointer("/rate_limit/retry_after_ms"),
Some(&json!(45000u64))
);
}
#[test]
fn parses_chatgpt_web_image_quota_from_conversation_init() {
let parsed = parse_chatgpt_web_conversation_init_response(

View File

@@ -37,9 +37,27 @@ pub fn provider_oauth_pkce_s256(verifier: &str) -> String {
pub fn parse_provider_oauth_callback_params(callback_url: &str) -> BTreeMap<String, String> {
let mut merged = BTreeMap::new();
let Ok(url) = Url::parse(callback_url.trim()) else {
let raw_callback_url = callback_url.trim();
let parsed_url = Url::parse(raw_callback_url).or_else(|_| {
Url::parse(&format!(
"https://aether.local/{}",
raw_callback_url.trim_start_matches('/')
))
});
let Ok(url) = parsed_url else {
return merged;
};
if url.query().is_none()
&& url.fragment().is_none()
&& raw_callback_url.contains('=')
&& !raw_callback_url.contains("://")
{
for (key, value) in
form_urlencoded::parse(raw_callback_url.trim_start_matches('?').as_bytes())
{
merged.insert(key.into_owned(), value.into_owned());
}
}
for (key, value) in form_urlencoded::parse(url.query().unwrap_or_default().as_bytes()) {
merged.insert(key.into_owned(), value.into_owned());
}
@@ -377,6 +395,28 @@ mod tests {
assert_eq!(params.get("state").map(String::as_str), Some("nonce-value"));
}
#[test]
fn parse_provider_oauth_callback_params_reads_relative_show_auth_token_url() {
let params = parse_provider_oauth_callback_params(
"show-auth-token?token=firebase-id-token&state=session-1&provider=google",
);
assert_eq!(
params.get("token").map(String::as_str),
Some("firebase-id-token")
);
assert_eq!(params.get("state").map(String::as_str), Some("session-1"));
assert_eq!(params.get("provider").map(String::as_str), Some("google"));
}
#[test]
fn parse_provider_oauth_callback_params_reads_raw_query_string() {
let params = parse_provider_oauth_callback_params("token=raw-token&state=session-raw");
assert_eq!(params.get("token").map(String::as_str), Some("raw-token"));
assert_eq!(params.get("state").map(String::as_str), Some("session-raw"));
}
#[test]
fn chatgpt_web_enrichment_extracts_identity_from_openai_claims() {
let access_token = sample_unsigned_jwt(json!({

View File

@@ -40,6 +40,7 @@ const AUTO_REMOVABLE_ACCOUNT_STATE_CODES: &[&str] = &[
"account_banned",
"account_suspended",
"account_disabled",
"account_quarantined",
"workspace_deactivated",
"account_forbidden",
];
@@ -275,7 +276,7 @@ fn resolve_from_metadata(
upstream_metadata: Option<&Value>,
) -> Option<PoolAccountState> {
for source in metadata_sources(provider_type, upstream_metadata) {
if json_bool(source.get("is_banned")) {
if json_bool(source.get("is_banned")) || json_bool(source.get("banned")) {
let reason = extract_reason(
source,
&["ban_reason", "forbidden_reason", "reason", "message"],
@@ -290,6 +291,18 @@ fn resolve_from_metadata(
recoverable: false,
});
}
if json_bool(source.get("is_quarantined")) || json_bool(source.get("quarantined")) {
let reason = extract_reason(source, &["quarantine_reason", "reason", "message"])
.unwrap_or_else(|| "账号处于隔离状态".to_string());
return Some(PoolAccountState {
blocked: true,
code: Some("account_quarantined".to_string()),
label: Some("账号隔离".to_string()),
reason: Some(reason),
source: Some("metadata".to_string()),
recoverable: false,
});
}
if json_bool(source.get("is_forbidden")) || json_bool(source.get("account_disabled")) {
let reason = extract_reason(
source,
@@ -574,6 +587,35 @@ mod tests {
assert!(!should_auto_remove_account_state(&state));
}
#[test]
fn resolves_windsurf_banned_and_quarantined_metadata_aliases() {
let banned = resolve_pool_account_state(
Some("windsurf"),
Some(&json!({
"windsurf": {
"banned": true,
"reason": "forbidden"
}
})),
None,
);
assert!(banned.blocked);
assert_eq!(banned.code.as_deref(), Some("account_banned"));
let quarantined = resolve_pool_account_state(
Some("windsurf"),
Some(&json!({
"windsurf": {
"quarantined": true
}
})),
None,
);
assert!(quarantined.blocked);
assert_eq!(quarantined.code.as_deref(), Some("account_quarantined"));
assert!(should_auto_remove_account_state(&quarantined));
}
#[test]
fn account_snapshot_ignores_refresh_failed_as_account_block() {
let snapshot = resolve_account_status_snapshot(