mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 18:59:50 +08:00
Merge remote-tracking branch 'origin/main' into fix/gemini-cli-v1internal
# Conflicts: # apps/aether-gateway/src/ai_serving/transport.rs # apps/aether-gateway/src/handlers/admin/provider/oauth/dispatch/batch/parse.rs # apps/aether-gateway/src/handlers/shared/catalog.rs # crates/aether-admin/src/provider/quota.rs # crates/aether-model-fetch/src/strategy.rs # crates/aether-provider-pool/src/lib.rs # crates/aether-provider-pool/src/service.rs # crates/aether-provider-transport/src/provider_types.rs # frontend/src/features/providers/components/ProviderDetailDrawer.vue # frontend/src/utils/__tests__/providerKeyQuota.spec.ts # frontend/src/utils/providerKeyQuota.ts # frontend/src/views/admin/PoolManagement.vue
This commit is contained in:
@@ -17,8 +17,11 @@ pub use provider::{ProviderPoolAdapter, ProviderPoolMemberInput};
|
||||
pub use providers::{
|
||||
build_antigravity_pool_quota_request, build_chatgpt_web_pool_quota_request,
|
||||
build_codex_pool_quota_request, build_gemini_cli_pool_quota_request,
|
||||
build_kiro_pool_quota_request, enrich_chatgpt_web_quota_metadata, grok_mode_id_for_model,
|
||||
grok_pool_tier_from_quota_bucket, grok_quota_window_key_for_model,
|
||||
build_kiro_pool_quota_request, build_windsurf_pool_model_configs_request,
|
||||
build_windsurf_pool_model_configs_request_with_base_url, build_windsurf_pool_quota_request,
|
||||
build_windsurf_pool_quota_request_with_base_url, build_windsurf_pool_rate_limit_request,
|
||||
build_windsurf_pool_rate_limit_request_with_base_url, enrich_chatgpt_web_quota_metadata,
|
||||
grok_mode_id_for_model, grok_pool_tier_from_quota_bucket, grok_quota_window_key_for_model,
|
||||
grok_supported_quota_windows_for_tier, normalize_chatgpt_web_image_quota_limit,
|
||||
AntigravityProviderPoolAdapter, ChatGptWebProviderPoolAdapter, CodexProviderPoolAdapter,
|
||||
DefaultProviderPoolAdapter, GeminiCliProviderPoolAdapter, GrokProviderPoolAdapter,
|
||||
@@ -26,6 +29,7 @@ pub use providers::{
|
||||
ANTIGRAVITY_FETCH_AVAILABLE_MODELS_PATH, CHATGPT_WEB_CONVERSATION_INIT_PATH,
|
||||
CHATGPT_WEB_DEFAULT_BASE_URL, CODEX_WHAM_USAGE_URL, GEMINI_CLI_RETRIEVE_USER_QUOTA_PATH,
|
||||
GEMINI_CLI_USER_AGENT, KIRO_USAGE_LIMITS_PATH, KIRO_USAGE_SDK_VERSION,
|
||||
WINDSURF_MODEL_CONFIGS_PATH, WINDSURF_RATE_LIMIT_PATH, WINDSURF_USER_STATUS_PATH,
|
||||
};
|
||||
pub use quota::{
|
||||
provider_pool_key_account_quota_exhausted, provider_pool_key_scheduling_label,
|
||||
@@ -70,7 +74,8 @@ mod tests {
|
||||
"gemini_cli",
|
||||
"grok",
|
||||
"kiro",
|
||||
"vertex_ai"
|
||||
"vertex_ai",
|
||||
"windsurf"
|
||||
]
|
||||
);
|
||||
assert!(service
|
||||
@@ -92,13 +97,15 @@ mod tests {
|
||||
"codex",
|
||||
"gemini_cli",
|
||||
"grok",
|
||||
"kiro"
|
||||
"kiro",
|
||||
"windsurf"
|
||||
]
|
||||
);
|
||||
assert!(service.supports_quota_refresh("codex"));
|
||||
assert!(service.supports_quota_refresh("antigravity"));
|
||||
assert!(service.supports_quota_refresh("grok"));
|
||||
assert!(service.supports_quota_refresh("gemini_cli"));
|
||||
assert!(service.supports_quota_refresh("windsurf"));
|
||||
assert_eq!(
|
||||
service.quota_refresh_unsupported_message("claude_code"),
|
||||
"Claude Code 暂不支持自动刷新额度:上游没有稳定可用的账号额度查询接口"
|
||||
@@ -150,6 +157,14 @@ mod tests {
|
||||
spec.json_body.as_ref().and_then(|body| body.get("project")),
|
||||
Some(&json!("project-1"))
|
||||
);
|
||||
assert_eq!(
|
||||
spec.headers.get("user-agent").map(String::as_str),
|
||||
Some(GEMINI_CLI_USER_AGENT)
|
||||
);
|
||||
assert!(spec
|
||||
.json_body
|
||||
.as_ref()
|
||||
.is_some_and(|body| body.get("userAgent").is_none()));
|
||||
assert_eq!(spec.client_api_format, "gemini:generate_content");
|
||||
assert_eq!(spec.provider_api_format, "gemini_cli:retrieve_user_quota");
|
||||
}
|
||||
@@ -272,6 +287,110 @@ mod tests {
|
||||
assert_eq!(metadata["image_quota_used"], json!(33.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windsurf_quota_request_uses_user_status_connect_rpc() {
|
||||
let spec = build_windsurf_pool_quota_request("key-ws", "session-token-123");
|
||||
|
||||
assert_eq!(spec.request_id, "windsurf-quota:key-ws");
|
||||
assert_eq!(spec.method, "POST");
|
||||
assert_eq!(
|
||||
spec.url,
|
||||
format!("https://server.codeium.com{WINDSURF_USER_STATUS_PATH}")
|
||||
);
|
||||
assert_eq!(spec.content_type.as_deref(), Some("application/json"));
|
||||
assert_eq!(
|
||||
spec.headers
|
||||
.get("connect-protocol-version")
|
||||
.map(String::as_str),
|
||||
Some("1")
|
||||
);
|
||||
assert_eq!(
|
||||
spec.json_body
|
||||
.as_ref()
|
||||
.and_then(|body| body.pointer("/metadata/apiKey"))
|
||||
.and_then(Value::as_str),
|
||||
Some("session-token-123")
|
||||
);
|
||||
assert_eq!(spec.provider_api_format, "windsurf:user_status");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windsurf_model_and_rate_limit_requests_use_connect_rpc_metadata() {
|
||||
let models = build_windsurf_pool_model_configs_request("key-ws", "api-key-123");
|
||||
let rate_limit = build_windsurf_pool_rate_limit_request("key-ws", "api-key-123");
|
||||
|
||||
assert_eq!(
|
||||
models.url,
|
||||
format!("https://server.codeium.com{WINDSURF_MODEL_CONFIGS_PATH}")
|
||||
);
|
||||
assert_eq!(
|
||||
rate_limit.url,
|
||||
format!("https://server.codeium.com{WINDSURF_RATE_LIMIT_PATH}")
|
||||
);
|
||||
for spec in [models, rate_limit] {
|
||||
assert_eq!(spec.method, "POST");
|
||||
assert_eq!(
|
||||
spec.headers
|
||||
.get("connect-protocol-version")
|
||||
.map(String::as_str),
|
||||
Some("1")
|
||||
);
|
||||
assert_eq!(
|
||||
spec.json_body
|
||||
.as_ref()
|
||||
.and_then(|body| body.pointer("/metadata/apiKey"))
|
||||
.and_then(Value::as_str),
|
||||
Some("api-key-123")
|
||||
);
|
||||
assert_eq!(spec.client_api_format, "openai:chat");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windsurf_rate_limit_metadata_keeps_member_schedulable() {
|
||||
let service = ProviderPoolService::with_builtin_adapters();
|
||||
let key = sample_key(Some(json!({
|
||||
"windsurf": {
|
||||
"updated_at": 1_700_000_000u64,
|
||||
"rate_limit": {
|
||||
"limited": true,
|
||||
"retry_after_ms": 60_000
|
||||
}
|
||||
}
|
||||
})));
|
||||
|
||||
let signals = service.member_signals("windsurf", &key, None);
|
||||
|
||||
assert!(!signals.quota_exhausted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windsurf_status_snapshot_ban_marks_member_exhausted() {
|
||||
let service = ProviderPoolService::with_builtin_adapters();
|
||||
let mut key = sample_key(Some(json!({
|
||||
"windsurf": {
|
||||
"updated_at": 1_700_000_000u64,
|
||||
"daily_remaining_percent": 100.0
|
||||
}
|
||||
})));
|
||||
key.status_snapshot = Some(json!({
|
||||
"quota": {
|
||||
"provider_type": "windsurf",
|
||||
"code": "banned",
|
||||
"exhausted": false,
|
||||
"windows": [{
|
||||
"code": "daily",
|
||||
"used_ratio": 0.0,
|
||||
"remaining_ratio": 1.0
|
||||
}]
|
||||
}
|
||||
}));
|
||||
|
||||
let signals = service.member_signals("windsurf", &key, None);
|
||||
|
||||
assert!(signals.quota_exhausted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preset_payload_derives_provider_support_from_capabilities() {
|
||||
let payload = build_admin_pool_scheduling_presets_payload();
|
||||
@@ -285,10 +404,13 @@ mod tests {
|
||||
.find(|item| item["name"] == "recent_refresh")
|
||||
.expect("recent_refresh should exist");
|
||||
|
||||
assert_eq!(free_first["providers"], json!(["codex", "grok", "kiro"]));
|
||||
assert_eq!(
|
||||
free_first["providers"],
|
||||
json!(["codex", "grok", "kiro", "windsurf"])
|
||||
);
|
||||
assert_eq!(
|
||||
recent_refresh["providers"],
|
||||
json!(["codex", "grok", "kiro"])
|
||||
json!(["codex", "grok", "kiro", "windsurf"])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user