refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构

- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
fawney19
2026-04-03 16:26:16 +08:00
parent 8f26e1a31f
commit 1d9c77522a
868 changed files with 1735 additions and 2433 deletions

View File

@@ -0,0 +1,386 @@
from __future__ import annotations
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request
from fastapi.responses import JSONResponse, Response
from sqlalchemy.orm import Session
from src.database import get_db
from . import gateway as gateway_impl
from .gateway_contract import (
CONTROL_EXECUTED_HEADER,
GatewayAuthContextRequest,
GatewayExecuteRequest,
GatewayResolveRequest,
GatewayStreamReportRequest,
GatewaySyncReportRequest,
classify_gateway_route,
)
router = APIRouter(
prefix="/api/internal/gateway",
tags=["Internal - Gateway"],
include_in_schema=False,
)
def _ensure_legacy_internal_gateway_request(request: Request) -> Response | None:
if gateway_impl._request_allows_legacy_chat_cli_internal_gateway(request):
return None
return gateway_impl._build_retired_internal_gateway_response()
@router.post("/resolve")
async def resolve_gateway_route(
request: Request, payload: GatewayResolveRequest
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
decision.auth_context = await gateway_impl._resolve_auth_context(payload, decision)
return JSONResponse(status_code=200, content=decision.model_dump(exclude_none=True))
@router.post("/auth-context")
async def resolve_gateway_auth_context(
request: Request,
payload: GatewayAuthContextRequest,
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
auth_context = await gateway_impl._resolve_auth_context_signature(
headers=payload.headers,
query_string=payload.query_string,
auth_endpoint_signature=payload.auth_endpoint_signature,
)
return JSONResponse(status_code=200, content={"auth_context": auth_context})
@router.post("/decision-sync")
async def decide_gateway_sync(
request: Request,
payload: GatewayExecuteRequest,
db: Session = Depends(get_db),
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
if not gateway_impl._allows_legacy_chat_cli_internal_route(request, decision):
return gateway_impl._build_retired_internal_gateway_response()
auth_context = await gateway_impl._resolve_gateway_execute_auth_context(
payload=payload,
decision=decision,
)
if auth_context is None or not auth_context.access_allowed:
return JSONResponse(status_code=200, content={"action": "fallback_plan"})
try:
resolved = await gateway_impl._build_gateway_sync_decision_response(
request=request,
payload=payload,
db=db,
auth_context=auth_context,
decision=decision,
)
except HTTPException as exc:
headers = dict(exc.headers or {})
headers[CONTROL_EXECUTED_HEADER] = "true"
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
headers=headers,
)
if resolved is None:
body: dict[str, object] = {"action": "fallback_plan"}
if payload.auth_context is None:
body["auth_context"] = auth_context.model_dump(exclude_none=True)
return JSONResponse(status_code=200, content=body)
if payload.auth_context is not None:
resolved.auth_context = None
return JSONResponse(status_code=200, content=resolved.model_dump(exclude_none=True))
@router.post("/decision-stream")
async def decide_gateway_stream(
request: Request,
payload: GatewayExecuteRequest,
db: Session = Depends(get_db),
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
if not gateway_impl._allows_legacy_chat_cli_internal_route(request, decision):
return gateway_impl._build_retired_internal_gateway_response()
auth_context = await gateway_impl._resolve_gateway_execute_auth_context(
payload=payload,
decision=decision,
)
if auth_context is None or not auth_context.access_allowed:
return JSONResponse(status_code=200, content={"action": "fallback_plan"})
try:
resolved = await gateway_impl._build_gateway_stream_decision_response(
request=request,
payload=payload,
db=db,
auth_context=auth_context,
decision=decision,
)
except HTTPException as exc:
headers = dict(exc.headers or {})
headers[CONTROL_EXECUTED_HEADER] = "true"
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
headers=headers,
)
if resolved is None:
body: dict[str, object] = {"action": "fallback_plan"}
if payload.auth_context is None:
body["auth_context"] = auth_context.model_dump(exclude_none=True)
return JSONResponse(status_code=200, content=body)
if payload.auth_context is not None:
resolved.auth_context = None
return JSONResponse(status_code=200, content=resolved.model_dump(exclude_none=True))
@router.post("/execute-sync")
async def execute_gateway_sync(
request: Request,
payload: GatewayExecuteRequest,
db: Session = Depends(get_db),
) -> Response:
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
return await gateway_impl._execute_gateway_control_request(
request=request,
payload=payload,
db=db,
require_stream=False,
)
@router.post("/execute-stream")
async def execute_gateway_stream(
request: Request,
payload: GatewayExecuteRequest,
db: Session = Depends(get_db),
) -> Response:
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
return await gateway_impl._execute_gateway_control_request(
request=request,
payload=payload,
db=db,
require_stream=True,
)
@router.post("/plan-stream")
async def plan_gateway_stream(
request: Request,
payload: GatewayExecuteRequest,
db: Session = Depends(get_db),
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
if not gateway_impl._allows_legacy_chat_cli_internal_route(request, decision):
return gateway_impl._build_retired_internal_gateway_response()
try:
planned = await gateway_impl._build_gateway_stream_plan_response(
request=request,
payload=payload,
db=db,
)
except HTTPException as exc:
headers = dict(exc.headers or {})
headers[CONTROL_EXECUTED_HEADER] = "true"
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
headers=headers,
)
if planned is None:
response = await gateway_impl._execute_gateway_control_request(
request=request,
payload=payload,
db=db,
require_stream=True,
)
if gateway_impl._is_gateway_control_executed_response(response):
return response
return gateway_impl._build_proxy_public_fallback_response()
if payload.auth_context is not None:
planned.auth_context = None
return JSONResponse(status_code=200, content=planned.model_dump(exclude_none=True))
@router.post("/plan-sync")
async def plan_gateway_sync(
request: Request,
payload: GatewayExecuteRequest,
db: Session = Depends(get_db),
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
if not gateway_impl._allows_legacy_chat_cli_internal_route(request, decision):
return gateway_impl._build_retired_internal_gateway_response()
try:
planned = await gateway_impl._build_gateway_sync_plan_response(
request=request,
payload=payload,
db=db,
)
except HTTPException as exc:
headers = dict(exc.headers or {})
headers[CONTROL_EXECUTED_HEADER] = "true"
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail},
headers=headers,
)
if planned is None:
response = await gateway_impl._execute_gateway_control_request(
request=request,
payload=payload,
db=db,
require_stream=False,
)
if gateway_impl._is_gateway_control_executed_response(response):
return response
return gateway_impl._build_proxy_public_fallback_response()
if payload.auth_context is not None:
planned.auth_context = None
return JSONResponse(status_code=200, content=planned.model_dump(exclude_none=True))
@router.post("/report-sync")
async def report_gateway_sync(
request: Request,
payload: GatewaySyncReportRequest,
background_tasks: BackgroundTasks,
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
if not gateway_impl._allows_legacy_chat_cli_report_route(request, payload.report_kind):
return gateway_impl._build_retired_internal_gateway_response()
payload_copy = payload.model_copy(deep=True)
if gateway_impl._gateway_sync_report_requires_inline(payload_copy):
db, cleanup = gateway_impl._resolve_gateway_background_db(getattr(request, "app", None))
try:
await gateway_impl._run_gateway_sync_report_background(payload_copy, db)
finally:
if cleanup is not None:
cleanup()
return JSONResponse(status_code=200, content={"ok": True})
background_tasks.add_task(
gateway_impl._run_gateway_sync_report_background_with_session,
payload_copy,
getattr(request, "app", None),
)
return JSONResponse(status_code=200, content={"ok": True})
@router.post("/finalize-sync")
async def finalize_gateway_sync(
request: Request,
payload: GatewaySyncReportRequest,
background_tasks: BackgroundTasks,
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
if not gateway_impl._allows_legacy_chat_cli_report_route(request, payload.report_kind):
return gateway_impl._build_retired_internal_gateway_response()
fast_response = await gateway_impl._maybe_build_gateway_core_sync_fast_success_response(payload)
if fast_response is not None:
if payload.report_kind in {
"openai_chat_sync_finalize",
"claude_chat_sync_finalize",
"gemini_chat_sync_finalize",
}:
background_tasks.add_task(
gateway_impl._run_gateway_chat_sync_finalize_background_with_session,
payload.model_copy(deep=True),
)
elif payload.report_kind in {
"openai_cli_sync_finalize",
"openai_compact_sync_finalize",
"claude_cli_sync_finalize",
"gemini_cli_sync_finalize",
}:
background_tasks.add_task(
gateway_impl._run_gateway_cli_sync_finalize_background_with_session,
payload.model_copy(deep=True),
)
fast_response.headers[CONTROL_EXECUTED_HEADER] = "true"
return fast_response
db, cleanup = gateway_impl._resolve_gateway_finalize_db(request)
try:
response = await gateway_impl._finalize_gateway_sync_response(
payload,
db=db,
background_tasks=background_tasks,
)
except Exception:
if cleanup is not None:
cleanup()
raise
if cleanup is not None:
background_tasks.add_task(cleanup)
response.headers[CONTROL_EXECUTED_HEADER] = "true"
return response
@router.post("/report-stream")
async def report_gateway_stream(
request: Request,
payload: GatewayStreamReportRequest,
background_tasks: BackgroundTasks,
) -> Response:
gateway_impl.ensure_loopback(request)
retired = _ensure_legacy_internal_gateway_request(request)
if retired is not None:
return retired
if not gateway_impl._allows_legacy_chat_cli_report_route(request, payload.report_kind):
return gateway_impl._build_retired_internal_gateway_response()
background_tasks.add_task(
gateway_impl._run_gateway_stream_report_background_with_session,
payload.model_copy(deep=True),
getattr(request, "app", None),
)
return JSONResponse(status_code=200, content={"ok": True})