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:
@@ -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"]
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user