@jimicze-opencode/opencode-tps
v0.1.0
Published
Real-time token speed metrics (PP and TG) displayed in the OpenCode sidebar
Maintainers
Readme
OpenCode TPS Plugin
Real-time token speed metrics — PP and TG speeds with session totals — displayed in the OpenCode sidebar.
A TUI plugin for OpenCode that monitors Prompt Processing (PP) and Token Generation (TG) speeds during every LLM interaction. Accumulates session-wide token totals including cache and reasoning tokens. Handles subagent nesting, connection drops, and rapid reconnects.
Features
- PP speed — input tokens per second (authoritative from
step-finish) - TG speed — output tokens per second, live
(cur)during streaming and(avg)after completion - Session totals — cumulative input, output, cache, and reasoning tokens across all messages
- Subagent nesting — multi-layer subagent tokens accumulate without corrupting main thread speeds
- Chaos-resilient — survives connection timeouts, rapid reconnects, stale events, and interleaved failure modes
- Configurable — toggle each metric on/off and adjust the refresh interval via JSON config
- Dual fallback chain —
message.updated+session.statusidle catch missedstep-finishevents - No build step — loaded directly by OpenCode at runtime as raw
.tsx
Tech Stack
| Component | Technology | Version |
|-----------|-----------|---------|
| Language | TypeScript | ^5.4.0 |
| UI Framework | SolidJS (via @opentui/solid) | runtime |
| Plugin API | @opencode-ai/plugin/tui | runtime |
| Test Runner | Vitest | ^1.6.0 |
| Runtime | Node.js | >= 18 |
| Module System | ESM (type: "module") | — |
Architecture
Three-layer design with strict separation of concerns:
┌─────────────────────────────────────────────┐
│ UI Layer (SolidJS JSX) │
│ - Registers in sidebar_content slot │
│ - Reads tracker.state() via reactive sigs │
│ - Uses api.theme.current for colors │
├─────────────────────────────────────────────┤
│ Tracker (createTracker) │
│ - SolidJS signals for state │
│ - accumulateTokens() for subagent routing │
│ - Guards: double-start, double-end, │
│ negative deltas, not-generating │
│ - Live TG speed ticker via setInterval │
├─────────────────────────────────────────────┤
│ Event Handlers (tui function) │
│ - message.part.updated → step-start/finish │
│ - message.part.delta → text deltas │
│ - session.status → busy/idle fallback │
│ - message.updated → final tokens fallback │
│ - Message-ID correlation for subagent │
│ detection + stale event rejection │
└─────────────────────────────────────────────┘Data Flow
step-start→startGeneration()— starts timer, marks prefill phase- First
textdelta →onFirstToken()— records first-token timestamp, ends prefill - Each delta →
addDeltaChars()— accumulates live character count step-finish(matching messageID) →endGeneration()— computes PP/TG speeds, accumulates session totalsstep-finish(subagent, different messageID) →accumulateTokens()— adds to session totals without affecting speeds or generation state- Fallbacks —
message.updated(authoritative tokens) orsession.status idle(char-based approximation)
Getting Started
Prerequisites
- Node.js v18+ (ES modules)
- npm v9+
- OpenCode with TUI plugin support
Installation
From npm (recommended)
{
"$schema": "https://opencode.ai/tui.json",
"plugin": ["@jimicze-opencode/opencode-tps"]
}Or via CLI:
opencode plugin @jimicze-opencode/opencode-tpsOpenCode auto-installs and caches it — no manual setup needed.
From source (development)
git clone <repo-url> ~/.config/opencode/plugins/tpsRegister in .opencode/tui.json:
{
"plugin": ["./plugins/tps-plugin.tsx"]
}Restart OpenCode. The plugin loads automatically.
No build step — OpenCode loads
.tsxdirectly at runtime.
Metrics
| Metric | Idle | Prefilling | Generating | Complete |
|--------|------|------------|------------|---------|
| Total | 1,234 total | 0 out | 42 out | 1,234 total |
| PP | -- | ... (blue) | 512 tok/s (avg) | 512 tok/s (avg) |
| TG | -- | ... (green) | 63 tok/s (cur) (green) | 63 tok/s (avg) |
| Cache | hidden | hidden | hidden | 200 tok |
| Reasoning | hidden | hidden | hidden | 30 tok |
Calculation
- PP Speed = input tokens / prefill time (authoritative from
step-finish) - TG Speed = output tokens / generation time (authoritative from
step-finish) - Live TG = chars generated / time since first token (approximate; no tokenizer access in TUI)
- Session Totals = cumulative across all assistant messages, never reset
Configuration
Create .opencode/plugins/tps-config.json to customize display:
{
"showTotal": true,
"showPpSpeed": true,
"showTgSpeed": true,
"showCache": true,
"showReasoning": true,
"liveIntervalMs": 150
}| Option | Default | Description |
|--------|---------|-------------|
| showTotal | true | Show total token count |
| showPpSpeed | true | Show PP speed |
| showTgSpeed | true | Show TG speed |
| showCache | true | Show cache tokens (when > 0) |
| showReasoning | true | Show reasoning tokens (when available) |
| liveIntervalMs | 150 | Live TG speed update interval (ms, clamped to >= 50) |
Project Structure
.
├── .opencode/
│ ├── plugins/
│ │ ├── tps-plugin.tsx # Source (457 lines)
│ │ └── tps-plugin.test.ts # Tests (2974 lines)
│ ├── tui.json # Plugin registration
│ ├── tsconfig-check.json # TypeScript config
│ └── opencode.json # OpenCode config
├── package.json # Dev dependencies
├── vitest.config.ts # Test config
└── .gitignore # Ignores docs/, AGENTS.md, .opencode/*.md
## Development
### Setup
```shell
npm install # install vitest + typescriptTest
npm test # vitest run — 163 tests
npm run test:watch # watch modeTypeCheck
npm run typecheck # tsc --noEmit via tsconfig-check.jsonNo lint or format tool configured — the project is intentionally minimal (1 source file).
Test Coverage
| Suite | Tests | Focus |
|-------|-------|-------|
| createTracker | ~80 | Guards, state transitions, accumulation, formatting, interval |
| accumulateTokens | 5 | Subagent-safe accumulation, speed preservation, nesting |
| chaos scenarios | 7 | Timeouts, reconnects, stale events, interleaved failures |
| event handlers | ~70 | All 4 handlers, malformed data, subagent routing, stale rejection |
| Total | 163 | 120 negative, 43 positive |
Test philosophy: negative tests outnumber positive. Every guard, edge case, and failure mode is tested before the happy path.
Troubleshooting
Plugin not showing in sidebar
- Check
.opencode/tui.jsonhas the correct path to./plugins/tps-plugin.tsx - Verify
~/.local/state/opencode/plugin-meta.jsonshowstps.statuswith a load count - Restart OpenCode — plugins load at session start
Metrics look wrong
- PP speed is very high — normal with fast models; prefill time can be < 20ms
- TG speed drops to zero —
step-finishwas likely missed; a fallback fromsession.statusormessage.updatedis used instead - Cache is zero — some providers don't report cache tokens; this is expected
- Session totals include subagent tokens — by design; subagent work contributes to the session total
Debug logs not visible
console.logfrom the plugin goes to the TUI/browser console, notopencode.log- Look for
[TPS]-prefixed messages in the browser console
Known Limitations
- Live TG speed is approximate — uses char count as a proxy for tokens (no tokenizer access in TUI)
- Session totals reset on OpenCode restart — this is intentional (session-level, not global)
- Idle fallback may slight double-count — if
step-finisharrives after idle fallback ran, the char proxy and authoritative tokens both accumulate. Acceptable trade-off for an edge case. - No formatter/linter configured — project is intentionally minimal
Contributing
The project uses a test-driven approach with extensive negative testing. See the test file for conventions, and the .opencode/ agent context files if you have access to them.
Backlog
Top candidates for future work:
- Per-model speed tracking
- UI polish (prefill spinner, toggle sidebar/footer)
- Cost estimation (if token pricing is available from API)
- Config option to reset session totals
License
MIT — see LICENSE.
