diff --git a/integrations/claude-code/plugins/powercontext/scripts/statusline.py b/integrations/claude-code/plugins/powercontext/scripts/statusline.py index bb2d092131..fdf653c363 100644 --- a/integrations/claude-code/plugins/powercontext/scripts/statusline.py +++ b/integrations/claude-code/plugins/powercontext/scripts/statusline.py @@ -24,7 +24,7 @@ from decimal import ROUND_HALF_UP, Decimal from pathlib import Path from time import monotonic -from typing import Any, Protocol +from typing import Any, Protocol, cast from urllib.error import HTTPError from urllib.request import Request @@ -77,8 +77,8 @@ def _scaled(value: float, suffix: str) -> str: return f"{rounded:.{digits}f}".removesuffix(".0") + suffix -def token_reduction(value: object) -> int | None: - """Return a validated signed reduction from one scoped-statistics response.""" +def _validated_totals(value: object) -> dict[str, Any] | None: + """Return the validated recall totals of one scoped-statistics response.""" if not isinstance(value, dict): return None @@ -102,7 +102,29 @@ def token_reduction(value: object) -> int | None: reduction = totals.get("token_reduction") if not isinstance(reduction, int) or isinstance(reduction, bool): return None - return reduction + return cast("dict[str, Any]", totals) + + +def token_reduction(value: object) -> int | None: + """Return a validated signed reduction from one scoped-statistics response.""" + + totals = _validated_totals(value) + return None if totals is None else totals["token_reduction"] + + +def measured_reduction(value: object) -> int | None: + """Return the reduction to display, or ``None`` when the window measured nothing. + + A window with no comparable preparations holds no estimate — estimation is + disabled, or the period had nothing comparable to measure — so it must read as + unavailable rather than as a measured zero. A window with comparable + preparations still reports a genuine zero reduction. + """ + + totals = _validated_totals(value) + if totals is None or totals["comparable_preparations"] == 0: + return None + return totals["token_reduction"] def savings_phrase(reduction: int | None, suffix: str) -> str: @@ -115,8 +137,8 @@ def savings_phrase(reduction: int | None, suffix: str) -> str: def format_statusline(today: object, month: object, *, color: bool = True) -> str: """Render the same two-window compression proxy used by OpenCode.""" - today_reduction = token_reduction(today) - month_reduction = token_reduction(month) + today_reduction = measured_reduction(today) + month_reduction = measured_reduction(month) if not color: return f"● PC online · {savings_phrase(today_reduction, 'today')} · {savings_phrase(month_reduction, 'in 30d')}" return "".join(( diff --git a/integrations/codex/plugins/powercontext/hooks/token_savings.py b/integrations/codex/plugins/powercontext/hooks/token_savings.py index 8fab9c39fc..acde8002b3 100644 --- a/integrations/codex/plugins/powercontext/hooks/token_savings.py +++ b/integrations/codex/plugins/powercontext/hooks/token_savings.py @@ -22,7 +22,7 @@ from decimal import ROUND_HALF_UP, Decimal from pathlib import Path from time import monotonic -from typing import Any +from typing import Any, cast from urllib.error import HTTPError from urllib.request import Request @@ -87,8 +87,8 @@ def _scaled(value: float, suffix: str) -> str: return f"{rounded:.{digits}f}".removesuffix(".0") + suffix -def token_reduction(value: object) -> int | None: - """Return a validated signed reduction from one scoped-statistics response.""" +def _validated_totals(value: object) -> dict[str, Any] | None: + """Return the validated recall totals of one scoped-statistics response.""" if not isinstance(value, dict): return None @@ -111,7 +111,29 @@ def token_reduction(value: object) -> int | None: reduction = totals.get("token_reduction") if not isinstance(reduction, int) or isinstance(reduction, bool): return None - return reduction + return cast("dict[str, Any]", totals) + + +def token_reduction(value: object) -> int | None: + """Return a validated signed reduction from one scoped-statistics response.""" + + totals = _validated_totals(value) + return None if totals is None else totals["token_reduction"] + + +def measured_reduction(value: object) -> int | None: + """Return the reduction to display, or ``None`` when the window measured nothing. + + A window with no comparable preparations holds no estimate — estimation is + disabled, or the period had nothing comparable to measure — so it must read as + unavailable rather than as a measured zero. A window with comparable + preparations still reports a genuine zero reduction. + """ + + totals = _validated_totals(value) + if totals is None or totals["comparable_preparations"] == 0: + return None + return totals["token_reduction"] def savings_phrase(reduction: int | None, suffix: str) -> str: @@ -125,8 +147,8 @@ def format_message(today: object, month: object) -> str: """Render the same two-window compression proxy used by OpenCode.""" return ( - f"PowerContext · {savings_phrase(token_reduction(today), 'today')}" - f" · {savings_phrase(token_reduction(month), 'in 30d')}" + f"PowerContext · {savings_phrase(measured_reduction(today), 'today')}" + f" · {savings_phrase(measured_reduction(month), 'in 30d')}" ) diff --git a/tests/claude_code_plugin/test_statusline.py b/tests/claude_code_plugin/test_statusline.py index 414c86c241..db48ec0f22 100644 --- a/tests/claude_code_plugin/test_statusline.py +++ b/tests/claude_code_plugin/test_statusline.py @@ -570,3 +570,63 @@ def test_render_does_not_pair_another_servers_authorization_with_the_host_server assert "PC online" in rendered assert received assert {authorization for _, authorization in received} == {None} + + +def _without_estimate() -> dict[str, object]: + """A valid window where estimation is disabled: no comparable preparations.""" + + return { + "recall": { + "estimator": None, + "totals": { + "preparations": 0, + "ready_preparations": 0, + "comparable_preparations": 0, + "baseline_tokens": 0, + "recalled_tokens": 0, + "token_reduction": 0, + }, + } + } + + +def test_a_window_without_estimates_reads_as_unavailable_not_zero(statusline_module: ModuleType) -> None: + assert statusline_module.format_statusline(_without_estimate(), _without_estimate(), color=False) == ( + "● PC online · no data today · no data in 30d" + ) + # A window that measured comparable preparations still reports a genuine zero. + assert statusline_module.format_statusline(_stats(0), _stats(0), color=False) == ( + "● PC online · saved 0 today · saved 0 in 30d" + ) + + +def test_render_keeps_a_response_without_estimates_online( + statusline_module: ModuleType, + monkeypatch, + tmp_path: Path, +) -> None: + monkeypatch.setattr( + statusline_module.sys, + "stdin", + StringIO(json.dumps({"workspace": {"current_dir": str(tmp_path)}})), + ) + with _serve(_stats_handler(_without_estimate())) as server_url: + monkeypatch.setenv("POWERCONTEXT_CLAUDE_SERVER_URL", server_url) + rendered = statusline_module.render("http://127.0.0.1:9000") + + assert "PC online" in rendered + assert "no data today" in rendered + + +def test_the_reduction_helpers_return_an_int_or_none_not_the_totals(statusline_module: ModuleType) -> None: + """A well-formed window yields numbers, never the validated totals mapping. + + The display helper resolves a missing estimate to ``None`` (unavailable), while + the validity helper still reads the well-formed zero so the integration stays + online. + """ + + assert statusline_module.token_reduction(_without_estimate()) == 0 + assert statusline_module.measured_reduction(_without_estimate()) is None + assert statusline_module.token_reduction(_stats(0)) == 0 + assert statusline_module.measured_reduction(_stats(0)) == 0 diff --git a/tests/codex_plugin/test_token_savings.py b/tests/codex_plugin/test_token_savings.py index 4c6584ab04..7411ffead9 100644 --- a/tests/codex_plugin/test_token_savings.py +++ b/tests/codex_plugin/test_token_savings.py @@ -740,3 +740,58 @@ def log_message(self, format: str, *args: Any) -> None: # noqa: A002 assert completed.returncode == 0 events = _diagnostics(io.StringIO(completed.stdout)) assert [event["outcome"] for event in events] == ["server_unavailable"] + + +def _without_estimate() -> dict[str, object]: + """A valid window where estimation is disabled: no comparable preparations.""" + + return { + "recall": { + "estimator": None, + "totals": { + "preparations": 0, + "ready_preparations": 0, + "comparable_preparations": 0, + "baseline_tokens": 0, + "recalled_tokens": 0, + "token_reduction": 0, + }, + } + } + + +def test_a_window_without_estimates_reads_as_unavailable_not_zero(token_savings_module: ModuleType) -> None: + assert token_savings_module.format_message(_without_estimate(), _without_estimate()) == ( + "PowerContext · no data today · no data in 30d" + ) + # A window that measured comparable preparations still reports a genuine zero. + assert token_savings_module.format_message(_stats(0), _stats(0)) == ( + "PowerContext · saved 0 today · saved 0 in 30d" + ) + + +def test_stop_keeps_a_response_without_estimates_online( + token_savings_module: ModuleType, + monkeypatch: pytest.MonkeyPatch, +) -> None: + with _serve(_stats_handler(_without_estimate())) as server_url: + output = _run_main(token_savings_module, monkeypatch, _settings(token_savings_module, server_url)) + + assert json.loads(output.getvalue()) == { + "continue": True, + "systemMessage": "PowerContext · no data today · no data in 30d", + } + + +def test_the_reduction_helpers_return_an_int_or_none_not_the_totals(token_savings_module: ModuleType) -> None: + """A well-formed window yields numbers, never the validated totals mapping. + + The display helper resolves a missing estimate to ``None`` (unavailable), while + the validity helper still reads the well-formed zero so the integration stays + online. + """ + + assert token_savings_module.token_reduction(_without_estimate()) == 0 + assert token_savings_module.measured_reduction(_without_estimate()) is None + assert token_savings_module.token_reduction(_stats(0)) == 0 + assert token_savings_module.measured_reduction(_stats(0)) == 0