npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

opencode-tps-meter

v0.4.2

Published

Live tokens-per-second meter for OpenCode (v1 and v2)

Readme

OpenCode TPS Meter

Real-time AI token throughput visualization for OpenCode

TypeScript Bun OpenCode NPM Version License

╔═══════════════════════════════════════════════════╗
║  92.4 TPS · avg 78.1 · 1,842 tok · cache 91%      ║
╚═══════════════════════════════════════════════════╝

A live tokens-per-second meter plugin for OpenCode. Track AI token throughput in real-time with a configurable rolling window display. Only tracks assistant role messages — user and system messages are automatically excluded from metrics. File parts are also excluded from token counting.

Note: Time display is disabled by default. Enable with showElapsed: true in configuration.


Features

  • Runs on both OpenCode generations — one package, v1 (opencode) and v2 (opencode2)
  • Accurate live rate — every chunk is rated over the interval it was generated in, so the reading is exact from the second chunk, never spikes at the start, and falls when a stream stalls
  • Exact final totals on v2 — provider-reported token counts replace the heuristic estimate when the turn ends
  • Calibrated estimate on v2 — the live token estimate is corrected against provider counts, learned and saved per model
  • Subagent throughput — while subagents run, the footer shows TPS Σ … | main … | explore … on both hosts
  • Prompt cache hit rate — cache 91% · session cache 64% once a step settles
  • Time to first token on v2 — in the detailed footer and the dashboard, and in the compact footer with showTtft
  • Smart Filtering — Counts only model output (text, reasoning and, on v2, tool-call arguments); excludes user prompts, tool results, patches, snapshots, and files
  • Multi-Session Support — Isolated tracking per session with automatic cleanup
  • Throttled UI Updates — Configurable update intervals to prevent UI flooding
  • Optional Time Display — Elapsed time display (disabled by default, enable with showElapsed: true)
  • TPS-Based Color Coding — Visual feedback in your theme's colors based on throughput speed; on v2 a theme switch recolors the meter live
  • Persistent TUI Meter — OpenCode TUI slot display beside the session prompt on supported versions
  • Opt-in Toast Fallback — Legacy toast UI is disabled by default; enable it only for older OpenCode surfaces
  • Zero Console Logging — Safe for TUI environments (no console.* calls)
  • Dual Format — ESM and CommonJS builds for maximum compatibility
  • Heuristic Token Counting — Fast approximation without heavy dependencies

OpenCode version support

One package supports both OpenCode generations. They install as separate binaries (opencode and opencode2) and can run side by side.

| | OpenCode v1 (opencode) | OpenCode v2 (opencode2) | |---|---|---| | Config key | "plugin" | "plugins" | | Meter renders in | TUI session prompt | TUI prompt footer status | | Registered as | opencode-tps-meter | opencode-tps-meter | | Default export shape | function or { id, server } | { id, setup } (object required) | | Toast fallback | Available (opt-in) | Not available — v2 server plugins have no UI surface |

Tested on OpenCode v2 (server reports features: server+tui, status: active), whose shipped binary implements the v2 TUI plugin API this package targets — its own built-in TUI plugins (opencode.notifications, diff-viewer) use Plugin.define({ id, setup }) with ctx.ui.slot({ append, render }) and ctx.data.on(...).


Installation

OpenCode v1

Install with OpenCode's plugin installer so the persistent TUI entrypoint is registered:

opencode plug opencode-tps-meter@latest

For manual installation, add the package to your TUI config (~/.config/opencode/tui.json or tui.jsonc) for persistent display:

{
  "plugin": ["opencode-tps-meter@latest"]
}

OpenCode v2 (opencode2)

npm install opencode-tps-meter

v2 replaced layered tui.json(c) files with a single global cli.json, and renamed the plugin config key to plugins. Register in opencode.json:

{
  "plugins": ["opencode-tps-meter@latest"]
}

The single package name is all you need — the default export carries both plugin shapes, so v1 reads server and v2 reads setup.

