refactor: drop the dead _vision_supported flag
Image rejections are now tracked per (provider, model) in agent._image_rejecting_models, and recover_before_classification gates on that set. Nothing reads agent._vision_supported any more, so the write in turn_recovery and the per-turn reset entry in turn_context are dead state. Remove both so the next reader doesn't assume a turn-global vision gate still exists; update the two tests that asserted or seeded the attribute.
This commit is contained in:
@@ -558,7 +558,7 @@ _PER_TURN_RESET_STATE: Tuple[Tuple[str, Any], ...] = (
|
||||
("_thinking_prefill_retries", 0), ("_post_tool_empty_retried", False),
|
||||
("_last_content_with_tools", None), ("_last_content_tools_all_housekeeping", False),
|
||||
("_mute_post_response", False), ("_unicode_sanitization_passes", 0),
|
||||
("_tool_guardrail_halt_decision", None), ("_vision_supported", True),
|
||||
("_tool_guardrail_halt_decision", None),
|
||||
("_iteration_budget_warning_injected", False),
|
||||
("_run_budget_wrapup_injected", False), ("_verification_stop_nudges", 0),
|
||||
("_pre_verify_nudges", 0),
|
||||
|
||||
@@ -244,14 +244,13 @@ def recover_before_classification(
|
||||
_err_status = getattr(api_error, "status_code", None)
|
||||
# 4xx-only gate: 5xx/timeouts are transient and take the retry path.
|
||||
_status_ok = _err_status is None or (400 <= int(_err_status) < 500)
|
||||
# Guarded PER MODEL, not by the turn-global ``_vision_supported``: in a fallback chain the next
|
||||
# model can reject images too, and a turn-wide flag would skip its recovery and fail the turn.
|
||||
# Guarded PER MODEL, not by a turn-global flag: in a fallback chain the next model can reject
|
||||
# images too, and a turn-wide flag would skip its recovery and fail the turn.
|
||||
_model_key = image_model_key(agent)
|
||||
_rejected = getattr(agent, "_image_rejecting_models", None)
|
||||
if not isinstance(_rejected, set):
|
||||
_rejected = agent._image_rejecting_models = set()
|
||||
if _model_key not in _rejected and _looks_like_image_content_rejection(_err_body) and _status_ok:
|
||||
agent._vision_supported = False
|
||||
# Send-path only. A rejection says what THIS model accepts, not what the conversation
|
||||
# holds: stripping ``messages`` (canonical history) and forcing a flush deleted every
|
||||
# image — and every image-only message — from state.db for good, so a later switch to a
|
||||
|
||||
@@ -227,8 +227,8 @@ class TestStripImagesDropsStaleApiContent:
|
||||
previously sent for a message, and the next turn substitutes it back into
|
||||
``content``. Leaving it in place on a message this function rewrote would
|
||||
replay the images the strip just removed — and the recovery cannot re-fire,
|
||||
because it sets ``_vision_supported = False`` and gates itself on that. The
|
||||
session would then send rejected images on every subsequent turn.
|
||||
because it records the model in ``_image_rejecting_models`` and gates itself
|
||||
on that. The session would then send rejected images on every subsequent turn.
|
||||
|
||||
Same contract the other content-rewrite paths follow (stale-confirmation
|
||||
redaction in ``replay_cleanup``, compression rewrites, merge-into-tail):
|
||||
@@ -317,7 +317,7 @@ class TestRejectionNeverReachesPersistedHistory:
|
||||
from types import SimpleNamespace
|
||||
|
||||
return SimpleNamespace(
|
||||
provider=provider, model=model, _vision_supported=True, _force_ascii_payload=False,
|
||||
provider=provider, model=model, _force_ascii_payload=False,
|
||||
_image_rejecting_models=set(), _db_flush_scan_prefix=7, log_prefix="",
|
||||
_vprint=lambda *a, **k: None,
|
||||
)
|
||||
|
||||
@@ -90,7 +90,6 @@ class _FakeAgent:
|
||||
self._stream_think_scrubber = None
|
||||
# Attributes the prologue assigns; recorded for assertions.
|
||||
self._invalid_tool_retries = -1
|
||||
self._vision_supported = None
|
||||
self._persist_calls = 0
|
||||
self._session_messages = []
|
||||
self._pending_cli_user_message = None
|
||||
@@ -383,10 +382,9 @@ def test_turn_start_replaces_stale_parent_history_with_compression_child():
|
||||
def test_applies_agent_side_effects():
|
||||
agent = _FakeAgent()
|
||||
_build(agent)
|
||||
# Retry counters reset, guardrails reset, vision re-armed, turn counted.
|
||||
# Retry counters reset, guardrails reset, turn counted.
|
||||
assert agent._invalid_tool_retries == 0
|
||||
assert agent._tool_guardrails.reset_called is True
|
||||
assert agent._vision_supported is True
|
||||
assert agent._user_turn_count == 1
|
||||
# Crash-resilience persistence fired once.
|
||||
assert agent._persist_calls == 1
|
||||
|
||||
Reference in New Issue
Block a user