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