pi-display-runtimes

Durable timing, token, and cost accounting for pi: compact transcript rows after every model request, tool call, compaction, branch summary, and multi-step agent run; a live below-editor activity widget while pi is busy; and /timings for a per-operation i

Packages

Package details

extension

Install pi-display-runtimes from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-display-runtimes
Package
pi-display-runtimes
Version
1.0.0
Published
Sep 13, 2026
Downloads
187/mo · 187/wk
Author
prawnbear
License
MIT
Types
extension
Size
89.2 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-display-runtimes

A pi extension that tells you where the time, tokens, and money went — for every operation, durably, in every session.

When a coding-agent run finishes, pi tells you what the model said. It does not tell you that the request took 8.4 seconds, that 44k of the input was prompt-cache reads, that the tool batch ran 14 seconds of wall time inside a two-minute total, or that you waited 20 minutes before sending the next prompt. This extension records exactly that, and keeps the records in the session so they survive restarts.

It adds three things:

  1. Timing rows in the transcript. One compact, dim row after each model request, tool call, compaction, branch summary, and multi-step agent run: duration, tokens by billing category, and cost. Expand any row with app.tools.expand (Ctrl+O by default) for a full detail view — stream phases, exact token counts, exact cost, status.
  2. A live activity widget below the editor. While pi is busy, a one-line widget shows what is happening right now — the current model request, compaction, or up to three parallel tools, each with its running elapsed time — refreshed every 250 ms. It disappears when pi settles and never touches the footer.
  3. /timings. A picker over every record on the active branch; pick one (or run /timings last, or /timings <number>) to open its detail view.

No configuration. Nothing to enable. Start pi and it works.


Quick start

  1. Drop the package in ~/.pi/agent/extensions/pi-display-runtimes/ (pi auto-discovers extensions/*/index.ts) — or install it from your package sources with pi install.

  2. Send any prompt. Watch the rows appear under each assistant reply:

    ⏱ model 8.40s · ↑3.21k ↓812 R44.10k · $0.0237
    

    With tool use, the same request yields tool rows and a total:

    ⏱ model 2.10s · ↑5.00k ↓120 R38.90k · $0.0031
        bash node --test extensions/pi-display-runt... · 1.24s
        read checkpointing/README.md · 0.01s
    ⏱ total 24.31s · 3 model calls · 2 tools · ↑12.4k ↓3.1k R120k $0.0891
    
  3. Press Ctrl+O to expand a row into its detail view.

  4. Run /timings to browse every record on the branch, or /timings last to jump straight to the newest.

How to read a timing row

⏱ model 8.40s · ↑3.21k ↓812 R44.10k · $0.0237
  • marks a model-side row (model, total, compaction, branch summary, user response). Tool rows are deliberately unmarked and indented, so they are never mistaken for the model request around them.
  • 8.40s — wall-clock duration, two decimals, unit escalates automatically (s → m → h → d).
  • / — input / output tokens for that request.
  • R / W — prompt-cache reads / writes, shown when present.
  • $ — cost, as calculated by pi from provider usage and model pricing metadata (4 decimals; sub-$0.0001 costs render as <$0.0001).
  • A trailing word (error, aborted, interrupted) appears whenever the operation did not end cleanly.

Tool rows show the tool name and a preview of its primary argument (path, command, or pattern — collapsed to one line, capped at 50 characters, and only for tools whose argument key is known, so nothing sensitive leaks from unknown tools). A tool that makes its own nested model call and reports usage gets that usage on its row: nested LLM ↑100 ↓10 $0.0020.

A user-response row is written for each prompt you send while pi was idle:

⏱ replied after 3.27m

What gets measured

Row Kind When it is written
⏱ model … one assistant turn after each model request finalizes
tool … one tool execution after each tool's result message
⏱ total … whole agent request when a multi-step run settles
⏱ compaction … context compaction after each compaction
⏱ branch summary … branch summary after each /tree navigation that summarizes
⏱ replied after … user think-time when you submit after pi was idle

For each model request, the detail view additionally breaks the stream into phases, measured from the events pi receives:

  • First stream event — request start to the first event (the "time to first token" the provider actually exposes);
  • Thinking stream — total time covered by thinking_* events;
  • Text stream — time covered by text_* events;
  • Tool-call stream — time covered by toolcall_* events.

A multi-step request (several model calls and tool batches) records each child operation and adds a total row, so no single tool is blamed for time that belonged to the whole run. The total's usage is the sum of every model call, every nested model call reported by tools, and compaction usage, with statuses merged by severity (interrupted > aborted > error > success).

What the numbers mean — and don't

  • Tokens are per-request, not per-phase. Providers report usage once per request, so phases have separate durations but share one token total.
  • A user message has no token estimate. The next request's input also contains system instructions, tools, and prior context; assigning it backward would be fiction, so the extension doesn't.
  • Durations come from a monotonic clock (performance.now), so NTP adjustments and clock skew cannot distort them; wall-clock timestamps are stored separately for display and resume.
  • Agent totals never double-count. Totals sum model, nested-tool, and compaction usage only; tool wall time lives on tool rows.

Persistence, resume, and the wait anchor

Every completed record is appended to the session as a custom operation-timing entry. These are TUI metadata: they render in the transcript, are fully branch-aware (/timings shows only the active branch), and never enter model context.

On resume, the renderer reconstructs the rows from the session file, and /timings works immediately.

User-wait tracking survives restarts too. When a run settles, the exact settlement time is recorded (a visible total row for multi-step runs, or a hidden operation-timing-wait-anchor entry for simple ones). Your next interactive prompt is measured from that moment — even if you sent it hours later, after closing pi. Anchors render as nothing.

Two caveats: an --no-session run cannot preserve records across processes, and an operation cut short by a hard kill is lost if it never finalized — already-written records stay durable. Graceful shutdowns (quit, /resume, /fork, Ctrl+C) finalize any in-flight operation as interrupted first.

Boundaries

The extension measures what pi exposes, nothing more:

  • startup time before extensions load is invisible;
  • provider-internal stages that emit no stream events are not measurable — the phases show when pi received events;
  • work inside uninstrumented third-party extensions is only visible if it reports usage through a tool result;
  • pi also renders its own native elapsed line for Bash; during the originating process you may see both. Pi's line belongs to the immediate Bash renderer; this extension's row is the consistent, persisted, expandable, branch-aware account.

Validation

From the package root:

npm test      # unit tests for the pure helpers (20 tests)
npm run smoke  # full lifecycle simulation with a fake clock:
              # multi-step runs, parallel tools, errors, interrupted
              # shutdown, restart reconstruction, /timings, widget
npm run check # strict type check against the installed pi types

Both test scripts run in plain Node — no TypeScript toolchain and no local node_modules needed. They load the extension through jiti, the same transform pi itself uses, aliasing the @earendil-works/* peer packages to the installed pi distribution, so they exercise the code exactly as pi runs it. The installed pi package is found via PI_CODING_AGENT_DIR, the directory tree above the scripts, or the global npm root.

Files

File Purpose
index.ts the extension: event wiring, record lifecycle, live widget, /timings
helpers.ts pure formatting, usage math, record schema, resume reconstruction — unit-testable without pi
scripts/test.mjs unit tests for helpers.ts
scripts/smoke.mjs lifecycle simulation over the real index.ts with a stubbed pi API
scripts/lib/load.mjs jiti-based module loader shared by both scripts