From deb2057e20a60012eda4dcefce50f255a86a4d11 Mon Sep 17 00:00:00 2001 From: oyasumi <121568595+kusonooyasumi@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:36:01 -0400 Subject: [PATCH] fix(tui): preserve cost when state is truncated (#1086) Co-authored-by: oyasumi --- strix/interface/tui/backend/projection.py | 6 +++-- tests/test_tui_backend_server.py | 28 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/strix/interface/tui/backend/projection.py b/strix/interface/tui/backend/projection.py index 22fa957e..3469aa4d 100644 --- a/strix/interface/tui/backend/projection.py +++ b/strix/interface/tui/backend/projection.py @@ -146,7 +146,9 @@ def bounded_state_projection(state: dict[str, Any]) -> dict[str, Any]: } for message in state["messages"][-5:] ] - state["usage"] = {} + state["usage"] = { + key: state["usage"][key] for key in ("total_tokens", "cost") if key in state["usage"] + } state["error"] = terminal_projection(state["error"], max_string=512) state["model_warning"] = terminal_projection(state["model_warning"], max_string=256) state["caido_url"] = terminal_projection(state["caido_url"], max_string=256) @@ -173,7 +175,7 @@ def bounded_state_projection(state: dict[str, Any]) -> dict[str, Any]: "model_warning": "", "caido_url": None, "messages": [], - "usage": {}, + "usage": state["usage"], "subscription": state["subscription"], "viewer_status": state["viewer_status"], "viewer_url": None, diff --git a/tests/test_tui_backend_server.py b/tests/test_tui_backend_server.py index d3e08088..eb4e3239 100644 --- a/tests/test_tui_backend_server.py +++ b/tests/test_tui_backend_server.py @@ -13,7 +13,7 @@ from agents.tool import ToolOutputImage from strix.config.settings import DEFAULT_MAX_TURNS from strix.interface.tui.backend.controller import TuiController -from strix.interface.tui.backend.projection import terminal_projection +from strix.interface.tui.backend.projection import bounded_state_projection, terminal_projection from strix.interface.tui.backend.protocol import ( MAX_COMMAND_BYTES, PROTOCOL_CAPABILITIES, @@ -215,7 +215,11 @@ def test_unicode_heavy_setup_state_stays_within_control_frame_limit() -> None: "Any", SimpleNamespace( caido_url="https://δΎ‹γˆ.example/" + "道" * 10_000, - get_total_llm_usage=lambda: {f"model-{index}": "θ²»" * 10_000 for index in range(20)}, + get_total_llm_usage=lambda: { + "total_tokens": 720_400, + "cost": 20.0, + **{f"model-{index}": "πŸ”’" * 10_000 for index in range(20)}, + }, ), ) server = TuiBackendServer(controller) @@ -226,6 +230,26 @@ def test_unicode_heavy_setup_state_stays_within_control_frame_limit() -> None: assert len(encoded) <= MAX_COMMAND_BYTES assert "πŸ”’".encode() in encoded assert snapshot["projection_truncated"] is True + assert snapshot["usage"] == {"total_tokens": 720_400, "cost": 20.0} + + +def test_defensive_state_projection_preserves_usage_summary() -> None: + controller = TuiController(args()) + controller.report_state = cast( + "Any", + SimpleNamespace( + caido_url=None, + get_total_llm_usage=lambda: {"total_tokens": 720_400, "cost": 20.0}, + ), + ) + state = controller.snapshot() + state["provider"] = None + state["future_oversized_field"] = "x" * 100_000 + + snapshot = bounded_state_projection(state) + + assert snapshot["projection_truncated"] is True + assert snapshot["usage"] == {"total_tokens": 720_400, "cost": 20.0} @pytest.mark.asyncio