From d29b19cfd666334e817009b1f05cf059fe38ca43 Mon Sep 17 00:00:00 2001 From: andrew1234-arch Date: Sun, 30 Aug 2026 16:10:23 +0100 Subject: [PATCH] fix(provider): classify Gemini's real context-overflow message correctly Gemini's actual context-overflow error, 'The input token count (X) exceeds the maximum number of tokens allowed (Y).', matches none of the existing _is_context_overflow markers, so it fell through to BAD_REQUEST instead of CONTEXT_OVERFLOW. This meant the runtime never triggered COMPACT_AND_RETRY for Gemini context-limit errors, and the raw error was surfaced to the user instead. --- src/agentos/provider/failures.py | 7 +++++- tests/test_provider_failures.py | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/agentos/provider/failures.py b/src/agentos/provider/failures.py index 0d447c288..4c050182a 100644 --- a/src/agentos/provider/failures.py +++ b/src/agentos/provider/failures.py @@ -87,10 +87,15 @@ def _is_context_overflow(text: str) -> bool: "input exceeds", "provider_request_budget_exhausted", "too many tokens", + # Gemini's canonical input-token-limit error, e.g. "The input token + # count (5911388) exceeds the maximum number of tokens allowed + # (1048576)." — does not contain "input exceeds" or "maximum + # context" since the token counts sit between the fixed phrases. + "input token count", + "exceeds the maximum number of tokens allowed", ) ) - def _is_policy_refusal(text: str) -> bool: return any( marker in text diff --git a/tests/test_provider_failures.py b/tests/test_provider_failures.py index ca4cbf856..c15fb7a2e 100644 --- a/tests/test_provider_failures.py +++ b/tests/test_provider_failures.py @@ -13,3 +13,43 @@ def test_provider_request_budget_exhausted_is_context_overflow() -> None: ) is ProviderFailureKind.CONTEXT_OVERFLOW ) + +def test_gemini_input_token_count_message_is_context_overflow() -> None: + # Gemini's real, canonical context-overflow error. Numbers vary per + # request but the surrounding phrasing is fixed. Before the fix this + # matched none of the context-overflow markers and fell through to + # BAD_REQUEST, so the runtime never triggered COMPACT_AND_RETRY. + message = ( + "The input token count (5911388) exceeds the maximum number of " + "tokens allowed (1048576)." + ) + + assert ( + classify_provider_error( + provider_name="gemini", + status_code=400, + raw_code="400", + message=message, + ) + is ProviderFailureKind.CONTEXT_OVERFLOW + ) + + +def test_gemini_input_token_count_message_is_context_overflow_regardless_of_token_counts() -> ( + None +): + # Same shape, different digit counts — guards against a marker that + # accidentally depends on a specific number of digits. + message = ( + "The input token count (132478) exceeds the maximum number of " + "tokens allowed (131072)." + ) + + assert ( + classify_provider_error( + provider_name="gemini", + status_code=400, + message=message, + ) + is ProviderFailureKind.CONTEXT_OVERFLOW + )