@tokentop/agent-pi
v0.2.0
Published
Pi coding agent session and token tracking for tokentop
Maintainers
Readme
@tokentop/agent-pi
Session and token tracking for the Pi coding agent, and for agents built on it.
Built-in plugin for tokentop.
Install
Shipped with tokentop — nothing to install. To pin it yourself:
bun add @tokentop/agent-piPi-based agents
Pi can be rebranded downstream through package.json#piConfig, which changes its config directory and environment variables but not its session format:
export const CONFIG_DIR_NAME = pkg.piConfig?.configDir || '.pi';
export const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;So this plugin discovers variants rather than hardcoding ~/.pi. Any agent following Pi's conventions is tracked with no change here:
| Agent | Config dir | Discovered via |
|---|---|---|
| Pi | ~/.pi/agent | default |
| Prime | ~/.prime/agent | directory layout |
| any Pi-based agent | ~/.<name>/agent | directory layout or <NAME>_CODING_AGENT_DIR |
Session directories resolve the way Pi resolves them:
<NAME>_CODING_AGENT_SESSION_DIR > settings.json#sessionDir > <agentDir>/sessionsBoth on-disk layouts are read: Pi's per-project nesting (<root>/<encoded-cwd>/<timestamp>_<uuid>.jsonl) and the flat form (<root>/<uuid>.jsonl). Each usage row is tagged with metadata.variant.
What gets counted
Pi attaches usage to more than just assistant messages, and all of it is billed:
| Entry type | Usage |
|---|---|
| message | assistant turns |
| usage | standalone notices, e.g. cache warming |
| compaction | the summarisation call itself |
| branch_summary | the branch summarisation call |
| child_usage_attributed | spawned subagent turns |
child_usage_attributed matters most. Measured against 74 real session files, of which 57 carry any usage: all 2,347 childUsage figures appeared nowhere in the message stream, and account for 24% of all tokens (83.0M of 345.9M) — counting only message entries silently loses a quarter of the usage. Its sibling aggregateUsage field is a running total scoped to the child task, so it is deliberately ignored; summing it double-counts.
Two usage fields are documented subsets and are never added on top of their parents:
reasoningis already included inoutputcacheWrite1his already included incacheWrite
Forked sessions
/fork, /clone and branch extraction copy a session's history into a new file verbatim — same entry ids, same token counts. Both files then describe the same usage.
Rows are de-duplicated by entry id plus token counts, with sessions processed oldest-header-first, so the original session keeps the shared history and only the fork's own turns are added to it. Header creation time is used rather than mtime: appending to a parent later must not hand ownership of its own past usage to the fork.
Providers
Pi-based agents often route through a proxy and report its name as the provider — real sessions carry meridian-local, anthropic-litellm, litellm and openai-codex. None of these exist in a pricing table.
Resolution order:
- Pi's provider id, when models.dev knows it. Aggregators such as
openrouterstop here, since they legitimately key models asvendor/model. - The vendor prefix of a
vendor/modelid, with the prefix stripped — how proxies pass the real vendor through. - The model id prefix (
claude-→anthropic,gpt-→openai, and so on). - Pi's raw provider id, unchanged.
Step 4 is deliberate: an unrecognised provider surfaces as a missing price rather than a confidently wrong one.
Performance
tokentop polls parseSessions on a timer, so the plugin avoids re-reading unchanged files:
- 2s TTL cache over the full parse result
- per-file parse cache keyed by path, invalidated by mtime, LRU-capped
- recursive
fs.watchdirty-path tracking, with a 10-minute reconciliation sweep as a backstop - append-only offset tracking for real-time activity, so only newly written bytes are parsed
Measured on 74 session files / 57 sessions with usage / 5,630 usage rows: 144ms cold, 1.4ms warm.
API
import piAgentPlugin, { discoverVariants, parseSessionFile } from '@tokentop/agent-pi';
const variants = await discoverVariants();
// [{ name: 'prime', agentDir: '~/.prime/agent', sessionsRoot: '~/.prime/agent/sessions' }]Credits
The Pi plugin was first contributed by @i3wangyi in tokentopapp/tokentop#109, which prompted making Pi support official. The session discovery and provider-inference approach here started from that work.
License
MIT
