fix(codex): drop the message id of Azure-trimmed reasoning turns too
_newest_reasoning_only pops older turns' codex_reasoning_items before the converter runs, so those turns looked reasoning-free and replayed their msg_* id with neither the reasoning item nor its rs_* id on the wire — the exact orphan shape #97427 rejects. The trimmed row now carries a transient codex_reasoning_trimmed marker and _replay_message_items honours it; the single-use _turn_has_encrypted_reasoning wrapper is inlined into that predicate. Test drives ResponsesApiTransport.build_kwargs on an Azure host.
This commit is contained in:
@@ -398,11 +398,6 @@ def _replay_reasoning_items(
|
||||
return replayed
|
||||
|
||||
|
||||
def _turn_has_encrypted_reasoning(msg: Dict[str, Any]) -> bool:
|
||||
"""True when the stored assistant turn produced an encrypted ``reasoning`` item alongside its message."""
|
||||
return any(isinstance(ri, dict) and ri.get("encrypted_content") for ri in _as_list(msg.get("codex_reasoning_items")))
|
||||
|
||||
|
||||
def _replay_message_items(
|
||||
msg: Dict[str, Any], *, is_github_responses: bool, current_issuer_kind: Optional[str] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
@@ -411,11 +406,14 @@ def _replay_message_items(
|
||||
A ``msg_*`` id minted in the same response as a ``reasoning`` item is bound to that item's ``rs_*`` id,
|
||||
which ``_replay_reasoning_items`` always strips (store=False). Replaying the message id alone is a
|
||||
deterministic HTTP 400 ("provided without its required 'reasoning' item", #97427/#97442), so the message
|
||||
id is dropped whenever its turn carried encrypted reasoning — replayed, suppressed or foreign-issuer —
|
||||
and the message goes out as content/status/phase only. Reasoning-free turns keep their id.
|
||||
id is dropped whenever its turn carried encrypted reasoning — replayed, suppressed, foreign-issuer or
|
||||
trimmed by the transport (``codex_reasoning_trimmed``) — and the message goes out as content/status/phase
|
||||
only. Reasoning-free turns keep their id.
|
||||
"""
|
||||
replayed: List[Dict[str, Any]] = []
|
||||
linked_to_reasoning = _turn_has_encrypted_reasoning(msg)
|
||||
linked_to_reasoning = bool(msg.get("codex_reasoning_trimmed")) or any(
|
||||
isinstance(ri, dict) and ri.get("encrypted_content") for ri in _as_list(msg.get("codex_reasoning_items"))
|
||||
)
|
||||
for raw_item in _as_list(msg.get("codex_message_items")):
|
||||
if not (isinstance(raw_item, dict) and raw_item.get("type") == "message" and raw_item.get("role") == "assistant"):
|
||||
continue
|
||||
|
||||
@@ -450,7 +450,8 @@ def _is_azure_responses(params: dict[str, Any]) -> bool:
|
||||
def _newest_reasoning_only(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Copy of ``messages`` keeping ``codex_reasoning_items`` only on the newest assistant row that has any.
|
||||
Foundry rejects a request that replays encrypted reasoning from more than one prior response (HTTP 400
|
||||
"Conflicting authenticated continuation identities", #105369). ``compaction`` checkpoints stay everywhere."""
|
||||
"Conflicting authenticated continuation identities", #105369). ``compaction`` checkpoints stay everywhere.
|
||||
A trimmed row is marked ``codex_reasoning_trimmed`` so the converter still drops its ``msg_*`` id (#97427)."""
|
||||
out: list[dict[str, Any]] = []
|
||||
newest_kept = False
|
||||
for msg in reversed(messages):
|
||||
@@ -458,7 +459,7 @@ def _newest_reasoning_only(messages: list[dict[str, Any]]) -> list[dict[str, Any
|
||||
if isinstance(items, list) and any(isinstance(i, dict) and i.get("type") != "compaction" for i in items):
|
||||
if newest_kept:
|
||||
checkpoints = [i for i in items if isinstance(i, dict) and i.get("type") == "compaction"]
|
||||
msg = dict(msg)
|
||||
msg = dict(msg, codex_reasoning_trimmed=True)
|
||||
if checkpoints:
|
||||
msg["codex_reasoning_items"] = checkpoints
|
||||
else:
|
||||
|
||||
@@ -481,6 +481,39 @@ class TestCodexBuildKwargs:
|
||||
reasoning = [item for item in kw["input"] if item.get("type") == "reasoning"]
|
||||
assert [item["encrypted_content"] for item in reasoning] == ["sealed-2"]
|
||||
|
||||
def test_azure_trimmed_reasoning_turn_still_drops_its_message_id(self, transport):
|
||||
"""The older turn's reasoning is trimmed for Azure (#105369) but its ``msg_*`` id is still bound to a
|
||||
``rs_*`` id that is no longer on the wire; the id must go with it (#97427). The newest turn's id is
|
||||
dropped too (its reasoning replays without id); a reasoning-free turn keeps its id."""
|
||||
def _turn(text, *, reasoning):
|
||||
msg = {
|
||||
"role": "assistant", "content": text,
|
||||
"codex_message_items": [{
|
||||
"type": "message", "role": "assistant", "status": "completed", "id": f"msg_{text}",
|
||||
"content": [{"type": "output_text", "text": text}],
|
||||
}],
|
||||
}
|
||||
if reasoning:
|
||||
msg["codex_reasoning_items"] = [{"type": "reasoning", "id": f"rs_{text}", "encrypted_content": f"sealed-{text}", "summary": []}]
|
||||
return msg
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "first"}, _turn("old", reasoning=True),
|
||||
{"role": "user", "content": "second"}, _turn("plain", reasoning=False),
|
||||
{"role": "user", "content": "third"}, _turn("new", reasoning=True),
|
||||
{"role": "user", "content": "fourth"},
|
||||
]
|
||||
kw = transport.build_kwargs(
|
||||
model="gpt-6-astra", messages=messages, tools=[],
|
||||
base_url="https://placeholder.openai.azure.com/openai/v1", replay_encrypted_reasoning=True,
|
||||
)
|
||||
reasoning = [i for i in kw["input"] if i.get("type") == "reasoning"]
|
||||
assert [i["encrypted_content"] for i in reasoning] == ["sealed-new"]
|
||||
by_text = {i["content"][0]["text"]: i for i in kw["input"] if i.get("type") == "message" and i.get("role") == "assistant"}
|
||||
assert "id" not in by_text["old"] and "id" not in by_text["new"]
|
||||
assert by_text["plain"]["id"] == "msg_plain"
|
||||
assert "codex_reasoning_items" in messages[1] # canonical history untouched
|
||||
|
||||
def test_default_responses_new_turn_replays_all_reasoning(self, transport):
|
||||
"""Non-Azure Responses endpoints keep cross-turn reasoning replay."""
|
||||
kw = transport.build_kwargs(
|
||||
|
||||
Reference in New Issue
Block a user