omp packages

@sven1103-agent/pi-metrics0.1.1

Live LLM latency HUD for the Pi coding agent: TTFT, inter-token latency and decode throughput.

GitHub Actions Published 7 days ago MIT ~605 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-metrics

CI Pi compatibility npm OpenSSF Scorecard Socket Badge

Live LLM latency HUD for the Pi coding agent: time to first token, inter-token latency and decode throughput — per call and per session. No network calls, no telemetry, no runtime dependencies.

The pi-metrics HUD, braille graphs

The same session in graphStyle: "block":

The pi-metrics HUD, block graphs

The animations are generated by scripts/demo.mjs, which drives the real renderer over a virtual clock. They play at real time — one second of animation is one second of decoding — so the cadence you see is the cadence the HUD actually has. The figures are illustrative: they exercise the states and the colour ramp the HUD can show, and are not a benchmark. Regenerate with scripts/record-demo.sh.

Contents

Install

pi install npm:@sven1103-agent/pi-metrics

Or pin a release from git:

pi install git:github.com/sven1103-agent/pi-metrics@v0.1.1
Pre-1.0: no compatibility promise

Per Semantic Versioning, a 0.y.z release makes no compatibility promise — behaviour, settings keys and the rendered layout can all change, in any version including a patch. 1.0.0 is the first release that will define a stable API, and it is the target this is being built towards. Until then, pin a tag if you need something that does not move.

What the HUD shows

A static frame, as text
 ─ pi-metrics · demo-model ─────────────────────────────────────────────────────────────── 3 calls ─
 ○ waiting  ● writing  ‖ paused  ✔ done  [██████░░░░░░]  14.9 tok/s  (est)  30 tok  waiting 900ms

 TPS tok/s  ·  fast 100  ·  avg 32  ·  p95 79                                  20s  ·  max 100 auto

                                                                     ⢸⣿⣿⣿⣿⡇
                                                                     ⢸⣿⣿⣿⣿⡇    ⢀⣀⣀⣀⣀
 ⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⢸⣿⣿⣿⣿⡇⠒⠒⠒⠒⣼⣿⣿⣿⣿⣦⣤⣤⣤⣤⣄⣀⣀⣀⣀⠒⠒⠒⠒⠒
 ⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⢸⣿⣿⣿⣿⣷⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⣀⣀⣀⣀
 ───────────────────────────────────────────────────────────────────────────────────────────────────
 TTFT ms  med 400ms  max 500ms                    │ ITL ms  med 22ms  max 38ms
 ⡇⡀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ │  ⡇
 ⣧⡇                                               │ ⢠⡇⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤
 ⣿⡇                                               │ ⣼⡇
 ⣿⡇⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂ │ ⣿⡇⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂⠂
 ───────────────────────────────────────────────────────────────────────────────────────────────────
 STATS  med conn/pre 100ms/300ms  med first 400ms  med ITL 22ms  p95 ITL 38ms  3 calls

The panel sits above the editor by default. From the top:

  • Header — the model name and the number of calls recorded this session.
  • State row — the live state, a pressure bar, throughput, and provenance.
  • TPS chart — session throughput over time.
  • HIST charts — TTFT and ITL, one column per call.
  • STATS row — session medians and the call count.

style: "compact" collapses the TPS and HIST charts to one line each, instead of the tall multi-row graphs.

The state row

All four states are drawn on every frame with the active one highlighted, so there is nothing to re-read mid-response:

Glyph State Meaning
○ waiting No token yet — connection and prompt reading, one wait.
● writing Tokens are flowing.
‖ paused A token is overdue; waiting Xs appears at the end of the row.
✔ done The call finished; the values freeze.

The rest of the row, left to right:

Element Meaning
Pressure bar Throughput against the tpsGood target — full at the target.
12.3 tok/s Current decode throughput.
(usage) / (est) Where the figure came from — see What it measures.
30 tok Output tokens for the call.
waiting 900ms Only while paused: time since the last token.

The TPS and HIST charts

The TPS chart is a session timeline, one column per completed writing second, height = tokens generated that second. It is never reset per call, so separate writing actions stay comparable. The title's left side is measurement (avg, p95); its right side is the chart's own axes — the recording window in seconds, then the tok/s ceiling, marked auto when derived from the session.

