fix(codex): canonicalise the custom-endpoint issuer kind

The openai SDK appends a trailing slash to `client.base_url`, so the aux
adapter stamped `other:https://h/v1/` while the main transport stamped
`other:https://h/v1`. On custom Responses endpoints every aux call
(compression, flush_memories) therefore dropped all main-minted reasoning
items as "foreign".

`_classify_responses_issuer` now strips whitespace and trailing slashes and
lowercases scheme+netloc before stamping. The aux adapter also derives its
route flags from `classify_responses_route` — the single owner of the
codex/xai/github predicates — instead of an inline chatgpt.com host check,
and reuses the same flags for the effort clamp.
This commit is contained in:
kshitijk4poor
2026-09-14 23:54:52 +05:30
committed by kshitij
parent cb49660bb4
commit 16986c4bff
3 changed files with 24 additions and 9 deletions
+6 -8
View File
@@ -1341,13 +1341,16 @@ class _CodexCompletionsAdapter:
_chat_messages_to_responses_input,
_classify_responses_issuer,
_wire_model_identity,
classify_responses_route,
)
model = kwargs.get("model", self._model)
wire_model = _wire_model_identity(model)
host = str(getattr(self._client, "base_url", "") or "")
is_xai = base_url_host_matches(host, "x.ai") or base_url_host_matches(host, "api.x.ai")
is_copilot = base_url_host_matches(host, "githubcopilot.com")
is_github = is_copilot or base_url_host_matches(host, "models.github.ai")
# Same route classifier as the main transport, so the issuer stamp matches what it minted.
route = classify_responses_route(SimpleNamespace(provider=None, base_url=host))
is_github = route.is_github_responses
# System → ``instructions``; the rest goes through the SINGLE shared chat→Responses
# converter (a private loop here once let role="tool" leak into input[]; the shared one
# encodes tool history as function_call/function_call_output).
@@ -1368,10 +1371,7 @@ class _CodexCompletionsAdapter:
# Aux requests run their own model; stamp/filter reasoning provenance against it, not the main agent's.
input_items = _chat_messages_to_responses_input(
replay_messages, is_github_responses=is_copilot,
current_issuer_kind=_classify_responses_issuer(
is_xai_responses=is_xai, is_github_responses=is_github,
is_codex_backend=base_url_host_matches(host, "chatgpt.com"), base_url=host,
),
current_issuer_kind=_classify_responses_issuer(base_url=host, **route._asdict()),
current_issuer_model=wire_model, native_compaction_eligible=False,
)
resp_kwargs: Dict[str, Any] = {
@@ -1400,13 +1400,11 @@ class _CodexCompletionsAdapter:
if isinstance(reasoning_cfg, dict) and reasoning_cfg.get("enabled") is not False:
# Truthy-only: Codex 400s on e.g. {"effort": null}, so falsy → default. Shared
# per-model clamp with the main transport ("max" is gpt-5.6-only; "minimal"/"ultra" rejected).
from agent.codex_responses_adapter import classify_responses_route
from agent.reasoning_effort import clamp_effort
from agent.transports.codex import _codex_efforts_for_route
is_codex_backend = classify_responses_route(SimpleNamespace(base_url=host)).is_codex_backend
effort = clamp_effort(
reasoning_cfg.get("effort") or "medium",
_codex_efforts_for_route(model, host, is_codex_backend=is_codex_backend),
_codex_efforts_for_route(model, host, is_codex_backend=route.is_codex_backend),
)
resp_kwargs["reasoning"] = {"effort": effort, "summary": "auto"}
resp_kwargs["include"] = ["reasoning.encrypted_content"]
+8 -1
View File
@@ -11,6 +11,7 @@ import unicodedata
import uuid
from types import SimpleNamespace
from typing import Any, Callable, Dict, Iterator, List, NamedTuple, Optional, TypeGuard
from urllib.parse import urlsplit, urlunsplit
from agent.message_sanitization import deterministic_call_id
from agent.prompt_builder import DEFAULT_AGENT_IDENTITY
@@ -27,7 +28,13 @@ def _classify_responses_issuer(
for flag, kind in ((is_xai_responses, "xai_responses"), (is_github_responses, "github_responses"), (is_codex_backend, "codex_backend")):
if flag:
return kind
return f"other:{base_url}" if base_url else "other"
if not base_url:
return "other"
# The openai SDK appends a trailing slash to ``client.base_url`` and hosts are case-insensitive, so the
# aux adapter and the main transport must canonicalise the same endpoint to one kind or aux calls drop
# every main-minted blob.
parts = urlsplit(str(base_url).strip().rstrip("/"))
return f"other:{urlunsplit((parts.scheme.lower(), parts.netloc.lower(), parts.path, parts.query, parts.fragment))}"
# Per-process throttle for the cross-issuer skip warning.
@@ -5,6 +5,7 @@ import pytest
from agent.codex_responses_adapter import (
_chat_content_to_responses_parts,
_chat_messages_to_responses_input,
_classify_responses_issuer,
_sanitize_replayed_fn_name,
_format_responses_error,
_normalize_codex_response,
@@ -610,6 +611,15 @@ def test_legacy_endpoint_stamped_item_without_model_replays_on_same_issuer():
assert not any(i.get("type") == "reasoning" for i in foreign)
def test_issuer_kind_is_canonical_across_trailing_slash_and_host_case():
# The openai SDK stores ``client.base_url`` with a trailing slash; the aux adapter and the main
# transport must agree on one issuer kind or aux calls drop every main-minted blob.
canonical = _classify_responses_issuer(base_url="https://h/v1")
assert _classify_responses_issuer(base_url="https://h/v1/") == canonical
assert _classify_responses_issuer(base_url=" HTTPS://H/v1 ") == canonical
assert _classify_responses_issuer(base_url="https://other/v1") != canonical
def test_preflight_codex_api_kwargs_drops_oversized_message_id_end_to_end():
kwargs = _preflight_codex_api_kwargs(
{