diff --git a/app/modules/api_keys/repository.py b/app/modules/api_keys/repository.py index 8fb9b77a8..4467ddbda 100644 --- a/app/modules/api_keys/repository.py +++ b/app/modules/api_keys/repository.py @@ -106,6 +106,11 @@ class ApiKeysRepository: def __init__(self, session: AsyncSession) -> None: self._session = session + async def update_last_used(self, key_id: str, *, commit: bool = True) -> None: + await self._session.execute(update(ApiKey).where(ApiKey.id == key_id).values(last_used_at=utcnow())) + if commit: + await self._session.commit() + @staticmethod def _build_account_costs(rows: Sequence[object]) -> list[ApiKeyAccountCost]: account_costs: list[ApiKeyAccountCost] = [] diff --git a/app/modules/proxy/_service/websocket/mixin.py b/app/modules/proxy/_service/websocket/mixin.py index 50308cbc7..9fbdb0e90 100644 --- a/app/modules/proxy/_service/websocket/mixin.py +++ b/app/modules/proxy/_service/websocket/mixin.py @@ -1676,8 +1676,21 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: and upstream_control is not None and upstream_control.reconnect_requested and upstream_reader is not None + and ( + # Keep receiving downstream controls while a live + # reader still owns pending work; only replay or a + # completed/empty drain needs immediate settlement. + upstream_reader.done() + or upstream_control.replay_request_state is not None + or not pending_requests + ) ): - await upstream_reader + try: + await upstream_reader + except asyncio.CancelledError: + current_task = asyncio.current_task() + if current_task is not None and current_task.cancelling(): + raise if replay_request_state is None: replay_request_state = upstream_control.replay_request_state upstream_reader = None @@ -1727,6 +1740,7 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: ("previous response", previous_response_owner_account_id), ) except ProxyResponseError as exc: + response_create_request_state = request_state error = _parse_openai_error(exc.payload) error_code = _normalize_error_code( error.code if error else None, @@ -1735,7 +1749,7 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: error_message = error.message if error and error.message else "Upstream error" error_type = error.type if error and error.type else "server_error" error_param = error.param if error else None - await proxy._release_websocket_request_state_reservation(request_state) + await proxy._release_websocket_request_state_reservation(response_create_request_state) await proxy._write_websocket_connect_failure( account_id=None, api_key=api_key,