* fix(opencode): price Vertex turns on a Vertex row and keep an unpriceable recorded cost through the cache (#1547)
OpenCode records the cost of each turn. For an id CodeBurn cannot price the
parser fell back to that cost, but the session cache stored no cost and
re-priced the cached tokens on every read, so the call showed $0. The
recorded cost now rides on the cached call as fallbackCostUSD and applies
only while the tokens price at $0, so a later catalog row or price
override still wins.
The session-level rollup used provider/model while per-message turns use
the bare id since #1543; it now uses the bare id too. google-vertex and
google-vertex-anthropic map to a metered Vertex route.
OpenCode and Kilo Code re-parse once; daily cache v37 re-derives settled days.
* fix(opencode): keep a declared-free id at $0 instead of its recorded cost
A model the user marked flat-rate, priced at a zero rate, or that looks
local prices at $0 on purpose. The recorded-cost fallback now skips those
ids at parse time and on cache read, through one guard in models.ts.
* fix(opencode): keep pre-upgrade history on OpenCode 2 databases and price v2 turns like 1.x
OpenCode 2 copies 1.x sessions into session_v2/session_message on first
start, but the copy drops compaction summary and task-tool turns, skips
messages it cannot decode, and can stop partway. A session present in both
generations now reads its frozen legacy turns first and adds only the v2
turns legacy does not hold (migrated rows keep their ids, so dedup skips
the copies).
v2 turns also used a provider/model id that failed pricing for ids such as
opencode/claude-haiku-4-5; they now use the bare id 1.x stored.
Reported by a user.
* Changelog: OpenCode 2 unpriced ids showed as $0
* feat(cli): filter usage by billing route and mode
Carry provider-observed billing provenance through Hermes and the session cache, register the real OpenRouter route, and add call-level route/billing filters to models, sessions, export, and audit.
Keep unknown billing unclassified, preserve existing pricing, and repair legacy included Hermes ledger baselines without rewriting genuine multi-observation history.
* feat(opencode): preserve OpenRouter route provenance
Carry OpenCode providerID through legacy SQLite, v2, file, and session-rollup paths so OpenRouter calls remain distinct from direct traffic. Invalidate OpenCode and KiloCode caches and cover strict route matching.
* feat(opencode): classify Amazon Bedrock route
Map OpenCode's exact amazon-bedrock providerID to the existing metered Bedrock route, including model families that cannot rely on Anthropic/OpenAI ID shapes. Invalidate OpenCode and KiloCode caches across the route expansion.
* docs(opencode): document billing route provenance
---------
Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
When an OpenCode database has the 2.x session_v2 tables and the legacy session/message/part tables, only the v2 rows were read, so sessions that never migrated during a 1.x to 2.x upgrade were silently dropped and lifetime totals fell. Discovery now also lists legacy sessions whose id is not present in session_v2, and each such session is read from the legacy tables. A session present in both is counted once and resolves to v2. Legacy-only and v2-only databases are unchanged, verified byte-identical on a real database. No cache version bump is needed.
Archiving a session in OpenCode only sets time_archived, but the discovery query and all three recursive session_tree walks filtered on it, so the session vanished from the sessions list, from per-project views and from any cache rebuild. All four predicates are gone; the rows stay in the database, so archived roots and children now parse like any other session. Kilo Code shares this parser and is covered.
Real data, a copy of a 193-session legacy OpenCode database: with nothing archived, main and the branch both read $109.91 / 4,009 calls / 177 sessions.
With three roots flagged archived, main fell to $100.75 / 2,875 / 174 while the branch held $109.91 / 4,009 / 177 — the gap is exactly those three sessions, $9.16023 and 1,134 calls, and nothing else.
The opencode and kilo-code parse versions move as well: an archived root came back on its own, but a root whose child was archived stayed a present, unchanged source, so a warm cache kept serving the parse that dropped the child.
That case measured $104.48 / 3,752 on a main-seeded warm cache and stayed there on the branch until the bump, which restores $109.91 / 4,009.
Tests 4,278 pass, locks 36, tsc clean; the three archived assertions fail on main across both schema generations. CI 19 green.
Fixes#1362.
OpenCode 2.x (mainline since 2.0.3) writes sessions to session_v2 and messages to session_message, tagged by a type column and ordered by seq, while the legacy session/message/part tables freeze at upgrade. Every session created after the upgrade was invisible to CodeBurn.
The parser now branches per database on sqlite_master: when the v2 tables exist they are read and the frozen legacy tables are ignored, otherwise the legacy path runs exactly as before. The two generations are never joined.
v2 payloads normalize into the shared message/part shape, so tokens, cost, model refs, tools, bash commands, user attribution, the parent_id child-session walk and the session-level rollup fallback behave as they did on 1.x.
Compaction rows carry their own cost and tokens in 2.0.3 and were counted as assistant messages on 1.x, so they are counted here too; a running compaction has no usage and yields nothing.
Verified against a real 1.x database: the opencode report is unchanged at $109.91, 4,009 calls, 177 sessions.
Fixes#1293.
When none of a session's messages produce a priced call, the SQLite
parser falls back to the totals on OpenCode's session row. That path
passed tokens_output alone to calculateCost and dropped
tokens_reasoning, while still reporting reasoningTokens, so thinking
tokens were shown but never charged. OpenCode bills reasoning at the
output rate and the per-message path already did the same.
Route both the fallback and buildAssistantCall through
billableOutputTokens so they share one rule with the cache-rehydration
path. The per-message change is behaviour-neutral for opencode and
kilo-code; it only removes the hand-rolled sum that let the fallback
drift.
Refs #1334
Broaden message/part parsing to handle OpenCode versions that store
tokens at the session level, use alternative field names, or emit
different part type identifiers. Adds session-level token fallback
when per-message parsing yields nothing.
Closes#392
npm was warning on every install that prebuild-install@7.1.3 is no
longer maintained. prebuild-install ships as a transitive dependency
of better-sqlite3 and upstream PR #1446 to replace it is still open,
so we switch to Node's built-in node:sqlite module (stable in Node 24,
experimental in Node 22/23) and remove the better-sqlite3 dep entirely.
- src/sqlite.ts: uses DatabaseSync from node:sqlite. The one-shot
ExperimentalWarning about SQLite on Node 22/23 is silenced for that
specific warning; other warnings pass through unchanged.
- package.json: engines.node bumped to >=22 (Node 20 EOL 2026-04-30),
better-sqlite3 and @types/better-sqlite3 removed, @types/node added
(it was coming in transitively via @types/better-sqlite3).
- tests/providers/opencode.test.ts: fixture DB creation switched to
node:sqlite (API parity for the CREATE TABLE + INSERT + prepare
path we use).
End-user install footprint shrinks from 167 to 40 packages and prints
zero deprecation warnings.
Credit: @primeminister for the report.
Reads session data from OpenCode's SQLite databases at
~/.local/share/opencode/. Reuses the existing better-sqlite3
adapter (same as Cursor), lazy-loaded so users without OpenCode
see no difference. Adds bashCommands to the provider interface
so shell command breakdowns work across all providers.
31 tests, schema validation, diagnostic stderr on failures.
Also fixes a pre-existing tsc error in currency.ts.