The HIST charts plot TTFT and ITL one column per call. In style: "normal" they sit side by side, with the session median drawn as a reference line and the median and max in the title; in compact, each gets a one-line sparkline beside its median.

Commands

Command Effect
/metrics toggle the HUD
/metrics on · /metrics off explicit toggle
/metrics report append a table of recent calls to the transcript
/metrics reset forget the calls recorded in this session
/metrics placement above · below move the panel
/metrics style compact · normal four lines, or multi-row graphs
/metrics graph braille · block graph glyphs
/metrics color calm · vivid · mono colour intensity

Display commands take effect immediately and last until Pi exits. Each one prints the settings.json line that makes the change permanent — the extension cannot write that file itself, because Pi's extension API exposes no settings-write call, and editing the file underneath Pi's settings manager risks dropping unrelated keys.

Settings

All optional, under the pi-metrics key in ~/.pi/agent/settings.json:

{
  "pi-metrics": {
    "enabled": true,
    "placement": "aboveEditor",
    "style": "normal",
    "graphStyle": "braille",
    "colorMode": "calm",
    "timeline": true,
    "liveTimeline": true,
    "history": 120,
    "tpsWindow": 120,
    "tpsMax": "auto",
    "tpsGood": 30,
    "tpsBad": 15,
    "tpsFast": 100,
    "charsPerToken": "auto",
    "cost": true,
    "persist": false
  }
}
Key Values Default Effect
enabled true / false true Show the HUD at startup.
placement aboveEditor / belowEditor, or above / below aboveEditor Panel above or below the editor.
style compact / normal normal Four lines, or the multi-row charts.
graphStyle braille / block braille Chart glyphs.
colorMode calm / vivid / mono calm Colour intensity.
timeline true / false true Draw the TPS and HIST timeline rows (compact style only).
liveTimeline true / false true Also draw the live TPS row (compact style only, needs timeline).
history number 120 Calls kept for the HIST timelines and /metrics report.
tpsWindow number, seconds 120 Seconds of history in the TPS chart.
tpsMax number / auto auto TPS chart ceiling; auto = session p90 plus headroom, never below tpsFast.
tpsGood number, tok/s 30 "Good" target line; fills the pressure bar and is the cold-start colour baseline.
tpsBad number, tok/s 15 "Bad" target line drawn on the TPS chart.
tpsFast number, tok/s 100 "Fast" target line; also floors the automatic chart ceiling.
charsPerToken number / auto auto Divisor for the character token estimate; auto calibrates from the session.
cost true / false true Per-call cost column in /metrics report and the persisted card.
persist true / false false Append a one-line card per call to the transcript.

placement accepts aboveEditor / belowEditor and, because a natural guess is otherwise silently ignored, also above / below. A value that is unusable in any other way is reported at startup as a warning and replaced by the default, so a typo cannot degrade the HUD quietly.

The pre-0.1.0 names rateWindow / rateMax / rateGood / rateBad / rateFast are still read as aliases, so an existing config keeps working. When both a legacy and a new key are set, the new key wins.

Why cost is not on the HUD

cost adds a per-call column to /metrics report and the persisted card. It is not on the HUD: Pi's own footer already carries the session cost, and that total also includes compaction and branch-summary usage this extension never sees, so a second session total here would disagree with the one directly above it.

Why tpsGood and tpsBad are not printed

They are targets, drawn as reference lines on the TPS chart, and configuration rather than measurement — printing them invited reading them as results. tpsGood also fills the pressure bar and is the colour baseline until the session has a few seconds of its own history; colour is otherwise relative (see below), so these thresholds do not decide it. tpsFast is the one that is printed, because it also floors the automatic chart ceiling.

What it measures

Term Meaning
TTFT Time to first token: request → first streamed token, as a session median.
ITL Inter-token latency: gap between streamed deltas, as a median and p95.
tok/s Decode throughput: output tokens over the decode window (first token → end).
TPS The session timeline of tok/s, one column per completed second.
TTOUT Time to output end: wall time of the whole response.

