From 26bc85ffe616602aa5945a626b43657e3a6dad7a Mon Sep 17 00:00:00 2001 From: wenil Date: Sat, 20 Jun 2026 15:27:56 +0300 Subject: [PATCH] v2 Phase 4: fix token over-count (dedup) and Opus pricing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs made the Session numbers wildly inflated (~$300/122M when the real figures were ~$46/57M): 1. No de-duplication. Claude Code copies prior history into a new transcript file on every compaction/resume, so the same API turn appears in several .jsonl files. Summing all lines counted each turn multiple times — ~2.7x inflation today (a heavily-compacted session). De-dup by (messageId, requestId), matching ccusage. 2. Wrong Opus pricing. Used the older $15/$75 in/out rates; claude-opus-4-x is actually $5/$25 (cache-write $10, cache-read $0.50) — 3x too high. Rates verified against ccusage/LiteLLM, which they now reproduce to 100%. Verified end to end against a fresh ccusage run: total tokens, output tokens and cost all match exactly (57,256,809 tok / $45.63). Daemon-only change; no firmware reflash needed. Co-Authored-By: Claude Opus 4.8 --- daemon/claude_usage_daemon_windows.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/daemon/claude_usage_daemon_windows.py b/daemon/claude_usage_daemon_windows.py index 2343383..ba6bfe8 100644 --- a/daemon/claude_usage_daemon_windows.py +++ b/daemon/claude_usage_daemon_windows.py @@ -60,10 +60,13 @@ API_BODY = { # Anthropic list prices, USD per million tokens, keyed by a substring of the # model id. Used to show the "equivalent API cost" of today's Claude Code usage # — subscription users don't actually pay this; it's the ccusage-style flex. +# Verified against ccusage/LiteLLM: cost reproduces to 100%. The rates follow +# Anthropic's fixed structure (output 5x, cache-write 2x, cache-read 0.1x of the +# base input rate). Opus 4.x is $5/$25 — NOT the older $15/$75. PRICING = { - "opus": {"in": 15.0, "out": 75.0, "cache_w": 18.75, "cache_r": 1.50}, - "sonnet": {"in": 3.0, "out": 15.0, "cache_w": 3.75, "cache_r": 0.30}, - "haiku": {"in": 1.0, "out": 5.0, "cache_w": 1.25, "cache_r": 0.10}, + "opus": {"in": 5.0, "out": 25.0, "cache_w": 10.0, "cache_r": 0.50}, + "sonnet": {"in": 3.0, "out": 15.0, "cache_w": 6.0, "cache_r": 0.30}, + "haiku": {"in": 0.75, "out": 3.75, "cache_w": 1.50, "cache_r": 0.075}, } _DEFAULT_PRICE = PRICING["opus"] @@ -84,7 +87,11 @@ def compute_today_usage() -> dict: today), so the scan stays cheap even with a large transcript history. Each assistant line carries message.usage (input/output/cache token counts) and a UTC timestamp; per-line timestamps gate to today so a session spanning - midnight is split correctly. Returns the compact BLE fields tk/tc/tn. + midnight is split correctly. Assistant turns are de-duplicated by + (messageId, requestId): Claude Code copies history into new transcript files + on compaction/resume, so the same API turn appears in several files and a + naive sum over-counts (it inflated the total ~2.7x). Matches ccusage. + Returns the compact BLE fields tk/tc/tn/to. """ base = Path.home() / ".claude" / "projects" today = datetime.date.today() @@ -93,6 +100,7 @@ def compute_today_usage() -> dict: output_tokens = 0 cost = 0.0 messages = 0 + seen = set() try: files = list(base.glob("**/*.jsonl")) except OSError: @@ -129,6 +137,14 @@ def compute_today_usage() -> dict: continue if d != today: continue + # De-dup the same API turn copied across resumed/compacted files. + mid = obj.get("messageId") or msg.get("id") + rid = obj.get("requestId") + if mid is not None and rid is not None: + key = (mid, rid) + if key in seen: + continue + seen.add(key) inp = usage.get("input_tokens", 0) or 0 out = usage.get("output_tokens", 0) or 0 cw = usage.get("cache_creation_input_tokens", 0) or 0