Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ class ResponseFailedEvent(TypedDict):


PREVIOUS_RESPONSE_STREAM_INCOMPLETE_MESSAGE = "Upstream websocket closed before response.completed"
PREVIOUS_RESPONSE_STALE_CODE = "codex_previous_response_stale"
PREVIOUS_RESPONSE_STALE_MESSAGE = "Upstream previous response anchor expired; retry without previous_response_id."
PREVIOUS_RESPONSE_NOT_FOUND_CODE = "previous_response_not_found"
PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE = "Previous response was not found; retry without previous_response_id."


def openai_error(code: str, message: str, error_type: str = "server_error") -> OpenAIErrorEnvelope:
Expand Down Expand Up @@ -91,7 +91,7 @@ def is_previous_response_not_found_error(
param: str | None,
message: str | None,
) -> bool:
if code == "previous_response_not_found":
if code == PREVIOUS_RESPONSE_NOT_FOUND_CODE:
return True
if code != "invalid_request_error" or param != "previous_response_id":
return False
Expand Down
4 changes: 2 additions & 2 deletions app/modules/proxy/_service/streaming/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@
UpstreamWebSocket,
)
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_CODE as PREVIOUS_RESPONSE_STALE_CODE,
PREVIOUS_RESPONSE_NOT_FOUND_CODE as PREVIOUS_RESPONSE_NOT_FOUND_CODE,
)
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_MESSAGE as PREVIOUS_RESPONSE_STALE_MESSAGE,
PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE as PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE,
)
from app.core.errors import (
PREVIOUS_RESPONSE_STREAM_INCOMPLETE_MESSAGE,
Expand Down
4 changes: 2 additions & 2 deletions app/modules/proxy/_service/streaming/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
from app.core.clients.proxy import compact_responses as core_compact_responses # noqa: F401
from app.core.clients.proxy import transcribe_audio as core_transcribe_audio # noqa: F401
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_CODE as PREVIOUS_RESPONSE_STALE_CODE,
PREVIOUS_RESPONSE_NOT_FOUND_CODE as PREVIOUS_RESPONSE_NOT_FOUND_CODE,
)
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_MESSAGE as PREVIOUS_RESPONSE_STALE_MESSAGE,
PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE as PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE,
)
from app.core.errors import (
response_failed_event,
Expand Down
16 changes: 12 additions & 4 deletions app/modules/proxy/_service/websocket/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
UpstreamWebSocketMessage,
)
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_CODE,
PREVIOUS_RESPONSE_STALE_MESSAGE,
PREVIOUS_RESPONSE_NOT_FOUND_CODE,
PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE,
PREVIOUS_RESPONSE_STREAM_INCOMPLETE_MESSAGE,
OpenAIErrorEnvelope,
openai_error,
Expand Down Expand Up @@ -1064,7 +1064,7 @@ def _websocket_continuity_error_fields(
expose_stale_previous_response_classifier: bool,
) -> tuple[str, str]:
if reason == "previous_response_not_found" and expose_stale_previous_response_classifier:
return PREVIOUS_RESPONSE_STALE_CODE, PREVIOUS_RESPONSE_STALE_MESSAGE
return PREVIOUS_RESPONSE_NOT_FOUND_CODE, PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE
return "stream_incomplete", PREVIOUS_RESPONSE_STREAM_INCOMPLETE_MESSAGE


Expand Down Expand Up @@ -1753,6 +1753,8 @@ def _app_error_to_websocket_event(exc: AppError) -> dict[str, JsonValue]:
def _wrapped_websocket_error_event(
status_code: int,
payload: OpenAIErrorEnvelope,
*,
expose_stale_previous_response_classifier: bool = False,
) -> dict[str, JsonValue]:
error = payload["error"]
error_code = _normalize_error_code(
Expand All @@ -1767,7 +1769,13 @@ def _wrapped_websocket_error_event(
message=error_message,
):
status_code = 502
payload = previous_response_stream_incomplete_error()
# On the Codex-native route, the caller has already sanitized this to
# the canonical code (see _sanitize_websocket_previous_response_error);
# do not re-mask it back to stream_incomplete. Every other caller
# (public /v1, or a raw error this function is seeing for the first
# time) keeps the existing stream_incomplete safety net.
if not expose_stale_previous_response_classifier:
payload = previous_response_stream_incomplete_error()
error_payload = cast(JsonValue, dict(payload["error"]))
event: dict[str, JsonValue] = {
"type": "error",
Expand Down
16 changes: 14 additions & 2 deletions app/modules/proxy/_service/websocket/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,11 @@ async def retire_current_upstream() -> None:
async with client_send_lock:
await websocket.send_text(
_serialize_websocket_error_event(
_wrapped_websocket_error_event(status_code, error_payload)
_wrapped_websocket_error_event(
status_code,
error_payload,
expose_stale_previous_response_classifier=codex_session_affinity,
)
)
)
continue
Expand Down Expand Up @@ -4973,7 +4977,15 @@ async def _emit_websocket_connect_failure(
await _release_websocket_response_create_gate(request_state, response_create_gate)
async with client_send_lock:
await websocket.send_text(
_serialize_websocket_error_event(_wrapped_websocket_error_event(status_code, payload))
_serialize_websocket_error_event(
_wrapped_websocket_error_event(
status_code,
payload,
expose_stale_previous_response_classifier=(
request_state.expose_stale_previous_response_classifier
),
)
)
)

async def _emit_websocket_proxy_request_timeout(
Expand Down
4 changes: 2 additions & 2 deletions app/modules/proxy/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@
from app.core.config.settings_cache import get_settings_cache
from app.core.crypto import TokenEncryptor
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_CODE as PREVIOUS_RESPONSE_STALE_CODE,
PREVIOUS_RESPONSE_NOT_FOUND_CODE as PREVIOUS_RESPONSE_NOT_FOUND_CODE,
)
from app.core.errors import (
PREVIOUS_RESPONSE_STALE_MESSAGE as PREVIOUS_RESPONSE_STALE_MESSAGE,
PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE as PREVIOUS_RESPONSE_NOT_FOUND_MESSAGE,
)
from app.core.errors import (
OpenAIErrorEnvelope,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-30
Loading
Loading