mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(tui): chrome cost from Nous portal headers only (F3)
The status-bar cost segment must show cost ONLY when running against the Nous portal — per-model cache/input/output pricing is unreliable across the model long tail, so a guessed figure is worse than none. - New nous_header_cost_usd(agent): the chrome cost source, derived ONLY from the x-nous-credits-* header delta (deliberately ignores the OpenRouter usage.cost accumulator). _get_usage now uses it for cost_usd, so a non-Nous session reports no cost and the TUI hides the segment. - The /usage accounting page is unchanged in spirit: it now reads real_session_cost_usd(agent) directly (both provider-reported sources) instead of the chrome-narrowed _get_usage cost_usd, so OpenRouter cost still shows there. Tests: new TestNousHeaderCost (header-only, OR-accumulator ignored, clamp, no-method); updated gateway _get_usage tests for the chrome narrowing; /usage page test still asserts the full provider-reported figure. 316 gateway + 25 cost tests green.
This commit is contained in:
parent
ef9232a2f7
commit
e01b04de46
4 changed files with 82 additions and 11 deletions
|
|
@ -919,6 +919,33 @@ def real_session_cost_usd(agent: Any) -> Optional[float]:
|
|||
return total
|
||||
|
||||
|
||||
def nous_header_cost_usd(agent: Any) -> Optional[float]:
|
||||
"""Session-cumulative cost in USD derived ONLY from the Nous portal
|
||||
``x-nous-credits-*`` header delta, or None.
|
||||
|
||||
This is the STATUS-BAR cost source (glitch 2026-06-13, F3): the TUI chrome
|
||||
must show cost ONLY when the session runs against the Nous portal, because
|
||||
the header delta is the one figure we can trust without re-deriving per-model
|
||||
cache/input/output pricing (which is unreliable across the model long tail).
|
||||
Unlike :func:`real_session_cost_usd`, this DELIBERATELY ignores the
|
||||
OpenRouter ``usage.cost`` accumulator — a non-Nous route reports no header,
|
||||
so the chrome hides its cost segment entirely.
|
||||
|
||||
The ``/usage`` accounting page keeps using ``real_session_cost_usd`` (both
|
||||
provider-reported sources); only the chrome bar narrows to header-only.
|
||||
"""
|
||||
try:
|
||||
spent_micros = agent.get_credits_spent_micros()
|
||||
except Exception:
|
||||
return None
|
||||
if spent_micros is None:
|
||||
return None
|
||||
try:
|
||||
return max(0, int(spent_micros)) / 1_000_000
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def has_known_pricing(
|
||||
model_name: str,
|
||||
provider: Optional[str] = None,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from agent.usage_pricing import extract_provider_cost_usd, real_session_cost_usd
|
||||
from agent.usage_pricing import extract_provider_cost_usd, nous_header_cost_usd, real_session_cost_usd
|
||||
|
||||
|
||||
# ── extract_provider_cost_usd — the per-response REAL cost reader ────────────
|
||||
|
|
@ -86,6 +86,32 @@ class TestRealSessionCost:
|
|||
assert real_session_cost_usd(agent) is None
|
||||
|
||||
|
||||
# ── nous_header_cost_usd — the CHROME status-bar cost (F3: header-only) ──────
|
||||
|
||||
|
||||
class TestNousHeaderCost:
|
||||
def test_header_delta_only(self):
|
||||
# 123_400 micros = $0.1234 — the Nous header source feeds the chrome.
|
||||
assert nous_header_cost_usd(_FakeAgent(credits_micros=123_400)) == pytest.approx(0.1234)
|
||||
|
||||
def test_openrouter_accumulator_ignored(self):
|
||||
# The OpenRouter usage.cost accumulator must NOT feed the chrome bar:
|
||||
# a non-Nous session (no header → None) reports no cost even when the
|
||||
# OpenRouter accumulator has a value.
|
||||
assert nous_header_cost_usd(_FakeAgent(actual=0.42)) is None
|
||||
|
||||
def test_no_header_is_none(self):
|
||||
assert nous_header_cost_usd(_FakeAgent()) is None
|
||||
|
||||
def test_negative_delta_clamped(self):
|
||||
# A mid-session top-up makes the delta negative — never show negative.
|
||||
assert nous_header_cost_usd(_FakeAgent(credits_micros=-50_000)) == 0.0
|
||||
|
||||
def test_agent_without_credits_method_is_none(self):
|
||||
agent = SimpleNamespace(session_actual_cost_usd=0.42)
|
||||
assert nous_header_cost_usd(agent) is None
|
||||
|
||||
|
||||
# ── Nous header fixture → real accumulator (full _capture_credits path) ─────
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7444,11 +7444,15 @@ def test_get_usage_cost_absent_when_provider_reports_nothing(monkeypatch):
|
|||
assert usage["input"] == 35_000
|
||||
|
||||
|
||||
def test_get_usage_cost_present_when_provider_reported(monkeypatch):
|
||||
def test_get_usage_cost_absent_for_openrouter_accumulator_chrome(monkeypatch):
|
||||
"""F3: the chrome status bar shows cost ONLY from the Nous header delta.
|
||||
An OpenRouter usage.cost accumulator alone (no x-nous-credits header) must
|
||||
NOT surface cost_usd in _get_usage — the chrome hides cost off-Nous. (The
|
||||
/usage accounting page still counts the accumulator; that's a separate path.)
|
||||
"""
|
||||
agent = _usage_agent(session_actual_cost_usd=0.4321)
|
||||
usage = server._get_usage(agent)
|
||||
assert usage["cost_usd"] == pytest.approx(0.4321)
|
||||
assert usage["cost_status"] == "actual"
|
||||
assert "cost_usd" not in usage
|
||||
|
||||
|
||||
def test_get_usage_cost_from_nous_credits_delta(monkeypatch):
|
||||
|
|
@ -7456,6 +7460,7 @@ def test_get_usage_cost_from_nous_credits_delta(monkeypatch):
|
|||
agent.get_credits_spent_micros = lambda: 250_000 # $0.25 real header delta
|
||||
usage = server._get_usage(agent)
|
||||
assert usage["cost_usd"] == pytest.approx(0.25)
|
||||
assert usage["cost_status"] == "actual"
|
||||
|
||||
|
||||
def test_compact_usage_per_model_rows_and_real_cost(monkeypatch):
|
||||
|
|
|
|||
|
|
@ -2154,14 +2154,17 @@ def _get_usage(agent) -> dict:
|
|||
usage["context_max"] = ctx_max
|
||||
usage["context_percent"] = max(0, min(100, round(ctx_used / ctx_max * 100)))
|
||||
usage["compressions"] = getattr(comp, "compression_count", 0) or 0
|
||||
# Cost: provider-REPORTED only (OpenRouter usage.cost accumulator and/or
|
||||
# the Nous x-nous-credits-* header delta). Never estimated. When nothing
|
||||
# was reported `cost_usd` is ABSENT (not $0.00) and the TUI hides its
|
||||
# cost segment — that's the intended UX.
|
||||
# Cost (chrome status bar): Nous portal header delta ONLY (F3, glitch
|
||||
# 2026-06-13). The OpenRouter usage.cost accumulator is deliberately NOT
|
||||
# used here — per-model cache/input/output pricing is unreliable across the
|
||||
# model long tail, so the bar shows cost ONLY on a Nous-portal session and
|
||||
# hides it everywhere else. `cost_usd` is ABSENT (not $0.00) when no header
|
||||
# was seen, and the TUI hides its cost segment. (The /usage accounting page
|
||||
# still uses real_session_cost_usd — both provider-reported sources.)
|
||||
try:
|
||||
from agent.usage_pricing import real_session_cost_usd
|
||||
from agent.usage_pricing import nous_header_cost_usd
|
||||
|
||||
real_cost = real_session_cost_usd(agent)
|
||||
real_cost = nous_header_cost_usd(agent)
|
||||
if real_cost is not None:
|
||||
usage["cost_usd"] = real_cost
|
||||
usage["cost_status"] = "actual"
|
||||
|
|
@ -2234,7 +2237,17 @@ def _compact_usage_text(session: dict) -> str:
|
|||
tail.append(f"compressions {u['compressions']}")
|
||||
lines.append(" " + " · ".join(tail))
|
||||
|
||||
cost_usd = u.get("cost_usd")
|
||||
# The /usage page reports the FULL provider-reported cost (OpenRouter
|
||||
# usage.cost accumulator AND/OR the Nous header delta) — NOT the chrome's
|
||||
# Nous-header-only figure (F3 narrowed `_get_usage["cost_usd"]` to the
|
||||
# status bar). Read it straight from real_session_cost_usd here so the
|
||||
# accounting page keeps both sources.
|
||||
try:
|
||||
from agent.usage_pricing import real_session_cost_usd
|
||||
|
||||
cost_usd = real_session_cost_usd(agent)
|
||||
except Exception:
|
||||
cost_usd = None
|
||||
if cost_usd is not None:
|
||||
lines.append(f" session cost: ${cost_usd:.4f} (provider-reported)")
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue