mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(codex): 增强 OAuth 导入解析与账号信息提取,优化维护调度器线程模型
Codex OAuth: - 导入解析支持附加账号字段(account_id/plan_type/user_id/email) - enrich_codex 扩展从 access_token 和直接字段提取账号信息 - parse_codex_id_token 支持 JWT/JSON 字符串/dict 三种输入格式 - request patching 新增 openai:compact 格式支持 - codex_usage_parser 新增 credits_unlimited 字段解析 维护调度器: - 同步 DB 操作迁移到线程池执行,避免阻塞事件循环 - 新增 request_candidates 定期清理任务 - 新增每周 VACUUM ANALYZE 数据库表维护任务 - 新增 enable_db_maintenance 配置项 前端: - ElapsedTimeText 从 setInterval 改为 requestAnimationFrame - 时间线过滤 available/unused 占位记录 - Usage 页面默认关闭全局自动刷新 - 移除号池管理中的配额更新时间显示
This commit is contained in:
70
tests/core/test_provider_oauth_utils_codex.py
Normal file
70
tests/core/test_provider_oauth_utils_codex.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import jwt
|
||||
|
||||
from src.core.provider_oauth_utils import parse_codex_id_token
|
||||
|
||||
|
||||
def _encode_unsigned_jwt(payload: dict[str, object]) -> str:
|
||||
token = jwt.encode(payload, key="", algorithm="none")
|
||||
return token.decode("utf-8") if isinstance(token, bytes) else token
|
||||
|
||||
|
||||
def test_parse_codex_id_token_extracts_auth_claim_fields() -> None:
|
||||
token = _encode_unsigned_jwt(
|
||||
{
|
||||
"email": "u@example.com",
|
||||
"https://api.openai.com/auth": {
|
||||
"chatgpt_account_id": "acc-1",
|
||||
"chatgpt_plan_type": "team",
|
||||
"chatgpt_user_id": "user-1",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
parsed = parse_codex_id_token(token)
|
||||
|
||||
assert parsed == {
|
||||
"email": "u@example.com",
|
||||
"account_id": "acc-1",
|
||||
"plan_type": "team",
|
||||
"user_id": "user-1",
|
||||
}
|
||||
|
||||
|
||||
def test_parse_codex_id_token_accepts_json_payload_string() -> None:
|
||||
payload = {
|
||||
"email": "u@example.com",
|
||||
"chatgpt_account_id": "acc-2",
|
||||
"chatgpt_plan_type": "plus",
|
||||
"chatgpt_user_id": "user-2",
|
||||
}
|
||||
|
||||
parsed = parse_codex_id_token(json.dumps(payload))
|
||||
|
||||
assert parsed == {
|
||||
"email": "u@example.com",
|
||||
"account_id": "acc-2",
|
||||
"plan_type": "plus",
|
||||
"user_id": "user-2",
|
||||
}
|
||||
|
||||
|
||||
def test_parse_codex_id_token_accepts_dict_payload() -> None:
|
||||
parsed = parse_codex_id_token(
|
||||
{
|
||||
"email": "u@example.com",
|
||||
"accountId": "acc-3",
|
||||
"planType": "enterprise",
|
||||
"userId": "user-3",
|
||||
}
|
||||
)
|
||||
|
||||
assert parsed == {
|
||||
"email": "u@example.com",
|
||||
"account_id": "acc-3",
|
||||
"plan_type": "enterprise",
|
||||
"user_id": "user-3",
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import jwt
|
||||
import pytest
|
||||
|
||||
from src.services.provider.adapters.codex.request_patching import (
|
||||
maybe_patch_request_for_codex,
|
||||
patch_openai_cli_request_for_codex,
|
||||
@@ -143,3 +146,37 @@ def test_codex_envelope_extra_headers_uses_account_id_header() -> None:
|
||||
headers = codex_oauth_envelope.extra_headers() or {}
|
||||
assert headers.get("Chatgpt-Account-Id") == "acc_123"
|
||||
set_codex_request_context(None)
|
||||
|
||||
|
||||
def _encode_unsigned_jwt(payload: dict[str, object]) -> str:
|
||||
token = jwt.encode(payload, key="", algorithm="none")
|
||||
return token.decode("utf-8") if isinstance(token, bytes) else token
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_enrich_codex_uses_access_token_when_id_token_missing() -> None:
|
||||
from src.services.provider.adapters.codex.plugin import enrich_codex
|
||||
|
||||
access_token = _encode_unsigned_jwt(
|
||||
{
|
||||
"email": "u@example.com",
|
||||
"https://api.openai.com/auth": {
|
||||
"chatgpt_account_id": "acc-access",
|
||||
"chatgpt_plan_type": "team",
|
||||
"chatgpt_user_id": "user-access",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
auth_config: dict[str, object] = {}
|
||||
out = await enrich_codex(
|
||||
auth_config=auth_config,
|
||||
token_response={"access_token": access_token},
|
||||
access_token=access_token,
|
||||
proxy_config=None,
|
||||
)
|
||||
|
||||
assert out["email"] == "u@example.com"
|
||||
assert out["account_id"] == "acc-access"
|
||||
assert out["plan_type"] == "team"
|
||||
assert out["user_id"] == "user-access"
|
||||
|
||||
@@ -417,6 +417,31 @@ def test_parse_codex_usage_missing_plan_type_infers_paid_windows() -> None:
|
||||
assert parsed["secondary_window_minutes"] == 300
|
||||
|
||||
|
||||
def test_parse_codex_usage_blank_credits_balance_is_ignored() -> None:
|
||||
parsed = parse_codex_wham_usage_response(
|
||||
{
|
||||
"plan_type": "team",
|
||||
"rate_limit": {
|
||||
"primary_window": {
|
||||
"used_percent": 10,
|
||||
"reset_after_seconds": 100,
|
||||
"reset_at": 1700000000,
|
||||
"limit_window_seconds": 18000,
|
||||
}
|
||||
},
|
||||
"credits": {
|
||||
"has_credits": False,
|
||||
"balance": "",
|
||||
"unlimited": "false",
|
||||
},
|
||||
}
|
||||
)
|
||||
assert parsed is not None
|
||||
assert parsed["has_credits"] is False
|
||||
assert parsed["credits_unlimited"] is False
|
||||
assert "credits_balance" not in parsed
|
||||
|
||||
|
||||
def test_parse_codex_usage_invalid_type_raises_diagnostic_error() -> None:
|
||||
with pytest.raises(CodexUsageParseError, match="rate_limit.primary_window 类型错误"):
|
||||
parse_codex_wham_usage_response({"rate_limit": {"primary_window": []}})
|
||||
|
||||
45
tests/unit/test_provider_oauth_import_parser.py
Normal file
45
tests/unit/test_provider_oauth_import_parser.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from src.api.admin import provider_oauth as module
|
||||
|
||||
|
||||
def test_parse_standard_oauth_import_entries_keeps_codex_hints() -> None:
|
||||
entries = module._parse_standard_oauth_import_entries(
|
||||
'[{"refresh_token":"rt_1","accountId":"acc-1","planType":"TEAM","userId":"u-1","email":"u@example.com"}]'
|
||||
)
|
||||
|
||||
assert entries == [
|
||||
{
|
||||
"refresh_token": "rt_1",
|
||||
"account_id": "acc-1",
|
||||
"plan_type": "team",
|
||||
"user_id": "u-1",
|
||||
"email": "u@example.com",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_parse_tokens_input_compatibility_wrapper() -> None:
|
||||
tokens = module._parse_tokens_input("token_a\ntoken_b")
|
||||
assert tokens == ["token_a", "token_b"]
|
||||
|
||||
|
||||
def test_apply_codex_import_hints_only_fills_missing_fields() -> None:
|
||||
auth_config = {
|
||||
"account_id": "existing-account",
|
||||
"plan_type": "",
|
||||
}
|
||||
module._apply_codex_import_hints(
|
||||
auth_config,
|
||||
{
|
||||
"account_id": "acc-1",
|
||||
"plan_type": "plus",
|
||||
"user_id": "user-1",
|
||||
"email": "u@example.com",
|
||||
},
|
||||
)
|
||||
|
||||
assert auth_config["account_id"] == "existing-account"
|
||||
assert auth_config["plan_type"] == "plus"
|
||||
assert auth_config["user_id"] == "user-1"
|
||||
assert auth_config["email"] == "u@example.com"
|
||||
Reference in New Issue
Block a user