feat(pool): 增加 OAuth 账号池管理功能

- 新增 pool manager / strategy / health_policy / cost_tracker / redis_ops 等核心模块
- 新增 pool admin API 路由与 schemas
- 新增 OAuth 账号类型解析 (oauth_plan)
- 前端增加 PoolManagement 页面、PoolConfigDialog、PoolImportDialog、PoolStatusCard 组件
- 补充 pool config / cost tracker / health policy / manager / strategy / trace 等测试
This commit is contained in:
fawney19
2026-02-27 13:58:58 +08:00
parent 579b5e4623
commit 76a6d0ce8e
35 changed files with 5761 additions and 0 deletions

166
src/core/oauth_plan.py Normal file
View File

@@ -0,0 +1,166 @@
from __future__ import annotations
import json
from typing import Any
from src.core.crypto import crypto_service
def normalize_oauth_plan_type(plan_type: Any) -> str | None:
if not isinstance(plan_type, str):
return None
normalized = plan_type.strip().lower()
if not normalized:
return None
return normalized
def extract_oauth_plan_type_from_auth_config_data(auth_config: Any) -> str | None:
if not isinstance(auth_config, dict):
return None
# Codex: plan_type (free/plus/team/enterprise)
plan_type = normalize_oauth_plan_type(auth_config.get("plan_type"))
if plan_type:
return plan_type
# Antigravity: tier (PAID/FREE/...)
tier = normalize_oauth_plan_type(auth_config.get("tier"))
if tier:
return tier
return None
def decrypt_auth_config_to_dict(
encrypted_auth_config: str | None,
*,
silent: bool = True,
) -> dict[str, Any] | None:
if not encrypted_auth_config:
return None
try:
decrypted = crypto_service.decrypt(encrypted_auth_config, silent=silent)
parsed = json.loads(decrypted)
if isinstance(parsed, dict):
return parsed
except Exception:
return None
return None
def _strip_provider_prefix(value: str, provider_type: str | None = None) -> str:
normalized = value.strip()
if not normalized:
return ""
prefixes: list[str] = []
if isinstance(provider_type, str) and provider_type.strip():
prefixes.append(provider_type.strip())
# Kiro 目前将套餐信息记录为 "KIRO FREE" / "KIRO PRO+"。
if "kiro" not in {p.lower() for p in prefixes}:
prefixes.append("kiro")
upper = normalized.upper()
for prefix in prefixes:
prefix_upper = prefix.upper()
if upper == prefix_upper:
return ""
if upper.startswith(f"{prefix_upper} "):
return normalized[len(prefix) :].strip()
return normalized
def extract_oauth_plan_type_from_upstream_metadata(
upstream_metadata: Any,
*,
provider_type: str | None = None,
) -> str | None:
if not isinstance(upstream_metadata, dict):
return None
kiro_meta = upstream_metadata.get("kiro")
if isinstance(kiro_meta, dict):
subscription_title = kiro_meta.get("subscription_title")
if isinstance(subscription_title, str):
normalized = _strip_provider_prefix(subscription_title, provider_type=provider_type)
return normalize_oauth_plan_type(normalized)
return None
def extract_oauth_plan_type(
encrypted_auth_config: str | None,
*,
upstream_metadata: Any = None,
provider_type: str | None = None,
silent: bool = True,
) -> str | None:
auth_config = decrypt_auth_config_to_dict(encrypted_auth_config, silent=silent)
plan_type = extract_oauth_plan_type_from_auth_config_data(auth_config)
if plan_type:
return plan_type
return extract_oauth_plan_type_from_upstream_metadata(
upstream_metadata, provider_type=provider_type
)
def normalize_antigravity_tier(raw_tier: Any) -> str | None:
normalized = normalize_oauth_plan_type(raw_tier)
if not normalized:
return None
if "ultra" in normalized:
return "ultra"
if "pro" in normalized or "paid" in normalized:
return "pro"
if "free" in normalized or "legacy" in normalized:
return "free"
return normalized
def _extract_antigravity_tier_raw(tier_obj: Any) -> str | None:
if isinstance(tier_obj, str):
stripped = tier_obj.strip()
return stripped or None
if isinstance(tier_obj, dict):
for key in ("id", "tierType"):
value = tier_obj.get(key)
if isinstance(value, str):
stripped = value.strip()
if stripped:
return stripped
return None
def _format_antigravity_tier_label(
normalized_tier: str,
*,
fallback_raw: str | None = None,
) -> str:
if normalized_tier == "ultra":
return "Ultra"
if normalized_tier == "pro":
return "Pro"
if normalized_tier == "free":
return "Free"
if fallback_raw:
return fallback_raw
return normalized_tier
def extract_antigravity_tier_from_code_assist(code_assist: Any) -> str:
if not isinstance(code_assist, dict):
return "Free"
paid_tier_raw = _extract_antigravity_tier_raw(code_assist.get("paidTier"))
paid_tier = normalize_antigravity_tier(paid_tier_raw)
if paid_tier:
return _format_antigravity_tier_label(paid_tier, fallback_raw=paid_tier_raw)
current_tier_raw = _extract_antigravity_tier_raw(code_assist.get("currentTier"))
current_tier = normalize_antigravity_tier(current_tier_raw)
if current_tier:
return _format_antigravity_tier_label(current_tier, fallback_raw=current_tier_raw)
return "Free"