fix(miner): count the prompt-cache tiers as input tokens in the engine's own parsers - #10252
Conversation
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-31 23:44:39 UTC
Review summary Nits — 4 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Decision record
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
loopover-ui | 3e60324 | Jul 31 2026, 10:54 PM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #10252 +/- ##
==========================================
+ Coverage 92.28% 92.30% +0.01%
==========================================
Files 939 939
Lines 114775 114810 +35
Branches 27715 27729 +14
==========================================
+ Hits 105920 105970 +50
+ Misses 7553 7534 -19
- Partials 1302 1306 +4
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…e's own parsers #10251 fixed this on the ORB side. Both of the engine's deliberately-parallel copies were left reading only `input_tokens`, so the miner still records a near-constant handful for every cached claude attempt. Anthropic, and therefore Claude Code and the Agent SDK, splits one prompt across three counters: input_tokens carries only the portion neither read from nor written to the prompt cache, with the rest in cache_read_input_tokens and cache_creation_input_tokens. With caching active -- which it is for every attempt the CLI runs -- essentially the whole prompt lands in the latter two. Sum the three, matching the ORB's totalInputTokens exactly, so the two parsers cannot report different numbers for the same envelope. Each tier keeps its own alias group because the three are additive components of one prompt, not names for one value; folding them into INPUT_TOKEN_KEYS would take the maximum and under-report again, just less severely. Absence stays absence: an envelope with no input counter at all yields undefined rather than a fabricated 0, and a tier present but zero contributes a real zero. Providers emitting no cache keys -- codex and the OpenAI-compatible bindings -- are byte-identical to before, which is what makes the shared extraction point safe to change. Closes #10246
ba9f06e to
3a419f9
Compare
…wn suite codecov/patch failed at 83.78% because totalInputTokens' body and its mergeCliUsage call site had zero hits in the engine flag's c8 report: the driver's behavior tests live entirely in the root vitest copy, which the engine flag never sees. Port the tier-sum scenarios into packages/loopover-engine/test/, the suite that actually carries this file's coverage. Verified locally: every changed line in both drivers now has hits in the engine lcov.
Summary
Rewritten after a collision. #10251 landed the ORB half of this fix while this branch was in flight, with a better measurement (2,048 input tokens across 1,000 calls over 48h — an average of exactly 2.0 — against $225.06 of real spend). This PR is now scoped to what #10251 did not touch: the engine's two deliberately-parallel copies of the same parser, which still read only
input_tokens.Anthropic — and therefore Claude Code and the Agent SDK — splits one prompt across three counters.
input_tokenscarries only the portion neither read from nor written to the prompt cache; with caching active, which it is for every attempt the CLI runs, essentially the whole prompt lands incache_read_input_tokensandcache_creation_input_tokens.So the miner still records a near-constant handful of input tokens per cached attempt, exactly as the ORB did before #10251.
Matching #10251 exactly, on purpose
The tiers are summed, mirroring the ORB's
totalInputTokensarithmetic rather than inventing a second convention. These two files are parallel copies by design (the no-src-import boundary), so a divergence in how they compute the same figure would be worse than the bug: the same envelope would report different numbers depending on which surface saw it.Each tier keeps its own alias group because the three are additive components of one prompt, not names for one value — folding them into
INPUT_TOKEN_KEYSwould take the maximum of the three and under-report again, just less severely.Absence stays absence: an envelope with no input counter at all yields
undefinedrather than a fabricated0, and a tier that is present but zero contributes a real zero. Providers emitting no cache keys — codex and the OpenAI-compatible bindings — are byte-identical to before, which is what makes changing the shared extraction point safe.Closes #10246
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateAlso run and green:
npm run build:miner, the@loopover/engineworkspace suite (961 tests, rebuilt first so thedist/-importing tests exercised the new code), andnpm run dead-exports:check.If any required check was skipped, explain why:
packages/loopover-engine/test/**and the root suite so each Codecov upload credits them independently.Tests added
A realistic cached envelope (
input_tokens: 2,cache_read_input_tokens: 48210,cache_creation_input_tokens: 1536→49748), asserted through both drivers; a genuinely-zero tier still contributing zero rather than being read as absent; no input counter at all yieldingundefined; and a no-cache-keys provider (codex / OpenAI-compatible) coming out byte-identical.Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.Token counts are metadata; no prompt or transcript text is added by this change.
UI Evidence
Not applicable — no visible UI, frontend, docs, or extension change.
Notes
What was dropped from the original version of this PR, and why. It had also changed
src/selfhost/ai.tsand emitted$ai_cache_read_input_tokens/$ai_cache_creation_input_tokensto PostHog with$ai_cache_reporting_exclusive: true, keeping$ai_input_tokensas the uncached remainder. #10251 chose the opposite and better-measured convention — sum the tiers into$ai_input_tokens, making the count inclusive. Rebasing the old version on top would have setexclusive: trueagainst an inclusive count, which makes PostHog add the cache on top of a figure that already contains it. That is precisely the over-billing in PostHog/posthog-js#3615, where costs came out 3.4× high for exactly that mismatch. So the ORB changes were dropped rather than merged.One genuine gap remains and is filed separately: PostHog now receives a correct total input figure but cannot tell how much of it was cache reads, which bill at a fraction of fresh input, so its derived
$ai_input_cost_usdover-states the input component.$ai_total_cost_usdis unaffected — it comes straight from the CLI. Fixing that means emitting the tiers alongside the inclusive total with$ai_cache_reporting_exclusive: false.