Do not use subpath specifiers in plugins. v2 treats every string in that array as an npm package name or a local path, so "opencode-tps-meter/v2" is not resolved as a subpath — it is attempted as a package install and fails. The opencode-tps-meter/v2 and /v2/tui exports exist for programmatic import only.

v2 also accepts inline options, which take priority over config files and environment variables:

{
  "plugins": [
    {
      "package": "opencode-tps-meter@latest",
      "options": { "showElapsed": true, "enableColorCoding": true }
    }
  ]
}

Quick Start

The plugin hooks into OpenCode events and starts tracking TPS after installation.

On v1, the meter renders in the session prompt area via opencode-tps-meter/tui. OpenCode's installer detects that entrypoint; manual installs need the package listed in TUI config.

On v2, TUI plugins are registered in the global ~/.config/opencode/cli.json under plugins (auto-migrated from v1's tui.json). Per OpenCode's loader spec, a package with an exports map is resolved via ./tui or ./server and never falls back to exports["."], so this package publishes both.

Registering a local checkout on v2: point the entry at the built dist directory, not the repository root, and use the same path in opencode.json and cli.json:

{ "plugins": ["file:///abs/path/to/opencode-tps-meter/dist"] }

For a local directory the loader resolves files by path (<dir>/server or <dir>/index for the server, <dir>/tui for the TUI, trying the usual extensions) and never reads package.json exports. The repository root has no index.* of its own, so it is skipped without an error; dist/ contains both index.mjs and tui.mjs. exports is only used for npm-package specs ("opencode-tps-meter").

Note on the v2 TUI module shape: v2 requires { id, setup }. Plugins exporting only v1's { id, tui } are rejected by the loader. This package's TUI module exports both keys, so it satisfies v1 and v2 from one file.

v2 ships a plugin manager dialog — open the command palette (ctrl+p) and run Open plugin manager dialog (plugins.list) to see which TUI plugins loaded, and to enable/disable them with space. Runtime enable/disable state is persisted and overrides config at startup, so check there first if the meter does not appear.

If you intentionally want the old toast UI on an older v1 surface, add the package to normal OpenCode plugin config too and set toastFallback: true or TPS_METER_TOAST_FALLBACK=true. This option does nothing on v2.

Package Exports

When using the plugin with OpenCode, you only need the default package export for your host generation.

import TpsMeterPlugin from 'opencode-tps-meter';          // v1 server (also carries the v2 shape)
import TpsMeterTuiPlugin from 'opencode-tps-meter/tui';   // v1 TUI (also carries the v2 shape)
import TpsMeterV2 from 'opencode-tps-meter/v2';           // v2 server only
import TpsMeterV2Tui from 'opencode-tps-meter/v2/tui';    // v2 TUI only

Internal tracker/tokenizer/UI helper modules are not public package exports. For experiments or forks, clone the repository and import helpers from local source paths instead of from the published package.

v2-only capabilities

These need APIs that do not exist on v1, so they are inert on opencode.

| Feature | What you get | v2 API | |---|---|---| | Calibrated live rate | The streaming estimate is corrected against provider token counts, learned per model | session.step.started (model/agent) + session.step.ended (tokens) | | Tool-call output counted | Arguments the model writes for a tool call (a file write, an edit) count as output, so a step that only calls a tool still has a real reading | session.tool.input.delta / .ended | | Tool time | Time the turn spent running tools, shown separately in the detailed footer; it is never inside a step's rate window | session.tool.called / .success / .failed | | Time to first token | Measured from turn start on the host clock; shown in the detailed footer and the dashboard, and in the compact footer with showTtft | session.execution.started + event created | | Hidden overhead | Tokens spent on auto-title and compaction that no step reports | session.usage.updated residual | | Per-subagent breakdown | Live footer aggregate — TPS Σ 318 \| main 63 \| explore 91 \| … — while subagents generate, on both hosts (v1 via session.created/.updated parentID + api.state.session; v2 via ctx.data.session.root / .family). Σ sums every session streaming tokens right now — a root waiting on its subagents drops out on its own, and counts again once it resumes. Children keep their spawn position (no column swapping), stay visible through long tool calls while the host reports them running, and drop off ~4s after they finish. The v2-only sidebar adds the full family breakdown panel | api.state.session (v1) / ctx.data.session (v2) | | Durable ledger + dashboard | Per-model mean/best throughput and TTFT, plus the token calibration, persisted and shared across windows; the dashboard closes with escape or q | ctx.storage.store, ctx.ui.router | | Wire-level TTFB | Provider dispatch to response headers, below the streaming pipeline | ctx.session.hook("http.request"/"http.response") |

Every one of these is feature-detected. A host that exposes none of the optional APIs still gets the footer meter and nothing else.

Commands (palette, /tps, or a key you bind):

/tps              open the throughput dashboard
/tps detail       toast with current stats
/tps detailed     footer shows tools / ttft / overhead
/tps compact      default footer
/tps hidden       hide the meter
/tps reset        clear the durable ledger

The dashboard closes with escape or q and returns to wherever you opened it from.

Latency. Token counting is incremental — each chunk is scanned once and never re-read, so absorbing a long response is linear rather than quadratic. The live rate is exact from the second chunk of a stream (about 100ms in on v2) with no seed value or smoothing lag, and the publish is synchronous, so a chunk is on screen about a millisecond after it arrives. The v2 display throttle defaults to 8ms; v1 keeps its 50ms default for the costlier toast path. Tune with updateIntervalMs (display throttle) and rollingWindowMs (averaging window); an explicit value overrides the default on either host.

Clock note. The TUI delivers events in ~10ms batches, so wall-clock time inside a handler is flush time shared by the whole batch. All timing uses each event's host-stamped created.

Peer dependencies

@opentui/solid and solid-js are optional peer dependencies rather than bundled dependencies. The TUI host supplies the renderer and reactive runtime; installing a second copy inside the plugin would give it a separate reactive graph and the meter would render once and never update.


Configuration

Configuration starts with built-in defaults, then merges these sources in order (later sources override earlier ones):

  1. Built-in Defaults
  2. Project Config (.opencode/tps-meter.json)
  3. Global Config (~/.config/opencode/tps-meter.json)
  4. Environment Variables (TPS_METER_*)
  5. v2 inline plugin options (options on the plugin entry, see Installation)

Note: Environment variables override both config files, and v2 inline options override everything. Global config overrides project config in the current implementation.

Environment Variables

# Core settings
TPS_METER_ENABLED=true                   # Enable/disable plugin
TPS_METER_TOAST_FALLBACK=false           # Opt into old toast UI when needed
TPS_METER_UPDATE_INTERVAL_MS=50          # UI update throttle (ms)
TPS_METER_INITIAL_DISPLAY_DELAY_MS=10    # Extra delay before the first live reading (ms)
TPS_METER_ROLLING_WINDOW_MS=1000          # TPS calculation window (ms)
TPS_METER_FORMAT=compact                  # compact | verbose | minimal (reserved, no effect yet)
TPS_METER_MIN_VISIBLE_TPS=0               # Minimum TPS to display

# Display toggles
TPS_METER_SHOW_AVERAGE=true
TPS_METER_SHOW_INSTANT=true
TPS_METER_SHOW_TOTAL_TOKENS=true
TPS_METER_SHOW_ELAPSED=false
TPS_METER_SHOW_CACHE_HIT=true
TPS_METER_SHOW_TTFT=false

# Token counting heuristic
TPS_METER_FALLBACK_HEURISTIC=chars_div_4  # chars_div_4 | chars_div_3 | words_div_0_75

# Color coding (visual feedback based on TPS speed)
TPS_METER_ENABLE_COLOR_CODING=false       # Enable color-coded TUI text
TPS_METER_SLOW_TPS_THRESHOLD=10           # Below this = red (slow)
TPS_METER_FAST_TPS_THRESHOLD=50           # Above this = green (fast)

JSON Configuration

Create .opencode/tps-meter.json in your project root:

{
  "enabled": true,
  "toastFallback": false,
  "updateIntervalMs": 50,
  "initialDisplayDelayMs": 10,
  "rollingWindowMs": 1000,
  "showAverage": true,
  "showInstant": true,
  "showTotalTokens": true,
  "showElapsed": false,
  "showCacheHit": true,
  "showTtft": false,
  "format": "compact",
  "minVisibleTPS": 0,
  "fallbackTokenHeuristic": "chars_div_4",
  "enableColorCoding": false,
  "slowTpsThreshold": 10,
  "fastTpsThreshold": 50
}

Enable Time Display

To show elapsed time in the meter:

{
  "showElapsed": true,
  "format": "compact"
}

Footer: 92.4 TPS · avg 78.1 · 1,842 tok · 00:23

Default Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | boolean | true | Enable/disable the plugin | | toastFallback | boolean | false | Emit legacy toast notifications from the server plugin | | updateIntervalMs | number | 50 | UI update interval in milliseconds | | initialDisplayDelayMs | number | 10 | Extra delay before the first live reading. A reading also waits until a rate is measurable (about 50ms of output) | | rollingWindowMs | number | 1000 | Rolling window for TPS calculation | | showAverage | boolean | true | Show average TPS in display | | showInstant | boolean | true | Show instantaneous TPS in display | | showTotalTokens | boolean | true | Show total token count | | showElapsed | boolean | false | Show elapsed time | | showCacheHit | boolean | true | Show the prompt cache hit rate (provider-reported) | | showTtft | boolean | false | Show time to first token in the footer | | format | string | "compact" | Reserved: compact, verbose or minimal is accepted but does not change the display yet | | minVisibleTPS | number | 0 | Minimum TPS value to trigger display | | fallbackTokenHeuristic | string | "chars_div_4" | Token counting method | | enableColorCoding | boolean | false | Enable TPS-based color coding | | slowTpsThreshold | number | 10 | TPS below this shows red (slow) | | fastTpsThreshold | number | 50 | TPS above this shows green (fast) |


Color Coding

Enable visual feedback with color-coded TUI text based on token throughput speed:

{
  "enableColorCoding": true,
  "slowTpsThreshold": 10,
  "fastTpsThreshold": 50
}

| Color | TPS Range | Meaning | |-------|-----------|---------| | 🔴 Red | Below slowTpsThreshold | Slow generation | | 🟡 Yellow | Between thresholds | Medium speed | | 🟢 Green | Above fastTpsThreshold | Fast generation | | 🟢 Green | Final stats | Message complete |

Note: Persistent TUI display supports color coding directly. Legacy toast color coding is available only when toastFallback is enabled and OpenCode exposes TUI toast methods (client.tui.showToast or client.tui.publish); the fallback client.toast methods only support info/success variants.


Cache Hit Rate

OpenCode reports provider token usage as { input, output, reasoning, cache: { read, write } }, where input EXCLUDES cache reads and writes. The meter therefore computes the prompt-side total as input + cache.read + cache.write and shows the cached share:

cache hit rate = cache.read / (input + cache.read + cache.write)

The meter appends cache 91% to the footer once a provider-reported step has settled. During streaming no cache accounting is available yet, so the segment is omitted rather than shown as 0% — nothing appears until the first step of a session has finished, which is expected and not a fault. Toggle with showCacheHit / TPS_METER_SHOW_CACHE_HIT. The figure is whole-percent, and rounding never crosses the 100 boundary: 12000 cached of 12040 prompt tokens reads 99%, not 100% — only a prompt with nothing left uncached reads 100%.

Time to First Token

TTFT is measured on v2 from the host's turn start to the first streamed token. The dashboard already reports a per-model mean, and /tps detailed puts the current turn's figure in the footer next to tool time and overhead. Set showTtft: true (or TPS_METER_SHOW_TTFT=true) to keep it in the compact footer as well:

92.4 TPS · avg 78.1 · 1,842 tok · ttft 1.2s

It stays on the last measured turn until the next one starts, and is omitted while the value is unknown, such as before the first token or on v1, which does not report a turn start. When the footer shows the subagent aggregate, the segment is left out because a family has no single first token.

What the Meter Shows

Footer (TUI, both hosts)

While output streams, the leading figure is the live rate:

92.4 TPS · avg 78.1 · 1,842 tok

Once the step settles it becomes the step's average, and the cache figures appear:

78.1 TPS · avg 78.1 · 1,842 tok · cache 91% · session cache 64%

showElapsed: true adds · 00:23, and showTtft: true (v2) adds · ttft 1.2s. On v2, /tps detailed appends tool time, time to first token and hidden overhead, e.g. · tools 4.2s · ttft 1.2s · +120 ovh.

While subagents generate, the footer switches to the family line. Same-named subagents carry a short session-id suffix:

TPS Σ 318 | main 63 | explore(9B5c) 91 | explore(A1dF) 86 | general 78 | +2

Toast (v1, opt-in with toastFallback)

TPS: 92.4 (avg 78.1) | tokens: 1,842 | 00:23 | cache: 91%

Each active subagent gets its own line below it.

The format option (compact, verbose, minimal) is accepted but changes neither surface yet.


Package API

The published package exposes only the OpenCode plugin entrypoints:

import TpsMeterPlugin from 'opencode-tps-meter';
import TpsMeterTuiPlugin from 'opencode-tps-meter/tui';
import TpsMeterV2 from 'opencode-tps-meter/v2';
import TpsMeterV2Tui from 'opencode-tps-meter/v2/tui';
  • opencode-tps-meter is the v1 server/toast fallback entrypoint. It does not emit toasts unless toastFallback is enabled. The default export is { id, server, setup }, so v1 reads server and a v2 host reads setup from the same module.
  • opencode-tps-meter/tui is the persistent v1 TUI entrypoint. The exported object carries both tui (v1) and setup (v2).
  • opencode-tps-meter/v2 and opencode-tps-meter/v2/tui are plain { id, setup } v2 definitions with no callable v1 shape, for hosts that reject the dual-shaped modules.

Tracker, tokenizer, and UI helper modules are internal implementation details and are not exported as public package subpaths. If you need those helpers for experimentation, clone or fork the repository and import them from local source files.


Token Counting Heuristics

| Method | Algorithm | Best For | Accuracy | |--------|-----------|----------|----------| | chars_div_4 | Math.ceil(chars / 4) | General text | ~75% | | words_div_0_75 | Math.ceil(words / 0.75) | English prose | ~80% | | chars_div_3 | Math.ceil(chars / 3) | Code | ~70% |

Note: This plugin uses fast heuristic token counting. It does not include gpt-tokenizer or similar heavy tokenization libraries to keep the bundle size small and avoid bundling issues.


How It Works

Event Handling (OpenCode v1)

The plugin subscribes to four OpenCode event types:

  1. message.part.delta — Processes live text deltas when OpenCode emits incremental streaming updates

    • Updates live TPS while preserving text cache state for later full-part updates
    • Zero-token text deltas, such as whitespace under word-based heuristics, are cached so later full updates do not re-count already streamed text
  2. message.part.updated — Processes full text/reasoning part updates

    • Role Filtering: Only tracks parts belonging to messages with role: "assistant"
    • User prompts excluded: Prevents TPS spikes from user input (which would appear as thousands of TPS since prompts arrive instantly)
    • Counted parts: Only text and reasoning are counted toward TPS
    • Ignored parts: tool, patch, snapshot, file, subtask, agent, retry, compaction
    • First reading: the live display begins once a rate is measurable (see How the Rate Is Measured)
    • Calculates delta tokens between consecutive updates
    • Updates tracker and throttled UI display
  3. message.updated — Handles message status changes

    • Records role information (user, assistant, system) for each message ID
    • Used to filter parts in message.part.updated events
    • Processes official token counts from API responses when available
    • Displays final stats when message completes
  4. session.idle — Persistence and cleanup trigger

    • Keeps the latest streamed TPS stats visible as an inactive status once they had a measurable rate
    • Removes tracker for the specific session
    • Clears all session-specific caches (role cache, token cache, part text cache)
    • Preserves completed session stats

Event Handling (OpenCode v2)

v2 removed the entire message.* event family and replaced it with granular session events. The core mapping:

| v1 event | v2 replacement | |---|---| | message.part.delta (field text) | session.text.delta | | message.part.updated (reasoning) | session.reasoning.delta | | message.updated (completed) | session.step.ended | | message.updated (info.tokens) | session.usage.updated | | session.idle | session.idle (unchanged) |

On top of these, the v2 entries listen to session.step.started / .failed, session.tool.* (calls and their arguments) and session.execution.*, for model attribution, tool time, time to first token and calibration.

Consequences worth knowing:

  • No role filtering is needed. session.text.delta and session.reasoning.delta are assistant output by construction, so v1's message-role cache, part-type filtering, and part-text extraction have no v2 equivalent — user prompts can no longer leak into the count.
  • Final totals are exact, not heuristic. session.step.ended carries provider-reported tokens.output and tokens.reasoning, which the meter prefers over its own character-based estimate. The heuristic still drives the live rolling rate, since deltas arrive before any usage report.
  • The settled average describes what streamed. It is the streamed tokens divided by the time they streamed over, corrected by the provider's count when the two agree within 2x. Reasoning the provider reports but the host never streamed is left out of that comparison, so hidden reasoning cannot inflate the rate.

session.step.ended reports the same finish reasons as v1 (stop, length, tool-calls, content-filter, error, unknown), so tool-call and error handling behave identically across both hosts: a tool-calls finish freezes the reading on screen instead of clearing it, and unknown discards it.

Output Counted

Only model output contributes to TPS:

  • text — Assistant output text
  • reasoning — Assistant reasoning stream
  • Tool-call arguments the model writes (v2 only, from session.tool.input.*)

Tool results, snapshots, patches and file contents are ignored: they are not tokens the model generated.

How the Rate Is Measured

Hosts batch output before publishing it (v2 flushes every ~100ms), so each chunk's tokens were generated over the interval since the previous chunk, not at the instant it arrived. The tracker rates every chunk over that interval:

  • The opening chunk counts toward the total but never toward a rate. It was generated over an interval the meter never saw (time to first token included). Dividing it by the few milliseconds before the first update is what produced five-digit readings.
  • No reading is shown until a rate is measurable (at least 50ms of generation, normally the second chunk). A response that arrives in a single chunk shows only its settled figures.
  • The live rate is the rolling window (rollingWindowMs), timed entirely by the timestamps tokens were recorded with: host event times on v2, local time on v1. Clocks are never mixed.
  • Tool-call arguments are rated like any other chunk, over the gap since the previous one. Many providers deliver a tool call whole, and the host then emits its start and end ~100ms apart, so that gap says nothing about how long the model took to write it.
  • A step too short to time (a single chunk, such as a tool call delivered whole) settles at its tokens over the step's whole duration, from when the step began. That includes time to first token, so it reads low rather than high.
  • Calibration is weighted by size and saved per model. Each step's provider count corrects the heuristic in proportion to how many tokens it covered, so a short tool call (whose count includes call framing) barely moves it. When reasoning streamed but the provider reports none, the step is left out, since its count cannot be compared.

Build System

This project uses Bun for building dual-format outputs:

# Install dependencies
bun install

# Run tests
bun test

# Build ESM + CJS outputs
bun run build

Build Outputs

  • dist/index.mjs — ESM build
  • dist/index.js — CommonJS build (with OpenCode compatibility fix)
  • dist/index.d.ts — TypeScript declarations
  • dist/tui.mjs — OpenCode TUI plugin ESM build
  • dist/tui.js — internal CommonJS TUI artifact generated by the build; public TUI loading uses the ESM opencode-tps-meter/tui export
  • dist/tui.d.ts — OpenCode TUI plugin declarations
  • dist/v2/server.mjs — OpenCode v2 server plugin (opencode-tps-meter/v2)
  • dist/v2/tui.mjs — OpenCode v2 TUI plugin (opencode-tps-meter/v2/tui)

Note: The CJS build requires a manual export fix for OpenCode compatibility:

// Replaces: module.exports = __toCommonJS(exports_src);
// With:
// module.exports = exports_src.default;
// module.exports.default = exports_src.default;
// Object.defineProperty(module.exports, "__esModule", { value: true });

Troubleshooting

Plugin Not Displaying

  • ✅ Verify TPS_METER_ENABLED is not set to false
  • ✅ v1: for persistent TUI display, install with opencode plug install opencode-tps-meter@latest or add the package to OpenCode's TUI config (~/.config/opencode/tui.json or tui.jsonc)
  • ✅ v2: use the "plugins" key, not "plugin" — and note tui.json/tui.jsonc are no longer read at all, having been replaced by a single global cli.json
  • ✅ v2: open the plugin manager (ctrl+p → Open plugin manager dialog) and check the meter is enabled; a failed load is logged to ~/.local/share/opencode/log/opencode.log as failed to load plugin
  • ✅ v2: confirm the host supplies @opentui/solid and solid-js; a nested copy inside the plugin gives the meter its own reactive graph, so it renders once and then freezes
  • ✅ Verify your installed package exposes opencode-tps-meter/tui for TUI plugin loading
  • ✅ If you still see a TPS Meter popup, you are seeing the old server plugin toast path; remove the package from normal OpenCode plugin config or set toastFallback: false / TPS_METER_TOAST_FALLBACK=false
  • ✅ For intentional toast fallback, set toastFallback: true and check that OpenCode client has tui.showToast, tui.publish, or toast.info methods
  • ✅ Ensure you're viewing assistant role messages (user/system are filtered)
  • ✅ Check that minVisibleTPS threshold is not set too high

Very High TPS Readings (Fixed)

Readings like TPS: 13590.0, or a five-digit average on a subagent, came from dividing a host's first batch of output by the few milliseconds before the first update. Since v0.4.1 every chunk is rated over the interval it was generated in, the opening chunk never counts toward a rate, and nothing is shown until a rate is measurable. Only assistant output is counted; user prompts and file parts are excluded. If you still see implausible figures, update to the latest version.

Incorrect Token Counts

  • For general text: Use fallbackTokenHeuristic: 'chars_div_4' (default)
  • For prose: Use fallbackTokenHeuristic: 'words_div_0_75'
  • For code: Use fallbackTokenHeuristic: 'chars_div_3'
  • Remember: Tool outputs, patches, snapshots, and file parts are always excluded from counting
  • This plugin uses fast heuristics, not exact tokenizers like gpt-tokenizer
  • On v2 the live estimate is calibrated against provider counts per model, and final totals are provider-reported

High CPU Usage

  • Increase updateIntervalMs (try 100ms or 200ms)
  • Increase initialDisplayDelayMs if the first live reading is too jumpy
  • Increase rollingWindowMs if using short windows
  • Disable showElapsed if not needed

Import Errors

Main Plugin (ESM & CommonJS):

import TpsMeterPlugin from 'opencode-tps-meter';
// or
const TpsMeterPlugin = require('opencode-tps-meter');

OpenCode TUI Plugin:

import TpsMeterTuiPlugin from 'opencode-tps-meter/tui';

Helper modules are internal and are not exported as package subpaths. If you need tracker/tokenizer internals, use a local repository checkout or fork.


Exported Types

export type {
  BufferEntry,         // Rate tracker chunk entry
  TPSTrackerOptions,   // Tracker configuration
  TPSTracker,          // Tracker interface
  UIManager,           // UI manager interface
  TokenCounter,        // Token counter interface
  Config,              // Plugin configuration
  OpenCodeClient,      // OpenCode client interface
  DisplayState,        // Display state structure
  AgentDisplayState,   // Per-agent display structure
  AgentIdentity,       // Agent identity metadata
  PluginContext,       // Plugin context
  Logger,              // Logger interface
  MessageEvent,        // Event structure
  PluginHandlers,      // Handler return type
} from 'opencode-tps-meter';

License

MIT


Made for the OpenCode community