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

@@ -75,6 +75,14 @@ def _wait_until(predicate: Any, *, timeout: float = 1.0, interval: float = 0.01)
assert predicate()
def _make_legacy_test_client(app: FastAPI) -> TestClient:
return TestClient(
app,
base_url="http://127.0.0.1",
headers={"x-aether-legacy-internal-gateway": "true"},
)
def test_finalize_sync_route_finalizes_openai_video_create_response(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -96,7 +104,7 @@ def test_finalize_sync_route_finalizes_openai_video_create_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -144,7 +152,7 @@ def test_finalize_sync_route_finalizes_openai_chat_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -193,7 +201,7 @@ def test_finalize_sync_route_finalizes_openai_cli_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -753,7 +761,7 @@ def test_finalize_sync_route_uses_chat_fast_path_without_db_session(
lambda: (_ for _ in ()).throw(AssertionError("create_session should not be called")),
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -802,7 +810,7 @@ def test_finalize_sync_route_uses_cli_fast_path_without_db_session(
lambda: (_ for _ in ()).throw(AssertionError("create_session should not be called")),
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -847,7 +855,7 @@ def test_finalize_sync_route_finalizes_openai_video_remix_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -896,7 +904,7 @@ def test_finalize_sync_route_finalizes_gemini_video_create_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -945,7 +953,7 @@ def test_finalize_sync_route_finalizes_openai_video_delete_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -984,7 +992,7 @@ def test_finalize_sync_route_finalizes_openai_video_cancel_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -1019,7 +1027,7 @@ def test_finalize_sync_route_finalizes_gemini_video_cancel_response(
finalize_mock,
)
client = TestClient(app, base_url="http://127.0.0.1")
client = _make_legacy_test_client(app)
response = client.post(
"/api/internal/gateway/finalize-sync",
json={
@@ -1040,3 +1048,278 @@ def test_finalize_sync_route_finalizes_gemini_video_cancel_response(
assert response.headers[CONTROL_EXECUTED_HEADER] == "true"
assert response.json() == {}
finalize_mock.assert_awaited_once()
@pytest.mark.asyncio
async def test_finalize_gateway_openai_video_create_sync_reuses_rust_owned_task(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.internal import gateway as gateway_module
background_mock = AsyncMock(return_value=None)
begin_pending_usage = MagicMock()
class FakeQuery:
def __init__(self, value: Any) -> None:
self._value = value
def filter(self, *args: Any, **kwargs: Any) -> "FakeQuery":
return self
def first(self) -> Any:
return self._value
class FakeDB:
def __init__(self) -> None:
self._mapping = {
"User": SimpleNamespace(id="user-123"),
"ApiKey": SimpleNamespace(id="key-123"),
"VideoTask": SimpleNamespace(id="task-local-123"),
}
def query(self, model: Any) -> FakeQuery:
return FakeQuery(self._mapping.get(getattr(model, "__name__", "")))
def add(self, obj: Any) -> None:
raise AssertionError("legacy task creation should be skipped")
class FakeOpenAIVideoHandler:
FORMAT_ID = "openai:video"
def __init__(self, **kwargs: Any) -> None:
self._normalizer = SimpleNamespace(
video_task_from_internal=lambda task: {
"id": task["id"],
"object": "video",
"status": "submitted",
}
)
def _task_to_internal(self, task: Any) -> dict[str, Any]:
return {"id": task.id}
monkeypatch.setattr(
"src.api.handlers.openai.video_handler.OpenAIVideoHandler",
FakeOpenAIVideoHandler,
)
monkeypatch.setattr(
"src.api.internal.gateway._run_gateway_video_finalize_submitted_background",
background_mock,
)
monkeypatch.setattr(
"src.services.usage.service.UsageService.begin_pending_usage",
begin_pending_usage,
)
payload = GatewaySyncReportRequest(
trace_id="trace-openai-video-rust-owner-123",
report_kind="openai_video_create_sync_success",
report_context={
"user_id": "user-123",
"api_key_id": "key-123",
"provider_id": "provider-123",
"endpoint_id": "endpoint-123",
"key_id": "provider-key-123",
"request_id": "req-openai-video-rust-owner-123",
"local_task_id": "task-local-123",
"provider_name": "openai",
"provider_api_format": "openai:video",
"rust_video_task_persisted": True,
},
status_code=200,
headers={"content-type": "application/json"},
body_json={"id": "ext-video-123", "status": "submitted"},
telemetry={"elapsed_ms": 42},
)
response = await gateway_module._finalize_gateway_openai_video_create_sync(
payload,
db=FakeDB(),
)
assert response.status_code == 200
assert json.loads(response.body) == {
"id": "task-local-123",
"object": "video",
"status": "submitted",
}
begin_pending_usage.assert_not_called()
background_mock.assert_awaited_once()
@pytest.mark.asyncio
async def test_finalize_gateway_openai_video_remix_sync_reuses_rust_owned_task(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.internal import gateway as gateway_module
class FakeQuery:
def __init__(self, value: Any) -> None:
self._value = value
def filter(self, *args: Any, **kwargs: Any) -> "FakeQuery":
return self
def first(self) -> Any:
return self._value
class FakeDB:
def __init__(self) -> None:
self._mapping = {
"User": SimpleNamespace(id="user-123"),
"ApiKey": SimpleNamespace(id="key-123"),
"VideoTask": SimpleNamespace(id="task-local-remix-123"),
}
def query(self, model: Any) -> FakeQuery:
return FakeQuery(self._mapping.get(getattr(model, "__name__", "")))
def add(self, obj: Any) -> None:
raise AssertionError("legacy remix task creation should be skipped")
class FakeOpenAIVideoHandler:
def __init__(self, **kwargs: Any) -> None:
self._normalizer = SimpleNamespace(
video_task_from_internal=lambda task: {
"id": task["id"],
"object": "video",
"status": "submitted",
}
)
def _task_to_internal(self, task: Any) -> dict[str, Any]:
return {"id": task.id}
def _get_task(self, task_id: str) -> Any:
raise AssertionError("legacy remix source lookup should be skipped")
monkeypatch.setattr(
"src.api.handlers.openai.video_handler.OpenAIVideoHandler",
FakeOpenAIVideoHandler,
)
payload = GatewaySyncReportRequest(
trace_id="trace-openai-video-remix-rust-owner-123",
report_kind="openai_video_remix_sync_success",
report_context={
"user_id": "user-123",
"api_key_id": "key-123",
"task_id": "task-source-123",
"request_id": "req-openai-video-remix-rust-owner-123",
"local_task_id": "task-local-remix-123",
"rust_video_task_persisted": True,
},
status_code=200,
headers={"content-type": "application/json"},
body_json={"id": "ext-remix-task-123", "status": "submitted"},
)
response = await gateway_module._finalize_gateway_openai_video_remix_sync(
payload,
db=FakeDB(),
)
assert response.status_code == 200
assert json.loads(response.body) == {
"id": "task-local-remix-123",
"object": "video",
"status": "submitted",
}
@pytest.mark.asyncio
async def test_finalize_gateway_gemini_video_create_sync_reuses_rust_owned_task(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from src.api.internal import gateway as gateway_module
background_mock = AsyncMock(return_value=None)
begin_pending_usage = MagicMock()
class FakeQuery:
def __init__(self, value: Any) -> None:
self._value = value
def filter(self, *args: Any, **kwargs: Any) -> "FakeQuery":
return self
def first(self) -> Any:
return self._value
class FakeDB:
def __init__(self) -> None:
self._mapping = {
"User": SimpleNamespace(id="user-123"),
"ApiKey": SimpleNamespace(id="key-123"),
"VideoTask": SimpleNamespace(short_id="short12345678"),
}
def query(self, model: Any) -> FakeQuery:
return FakeQuery(self._mapping.get(getattr(model, "__name__", "")))
def add(self, obj: Any) -> None:
raise AssertionError("legacy gemini task creation should be skipped")
class FakeGeminiVeoHandler:
FORMAT_ID = "gemini:video"
def __init__(self, **kwargs: Any) -> None:
self._normalizer = SimpleNamespace(
video_task_from_internal=lambda task: {
"name": f"models/veo-3/operations/{task['id']}",
"done": False,
"metadata": {},
}
)
def _task_to_internal(self, task: Any) -> dict[str, Any]:
return {"id": task.short_id}
monkeypatch.setattr(
"src.api.handlers.gemini.video_handler.GeminiVeoHandler",
FakeGeminiVeoHandler,
)
monkeypatch.setattr(
"src.api.internal.gateway._run_gateway_video_finalize_submitted_background",
background_mock,
)
monkeypatch.setattr(
"src.services.usage.service.UsageService.begin_pending_usage",
begin_pending_usage,
)
payload = GatewaySyncReportRequest(
trace_id="trace-gemini-video-rust-owner-123",
report_kind="gemini_video_create_sync_success",
report_context={
"user_id": "user-123",
"api_key_id": "key-123",
"provider_id": "provider-123",
"endpoint_id": "endpoint-123",
"key_id": "provider-key-123",
"request_id": "req-gemini-video-rust-owner-123",
"model": "veo-3",
"local_short_id": "short12345678",
"provider_name": "gemini",
"provider_api_format": "gemini:video",
"rust_video_task_persisted": True,
},
status_code=200,
headers={"content-type": "application/json"},
body_json={"name": "operations/ext-video-123"},
telemetry={"elapsed_ms": 48},
)
response = await gateway_module._finalize_gateway_gemini_video_create_sync(
payload,
db=FakeDB(),
)
assert response.status_code == 200
assert json.loads(response.body) == {
"name": "models/veo-3/operations/short12345678",
"done": False,
"metadata": {},
}
begin_pending_usage.assert_not_called()
background_mock.assert_awaited_once()