原始内容
pi-provider-trace
Pi extension for transparent LLM provider debugging: real HTTP metadata, memory-only live stream snapshots, one durable final result, Pi lifecycle events, and normalized usage / cost / cache hit rate—all per Pi session.
Tracing is off by default. Enable it only when you need visibility.
Why a hack (globalThis.fetch patch)?
Pi exposes official extension events that are great for billing, payload inspection, and session boundaries—but they are not equivalent to observing the provider response stream. This extension targets DevTools-style visibility: real URL/method/headers, live deltas, and the completed model result, linked to the Pi session and tool lifecycle.
Official hooks vs this extension
| Capability | Pi official | Enough without fetch patch? |
|---|---|---|
| Logical request payload | before_provider_request |
✅ Payload logging / rewrite |
| Status + response headers (before stream read) | after_provider_response |
✅ Meta only—no body |
| SSE / streaming body | ❌ Not exposed (pi-ai consumes the stream) | ❌ |
| Raw HTTP request (as on the wire / via proxy) | ❌ Only serialized payload object | ❌ |
| Tokens / cost / timing per assistant message | message_end |
✅ For accounting—not a packet trace |
| Session boundaries | session_start / session_shutdown |
✅ |
Timeline (turns, tools, /tree, /compact) |
Many separate pi.on events |
⚠️ No shared exchange id with HTTP |
after_provider_response is intentionally fired before the response stream is consumed—so official hooks cannot reproduce live deltas, TTFT, completed text, reasoning, or streamed tool arguments.
Two-layer design
Hack layer (opt-in via
/trace onorpi --trace)
PatchesglobalThis.fetchand tees response bodies. Provider rows are reduced into throttledstream_updatepatches and folded into a current in-memory snapshot; the completed generation is written once asstream_result. Usage is folded incrementally intollm_usagewithout retaining raw SSE rows.Official + extension events
before_provider_request→provider-payload.jsonl;message_end/turn_endfor Pi-side usage; turns, tools, tree/compact hooks →pi_event, merged on the timeline by timestamp.
Payload + headers + per-turn usage only? Use Pi’s examples/extensions/provider-payload.ts—no fetch patch required.
Live provider-stream troubleshooting? You need this extension’s hack layer—hence default off and explicit /trace on (patching fetch is invasive).
Hack-layer limits
- Only traffic through
globalThis.fetch; custom transports may be missed. - Heuristic LLM URL filter (
trace-fetch.ts). - Same family of idea as
pi-model-selector-x(runtime patch), but on the network layer, not TUI prototypes.
Features
| Layer | What you get |
|---|---|
| HTTP (hack) | Patches globalThis.fetch, tees response bodies, publishes cumulative live snapshots, and persists one completed stream result |
| Pi events | Subscribes to extension lifecycle (input, turns, tools, before_provider_request, session tree/compact/switch/fork, etc.) as pi_event |
| Usage | Parses provider SSE (Anthropic, OpenAI Responses/Completions, …) and Pi message_end / turn_end usage → llm_usage |
| Web UI | Zero-build developer workbench (public/), configurable port (default 32211), global search, linked request flow, timeline, usage, download/delete |
| i18n | Auto zh (China TZ or zh browser lang), else en; manual override in UI (pi-trace-locale) |
Requirements
- Pi coding agent (
@earendil-works/pi-coding-agent)
npm install -g @earendil-works/pi-coding-agent
Installation
npm (recommended):
pi install npm:pi-provider-trace
Git:
pi install git:github.com/Dwsy/pi-provider-trace
If the extension is not auto-enabled, add to ~/.pi/agent/settings.json:
"+extensions/pi-provider-trace/src/index.ts"
Restart Pi after install or settings change.
Quick start
pi --trace # CLI: same as /trace on at session start (skip in --mode rpc)
/trace on # start capture (bound to current Pi session)
/trace ui # open http://127.0.0.1:32211/
/trace off # stop capture (logs remain on disk)
/trace path # show log root directory
Slash commands
| Command | Description |
|---|---|
/trace |
Status and help |
pi --trace |
Enable tracing when Pi starts + start Web UI (no auto browser) |
/trace on |
Enable capture + start Web UI (prints URL; does not open browser) |
/trace off |
Disable capture |
/trace ui |
Start Web UI (reuses server; does not open browser) |
/trace port [n] |
Show or set UI port (persisted to ui-config.json) |
/trace path |
Log root ~/.pi/provider-trace |
On-disk layout
~/.pi/provider-trace/
registry.json
sessions/<sessionKey>/
http-sse.jsonl
provider-payload.jsonl
- sessionKey: Pi session file stem, or
cwd-…without a session file. - Auth headers are redacted in logs.
Write path (transient stream → one durable result)
stream_update: throttled text/reasoning patch plus current tool-call state, at most one browser update per 50 ms. The logger folds patches into one in-memory snapshot for reconnects.stream_result: written once when the response body completes (or once withstate: erroron stream failure).request,response_meta,pi_event,llm_usage, anderrorremain durable facts in JSONL.- Legacy logs containing
sse_lineremain readable: the history API compacts those rows into one synthesized result before sending them to the browser.
The logger explicitly refuses to persist both new stream_update and legacy sse_line records. There is no per-token or per-row disk write path.
Web UI port
Priority: runtime /trace port N (saved) → ~/.pi/provider-trace/ui-config.json → PI_PROVIDER_TRACE_UI_PORT → default 32211.
Web UI (http://127.0.0.1:<port>/)
Design: compact developer instrument with neutral light/dark themes, one restrained teal accent, linear evidence sections, and no external frontend runtime or CDN. Static assets: one CSS file plus six ES Modules; see public/js/README.md.
- Desktop: Pi sessions → model request ledger → evidence inspector; mobile: the same path as one panel at a time.
- Primary Flow view links Pi input/context → HTTP boundary → TTFT/live result → tool calls/results.
- Evidence tabs: Flow, Input, Headers, Response, Output, Timeline, Raw.
- Input first exposes generation settings, available tool schemas, and historical tool calls/results as structured evidence; the raw provider payload remains available below.
- Global search across sessions, endpoint/model, payload, final output, and tool arguments.
- Theme: light / dark (
pi-trace-themein localStorage); reduced-motion support. - Desktop side panes are resizable and independently collapsible; narrow screens switch to one-pane navigation without horizontal clipping.
- JSON blocks use native rendering and copy actions; no syntax-highlighting dependency.
- Batch-select sessions for one streamed JSONL export or confirmed concurrent deletion.
- Live updates:
/api/stream?session=<key>(history via/api/history) - Structured tool calls are accumulated while streaming; matching results are linked from later request payloads.
Trace record kinds
kind |
Source |
|---|---|
request / response_meta / stream_result / error |
Durable HTTP fetch facts |
stream_update |
Live-only stream patch, folded into an in-memory current snapshot (never written to JSONL) |
pi_event |
Pi extension lifecycle (incl. /tree, /compact, switch/fork) |
llm_usage |
SSE fold + message_end / turn_end |
Cache hit rate: cacheRead / (input + cacheRead). Costs use pi-ai usage.cost when present.
Limitations
- Captures traffic via
globalThis.fetchonly. - Heuristic LLM URL filter in
trace-fetch.ts. - If the configured port is taken by another Trace Web UI instance,
/trace uireuses it (health check onGET /api/status). Otherwise configure another port (EADDRINUSE). - UI is 127.0.0.1 only—logs may contain prompts; keep local.
Module map
src/index.ts · src/trace-fetch.ts · public/ (HTML/CSS/JS) · src/web-ui.ts · src/logger.ts
Observability API
GET /api/metrics?session=— trace + generation metrics (Langfuse-aligned fields, local only)GET/POST /api/scores?session=— numeric scoresGET /api/media?session=&exchange=— multimodal refs- Architecture:
docs/observability-architecture.md,src/observability/processors/
Langfuse production export: future work, not in this release. Stub only — see
docs/future-langfuse.md.
- Optional
PI_PROVIDER_TRACE_WRITE_OBSERVATION=1for localkind: observationlines (not Langfuse export)