fix(compressor): widen empty-content abort to sibling no-response shapes + snapshot state field

Follow-up to PR #94531 salvage:
- classify the auxiliary boundary's terminal 'None response' /
  'invalid response' errors (#7264) into the same empty-content abort
  carve-out so those shapes also preserve the session (#94459's wider
  classification, sibling shapes from #94448)
- register _last_summary_empty_content_failure in
  _COMPRESSOR_ATTEMPT_STATE_FIELDS so pre-commit hard-cancel rollback
  restores the flag (conversation_compression snapshot allow-list)
- tests: cooldown re-entry keeps aborting; both sibling shapes abort
- attribution: map zhangyswx@163.com -> YusenZhang0601
This commit is contained in:
kshitijk4poor
2026-08-25 14:36:05 +05:30
committed by kshitij
parent fa210e5a96
commit 4ba2608524
4 changed files with 66 additions and 2 deletions

View File

@@ -5150,8 +5150,14 @@ This compaction should PRIORITISE preserving all information related to the focu
_is_streaming_closed = _is_connection_error(e)
# Provider returned HTTP 200 with empty or whitespace body (e.g.
# degraded proxy channel / upstream provider fault; #94448).
_is_empty_content = (
isinstance(e, RuntimeError) and "empty content" in _err_str
_is_empty_content = isinstance(e, RuntimeError) and (
"empty content" in _err_str
# Sibling terminal "no usable response" shapes from the
# auxiliary boundary's _validate_llm_response (#7264): a None
# response or a malformed/missing choices[0].message — same
# degraded-provider class (#94448).
or "llm returned none response" in _err_str
or "llm returned invalid response" in _err_str
)
# Authentication, permission, and exhausted-quota failures are NOT
# transient or fixable by retrying the same request. Flag them so

View File

@@ -289,6 +289,7 @@ _COMPRESSOR_ATTEMPT_STATE_FIELDS = (
"_last_compress_aborted",
"_last_summary_auth_failure",
"_last_summary_network_failure",
"_last_summary_empty_content_failure",
"_last_aux_model_failure_error",
"_last_aux_model_failure_model",
"_summary_model_fallen_back",

View File

@@ -0,0 +1,2 @@
YusenZhang0601
# PR #94531 salvage (#94448)

View File

@@ -966,6 +966,61 @@ class TestAuthFailureAborts:
assert c._last_summary_fallback_used is False
assert c._last_summary_dropped_count == 0
# Cooldown re-entry must keep aborting, same as network/auth —
# _generate_summary() returns None from the cooldown early-return
# without re-asserting the flag, so compress() must still see it.
second = c.compress(msgs, current_tokens=999999)
assert second == msgs
assert c._last_compress_aborted is True
assert c._last_summary_fallback_used is False
def test_auxiliary_none_response_aborts_compression(self):
"""Sibling shape (#94459, from #7264): the auxiliary boundary's own
terminal "None response" error is the same degraded-provider class
and must ABORT, not fall through to the destructive fallback."""
with patch("agent.context_compressor.get_model_context_length", return_value=100000):
c = ContextCompressor(
model="test",
quiet_mode=True,
protect_first_n=2,
protect_last_n=2,
abort_on_summary_failure=False,
)
msgs = self._msgs(12)
with patch(
"agent.context_compressor.call_llm",
side_effect=RuntimeError("Auxiliary compression: LLM returned None response"),
):
result = c.compress(msgs, current_tokens=999999, force=True)
assert result == msgs
assert c._last_compress_aborted is True
assert c._last_summary_empty_content_failure is True
def test_auxiliary_invalid_response_aborts_compression(self):
"""Sibling shape (#94459, from #7264): malformed/missing
choices[0].message terminal error must ABORT the same way."""
with patch("agent.context_compressor.get_model_context_length", return_value=100000):
c = ContextCompressor(
model="test",
quiet_mode=True,
protect_first_n=2,
protect_last_n=2,
abort_on_summary_failure=False,
)
msgs = self._msgs(12)
with patch(
"agent.context_compressor.call_llm",
side_effect=RuntimeError(
"Auxiliary compression: LLM returned invalid response "
"(type=str): 'oops'. Expected object with .choices[0].message "
"— check provider adapter or custom endpoint compatibility."
),
):
result = c.compress(msgs, current_tokens=999999, force=True)
assert result == msgs
assert c._last_compress_aborted is True
assert c._last_summary_empty_content_failure is True
class TestSummaryFallbackToMainModel:
"""When ``summary_model`` differs from the main model and the summary LLM