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.
This commit is contained in:
r266-tech 2026-06-22 09:32:56 +08:00 committed by kshitij
parent 6c3545d9e9
commit 83b7c52ece
2 changed files with 56 additions and 4 deletions

View file

@ -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

View file

@ -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