From 83b7c52ece76b73ee878e9336722572e7d273cab Mon Sep 17 00:00:00 2001 From: r266-tech Date: Mon, 22 Jun 2026 09:32:56 +0800 Subject: [PATCH] fix(tui_gateway): don't fall back context_used to cumulative session_total_tokens _get_usage substituted the cumulative lifetime session_total_tokens into the current-window context_used when an external context engine did not report last_prompt_tokens, producing impossible status-bar readings (e.g. 1.9m/120k clamped to 100%). Populate context_used/percent only from a real current occupancy; leave the gauge unset otherwise. The built-in compressor always reports last_prompt_tokens, so it's unaffected. Fixes #50421. --- tests/test_tui_gateway_server.py | 39 ++++++++++++++++++++++++++++++++ tui_gateway/server.py | 21 +++++++++++++---- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 2487e6b95e4..7b4cb62efc1 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -8474,3 +8474,42 @@ class TestResolveRuntimeWithFallback: assert agent.model == "gpt-5.5" assert captured["provider"] == "deepseek" + + +def test_get_usage_does_not_substitute_cumulative_total_for_context_used(): + """An external context engine that does not report last_prompt_tokens must + not have the cumulative lifetime session_total_tokens shown as its current + context occupancy — that substitution produced impossible 1.9m/120k (100%) + status-bar readings (#50421). With no real current occupancy known, + context_used/percent stay unset rather than wrong.""" + agent = types.SimpleNamespace( + model="test-model", + session_total_tokens=1_900_000, + context_compressor=types.SimpleNamespace( + last_prompt_tokens=0, + context_length=120_000, + compression_count=0, + ), + ) + usage = server._get_usage(agent) + assert usage.get("context_used") != 1_900_000 + assert "context_used" not in usage + assert "context_percent" not in usage + + +def test_get_usage_reports_real_current_occupancy(): + """When the compressor reports a real current prompt size, context_used is + that value (not the cumulative total) and the percent is sane.""" + agent = types.SimpleNamespace( + model="test-model", + session_total_tokens=1_900_000, + context_compressor=types.SimpleNamespace( + last_prompt_tokens=60_000, + context_length=120_000, + compression_count=2, + ), + ) + usage = server._get_usage(agent) + assert usage["context_used"] == 60_000 + assert usage["context_max"] == 120_000 + assert usage["context_percent"] == 50 diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 6c39b3a66f4..bfdded9dff3 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2964,12 +2964,25 @@ def _get_usage(agent) -> dict: } comp = getattr(agent, "context_compressor", None) if comp: - ctx_used = getattr(comp, "last_prompt_tokens", 0) or usage["total"] or 0 + # context_used is the *current-window* occupancy. Do NOT fall back to + # usage["total"] (cumulative lifetime session_total_tokens): for an + # external context engine that doesn't report last_prompt_tokens that + # substitution showed lifetime totals as the live context fill, yielding + # impossible readings such as 1.9m/120k clamped to 100% (#50421). + # + # Per the issue, populate context_used/percent only from a *real* + # current-occupancy value and "leave it unknown otherwise" — so a falsy + # last_prompt_tokens (0 or missing, i.e. an engine that doesn't track + # per-window occupancy) intentionally emits no gauge rather than a + # fabricated 0% or the old cumulative reading. The built-in compressor + # always reports a real last_prompt_tokens once a turn runs, so it is + # unaffected. + last_prompt = getattr(comp, "last_prompt_tokens", 0) or 0 ctx_max = getattr(comp, "context_length", 0) or 0 - if ctx_max: - usage["context_used"] = ctx_used + if ctx_max and last_prompt: + usage["context_used"] = last_prompt usage["context_max"] = ctx_max - usage["context_percent"] = max(0, min(100, round(ctx_used / ctx_max * 100))) + usage["context_percent"] = max(0, min(100, round(last_prompt / ctx_max * 100))) usage["compressions"] = getattr(comp, "compression_count", 0) or 0 # Live count of background/async subagents still running (delegate_task # batches + background single delegations). Mirrors the classic CLI status