mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
* fix(codex-responses): gracefully recover from invalid_encrypted_content (salvage #10144) When an OpenAI-compatible Responses API surface accepts an initial request but later rejects the replayed `codex_reasoning_items` encrypted blob with HTTP 400 `invalid_encrypted_content`, the session previously got stuck retrying the same poisoned payload. Recovery: classify the error as a dedicated FailoverReason, and on the first hit disable encrypted reasoning replay for the rest of the session, strip cached items from message history, and retry once. Changes: * error_classifier: add FailoverReason.invalid_encrypted_content branch in _classify_400 (before context_overflow so the messages that mention 'encrypted content … could not be verified' don't trip context heuristics), in _classify_by_error_code, and extend _extract_error_code to peek inside wrapped JSON in error.message and ignore the bare '400' as a code. * agent_init: initialize `_codex_reasoning_replay_enabled = True` on every agent. * run_agent: add AIAgent._disable_codex_reasoning_replay() helper that flips the flag and pops cached items. * codex_responses_adapter: thread a `replay_encrypted_reasoning` kwarg through _chat_messages_to_responses_input so that when the flag is False we don't replay codex_reasoning_items. * transports/codex.py: read `replay_encrypted_reasoning` from params, thread it into the adapter, and gate the `include=['reasoning.encrypted_content']` request hint on it. * chat_completion_helpers: pass the agent's replay flag through to the transport. * conversation_loop: in the retry loop, add an invalid_encrypted_content recovery branch that fires once per session, only when api_mode == codex_responses, only when replay is still enabled, and only when at least one assistant message in history actually carries cached reasoning items (otherwise the 400 has nothing to do with our cache and the normal retry path handles it). Tests: * test_error_classifier: new wrapped-JSON _extract_error_code case; new TestClassifyApiError cases proving the 400 is retryable with no fallback, that the broad message match doesn't catch a generic 'parsed' message, and that the error code match is case-insensitive. * test_run_agent_codex_responses: end-to-end test of the recovery branch firing once and disabling replay, plus a sibling test that proves the branch does *not* fire (and the flag stays True) when history has no cached reasoning items. Salvages PR #10144 onto the post-refactor module layout (error_classifier / codex_responses_adapter / transports/codex / conversation_loop / agent_init) since the original diff was written against the pre-refactor monolithic run_agent.py. * chore(release): map victorGPT in AUTHOR_MAP for #10144 salvage --------- Co-authored-by: victorGPT <wuxuebin1993@gmail.com>
This commit is contained in:
parent
9d3e9316f4
commit
b6ca56f651
10 changed files with 342 additions and 5 deletions
|
|
@ -1019,6 +1019,7 @@ def run_conversation(
|
|||
nous_auth_retry_attempted=False
|
||||
copilot_auth_retry_attempted=False
|
||||
thinking_sig_retry_attempted = False
|
||||
invalid_encrypted_content_retry_attempted = False
|
||||
image_shrink_retry_attempted = False
|
||||
multimodal_tool_content_retry_attempted = False
|
||||
oauth_1m_beta_retry_attempted = False
|
||||
|
|
@ -2296,6 +2297,49 @@ def run_conversation(
|
|||
)
|
||||
continue
|
||||
|
||||
# ── Invalid encrypted reasoning replay recovery ───────
|
||||
# OpenAI Responses API surfaces (and some compatible relays)
|
||||
# return HTTP 400 ``invalid_encrypted_content`` when a
|
||||
# replayed ``codex_reasoning_items`` blob from a previous
|
||||
# turn fails verification (provider rotated the encryption
|
||||
# key, the route doesn't actually persist reasoning state,
|
||||
# etc.). Recovery: disable replay for the rest of the
|
||||
# session, strip cached items from history, retry once.
|
||||
# One-shot — if a second 400 fires we fall through to the
|
||||
# normal retry/backoff path. Only fires for codex_responses
|
||||
# mode with at least one assistant message that has cached
|
||||
# ``codex_reasoning_items``; without replay state, the
|
||||
# error is unrelated to our cache so the normal retry path
|
||||
# handles it (the provider is rejecting something else).
|
||||
if (
|
||||
classified.reason == FailoverReason.invalid_encrypted_content
|
||||
and not invalid_encrypted_content_retry_attempted
|
||||
and agent.api_mode == "codex_responses"
|
||||
and bool(getattr(agent, "_codex_reasoning_replay_enabled", True))
|
||||
and any(
|
||||
isinstance(_m, dict)
|
||||
and _m.get("role") == "assistant"
|
||||
and isinstance(_m.get("codex_reasoning_items"), list)
|
||||
and _m.get("codex_reasoning_items")
|
||||
for _m in messages
|
||||
)
|
||||
):
|
||||
invalid_encrypted_content_retry_attempted = True
|
||||
replay_stats = agent._disable_codex_reasoning_replay(messages)
|
||||
agent._vprint(
|
||||
f"{agent.log_prefix}⚠️ Encrypted reasoning replay was rejected by the provider — "
|
||||
f"disabled replay and stripped {replay_stats['items']} item(s) from "
|
||||
f"{replay_stats['messages']} message(s), retrying...",
|
||||
force=True,
|
||||
)
|
||||
logger.warning(
|
||||
"%sInvalid encrypted reasoning recovery: disabled replay and stripped %d items from %d messages",
|
||||
agent.log_prefix,
|
||||
replay_stats["items"],
|
||||
replay_stats["messages"],
|
||||
)
|
||||
continue
|
||||
|
||||
# ── llama.cpp grammar-parse recovery ──────────────────
|
||||
# llama.cpp's ``json-schema-to-grammar`` converter rejects
|
||||
# regex escape classes (``\d``, ``\w``, ``\s``) and most
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue