fix(cost): emit opencode multi-step usage as deltas (#1036) - #1040
Open
vishnujayvel wants to merge 3 commits into
Open
fix(cost): emit opencode multi-step usage as deltas (#1036)#1040vishnujayvel wants to merge 3 commits into
vishnujayvel wants to merge 3 commits into
Conversation
message.updated fires once per step-finish with a CUMULATIVE turn cost, but db.insertEvent always appends. Multi-step turns therefore over-counted under additive aggregation (0.02 then 0.05 stored as 0.07 instead of 0.05). Convert the cumulative figure to a per-step delta (same incremental shape as codex/kimi) keyed by assistant message id, and drop no-op refreshes so last- step token snapshots are not double-counted. Update the misleading "idempotent at the cost column" comment. Fixes mksglu#1036
…glu#1036) Long-lived plugin processes could unbounded-grow lastCumulativeCostByMessage. No message-completion bus event is consumed by the plugin lifecycle, so cap the Map at 1000 keys via insertion-order FIFO (rememberOpencodeCumulativeCost). Updating an existing key does not grow the map. Add unit tests for eviction.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What / Why / How
What: Stop the opencode adapter from over-counting multi-step turn cost under additive aggregation.
Why:
message.updatedfires once per step-finish with a cumulative turn cost, whiledb.insertEventalways appends a newagent_usagerow. A 2-step turn (cost 0.02 then 0.05) was stored as 0.02 + 0.05 = 0.07 instead of the true turn cost 0.05. The old comment claimed re-emitting was "idempotent at the cost column," but there is no cost-column upsert.Reported by @EvolveAegis in #1036; dynamic PoC + citation corrections also by @EvolveAegis.
How: Convert the cumulative figure to a per-step delta (same incremental shape as the codex/kimi adapters) via
toOpencodeUsageStepDelta, keyed by assistant message id in the opencode plugin. Skip no-op refreshes when the cumulative cost does not advance so last-step token snapshots are not double-counted. Bound the in-memory high-water map with insertion-order FIFO (~1000 keys) so long-lived plugin processes cannot unbounded-grow memory. Update the misleading comment.Fixes #1036
Limitations
Plugin-process restart vs durable DB. The cumulative high-water map is in-memory only. A plugin-process restart clears it while prior
agent_usagerows remain in the session DB. The firstmessage.updatedafter restart for an in-flight multi-step turn re-emits the full cumulative cost as if it were a first observation → a one-time double-count on that narrow edge. Still strictly better than status quo, which over-counted every multi-step turn.Missing
info.idkey collision. Wheninfo.idis absent the map key falls back to baresessionId. Two assistant messages in the same session that both lackidshare one high-water entry; the second message's first fire is treated as a continuation of the first. If its cumulative cost is not greater, thedelta <= 0guard silently drops it (fails toward under-counting; no signal/log).FIFO eviction does not refresh insertion position. Re-setting an existing key does not move it to the newest slot. A message still in flight after 1000 newer keys arrive is evicted; its next fire sees no prior cumulative and re-emits the full figure — the same one-time double-count as the restart edge. Practically unreachable at cap 1000 within one turn.
Affected platforms
Test plan
npx vitest run tests/session/parse-opencode-usage.test.ts— 20 passednpx vitest run tests/session/parse-opencode-usage.test.ts tests/opencode-plugin.test.ts(+ related)npx tsc --noEmitcleanChecklist
npm run typecheck/tsc --noEmitpassesnextbranch (unless hotfix) — staged offmainat base3bad0f4per work-order; retarget if maintainers prefernextCross-platform notes
Our CI runs on Ubuntu, macOS, and Windows.
path.join()/path.resolve(), never hardcode/separatorsreadFileSync(0)breaks on Windowsos.tmpdir(), never hardcode/tmp