feat: 扩展 Rust gateway 全功能模块,新增 billing/crypto/wallet crate 及完整数据层

- 新增 aether-billing、aether-crypto、aether-wallet 独立 crate
- aether-data 扩展 repository 层:announcements、auth_modules、billing、
  candidate_selection、gemini_file_mappings、global_models、management_tokens、
  oauth_providers、proxy_nodes、quota、users、wallet 等模块
- aether-gateway 新增 api/auth/billing/control/middleware/scheduler/usage/
  video_tasks/hooks/maintenance/model_fetch/provider_transport 等功能模块
- 重构 executor decision 和 gateway state 为模块目录结构
- 新增 gateway router、frontdoor 路由层及对应测试
- Python 侧 API 路由重构,新增 compat/support 模块
- 前端 Logo 组件更新及 Provider 管理页面调整
This commit is contained in:
fawney19
2026-03-31 19:19:04 +08:00
parent b5a0070023
commit ddf18fed9a
690 changed files with 235087 additions and 16301 deletions

View File

@@ -2877,3 +2877,286 @@ async def test_build_openai_cli_stream_plan_rejects_force_rewrite_envelope(
)
assert result is None
@pytest.mark.asyncio
async def test_build_openai_cli_stream_plan_rejects_codex_transport_for_direct_executor(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-stream-codex-transport-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello", "stream": True}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-stream-codex-transport-123",
name="codex",
proxy=None,
),
endpoint=SimpleNamespace(id="endpoint-cli-stream-codex-transport-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-stream-codex-transport-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-stream-codex-transport-123",
)
fake_upstream_request = SimpleNamespace(
url="https://chatgpt.com/backend-api/codex/responses",
headers={"content-type": "application/json", "accept": "text/event-stream"},
payload={"model": "gpt-5", "input": "hello", "stream": True},
upstream_is_stream=True,
envelope=None,
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-stream-codex-transport-123"),
SimpleNamespace(id="api-key-cli-stream-codex-transport-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-stream-codex-transport-123",
api_key_id="api-key-cli-stream-codex-transport-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello", "stream": True},
auth_context=auth_context,
)
result = await _build_openai_cli_stream_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is None
@pytest.mark.asyncio
async def test_build_openai_cli_sync_plan_rejects_codex_transport_for_direct_executor(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.handlers.openai_cli import OpenAICliAdapter
adapter = OpenAICliAdapter()
fake_context = SimpleNamespace(
path_params={},
request_id="req-cli-sync-codex-transport-123",
client_ip="127.0.0.1",
user_agent="pytest",
start_time=0.0,
original_headers={"content-type": "application/json", "authorization": "Bearer test-key"},
query_params={},
client_content_encoding=None,
extra={"perf": None},
)
fake_context.ensure_json_body_async = AsyncMock(
return_value={"model": "gpt-5", "input": "hello"}
)
fake_candidate = SimpleNamespace(
provider=SimpleNamespace(
id="provider-cli-sync-codex-transport-123",
name="codex",
proxy=None,
request_timeout=None,
),
endpoint=SimpleNamespace(id="endpoint-cli-sync-codex-transport-123", api_format="openai:cli"),
key=SimpleNamespace(id="key-cli-sync-codex-transport-123", proxy=None),
mapping_matched_model="gpt-5",
needs_conversion=False,
output_limit=None,
request_candidate_id="cand-cli-sync-codex-transport-123",
)
fake_upstream_request = SimpleNamespace(
url="https://chatgpt.com/backendapi/codex/responses",
headers={"content-type": "application/json"},
payload={"model": "gpt-5", "input": "hello"},
upstream_is_stream=False,
envelope=None,
tls_profile=None,
)
class FakeCliHandler:
primary_api_format = "openai:cli"
def __init__(self, **kwargs: object) -> None:
pass
def extract_model_from_request(
self, body: dict[str, object], path_params: dict[str, str]
) -> str:
return "gpt-5"
def _resolve_capability_requirements(self, **kwargs: object) -> None:
return None
async def _resolve_preferred_key_ids(self, **kwargs: object) -> None:
return None
async def _get_mapped_model(self, **kwargs: object) -> str:
return "gpt-5"
async def _build_upstream_request(self, **kwargs: object) -> SimpleNamespace:
return fake_upstream_request
monkeypatch.setattr(adapter, "authorize", lambda context: None)
monkeypatch.setattr(
type(adapter),
"HANDLER_CLASS",
property(lambda self: FakeCliHandler),
)
monkeypatch.setattr(
adapter,
"_merge_path_params",
lambda body, path_params: body,
raising=False,
)
monkeypatch.setattr(
"src.api.internal.gateway._resolve_gateway_sync_adapter",
lambda decision, path: (adapter, {}),
)
monkeypatch.setattr(
"src.api.internal.gateway._load_gateway_auth_models",
lambda db, auth_context: (
SimpleNamespace(id="user-cli-sync-codex-transport-123"),
SimpleNamespace(id="api-key-cli-sync-codex-transport-123"),
),
)
monkeypatch.setattr(
"src.api.internal.gateway.get_pipeline",
lambda: SimpleNamespace(_check_user_rate_limit=AsyncMock(return_value=None)),
)
monkeypatch.setattr(
"src.api.internal.gateway._build_gateway_request_context",
lambda **kwargs: fake_context,
)
monkeypatch.setattr(
"src.api.internal.gateway._select_gateway_direct_candidate",
AsyncMock(return_value=fake_candidate),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_proxy_info_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.resolve_delegate_config_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.build_proxy_url_async",
AsyncMock(return_value=None),
)
monkeypatch.setattr(
"src.services.proxy_node.resolver.get_system_proxy_config_async",
AsyncMock(return_value=None),
)
decision = classify_gateway_route("POST", "/v1/responses")
auth_context = GatewayAuthContext(
user_id="user-cli-sync-codex-transport-123",
api_key_id="api-key-cli-sync-codex-transport-123",
access_allowed=True,
)
payload = GatewayExecuteRequest(
method="POST",
path="/v1/responses",
headers={"content-type": "application/json", "authorization": "Bearer test-key"},
body_json={"model": "gpt-5", "input": "hello"},
auth_context=auth_context,
)
result = await _build_openai_cli_sync_plan(
request=SimpleNamespace(),
payload=payload,
db=object(),
auth_context=auth_context,
decision=decision,
)
assert result is None