mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
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:
@@ -0,0 +1,594 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import asdict
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from fastapi import BackgroundTasks, HTTPException, Request
|
||||
from fastapi.responses import JSONResponse, Response
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.api.base.pipeline import get_pipeline
|
||||
from src.core.api_format.headers import extract_client_api_key_for_endpoint_with_query
|
||||
from src.core.api_format.metadata import get_auth_config_for_endpoint
|
||||
from src.core.crypto import crypto_service
|
||||
from src.core.http_compression import normalize_content_encoding
|
||||
from src.core.logger import logger
|
||||
from src.database import create_session, get_db
|
||||
from src.models.database import ApiKey, RequestCandidate, User
|
||||
from src.services.auth.service import AuthService
|
||||
from src.utils.async_utils import safe_create_task
|
||||
|
||||
from .common import ensure_loopback
|
||||
from .gateway_contract import (
|
||||
_GEMINI_FILES_DOWNLOAD_ROUTE_RE,
|
||||
_GEMINI_FILES_RESOURCE_ROUTE_RE,
|
||||
_GEMINI_MODEL_OPERATION_CANCEL_RE,
|
||||
_GEMINI_OPERATION_CANCEL_RE,
|
||||
_GEMINI_SYNC_ROUTE_RE,
|
||||
_GEMINI_VIDEO_CREATE_ROUTE_RE,
|
||||
_GEMINI_VIDEO_MODEL_OPERATION_ANY_RE,
|
||||
_OPENAI_VIDEO_CANCEL_ROUTE_RE,
|
||||
_OPENAI_VIDEO_CONTENT_ROUTE_RE,
|
||||
_OPENAI_VIDEO_REMIX_ROUTE_RE,
|
||||
_OPENAI_VIDEO_TASK_ROUTE_RE,
|
||||
CONTROL_ACTION_HEADER,
|
||||
CONTROL_ACTION_PROXY_PUBLIC,
|
||||
CONTROL_EXECUTED_HEADER,
|
||||
GatewayAuthContext,
|
||||
GatewayExecuteRequest,
|
||||
GatewayExecutionDecisionResponse,
|
||||
GatewayExecutionPlanResponse,
|
||||
GatewayResolveRequest,
|
||||
GatewayRouteDecision,
|
||||
GatewayStreamReportRequest,
|
||||
GatewaySyncReportRequest,
|
||||
classify_gateway_route,
|
||||
)
|
||||
|
||||
|
||||
class _GatewayProxy:
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
from . import gateway as gateway_module
|
||||
|
||||
return getattr(gateway_module, name)
|
||||
|
||||
|
||||
gateway_module = _GatewayProxy()
|
||||
|
||||
LEGACY_CHAT_CLI_CONTROL_EXECUTE_FALLBACK_HEADER = "x-aether-control-execute-fallback"
|
||||
LEGACY_CHAT_CLI_INTERNAL_GATEWAY_HEADER = "x-aether-legacy-internal-gateway"
|
||||
_LEGACY_CHAT_CLI_INTERNAL_GATEWAY_TRUE_VALUES = {"1", "true", "yes", "on"}
|
||||
_LEGACY_CHAT_CLI_REPORT_KIND_PREFIXES = (
|
||||
"openai_chat_",
|
||||
"claude_chat_",
|
||||
"gemini_chat_",
|
||||
"openai_cli_",
|
||||
"openai_compact_",
|
||||
"claude_cli_",
|
||||
"gemini_cli_",
|
||||
)
|
||||
|
||||
|
||||
def _is_legacy_chat_cli_control_route(decision: GatewayRouteDecision) -> bool:
|
||||
return decision.route_class == "ai_public" and decision.route_kind in {
|
||||
"chat",
|
||||
"cli",
|
||||
"compact",
|
||||
}
|
||||
|
||||
|
||||
def _request_allows_legacy_chat_cli_internal_gateway(request: Request) -> bool:
|
||||
for key, value in request.headers.items():
|
||||
normalized_key = str(key or "").strip().lower()
|
||||
if normalized_key not in {
|
||||
LEGACY_CHAT_CLI_INTERNAL_GATEWAY_HEADER,
|
||||
LEGACY_CHAT_CLI_CONTROL_EXECUTE_FALLBACK_HEADER,
|
||||
}:
|
||||
continue
|
||||
return (
|
||||
str(value or "").strip().lower()
|
||||
in _LEGACY_CHAT_CLI_INTERNAL_GATEWAY_TRUE_VALUES
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _allows_legacy_chat_cli_internal_route(
|
||||
request: Request,
|
||||
decision: GatewayRouteDecision,
|
||||
) -> bool:
|
||||
if not _is_legacy_chat_cli_control_route(decision):
|
||||
return True
|
||||
return _request_allows_legacy_chat_cli_internal_gateway(request)
|
||||
|
||||
|
||||
def _is_legacy_chat_cli_report_kind(report_kind: str | None) -> bool:
|
||||
normalized = str(report_kind or "").strip().lower()
|
||||
if not normalized:
|
||||
return False
|
||||
return normalized.startswith(_LEGACY_CHAT_CLI_REPORT_KIND_PREFIXES)
|
||||
|
||||
|
||||
def _allows_legacy_chat_cli_report_route(request: Request, report_kind: str | None) -> bool:
|
||||
if not _is_legacy_chat_cli_report_kind(report_kind):
|
||||
return True
|
||||
return _request_allows_legacy_chat_cli_internal_gateway(request)
|
||||
|
||||
|
||||
def _build_retired_internal_gateway_response() -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=410,
|
||||
content={"detail": "legacy internal gateway route removed; use public proxy"},
|
||||
)
|
||||
|
||||
|
||||
def _allows_legacy_chat_cli_control_execute(
|
||||
request: Request,
|
||||
payload: GatewayExecuteRequest,
|
||||
decision: GatewayRouteDecision,
|
||||
) -> bool:
|
||||
if not _is_legacy_chat_cli_control_route(decision):
|
||||
return True
|
||||
if not _allows_legacy_chat_cli_internal_route(request, decision):
|
||||
return False
|
||||
|
||||
for key, value in (payload.headers or {}).items():
|
||||
if str(key or "").strip().lower() != LEGACY_CHAT_CLI_CONTROL_EXECUTE_FALLBACK_HEADER:
|
||||
continue
|
||||
return str(value or "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
return False
|
||||
|
||||
|
||||
async def _execute_gateway_control_request(
|
||||
*,
|
||||
request: Request,
|
||||
payload: GatewayExecuteRequest,
|
||||
db: Session,
|
||||
require_stream: bool,
|
||||
) -> Response:
|
||||
gateway_module.ensure_loopback(request)
|
||||
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
|
||||
if _is_gemini_files_route(decision):
|
||||
return await _execute_gateway_files_control_request(
|
||||
request=request,
|
||||
payload=payload,
|
||||
db=db,
|
||||
require_stream=require_stream,
|
||||
)
|
||||
if not _allows_legacy_chat_cli_control_execute(request, payload, decision):
|
||||
return gateway_module._build_retired_internal_gateway_response()
|
||||
|
||||
adapter, path_params = gateway_module._resolve_gateway_sync_adapter(decision, payload.path)
|
||||
if adapter is None:
|
||||
return gateway_module._build_proxy_public_fallback_response()
|
||||
|
||||
is_stream_request = _is_stream_request_payload(payload.body_json, path_params)
|
||||
if is_stream_request != require_stream:
|
||||
return gateway_module._build_proxy_public_fallback_response()
|
||||
|
||||
auth_context = await gateway_module._resolve_gateway_execute_auth_context(
|
||||
payload=payload,
|
||||
decision=decision,
|
||||
)
|
||||
if auth_context is None or not auth_context.access_allowed:
|
||||
return gateway_module._build_proxy_public_fallback_response()
|
||||
|
||||
try:
|
||||
effective_request = (
|
||||
gateway_module._build_gateway_forward_request(request=request, payload=payload)
|
||||
if _is_video_route(decision)
|
||||
else request
|
||||
)
|
||||
user, api_key = gateway_module._load_gateway_auth_models(db, auth_context)
|
||||
pipeline = gateway_module.get_pipeline()
|
||||
await pipeline._check_user_rate_limit(effective_request, db, user, api_key)
|
||||
context = gateway_module._build_gateway_request_context(
|
||||
request=effective_request,
|
||||
payload=payload,
|
||||
db=db,
|
||||
user=user,
|
||||
api_key=api_key,
|
||||
adapter=adapter,
|
||||
path_params=path_params,
|
||||
balance_remaining=auth_context.balance_remaining,
|
||||
)
|
||||
authorize_result = adapter.authorize(context)
|
||||
if hasattr(authorize_result, "__await__"):
|
||||
await authorize_result
|
||||
response = await adapter.handle(context)
|
||||
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,
|
||||
)
|
||||
|
||||
response.headers[CONTROL_EXECUTED_HEADER] = "true"
|
||||
return response
|
||||
|
||||
|
||||
def _is_gateway_control_executed_response(response: Response) -> bool:
|
||||
return str(response.headers.get(CONTROL_EXECUTED_HEADER) or "").strip().lower() == "true"
|
||||
|
||||
|
||||
async def _execute_gateway_files_control_request(
|
||||
*,
|
||||
request: Request,
|
||||
payload: GatewayExecuteRequest,
|
||||
db: Session,
|
||||
require_stream: bool,
|
||||
) -> Response:
|
||||
if require_stream:
|
||||
return gateway_module._build_proxy_public_fallback_response()
|
||||
|
||||
decision = classify_gateway_route(payload.method, payload.path, payload.headers)
|
||||
auth_context = await gateway_module._resolve_gateway_execute_auth_context(
|
||||
payload=payload,
|
||||
decision=decision,
|
||||
)
|
||||
if auth_context is None or not auth_context.access_allowed:
|
||||
return gateway_module._build_proxy_public_fallback_response()
|
||||
|
||||
try:
|
||||
user, api_key = gateway_module._load_gateway_auth_models(db, auth_context)
|
||||
gateway_request = gateway_module._build_gateway_forward_request(
|
||||
request=request, payload=payload
|
||||
)
|
||||
pipeline = gateway_module.get_pipeline()
|
||||
await pipeline._check_user_rate_limit(gateway_request, db, user, api_key)
|
||||
response = await _dispatch_gateway_files_handler(gateway_request, payload)
|
||||
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,
|
||||
)
|
||||
|
||||
response.headers[CONTROL_EXECUTED_HEADER] = "true"
|
||||
return response
|
||||
|
||||
|
||||
def _is_gemini_files_route(decision: GatewayRouteDecision) -> bool:
|
||||
return (
|
||||
decision.route_class == "ai_public"
|
||||
and decision.route_family == "gemini"
|
||||
and decision.route_kind == "files"
|
||||
)
|
||||
|
||||
|
||||
def _is_video_route(decision: GatewayRouteDecision) -> bool:
|
||||
return decision.route_class == "ai_public" and decision.route_kind == "video"
|
||||
|
||||
|
||||
async def _dispatch_gateway_files_handler(
|
||||
request: Request,
|
||||
payload: GatewayExecuteRequest,
|
||||
) -> Response:
|
||||
from src.api.public.gemini_files import (
|
||||
delete_file,
|
||||
download_file,
|
||||
get_file,
|
||||
list_files,
|
||||
upload_file,
|
||||
)
|
||||
|
||||
method = str(payload.method or "").strip().upper()
|
||||
path = str(payload.path or "").strip()
|
||||
|
||||
if method == "POST" and path == "/upload/v1beta/files":
|
||||
return await upload_file(request)
|
||||
|
||||
if method == "GET" and path == "/v1beta/files":
|
||||
query_params = gateway_module._parse_query_string(payload.query_string)
|
||||
page_size: int | None = None
|
||||
if query_params.get("pageSize") not in {None, ""}:
|
||||
try:
|
||||
page_size = int(str(query_params["pageSize"]))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail="Invalid pageSize") from exc
|
||||
return await list_files(
|
||||
request,
|
||||
pageSize=page_size,
|
||||
pageToken=query_params.get("pageToken"),
|
||||
)
|
||||
|
||||
download_match = _GEMINI_FILES_DOWNLOAD_ROUTE_RE.match(path)
|
||||
if method == "GET" and download_match:
|
||||
return await download_file(download_match.group("file_id"), request)
|
||||
|
||||
resource_match = _GEMINI_FILES_RESOURCE_ROUTE_RE.match(path)
|
||||
if resource_match:
|
||||
file_name = resource_match.group("file_name")
|
||||
if method == "GET":
|
||||
return await get_file(file_name, request)
|
||||
if method == "DELETE":
|
||||
return await delete_file(file_name, request)
|
||||
|
||||
return gateway_module._build_proxy_public_fallback_response()
|
||||
|
||||
|
||||
def _extract_gemini_path_params(path: str) -> dict[str, Any]:
|
||||
match = _GEMINI_SYNC_ROUTE_RE.match(str(path or "").strip())
|
||||
if not match:
|
||||
return {}
|
||||
action = str(match.group("action") or "").strip()
|
||||
return {
|
||||
"model": str(match.group("model") or "").strip(),
|
||||
"stream": action == "streamGenerateContent",
|
||||
}
|
||||
|
||||
|
||||
def _extract_openai_video_path_params(path: str) -> dict[str, Any]:
|
||||
normalized_path = str(path or "").strip()
|
||||
for route_re, extra in (
|
||||
(_OPENAI_VIDEO_CANCEL_ROUTE_RE, {"action": "cancel"}),
|
||||
(_OPENAI_VIDEO_REMIX_ROUTE_RE, {}),
|
||||
(_OPENAI_VIDEO_CONTENT_ROUTE_RE, {}),
|
||||
(_OPENAI_VIDEO_TASK_ROUTE_RE, {}),
|
||||
):
|
||||
match = route_re.match(normalized_path)
|
||||
if match:
|
||||
params = {"task_id": str(match.group("task_id") or "").strip()}
|
||||
params.update(extra)
|
||||
return params
|
||||
return {}
|
||||
|
||||
|
||||
def _extract_gemini_video_path_params(path: str) -> dict[str, Any]:
|
||||
normalized_path = str(path or "").strip()
|
||||
create_match = _GEMINI_VIDEO_CREATE_ROUTE_RE.match(normalized_path)
|
||||
if create_match:
|
||||
return {"model": str(create_match.group("model") or "").strip()}
|
||||
|
||||
model_operation_match = _GEMINI_VIDEO_MODEL_OPERATION_ANY_RE.match(normalized_path)
|
||||
if model_operation_match:
|
||||
operation_name = (
|
||||
f"models/{str(model_operation_match.group('model') or '').strip()}/operations/"
|
||||
f"{str(model_operation_match.group('operation_id') or '').strip()}"
|
||||
)
|
||||
params = {"task_id": operation_name}
|
||||
if model_operation_match.group("cancel"):
|
||||
params["action"] = "cancel"
|
||||
return params
|
||||
|
||||
if normalized_path == "/v1beta/operations":
|
||||
return {}
|
||||
|
||||
if normalized_path.startswith("/v1beta/operations/"):
|
||||
operation_id = normalized_path[len("/v1beta/operations/") :].strip()
|
||||
if operation_id.endswith(":cancel"):
|
||||
return {"task_id": operation_id[: -len(":cancel")], "action": "cancel"}
|
||||
return {"task_id": operation_id}
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def _is_stream_request_payload(
|
||||
body_json: dict[str, Any],
|
||||
path_params: dict[str, Any] | None = None,
|
||||
) -> bool:
|
||||
if bool((path_params or {}).get("stream")):
|
||||
return True
|
||||
return bool(body_json.get("stream"))
|
||||
|
||||
|
||||
def _load_gateway_auth_models(
|
||||
db: Session,
|
||||
auth_context: GatewayAuthContext,
|
||||
) -> tuple[User, ApiKey]:
|
||||
user = db.query(User).filter(User.id == auth_context.user_id).first()
|
||||
api_key = db.query(ApiKey).filter(ApiKey.id == auth_context.api_key_id).first()
|
||||
if not user or not api_key:
|
||||
raise HTTPException(status_code=401, detail="无效的API密钥")
|
||||
if not user.is_active or user.is_deleted:
|
||||
raise HTTPException(status_code=401, detail="无效的API密钥")
|
||||
if not api_key.is_active:
|
||||
raise HTTPException(status_code=401, detail="无效的API密钥")
|
||||
if api_key.is_locked and not api_key.is_standalone:
|
||||
raise HTTPException(status_code=403, detail="该密钥已被管理员锁定,请联系管理员")
|
||||
if str(api_key.user_id) != str(user.id):
|
||||
raise HTTPException(status_code=401, detail="无效的API密钥")
|
||||
return user, api_key
|
||||
|
||||
|
||||
def _build_gateway_request_context(
|
||||
*,
|
||||
request: Request,
|
||||
payload: GatewayExecuteRequest,
|
||||
db: Session,
|
||||
user: User,
|
||||
api_key: ApiKey,
|
||||
adapter: Any,
|
||||
path_params: dict[str, Any],
|
||||
balance_remaining: float | None,
|
||||
) -> ApiRequestContext:
|
||||
request_id = str(
|
||||
payload.trace_id or getattr(request.state, "request_id", "") or uuid.uuid4().hex[:8]
|
||||
)
|
||||
request.state.request_id = request_id
|
||||
request.state.user_id = user.id
|
||||
request.state.api_key_id = api_key.id
|
||||
request.state.prefetched_balance_remaining = balance_remaining
|
||||
|
||||
original_headers = {str(k): str(v) for k, v in (payload.headers or {}).items()}
|
||||
client_accept_encoding = str(original_headers.get("accept-encoding") or "").strip() or None
|
||||
raw_body = gateway_module._extract_gateway_raw_body(payload)
|
||||
return ApiRequestContext(
|
||||
request=request,
|
||||
db=db,
|
||||
user=user,
|
||||
api_key=api_key,
|
||||
request_id=request_id,
|
||||
start_time=time.time(),
|
||||
request_method=str(payload.method or request.method or "GET").upper(),
|
||||
request_path=str(payload.path or request.url.path or "/"),
|
||||
client_ip=request.client.host if request.client else "127.0.0.1",
|
||||
user_agent=str(original_headers.get("user-agent") or "unknown"),
|
||||
original_headers=original_headers,
|
||||
query_params=gateway_module._parse_query_string(payload.query_string),
|
||||
request_content_type=str(original_headers.get("content-type") or "").strip() or None,
|
||||
raw_body=raw_body,
|
||||
json_body=(dict(payload.body_json) if payload.body_json else None),
|
||||
balance_remaining=balance_remaining,
|
||||
mode=getattr(getattr(adapter, "mode", None), "value", "standard"),
|
||||
api_format_hint=(
|
||||
adapter.allowed_api_formats[0]
|
||||
if getattr(adapter, "allowed_api_formats", None)
|
||||
else None
|
||||
),
|
||||
path_params=dict(path_params or {}),
|
||||
client_content_encoding=normalize_content_encoding(
|
||||
original_headers.get("content-encoding")
|
||||
),
|
||||
client_accept_encoding=client_accept_encoding,
|
||||
)
|
||||
|
||||
|
||||
def _build_gateway_forward_request(
|
||||
*,
|
||||
request: Request,
|
||||
payload: GatewayExecuteRequest,
|
||||
) -> Request:
|
||||
body = gateway_module._decode_gateway_body(payload)
|
||||
header_items = [
|
||||
(str(key).encode("latin-1"), str(value).encode("latin-1"))
|
||||
for key, value in (payload.headers or {}).items()
|
||||
]
|
||||
scope = {
|
||||
"type": "http",
|
||||
"http_version": "1.1",
|
||||
"method": str(payload.method or "GET").upper(),
|
||||
"scheme": "http",
|
||||
"path": str(payload.path or "/"),
|
||||
"raw_path": str(payload.path or "/").encode("utf-8"),
|
||||
"query_string": str(payload.query_string or "").encode("utf-8"),
|
||||
"headers": header_items,
|
||||
"client": ("127.0.0.1", 0),
|
||||
"server": ("127.0.0.1", 80),
|
||||
"app": request.app,
|
||||
"state": {"request_id": str(payload.trace_id or uuid.uuid4().hex[:8])},
|
||||
}
|
||||
|
||||
received = False
|
||||
|
||||
async def receive() -> dict[str, object]:
|
||||
nonlocal received
|
||||
if received:
|
||||
return {"type": "http.request", "body": b"", "more_body": False}
|
||||
received = True
|
||||
return {"type": "http.request", "body": body, "more_body": False}
|
||||
|
||||
return Request(scope, receive)
|
||||
|
||||
|
||||
def _build_proxy_public_fallback_response() -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={"action": CONTROL_ACTION_PROXY_PUBLIC},
|
||||
headers={CONTROL_ACTION_HEADER: CONTROL_ACTION_PROXY_PUBLIC},
|
||||
)
|
||||
|
||||
|
||||
def _stream_executor_requires_python_rewrite(
|
||||
*,
|
||||
envelope: Any = None,
|
||||
needs_conversion: bool,
|
||||
provider_api_format: str | None = None,
|
||||
client_api_format: str | None = None,
|
||||
) -> bool:
|
||||
provider_api_format = str(provider_api_format or "").strip().lower()
|
||||
client_api_format = str(client_api_format or "").strip().lower()
|
||||
if needs_conversion:
|
||||
if (
|
||||
envelope is None
|
||||
and (
|
||||
(
|
||||
provider_api_format in {"claude:chat", "gemini:chat"}
|
||||
and client_api_format == "openai:chat"
|
||||
)
|
||||
or (
|
||||
provider_api_format in {"claude:cli", "gemini:cli"}
|
||||
and client_api_format in {"openai:cli", "openai:compact"}
|
||||
)
|
||||
)
|
||||
) or (
|
||||
str(getattr(envelope, "name", "") or "").strip().lower() == "antigravity:v1internal"
|
||||
and (
|
||||
(provider_api_format == "gemini:chat" and client_api_format == "openai:chat")
|
||||
or (
|
||||
provider_api_format == "gemini:cli"
|
||||
and client_api_format in {"openai:cli", "openai:compact"}
|
||||
)
|
||||
)
|
||||
):
|
||||
return False
|
||||
return True
|
||||
if envelope is None:
|
||||
return False
|
||||
try:
|
||||
requires_rewrite = bool(envelope.force_stream_rewrite())
|
||||
except Exception:
|
||||
return True
|
||||
if not requires_rewrite:
|
||||
return False
|
||||
|
||||
envelope_name = str(getattr(envelope, "name", "") or "").strip().lower()
|
||||
if (
|
||||
envelope_name == "antigravity:v1internal"
|
||||
and provider_api_format == client_api_format
|
||||
and provider_api_format in {"gemini:chat", "gemini:cli"}
|
||||
):
|
||||
return False
|
||||
if (
|
||||
envelope_name == "kiro:generateassistantresponse"
|
||||
and provider_api_format == "claude:cli"
|
||||
and client_api_format == "claude:cli"
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _serialize_gateway_sync_proxy(proxy: Any) -> dict[str, Any] | None:
|
||||
if proxy is None:
|
||||
return None
|
||||
|
||||
raw = asdict(proxy)
|
||||
return {key: value for key, value in raw.items() if value is not None}
|
||||
|
||||
|
||||
def _serialize_gateway_sync_timeouts(timeouts: Any) -> dict[str, Any] | None:
|
||||
if timeouts is None:
|
||||
return None
|
||||
|
||||
raw = asdict(timeouts)
|
||||
return {key: value for key, value in raw.items() if value is not None}
|
||||
|
||||
|
||||
def _extract_gateway_upstream_auth(
|
||||
provider_request_headers: dict[str, str],
|
||||
*,
|
||||
provider_api_format: str,
|
||||
key: Any,
|
||||
) -> tuple[str, str]:
|
||||
normalized_headers = {
|
||||
str(header_name).strip().lower(): str(header_value).strip()
|
||||
for header_name, header_value in (provider_request_headers or {}).items()
|
||||
if str(header_name).strip() and str(header_value).strip()
|
||||
}
|
||||
for header_name in ("authorization", "x-api-key", "x-goog-api-key"):
|
||||
header_value = normalized_headers.get(header_name)
|
||||
if header_value:
|
||||
return header_name, header_value
|
||||
|
||||
auth_header, auth_type = get_auth_config_for_endpoint(provider_api_format)
|
||||
decrypted_key = crypto_service.decrypt(key.api_key)
|
||||
auth_value = f"Bearer {decrypted_key}" if auth_type == "bearer" else decrypted_key
|
||||
return str(auth_header or "").strip() or "authorization", auth_value
|
||||
Reference in New Issue
Block a user