Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 同一代理在 OpenCode 侧仍是旧逻辑,三个宿主从此显示不同的值

本 PR 只改了两个 Python 宿主,但这段代理还有第三份实现,且两份被改文件的 docstring 明写"Render the same two-window compression proxy used by OpenCode"。OpenCode 侧仍然是 reductionOf(value) = tokenTotals(value)?.token_reduction(integrations/opencode/plugins/powercontext/src/tui.tsx:124),并同步编译进 integrations/opencode/plugins/powercontext/lib/tui.js:2157。

后果:同一个 recall.estimator: null + 全零 totals 的响应,Claude Code 与 Codex 现在渲染 no data today · no data in 30d,OpenCode 仍渲染 saved 0 today · saved 0 in 30d。也就是说本 PR 修掉的是"缺估算被当成 0",但用户换宿主仍会看到被当成 0 的那一版,两个宿主之间也第一次出现同 payload 不同文案。

issue #1926 只列了这两个文件,所以这不是本 PR 的范围错误;建议二选一:顺手把 measuredReduction 等价逻辑加到 OpenCode 的 src 与 lib(lib/tui.js 是提交进仓库的构建产物,两处都要改),或在本 PR 内明确记录一个后续 issue,避免这份代理继续分叉。

另:test_a_window_without_estimates_reads_as_unavailable_not_zero 断言的是 _stats(0) 仍渲染 saved 0,这与 comparable_preparations > 0 的分界一致,覆盖到位。

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((
Expand Down
34 changes: 28 additions & 6 deletions integrations/codex/plugins/powercontext/hooks/token_savings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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')}"
)


Expand Down
60 changes: 60 additions & 0 deletions tests/claude_code_plugin/test_statusline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
55 changes: 55 additions & 0 deletions tests/codex_plugin/test_token_savings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading