v0.1.2
Changelog
All notable changes to @devinoldenburg/opencode-tps-meter are documented here. The format
follows Keep a Changelog and this project adheres to
Semantic Versioning (see VERSIONING.md).
Unreleased
0.1.2 — 2026-06-21
Fixed
- RateMeter precision.
smooth()now seeds from the first measurable instantaneous rate
instead of zero, eliminating cold-start under-reporting. Non-positive and non-finite deltas
are ignored but still refresh_lastAtfor accurateactive()state. Non-monotonic
(backwards) timestamps are rejected. The internal_windowSuminvariant is now guarded
with an explicit error throw instead of a silent reset. Theactive()boundary is exclusive
(< windowMs), removing a 1 ms false positive at exactly the window boundary.rate()span
uses the true first-sample timestamp rather than_startedAt, improving precision for streams
with multiple deltas. - GenerationTimer fixes. Zero-token deltas no longer consume the post-gap prime marker via a
_pendingPrimeflag, so the first real token after a tool-call gap is correctly classified as
a resume chunk. Non-monotonic timestamps are rejected.setTokens()now scales_primeTokens
proportionally when the provider's exact token count differs from the streamed estimate, keeping
decodeTokensandtps()truly exact at completion. - Formatter correctness.
fmtRatenow crosses into "k" format at≥ 999.5instead of
≥ 1000, so values like 999.7 display as"1k"rather than"1000".fmtTokensguards
negative inputs and returns"0".fmtMsrounds to the nearest second before splitting into
minutes, eliminating"Xm60s"overflow at minute boundaries.fmtCostreturns"$0"for
negative and zero values.sparklineclampsmaxto at leastmin, handling inverted
min/max pairs. - View layer. The empty-segment filter now also catches
text: "", removing unnecessary
empty<span>DOM nodes. The stale-copyDEFAULTSobject inview.jsis replaced with a
shared import fromconfig.js, so display config can no longer drift out of sync. Streaming
messages with history now show both the live-detail and last-message rows instead of one
hiding the other. The sparkline reappears at turn completion (idle state). - Config resolution.
ordernow has a0floor clamp, matching all other numeric options.
Boolean options (showSparkline,showSession,showWaits,showTotals,showCost,
showCache) use a unifiedbool()helper with consistent opt-in/opt-out semantics and
environment-variable overrides. Named constants (MIN_ORDER,MIN_POLL_MS, …) replace
inline magic numbers.OPENCODE_TPS_METERempty string now disables (added toisFalsy).
isTruthyhandles"yes"/"on"/"1"forOPENCODE_TPS_METER_DISABLE.options.enabled=true
now overrides the env disable flag.num()options correctly fall back to environment
variables when the option is omitted or nullish, rather than treating explicit0as unset.
The metric env variable (OPENCODE_TPS_METER_METRIC) can now switch back to"generated"
when the option is set to"output". - TUI plugin reliability. The per-session
partLenmap now tracks{ length, messageID }
per part and is cleaned up onmessage.removed, preventing memory leaks. The chars-per-token
calibration ratio is now per-model (ratioByModel) rather than a single session-global
variable, fixing cross-model calibration corruption. Calibration falls back to partLen state
whenstate.part()returns empty for completed messages.sessionIDcomparisons use string
coercion to avoid type-mismatch silent event drops.onCleanupis registered before
setInterval, so a component construction throw no longer orphans the interval timer.
TypeScript annotations added throughout. Delta-fallback no longer inflates token count on
first sight of already-large part text. - Installer safety and correctness. Malformed existing
tui.json/package.jsonnow causes
a clear error exit instead of silent overwrite. Rotating backup files (file.bak,
file.bak.1, …) replace the single.bakthat was silently overwritten.--dirfollowed by
the next flag now correctly errors instead of capturing the flag name as a directory. The
--helptext is a static constant rather than a fragile source-line-range parse. Tuple-format
plugin entries["@devinoldenburg/opencode-tps-meter", { … }]are now detected and migrated
to@devinoldenburg/opencode-tps-meter/tuipreserving their options.--uninstallnow also
removes the package dependency frompackage.json. A failednpm installnow sets a non-zero
exit code and returns rather than printing a misleading success message. - Package metadata and exports. The package root (
".") now exportsplugins/tps/root.js,
which re-exports all core named exports AND provides adefault { id, tui }TUI plugin shim,
so existing OpenCode configs using["@devinoldenburg/opencode-tps-meter"]continue to work.
The explicit TUI path"./tui"is unchanged. TypeScript declaration file (index.d.ts) added
for the"./core"surface. Peer dependency ranges tightened from"*"to
@opentui/solid@>=0.4.1 <1andsolid-js@>=1.9.12 <2.package-lock.jsonis now committed
for reproducible development/CI installs; the ignore rule was removed.NOTES.mdis excluded
from the published npm tarball. - Calibration robustness.
MIN_RATIOlowered from1.2to0.25for CJK and other
high-density languages.calibrateRatiowithalpha=0now emits a diagnostic warning rather
than silently freezing calibration. Then0()fallback no longer passes negative numbers
through (returns0instead).aggregatenow surfacesdecodeSourcemetadata. - CI, release, and tooling. The
testjob now runs onubuntu-latest,macos-latest, and
windows-latestwith Node 20.11, 20, 22, and 24. A TypeScript check step (npx tsc --noEmit)
was added.npm ciruns before tests.pull_requesttriggers on all branches. The Bun
plugin-verification job and release workflow now both runnpm run verify:plugin(with Bun
setup in release). Release publishes prerelease versions under thenexttag. Dependabot
configuration added for npm and GitHub Actions weekly updates..gitignorecovers*.tgz,
*.bak, and*.bak.*. - Demo and verification tools.
demo.mjsnow feeds pre-computed schedule token counts
directly instead of round-tripping throughtokensFromChars, eliminating floating-point
drift. The--citool-gap detection uses the actualTOOL_GAPconstant. The CI smoke-test
assertion checks the first line starts with"TPS"instead of a fragile.includescheck.
The terminal cursor is restored on crash.verify-plugin.mjswraps the empty-session renderer
call in a try/catch and uses an exact string match for the expected "No renderer found" error.
install-peers.mjsversion-pins peer packages and copies transitive dependencies without
overwriting existing packages; the temporary directory is cleaned up.
0.1.1 — 2026-06-20
Fixed
- Documentation accuracy pass. Removed a duplicated "Calibration" section in
ARCHITECTURE.md; corrected the publicexportskey notation (.not./) in
VERSIONING.md; documented the previously-undocumentedshowSparklineoption and the
OPENCODE_TPS_METER=0disable form inREADME.md; added--helpto the documented
installer flags (README + CHANGELOG); updated the internalNOTES.mdto reflect the public,
published repository. - Removed a dead, never-read
--globalflag branch fromscripts/install.mjs(no behavior
change).
0.1.0 — 2026-06-20
Initial release.
Added
- Pure generation TPS — measures
tokens ÷ active-generation time, where active time is the
summed gaps between streamed tokens minus any gap large enough to be a tool call / wait
(gapMs, default 1500 ms). Tool execution, permission prompts, and provider stalls are
excluded from the rate and surfaced separately as−Ns wait. Prefill/resume "prime" tokens are
excluded from the numerator, so a constant-rate stream measures its true rate to the token —
proven by a server-stream simulation. - Consistent live + exact — the live headline is the in-flight message's active-generation
rate; on completion it locks to the provider's exacttokens.{output,reasoning}over the same
measured time, so the number doesn't jump. - Measured time-to-first-token, a sparkline (windowed
RateMeter) that dips during a tool
call while the headline holds steady, and pooled session average + peak. - No native duplication — token totals and cost (shown by OpenCode's Context section) are off
by default (showTotals/showCost); the meter shows throughput, TTFT, and excluded wait. - Self-calibrating characters→token ratio, learned per model from each completed message's
exact token count. - Additive rendering into the stacking
sidebar_contentslot (order 150) — never replaces
native sidebar sections. - Theme-aware colors with per-tone overrides; configurable metric (default
generated),
gap threshold, detail level, sparkline, and labels, via plugin options or environment variables. - Installer (
scripts/install.mjs, the package bin) — idempotent, reversible, with
--local/--dir/--no-install/--dry-run/--uninstall/--print/--helpmodes. - Pure, framework-free core (
plugins/tps/*.js) with 62 unit + integration tests
asserting hand-computed exact expectations. - Tooling: a runnable terminal demo (
tools/demo.mjs, with a tool-call gap and a
deterministic--cimode), a Bun-based plugin verifier (tools/verify-plugin.mjs), and a peer
installer (tools/install-peers.mjs).
What's Changed
- Fix triaged TPS meter issue batch by @devinoldenburg in #141
New Contributors
- @devinoldenburg made their first contribution in #141
Full Changelog: v0.1.1...v0.1.2