diff --git a/shell/plugins/model-usage/scripts/codex_usage_scanner.py b/shell/plugins/model-usage/scripts/codex_usage_scanner.py index 44804c6a..fba11d07 100644 --- a/shell/plugins/model-usage/scripts/codex_usage_scanner.py +++ b/shell/plugins/model-usage/scripts/codex_usage_scanner.py @@ -213,12 +213,17 @@ def scan_native_codex_sessions(): if payload.get("type") != "token_count": continue info = payload.get("info") or {} - usage = info.get("total_token_usage") or {} - input_tokens = number(usage.get("input_tokens")) - output_tokens = number(usage.get("output_tokens")) + number(usage.get("reasoning_output_tokens")) + # total_token_usage is cumulative for the session. Adding every + # snapshot makes usage grow quadratically, so count the last turn. + usage = info.get("last_token_usage") or {} cache_read = number(usage.get("cached_input_tokens")) - cache_write = 0 - if not (input_tokens or output_tokens or cache_read): + cache_write = number(usage.get("cache_write_input_tokens")) + # Cached tokens are included in input_tokens, and reasoning tokens + # are included in output_tokens. Keep the cache split without + # counting either category twice. + input_tokens = max(0, number(usage.get("input_tokens")) - cache_read - cache_write) + output_tokens = number(usage.get("output_tokens")) + if not (input_tokens or output_tokens or cache_read or cache_write): continue day = local_day(entry.get("timestamp") or path.stat().st_mtime) add_usage(day, str(path), current_model, input_tokens, output_tokens, cache_read, cache_write) diff --git a/test/shell.d/model-usage-claude-scanner-test.sh b/test/shell.d/model-usage-claude-scanner-test.sh new file mode 100644 index 00000000..c41333cb --- /dev/null +++ b/test/shell.d/model-usage-claude-scanner-test.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +source "$(dirname "$0")/base-test.sh" + +require_command jq +require_command python3 + +TEST_HOME=$(mktemp -d) +trap 'rm -rf "$TEST_HOME"' EXIT + +projects="$TEST_HOME/.claude/projects/example" +mkdir -p "$projects" + +timestamp="$(date +%Y-%m-%d)T12:00:00Z" +cat >"$projects/session.jsonl" <"$TEST_HOME/bin/codex" <<'EOF' +#!/bin/bash + +while read -r request; do + id=$(jq -r '.id // empty' <<<"$request") + method=$(jq -r '.method // empty' <<<"$request") + + case "$method" in + initialize) + jq -cn --argjson id "$id" '{id: $id, result: {}}' + ;; + account/read) + jq -cn --argjson id "$id" '{id: $id, result: {account: {}}}' + ;; + account/rateLimits/read) + jq -cn --argjson id "$id" '{id: $id, result: {rateLimits: {}}}' + ;; + esac +done +EOF +chmod +x "$TEST_HOME/bin/codex" + +timestamp="$(date +%Y-%m-%d)T12:00:00Z" +session="$TEST_HOME/.codex/sessions/$(date +%Y/%m/%d)/rollout.jsonl" +cat >"$session" <