From 5e7320fba03afb3100ac39543cb5552f2f35664f Mon Sep 17 00:00:00 2001 From: knope <102312680+not-knope@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:49:58 +0200 Subject: [PATCH] fix(interface): clarify cached vs new input token breakdown Co-authored-by: Cursor --- strix/interface/utils.py | 20 +++++++--- tests/test_interface_stats.py | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 tests/test_interface_stats.py diff --git a/strix/interface/utils.py b/strix/interface/utils.py index bffc0d47d..67f5b2af3 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -296,16 +296,26 @@ def _build_llm_usage_stats( input_tokens = _int_stat(usage, "input_tokens") output_tokens = _int_stat(usage, "output_tokens") cached_tokens = _detail_value(usage, "input_tokens_details", "cached_tokens") + uncached_tokens = max(0, input_tokens - cached_tokens) cost = _float_stat(usage, "cost") stats_text.append("\n") - stats_text.append("Input Tokens ", style="dim") - stats_text.append(format_token_count(input_tokens), style="white") - - if live or cached_tokens > 0: + if cached_tokens > 0: + stats_text.append("Input (total) ", style="dim") + stats_text.append(format_token_count(input_tokens), style="white") stats_text.append(" · ", style="dim white") - stats_text.append("Cached Tokens ", style="dim") + stats_text.append("Cached (in total) ", style="dim") stats_text.append(format_token_count(cached_tokens), style="white") + stats_text.append(" · ", style="dim white") + stats_text.append("New input ", style="dim") + stats_text.append(format_token_count(uncached_tokens), style="white") + else: + stats_text.append("Input Tokens ", style="dim") + stats_text.append(format_token_count(input_tokens), style="white") + if live: + stats_text.append(" · ", style="dim white") + stats_text.append("Cached (in total) ", style="dim") + stats_text.append(format_token_count(cached_tokens), style="white") separator = "\n" if live else " · " stats_text.append(separator, style="dim white") diff --git a/tests/test_interface_stats.py b/tests/test_interface_stats.py new file mode 100644 index 000000000..536c081a5 --- /dev/null +++ b/tests/test_interface_stats.py @@ -0,0 +1,70 @@ +"""Tests for TUI / CLI usage stats rendering.""" + +from __future__ import annotations + +from types import SimpleNamespace +from unittest.mock import MagicMock + +from strix.interface.utils import build_final_stats_text, build_live_stats_text + + +def _report_state_with_usage(usage: dict) -> SimpleNamespace: + state = SimpleNamespace(vulnerability_reports=[]) + state.get_total_llm_usage = MagicMock(return_value=usage) + return state + + +def test_usage_stats_without_cache_uses_simple_input_label() -> None: + text = build_final_stats_text( + _report_state_with_usage( + { + "requests": 1, + "input_tokens": 1_000_000, + "output_tokens": 500_000, + "cost": 0.5, + } + ) + ) + + plain = text.plain + assert "Input Tokens 1.0M" in plain + assert "Cached" not in plain + assert "New input" not in plain + + +def test_live_mode_without_cache_still_shows_cached_row() -> None: + """Live sidebar always exposes the cache metric, even when it is still zero.""" + text = build_live_stats_text( + _report_state_with_usage( + { + "requests": 1, + "input_tokens": 1_000_000, + "output_tokens": 500_000, + } + ) + ) + + plain = text.plain + assert "Input Tokens 1.0M" in plain + assert "Cached (in total) 0" in plain + assert "New input" not in plain + assert plain.index("Input Tokens") < plain.index("Cached (in total)") + + +def test_usage_stats_with_cache_shows_subset_breakdown() -> None: + text = build_live_stats_text( + _report_state_with_usage( + { + "requests": 1, + "input_tokens": 20_000_000, + "output_tokens": 1_000_000, + "input_tokens_details": {"cached_tokens": 19_000_000}, + } + ) + ) + + plain = text.plain + assert "Input (total) 20.0M" in plain + assert "Cached (in total) 19.0M" in plain + assert "New input 1.0M" in plain + assert plain.index("Input (total)") < plain.index("Cached (in total)") < plain.index("New input")