Add Cursor token usage sync from state.vscdb to runtime log#294
Add Cursor token usage sync from state.vscdb to runtime log#294jqdsouza wants to merge 1 commit into
Conversation
…sync Cursor's hook payloads carry no token counts, so token views had no Cursor coverage. This adds the two local-only mechanisms: - Hook passthrough: beacon-hooks now normalizes token-usage fields from any hook payload into canonical gen_ai.usage (OTel GenAI semconv names) via a tolerant alias extractor wired into emitHookEvent and recordLocalEdit. Inert today; activates automatically if Cursor ships usage in hooks. - beacon token-usage sync-cursor: an explicit, read-only, offline command backed by internal/cursorusage that snapshots Cursor's state.vscdb, extracts runtime-recorded per-generation token counts from bubble rows, and appends token.usage events (harness=cursor, session=conversation id) through the endpoint writer. Idempotent via a per-bubble state file with rebuild-from-log recovery; zero-count generations are skipped, never estimated; cost is never derived locally. Synced events carry raw.metric_name=cursor.db.token.usage so the existing channel dedupe in internal/tokens prefers future hook-reported usage per (harness, session); a regression test locks the contract in. Adds modernc.org/sqlite (pure Go, CGO_ENABLED=0-compatible) to cli/beacon only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EHe58DhhybqUq5Yxw4yA3J
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues. You can view the agent here.
Reviewed by Cursor Bugbot for commit c35a975. Configure here.
| if err := state.RebuildFromLog(opts.LogPath); err != nil { | ||
| return sum, fmt.Errorf("rebuild state from log: %w", err) | ||
| } | ||
| } |
There was a problem hiding this comment.
Partial rebuild saves broken state
High Severity
When RebuildFromLog fails after marking some bubbles, the deferred Save still persists that partial state. The next run sees a non-empty Composers map, skips rebuild, and re-emits already-logged generations that were never marked, double-counting tokens in rollups.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c35a975. Configure here.
| } | ||
| if g.Timestamp.IsZero() { | ||
| g.Timestamp = now | ||
| } |
There was a problem hiding this comment.
Undated generations bypass since filter
Medium Severity
--since only skips generations that already have a non-zero timestamp. Bubbles with TimestampSource=sync always pass the filter, then receive now after sorting, so old undated rows still sync and are written before older dated events despite the chronological append contract.
Reviewed by Cursor Bugbot for commit c35a975. Configure here.


Summary
Adds
beacon token-usage sync-cursor, a new command that extracts runtime-recorded token usage from Cursor's local state store (state.vscdb) and appends it to the Beacon runtime log as canonicaltoken.usageevents. This enables token usage reporting for Cursor sessions, which currently carry no token counts in hook payloads.Key Changes
New
cursorusagepackage (cli/beacon/internal/cursorusage/):db.go: Platform-specific Cursor state database path resolution and snapshot handling (copies DB + WAL to temp directory for safe, offline reading)extract.go: Extracts generations from thecursorDiskKVtable with schema-drift tolerance (handles multiple token container and field name spellings across Cursor versions)map.go: Converts extracted generations to canonicaltoken.usageendpoint events with cursor harness attributionsync.go: Orchestrates the sync sweep with dedup state tracking and idempotent re-runsstate.go: Manages per-composer dedup state (JSON file) and log-based state recoveryNew CLI command (
cli/beacon/cmd/token_usage_sync_cursor.go):beacon token-usage sync-cursorwith flags for DB path, state file, log path, dry-run (--print), timestamp filtering (--since), and state rebuildHook payload usage extraction (
cli/beacon-hooks/cmd/genai_usage.go):extractGenAIUsage()function that normalizes token usage from hook payloads across multiple container and field name spellingsapplyGenAIUsageFields()merges extracted usage intogen_ai.usagefor Cursor hooks (afterAgentThought, beforeSubmitPrompt, recordLocalEdit)Comprehensive test coverage:
cursorusage_test.go: Tests extraction with schema variations, zero-count skipping, parse error handling, WAL reading, event serialization, and idempotent syncinggenai_usage_test.go: Tests usage extraction from various payload shapes and hook integrationtoken_usage_sync_cursor_test.go: Tests command registration and integration with token usage reportingDocumentation (
docs/cli/token-usage-sync-cursor.mdx):token-usage.mdxandcursor.mdxto reference the new sync capabilityImplementation Details
~/.beacon/cursor/usage-sync-state.json) and skipped on re-runs; state is rebuilt from the runtime log if lostraw.metric_name=cursor.db.token.usageso they are suppressed if Cursor ever ships usage in hook payloads (hook events take precedence in rollups)https://claude.ai/code/session_01EHe58DhhybqUq5Yxw4yA3J
Note
Medium Risk
Touches local SQLite reads and appends to the runtime JSONL log with new dedup semantics; mistakes could mis-attribute or double-count tokens, though the design is read-only against Cursor and prefers hook usage over sync.
Overview
Adds offline Cursor token coverage via
beacon token-usage sync-cursor, which snapshots Cursor's localstate.vscdb(including WAL), best-effort reads per-generation counts fromcursorDiskKV, and appends canonicaltoken.usageevents withgen_ai.usage,harness.name=cursor, andsession.idmatching hook conversation ids. Zero-count rows are skipped; dedup state (~/.beacon/cursor/usage-sync-state.json) plus log rebuild keep re-runs idempotent;--print,--since, and--rebuild-stateare supported.Forward-compatible hook path: new
extractGenAIUsage/applyGenAIUsageFieldsnormalize assorted payload spellings intogen_ai.usageon common hook emitters (including file edits viarecordLocalEdit);model_idis accepted for model attribution.Rollup contract: synced events use
raw.metric_name=cursor.db.token.usagesobeacon token-usage/ dashboard dedupe suppresses them when hook-reported usage exists for the same harness/session. Docs andCLAUDE.mddescribe the Cursor gap and sync workflow;modernc.org/sqlitebacks read-only DB access.Reviewed by Cursor Bugbot for commit c35a975. Bugbot is set up for automated code reviews on this repo. Configure here.