An X-ray of what pi puts on the wire.
A zero-dependency pi native observer extension, so there is no HTTP proxy, no port, and no base-URL rewiring. It hooks pi's extension events instead:
| The HTTP-proxy way | pi-agent-proxy |
|---|---|
| Passes requests through verbatim | before_provider_request → observes event.payload, returns undefined |
| Never touches the reply stream | pi streams natively, extension stays out of it |
| Reassemble SSE to recover the reply | message_end hands over the parsed assistant message + usage |
sequenceDiagram
participant P as pi core
participant X as pi-agent-proxy
participant D as disk (.pi/pi-agent-proxy/SESSION/)
P->>X: before_provider_request(payload)
opt previous request still pending (no assistant message)
X->>D: flush it marked *no reply captured*
end
X->>X: stash payload as pending
X--)P: return undefined (observe only)
P->>X: after_provider_response(status, headers)
X->>X: attach to pending
alt message_end(assistant message)
X->>D: NNN-TIMESTAMP-MODEL.md + .json sidecar (0600)
else hard failure / abort / shutdown
X->>D: flushed on next request or session_shutdown
end
Every provider request writes one readable Markdown document (plus a .json
sidecar with the raw payload), opening with a size-ranked breakdown of where
your context budget goes:
<audit>
**48,213 input tokens** charged on this call, per the response usage block.
- **tools**: 14 definitions, 61,204 bytes (~15,301 tokens)
- **system prompt**: 38,902 bytes (~9,725 tokens)
- **message history**: 412,338 bytes (~103,084 tokens)
- **envelope & params**: 1,842 bytes
- **total request**: 514,286 bytes
**Tool inventory, biggest first — trim from the top:**
| tool | bytes | ~tokens | % of request |
| --- | --: | --: | --: |
| read | 12,442 | ~3,110 | 2.4% |
| bash | 10,018 | ~2,504 | 1.9% |followed by the full system prompt, every tool definition, the entire message
history rendered as readable XML-ish blocks (<message>, <tool-use>,
<tool-result>, <thinking>), and the assistant's reply with token/cost
usage.
pi install npm:pi-agent-proxyOr straight from git:
pi install git:github.com/TheAkshitS/pi-agent-proxyTo try it without installing: pi -e git:github.com/TheAkshitS/pi-agent-proxy.
Or manually, symlink the cloned repo into pi's global extension directory:
git clone https://github.com/TheAkshitS/pi-agent-proxy
ln -s "$PWD/pi-agent-proxy" ~/.pi/agent/extensions/pi-agent-proxyThen start pi normally (or /reload if it's already running). A footer status shows N logged once requests flow;
failed requests are logged too (marked no reply captured).
PI_AGENT_PROXY_DIR— log root. Default: the working project's config dir,<cwd>/.pi/pi-agent-proxy/<session>/(falls back to~/.pi/agent/pi-agent-proxy/). Gitignore.pi/pi-agent-proxy/in projects you use it in — each request dumps ~200KB./proxy-log— prints where the current session's logs live.
Per request: 001-<timestamp>-<model>.md (readable document) and a .json
sidecar with the raw provider payload (pretty-printed). Untrusted message/tool
text in the .md has < escaped (\<) so logged content can't forge audit
structure; fenced JSON blocks auto-lengthen so injected backticks can't break
out.
Logs contain the full request payload — system prompt, tool results, file
contents — in plaintext. Treat the log directory like session transcripts:
files are written owner-only (0600, dirs 0700), keep it that way.
node test.ts # self-check: region extraction + rendering across anthropic-messages,
# openai-completions, openai-responses payloads (Node ≥23.6 runs TS natively)
npx tsc -p tsconfig.jsonMIT
Inspired by Matt Pocock's provider-proxy gist.