A few properties are worth stating explicitly, because the figures can mislead without them:

  • conn / prefill — /metrics report splits TTFT into conn (request → response headers) and prefill (headers → first token) when the provider exposes the boundary. They are two separate medians, and medians are not additive, so they need not sum to the med first beside them. conn folds in server-side queueing, so the split compares calls to the same provider rather than across providers.
  • (usage) vs (est) — throughput is tagged with its source: (usage) when the provider reported usage.output (the call is finished and its TPS buckets were rescaled to the exact count), (est) in amber while the figure still comes from streamed characters. Estimates are never shown as if they were measured.
  • Latency is the median; throughput is the mean. Latency has a long right tail (cold connections, provider queueing) where single outliers drag a mean away from the typical call. Throughput has no such noise: a stalled second is delivered throughput, and a median would flatter it.
  • Colour is relative, not absolute. One cool-to-hot hue ramp carries severity as the ratio to the session's own typical value, so the same palette means the same thing on an 8 tok/s local model and a 200 tok/s hosted one. A third of typical is the cool end, typical is the middle, three times typical is the warm end. In the charts the ramp is applied vertically: a bar's top edge carries the colour of its own value and the fill fades toward the axis baseline below. colorMode picks how loud it is: calm keeps the theme's own colours, vivid uses saturated anchors, mono drops hue entirely.
  • TTOUT is not on the HUD panel. It appears in /metrics report, in the persisted card, and in the one-line status shown when the HUD is toggled off — alongside the per-call cost.
About the character estimate

When a provider reports no usage, tokens are estimated from streamed characters divided by charsPerToken. Measured across 39,000 real calls against provider-reported counts, the true characters-per-token ranges from ~3.0 (DeepSeek) to ~3.8 (Kimi), so a fixed 4 is off by ~19% typically and ~49% at p90. The default auto calibrates from the session's own completed calls; set a number for reproducibility.

Why the automatic TPS ceiling uses p90

The auto ceiling is the session p90 plus headroom, floored at tpsFast. At the sample counts this chart has, p98 is the maximum — percentile takes the nearest index, so p98 of ten seconds is the tenth — so one outlier second used to move the ceiling and rescale every other bar with it. p95 does not fix that either: it is still the maximum until about a dozen samples. p90 survives both, and a bimodal (half cached) session as well. The deliberate trade: a genuine peak exceeds the ceiling and renders at full height.

Supported Pi versions

Each version is its own CI job, so the Checks tab names the exact release that broke:

Pi Typechecks clean Loads
1.3.0 · 1.2.0 · 1.1.0 ✅ gating ✅ gating
1.0.4 · 1.0.0 · 0.99.2 ⚠️ errors reported, not asserted ✅ gating
< 0.85.1 ❌ ❌ does not load

Below 1.1.0 the published typings are incomplete: the extension loads, but does not typecheck. That lane asserts the load gate and only reports the error count as evidence for the floor — it never requires errors to be present, so an upstream backport lowers the floor without breaking the build. The @earendil-works/pi-ai peer floor matches at >=1.1.0; the other two stay at * as Pi requires.

Interactive rendering is verified manually. The widget is TUI-only, so a headless run cannot exercise it. Do not read these badges as covering what the HUD looks like.

What it does not do

  • It does not send anything anywhere. No network calls, no telemetry, no credential access. See SECURITY.md for the full scope, written against the source, and for how to report a problem privately.
  • It installs nothing. package.json declares no runtime dependencies, so adding this package adds no code for you to audit — npm audit has no tree to report on for it.
  • It does not show a session cost. Pi's footer already carries that total; this extension only adds a per-call cost column to /metrics report and the persisted card.
  • It does not measure prompt tokens per second; it measures decode throughput.
  • It does not persist history across sessions unless persist is enabled.

Maintained by

Sven Fillinger, Research Software Engineer, University of Tübingen, through my agent alias @sven1103-agent — the account that authors the commits here. Same person; the alias exists so the automation is labelled as automation.

The npm package is published from an npm account of the same name, for the same reason: npm view @sven1103-agent/pi-metrics maintainers names the agent account, not a human one, so the automation is visible in the registry too rather than being fronted. I own that account and its recovery factors, so it stays accountable to a person.

License

MIT