From 222126db1d7dda3c7b1f83b80674d44c1969f97e Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Tue, 16 Jun 2026 19:43:25 +0530 Subject: [PATCH] Revert "gateway: compact /usage with current-session per-model costs" This reverts commit 364b93a4b985db211c02dfb62ca97172a236cc56. --- agent/account_usage.py | 47 +----- hermes_state.py | 30 ---- tests/agent/test_provider_cost_capture.py | 34 ----- tests/test_hermes_state.py | 31 ---- tests/test_tui_gateway_server.py | 174 ---------------------- tui_gateway/server.py | 107 ------------- 6 files changed, 7 insertions(+), 416 deletions(-) diff --git a/agent/account_usage.py b/agent/account_usage.py index 18dc2291c9c..da02af3c478 100644 --- a/agent/account_usage.py +++ b/agent/account_usage.py @@ -243,17 +243,6 @@ def nous_credits_lines(*, markdown: bool = False, timeout: float = 10.0) -> list renders from that fixture instead of the real portal (so the block + gauge are testable without a live account). Throwaway scaffolding. """ - snapshot = _fetch_nous_credits_snapshot(timeout=timeout) - return render_account_usage_lines(snapshot, markdown=markdown) - - -def _fetch_nous_credits_snapshot(timeout: float = 10.0) -> Optional[AccountUsageSnapshot]: - """Auth-gate + portal fetch + snapshot build for the Nous credits block. - - Shared by ``nous_credits_lines`` (full block) and - ``nous_credits_compact_line`` (one-liner). Honors the - HERMES_DEV_CREDITS_FIXTURE dev override. Fail-open → None. - """ # Dev fixture short-circuit — render /usage from the injected state, no portal. try: from agent.credits_tracker import dev_fixture_credits_state @@ -262,16 +251,17 @@ def _fetch_nous_credits_snapshot(timeout: float = 10.0) -> Optional[AccountUsage except Exception: fixture = None if fixture is not None: - return _snapshot_from_credits_state(fixture) + snapshot = _snapshot_from_credits_state(fixture) + return render_account_usage_lines(snapshot, markdown=markdown) try: from hermes_cli.auth import get_provider_auth_state tok = (get_provider_auth_state("nous") or {}).get("access_token") if not (isinstance(tok, str) and tok.strip()): - return None + return [] except Exception: - return None + return [] try: import concurrent.futures @@ -281,36 +271,13 @@ def _fetch_nous_credits_snapshot(timeout: float = 10.0) -> Optional[AccountUsage account = pool.submit( get_nous_portal_account_info, force_fresh=True ).result(timeout=timeout) - return build_nous_credits_snapshot(account) + snapshot = build_nous_credits_snapshot(account) + return render_account_usage_lines(snapshot, markdown=markdown) except Exception: # Fail-open (caller shows nothing), but leave a breadcrumb so a dead # /usage credits block is diagnosable in agent.log without a dev flag. logger.debug("credits ▸ /usage portal fetch/render failed (fail-open)", exc_info=True) - return None - - -def nous_credits_compact_line(*, timeout: float = 10.0) -> Optional[str]: - """One-line Nous credits summary for the compact /usage view, or None. - - Condenses the snapshot's own detail strings (stable, locally-built - formats) into ``Nous credits (Plan): Total usable: $X · Renews: …``. - Same gating/fail-open semantics as ``nous_credits_lines``. - """ - snap = _fetch_nous_credits_snapshot(timeout=timeout) - if snap is None or not snap.available: - return None - picked = [ - d for d in snap.details - if d.startswith(("Total usable:", "Renews:", "Status:")) - ] - if not picked: - picked = [d for d in snap.details if not d.startswith("Manage / top up:")][:2] - if not picked: - return None - title = snap.title - if snap.plan: - title += f" ({snap.plan})" - return f"{title}: " + " · ".join(picked) + return [] def _snapshot_from_credits_state(state) -> Optional[AccountUsageSnapshot]: diff --git a/hermes_state.py b/hermes_state.py index 0132a2b7833..8ffe8c25f68 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -1695,36 +1695,6 @@ class SessionDB: return self._execute_write(_do) or 0 - def usage_totals(self, days: int = 30) -> Dict[str, Any]: - """Aggregate usage for sessions started in the last ``days``. - - ``reported_cost_usd`` sums only provider-REPORTED ``actual_cost_usd`` - (never estimates) and is None when no session in the window has a - reported cost — callers must hide cost rather than print $0.00. - """ - cutoff = time.time() - days * 86400 - with self._lock: - row = self._conn.execute( - """SELECT COUNT(*) AS sessions, - COALESCE(SUM(input_tokens), 0) - + COALESCE(SUM(cache_read_tokens), 0) - + COALESCE(SUM(cache_write_tokens), 0) AS input_tokens, - COALESCE(SUM(output_tokens), 0) AS output_tokens, - COALESCE(SUM(api_call_count), 0) AS api_calls, - SUM(actual_cost_usd) AS reported_cost_usd - FROM sessions WHERE started_at >= ?""", - (cutoff,), - ).fetchone() - result = dict(row) if row else {} - return { - "days": days, - "sessions": int(result.get("sessions") or 0), - "input_tokens": int(result.get("input_tokens") or 0), - "output_tokens": int(result.get("output_tokens") or 0), - "api_calls": int(result.get("api_calls") or 0), - "reported_cost_usd": result.get("reported_cost_usd"), - } - def get_session(self, session_id: str) -> Optional[Dict[str, Any]]: """Get a session by ID.""" with self._lock: diff --git a/tests/agent/test_provider_cost_capture.py b/tests/agent/test_provider_cost_capture.py index 73df27b4044..5399d81a907 100644 --- a/tests/agent/test_provider_cost_capture.py +++ b/tests/agent/test_provider_cost_capture.py @@ -184,37 +184,3 @@ class TestOpenRouterUsageParam: is_openrouter=False, ) assert "usage" not in (kwargs.get("extra_body") or {}) - - -# ── nous_credits_compact_line — one-liner for the compact /usage page ─────── - - -class TestNousCreditsCompactLine: - def test_condenses_snapshot_details(self, monkeypatch): - import agent.account_usage as au - - snap = au.AccountUsageSnapshot( - provider="nous", - source="portal-account", - fetched_at=au._utc_now(), - title="Nous credits", - plan="Ultra", - details=( - "Subscription credits: $-0.79", - "Top-up credits: $988.99", - "Total usable: $988.99", - "Renews: 2026-06-11T08:14:55.000Z", - "Manage / top up: https://portal.nousresearch.com/billing", - ), - ) - monkeypatch.setattr(au, "_fetch_nous_credits_snapshot", lambda timeout=10.0: snap) - line = au.nous_credits_compact_line() - assert line == ( - "Nous credits (Ultra): Total usable: $988.99 · Renews: 2026-06-11T08:14:55.000Z" - ) - - def test_none_when_no_snapshot(self, monkeypatch): - import agent.account_usage as au - - monkeypatch.setattr(au, "_fetch_nous_credits_snapshot", lambda timeout=10.0: None) - assert au.nous_credits_compact_line() is None diff --git a/tests/test_hermes_state.py b/tests/test_hermes_state.py index 605c9af4e2b..f4258f2b915 100644 --- a/tests/test_hermes_state.py +++ b/tests/test_hermes_state.py @@ -158,37 +158,6 @@ class TestSessionLifecycle: assert session["api_call_count"] == 5 assert session["input_tokens"] == 300 - def test_update_token_counts_actual_cost_null_keeps_value(self, db): - """A NULL actual_cost_usd delta must not touch the stored REAL cost.""" - db.create_session(session_id="s1", source="cli") - db.update_token_counts("s1", input_tokens=100, actual_cost_usd=0.25) - db.update_token_counts("s1", input_tokens=100, actual_cost_usd=None) - db.update_token_counts("s1", input_tokens=100, actual_cost_usd=0.10) - - session = db.get_session("s1") - assert session["actual_cost_usd"] == pytest.approx(0.35) - - def test_usage_totals_reported_cost_none_when_nothing_reported(self, db): - """usage_totals must distinguish 'no reported cost' (None) from $0.""" - db.create_session(session_id="s1", source="cli") - db.update_token_counts("s1", input_tokens=100, output_tokens=50, api_call_count=1) - - totals = db.usage_totals(days=30) - assert totals["sessions"] == 1 - assert totals["input_tokens"] == 100 - assert totals["output_tokens"] == 50 - assert totals["reported_cost_usd"] is None - - def test_usage_totals_sums_reported_costs(self, db): - db.create_session(session_id="s1", source="cli") - db.create_session(session_id="s2", source="tui") - db.update_token_counts("s1", input_tokens=100, actual_cost_usd=0.20) - db.update_token_counts("s2", input_tokens=300, actual_cost_usd=0.05) - - totals = db.usage_totals(days=30) - assert totals["sessions"] == 2 - assert totals["reported_cost_usd"] == pytest.approx(0.25) - def test_update_token_counts_backfills_model_when_null(self, db): db.create_session(session_id="s1", source="telegram") db.update_token_counts("s1", input_tokens=10, output_tokens=5, model="openai/gpt-5.4") diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 6e8674252cd..37985af5913 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -7848,180 +7848,6 @@ def test_session_list_truncated_false_on_normal_paths(monkeypatch): assert filtered["result"]["truncated"] is False -# ── /usage: compact in-process page with real-only costs ───────────────────── - - -def _usage_agent(**overrides): - """SimpleNamespace agent with realistic session counters for /usage.""" - base = dict( - model="anthropic/claude-sonnet-4.6", - provider="openrouter", - base_url="https://openrouter.ai/api/v1", - session_input_tokens=35_000, - session_output_tokens=10_000, - session_cache_read_tokens=5_000, - session_cache_write_tokens=2_000, - session_reasoning_tokens=0, - session_prompt_tokens=40_000, - session_completion_tokens=10_000, - session_total_tokens=50_000, - session_api_calls=5, - session_actual_cost_usd=None, - session_model_usage={}, - context_compressor=None, - ) - base.update(overrides) - return types.SimpleNamespace(**base) - - -def _mute_usage_externals(monkeypatch): - import agent.account_usage as account_usage - - monkeypatch.setattr(server, "_get_db", lambda: None) - monkeypatch.setattr(account_usage, "nous_credits_compact_line", lambda **kw: None) - - -def test_get_usage_cost_absent_when_provider_reports_nothing(monkeypatch): - """No estimation: even a model with known pricing gets NO cost_usd field.""" - agent = _usage_agent() - usage = server._get_usage(agent) - assert "cost_usd" not in usage - assert usage["input"] == 35_000 - - -def test_get_usage_cost_present_when_provider_reported(monkeypatch): - 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" - - -def test_get_usage_cost_from_nous_credits_delta(monkeypatch): - agent = _usage_agent(provider="nous") - 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) - - -def test_compact_usage_per_model_rows_and_real_cost(monkeypatch): - _mute_usage_externals(monkeypatch) - agent = _usage_agent( - session_actual_cost_usd=0.42, - session_model_usage={ - "anthropic/claude-sonnet-4.6": { - "calls": 4, "input": 30_000, "output": 9_000, - "cache_read": 5_000, "cache_write": 2_000, "cost_usd": 0.42, - }, - "deepseek/deepseek-chat": { - "calls": 1, "input": 5_000, "output": 1_000, - "cache_read": 0, "cache_write": 0, "cost_usd": None, - }, - }, - ) - text = server._compact_usage_text(_session(agent=agent)) - - assert "Session — anthropic/claude-sonnet-4.6 (openrouter)" in text - sonnet_row = next(l for l in text.splitlines() if "claude-sonnet-4.6" in l and "reqs" in l) - assert "reqs 4" in sonnet_row and "$0.4200" in sonnet_row - deepseek_row = next(l for l in text.splitlines() if "deepseek-chat" in l) - # Cost not reported for this model → no dollar figure on its row. - assert "reqs 1" in deepseek_row and "$" not in deepseek_row - assert "session cost: $0.4200 (provider-reported)" in text - assert "/usage full" in text - - -def test_compact_usage_absent_cost_never_renders_zero(monkeypatch): - _mute_usage_externals(monkeypatch) - agent = _usage_agent() # nothing reported - text = server._compact_usage_text(_session(agent=agent)) - assert "session cost: not reported by provider" in text - assert "$0.00" not in text - - -def test_compact_usage_no_agent(monkeypatch): - _mute_usage_externals(monkeypatch) - text = server._compact_usage_text(_session(agent=None) | {"agent": None}) - assert "no API calls yet" in text - - -def test_compact_usage_recent_summary_and_credits_line(monkeypatch): - import agent.account_usage as account_usage - - class _DB: - def usage_totals(self, days=30): - return { - "days": 30, "sessions": 12, "input_tokens": 1_200_000, - "output_tokens": 90_000, "api_calls": 64, - "reported_cost_usd": 4.5678, - } - - monkeypatch.setattr(server, "_get_db", lambda: _DB()) - monkeypatch.setattr( - account_usage, "nous_credits_compact_line", - lambda **kw: "Nous credits (Ultra): Total usable: $988.99 · Renews: 2026-06-11", - ) - text = server._compact_usage_text(_session(agent=_usage_agent())) - assert "Last 30d: 12 sessions" in text - assert "reported cost $4.57" in text - assert "Nous credits (Ultra)" in text - - -def test_compact_usage_recent_summary_hides_unreported_cost(monkeypatch): - _mute_usage_externals(monkeypatch) - - class _DB: - def usage_totals(self, days=30): - return { - "days": 30, "sessions": 3, "input_tokens": 10_000, - "output_tokens": 2_000, "api_calls": 7, - "reported_cost_usd": None, - } - - monkeypatch.setattr(server, "_get_db", lambda: _DB()) - text = server._compact_usage_text(_session(agent=_usage_agent())) - assert "Last 30d: 3 sessions" in text - assert "reported cost" not in text - - -def test_slash_exec_usage_is_answered_in_process(monkeypatch): - """/usage must not hit the slash worker (it has no live agent).""" - _mute_usage_externals(monkeypatch) - server._sessions["sid-usage"] = _session(agent=_usage_agent()) - try: - resp = server.handle_request( - {"id": "1", "method": "slash.exec", - "params": {"session_id": "sid-usage", "command": "usage"}} - ) - out = resp["result"]["output"] - assert "Session — anthropic/claude-sonnet-4.6" in out - # Worker untouched. - assert server._sessions["sid-usage"]["slash_worker"] is None - finally: - server._sessions.pop("sid-usage", None) - - -def test_slash_exec_usage_full_falls_through_to_worker(monkeypatch): - ran = [] - - class _Worker: - def run(self, cmd): - ran.append(cmd) - return "detailed legacy page" - - sess = _session(agent=_usage_agent()) - sess["slash_worker"] = _Worker() - server._sessions["sid-usage-full"] = sess - try: - monkeypatch.setattr(server, "_mirror_slash_side_effects", lambda *a: "") - resp = server.handle_request( - {"id": "1", "method": "slash.exec", - "params": {"session_id": "sid-usage-full", "command": "usage full"}} - ) - assert resp["result"]["output"] == "detailed legacy page" - assert ran == ["usage full"] - finally: - server._sessions.pop("sid-usage-full", None) - def test_slash_worker_close_reaps_zombie_and_closes_fds(): """A hung worker is SIGKILLed, the zombie reaped, all pipes closed — once.""" calls = {k: 0 for k in ("terminate", "kill", "wait", "stdin", "stdout", "stderr")} diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 9c7a0e90875..423545e429f 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2321,102 +2321,6 @@ def _get_usage(agent) -> dict: return usage -def _compact_usage_text(session: dict) -> str: - """Compact /usage page: current session per-model rows + a tight recent - summary + a one-line Nous credits gauge. - - Costs are provider-REPORTED only. When a provider reports nothing the - cost is simply omitted (never rendered as $0.00). The detailed legacy - page stays reachable via `/usage full` (slash-worker → CLI path). - """ - from agent.usage_pricing import format_token_count_compact as _fmt - - agent = session.get("agent") - lines: list[str] = [] - - calls = (getattr(agent, "session_api_calls", 0) or 0) if agent is not None else 0 - if agent is not None and calls > 0: - u = _get_usage(agent) - header = f"Session — {u['model']}" - provider = getattr(agent, "provider", None) - if provider: - header += f" ({provider})" - lines.append(header) - - per_model = getattr(agent, "session_model_usage", None) or {} - rows = list(per_model.items()) or [( - u["model"], - { - "calls": u["calls"], "input": u["input"], "output": u["output"], - "cache_read": u["cache_read"], "cache_write": u["cache_write"], - "cost_usd": None, - }, - )] - name_w = max(len(name or "?") for name, _ in rows) - for name, row in rows: - cells = [ - f"{(name or '?'):<{name_w}}", - f"reqs {row.get('calls', 0)}", - f"in {_fmt(int(row.get('input', 0) or 0))}", - f"out {_fmt(int(row.get('output', 0) or 0))}", - ] - cache_read = int(row.get("cache_read", 0) or 0) - if cache_read: - cells.append(f"cache {_fmt(cache_read)}") - cost = row.get("cost_usd") - if cost is not None: - cells.append(f"${cost:.4f}") - lines.append(" " + " · ".join(cells)) - - ctx_pct = u.get("context_percent") - tail = [f"total {_fmt(int(u['total'] or 0))} tokens", f"{u['calls']} calls"] - if ctx_pct is not None: - tail.append(f"context {ctx_pct}%") - if u.get("compressions"): - tail.append(f"compressions {u['compressions']}") - lines.append(" " + " · ".join(tail)) - - cost_usd = u.get("cost_usd") - if cost_usd is not None: - lines.append(f" session cost: ${cost_usd:.4f} (provider-reported)") - else: - lines.append(" session cost: not reported by provider") - else: - lines.append("Session — no API calls yet") - - # Tight recent summary from the session DB (real costs only). - try: - db = _get_db() - totals = db.usage_totals(days=30) if db is not None else None - except Exception: - totals = None - if totals and totals.get("sessions"): - from agent.usage_pricing import format_token_count_compact as _fmt30 - - parts = [ - f"{totals['sessions']} sessions", - f"in {_fmt30(totals['input_tokens'])}", - f"out {_fmt30(totals['output_tokens'])}", - ] - reported = totals.get("reported_cost_usd") - if reported is not None: - parts.append(f"reported cost ${float(reported):.2f}") - lines.append("Last 30d: " + " · ".join(parts)) - - # Nous credits one-liner (account-level; independent of the live agent). - try: - from agent.account_usage import nous_credits_compact_line - - credits_line = nous_credits_compact_line() - except Exception: - credits_line = None - if credits_line: - lines.append(credits_line) - - lines.append("(/usage full — detailed page)") - return "\n".join(lines) - - def _probe_credentials(agent) -> str: """Light credential check at session creation — returns warning or ''.""" try: @@ -9676,17 +9580,6 @@ def _(rid, params: dict) -> dict: except Exception as e: return _ok(rid, {"output": f"Plugin command error: {e}"}) - # /usage — answered in-process from the LIVE agent's session counters. - # The slash worker is a separate subprocess that resumes the session - # WITHOUT an agent, so it can never see current-session tokens/costs - # (it only printed the Nous credits block). `/usage full` still falls - # through to the worker for the detailed CLI page. - if _cmd_base == "usage" and _cmd_arg.strip().lower() not in {"full", "--full"}: - try: - return _ok(rid, {"output": _compact_usage_text(session)}) - except Exception: - pass # fall through to the slash worker - worker = session.get("slash_worker") if not worker: try: