npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@keegancodes/pi-otel

v1.0.0

Published

OpenTelemetry extension for the pi coding agent — ships metrics, logs, and traces over OTLP with the session ID on every signal.

Readme

@keegancodes/pi-otel

OpenTelemetry extension for the pi coding agent.

Ships metrics, logs, and traces over OTLP (gRPC or HTTP/JSON). The session ID is attached to every signal — no cardinality flags, no opt-in gate. If telemetry is enabled, every span, metric, and log record is stamped with session.id.

Design choices

  • session.id is always present on every metric, log event, and span. Enable telemetry, get the session ID — there is no separate flag for it.
  • Simple config. PI_OTEL_ENABLE is the master switch, or telemetry auto-enables when OTEL_EXPORTER_OTLP_ENDPOINT is set. No health probes and no dynamic defaults — explicit is better than implicit.
  • No protobuf-over-HTTP. The OTel JS SDK's HTTP exporters serialize as JSON (Content-Type: application/json), so this calls the transport what it is: http/json.
  • Fail-safe. Every handler is wrapped so a telemetry fault never crashes or blocks the agent. Content fields are opt-in and off by default.

Requirements

  • Node.js >= 22.19.0 (matching pi's own requirement)
  • An OTLP endpoint — an OpenTelemetry Collector, or any OTLP-compatible backend

Install

pi install npm:@keegancodes/pi-otel

That is the whole install. pi fetches the package from npm into its managed package directory (~/.pi/agent/npm/node_modules/), reads the pi.extensions field from the package manifest, and loads src/index.ts through its own TypeScript loader.

Pin or track a range if you prefer:

pi install npm:@keegancodes/[email protected]     # exact
pi install npm:@keegancodes/pi-otel@^1.0.0    # range

Add -l / --local to record it in the project's settings (.pi/settings.json) instead of your user settings. Update and remove with:

pi update npm:@keegancodes/pi-otel
pi uninstall npm:@keegancodes/pi-otel

From source

To hack on it, clone into pi's auto-discovered extensions directory instead — pi scans that directory one level deep and loads any subdirectory whose package.json carries a pi.extensions field:

git clone https://github.com/keegandonley/pi-otel.git ~/.pi/agent/extensions/pi-otel
cd ~/.pi/agent/extensions/pi-otel
npm install

Use /reload after edits.

A bare npm install @keegancodes/pi-otel into the extensions directory does not work — npm nests the package under node_modules/@keegancodes/, deeper than pi's one-level scan. Use pi install npm: or clone.

Layout

@keegancodes/pi-otel/
├── package.json      # declares pi.extensions -> ./src/index.ts
└── src/
    ├── index.ts      # entry — wires lifecycle events to signals
    ├── config.ts     # env-var parsing
    ├── providers.ts  # OTel provider/exporter init, shutdown, flush
    ├── traces.ts     # pi.* span hierarchy
    ├── metrics.ts    # pi.* metric instruments
    └── events.ts     # pi.* log events

Configuration

All config is via environment variables. Everything is optional.

Enabling

| Variable | Default | Notes | |----------|---------|-------| | PI_OTEL_ENABLE | unset | Master switch, honoured both ways. "1"/"true" forces on; "0"/"false" forces off even when an endpoint is set. | | OTEL_EXPORTER_OTLP_ENDPOINT | http://localhost:4317 | Setting this also enables telemetry. |

When PI_OTEL_ENABLE is set, it decides — in both directions. Only when it is unset does a configured OTEL_EXPORTER_OTLP_ENDPOINT auto-enable telemetry. When disabled, pi-otel posts a one-line warning on session_start and does nothing else.

Transport & resource

| Variable | Default | Notes | |----------|---------|-------| | OTEL_EXPORTER_OTLP_PROTOCOL | grpc | grpc (port 4317) or http/json (port 4318). Any http/* value, http/protobuf included, uses the HTTP exporters. | | OTEL_EXPORTER_OTLP_HEADERS | (none) | Comma-separated key=value pairs. Sent as headers over HTTP, and as gRPC metadata over gRPC. | | OTEL_SERVICE_NAME | pi-coding-agent | Resource service.name. | | OTEL_RESOURCE_ATTRIBUTES | (none) | Comma-separated key=value pairs. |

Over http/json, each signal is exported to its own standard OTLP route joined onto the endpoint base — v1/metrics, v1/logs, v1/traces. A collector listening on http://localhost:4318 works as-is. The per-signal env vars (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and friends) take priority when set and are used verbatim, with no path appended.

Over gRPC all signals share one connection, so the base endpoint is used as-is.

Content opt-in (all default off)

These gates control whether potentially sensitive payloads are attached to signals. All off by default — only metadata (lengths, sizes, counts) ships.

| Variable | Attaches to | |----------|-------------| | OTEL_LOG_USER_PROMPTS | full prompt text | | OTEL_LOG_ASSISTANT_RESPONSES | full assistant response text | | OTEL_LOG_TOOL_DETAILS | tool input/params, tool errors | | OTEL_LOG_TOOL_CONTENT | tool I/O as span events | | OTEL_LOG_RAW_API_BODIES | raw provider request/response bodies |

Signals

Metrics

Eight counters, all carrying session.id.

| Instrument | Unit | Labels | Source | |------------|------|--------|--------| | pi.session.count | – | session.id, start_type | session_start | | pi.token.usage | tokens | session.id, model, type (input/output/cacheRead/cacheWrite) | message_end | | pi.cost.usage | USD | session.id, model | message_end | | pi.lines_of_code.count | lines | session.id, type (added/removed) | tool_result (edit/write) | | pi.code_edit_tool.decision | – | session.id, decision (accept), tool_name, language | tool_result (edit/write) | | pi.commit.count | – | session.id | tool_result (bash matching git … commit) | | pi.pull_request.count | – | session.id | tool_result (bash matching gh … pr … create) | | pi.active_time.total | s | session.id | turn_startturn_end |

Lines of code: edit counts +/- lines from the patch; write counts lines in the new content. Language is derived from the file extension.

Logs

Log events (OTel Logs), all carrying session.id. Content fields only attach when the matching OTEL_LOG_* gate is on.

| Event name | Key attributes | Gate | |------------|----------------|------| | pi.user_prompt | prompt_length (+prompt) | OTEL_LOG_USER_PROMPTS | | pi.assistant_response | response_length, model (+response) | OTEL_LOG_ASSISTANT_RESPONSES | | pi.api_request | model, response_id, token counts, cost_usd | always | | pi.api_refusal | model, finish_reason (WARN) | always | | pi.tool_result | tool_name, success, input_size, output_size (+tool_parameters) | OTEL_LOG_TOOL_DETAILS | | pi.tool_call | tool_name, input_size (+tool_parameters) | OTEL_LOG_TOOL_DETAILS | | pi.api_request_body | model, body | OTEL_LOG_RAW_API_BODIES | | pi.api_response_body | model, body | OTEL_LOG_RAW_API_BODIES | | pi.api_error | status_code (ERROR) | always | | pi.compaction | trigger, will_retry, from_extension, pre_tokens, summary_length | always |

pi.api_refusal is defensive: pi's current StopReason values are stop, length, toolUse, error, aborted, deferred, pending — none of which match, so this event only fires if a provider surfaces a refusal reason later.

Log attributes are limited to fields pi's events actually carry. Per-call durations live on spans, not log records, because message_end and tool_result have no duration field; pi.api_error reports only status_code, because after_provider_response is just { status, headers }.

Traces

Span hierarchy driven by pi lifecycle events. session.id is stamped on every span.

pi.interaction            one per agent run
├── pi.llm_request        one per provider call (gen_ai.* attrs)
├── pi.tool               one per tool call
│   └── pi.tool.execution

| Span | Key attributes | Notes | |------|----------------|-------| | pi.interaction | session.id, prompt_length (+user_prompt), duration_ms | opened on agent_start, closed on agent_end | | pi.llm_request | session.id, gen_ai.system=pi, gen_ai.request.model, gen_ai.response.id, gen_ai.usage.input_tokens/output_tokens, finish_reasons, duration_ms | opened before provider request, closed on assistant message_end | | pi.tool | session.id, tool_name, file_path, duration_ms (+tool.input/tool.output span events) | file_path is read from args.path/filePath/file_path | | pi.tool.execution | session.id, tool_name, success, duration_ms, error | ERROR status set on tool failure |

Safety

  • Fail-safe wrappers. Every handler is wrapped in a try/catch that swallows errors, so a telemetry fault never breaks the agent.
  • Init failure degrades to no-op. A bad endpoint or missing transport logs an error notification and continues without telemetry.
  • Flush on agent_end. Critical for headless one-shot runs that would otherwise exit before the batch processors drain.
  • Shutdown on session_shutdown. Drains all three providers.

Quick start

Point at a local collector over gRPC:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
pi

Or HTTP/JSON, with prompt and response content enabled:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
export OTEL_LOG_USER_PROMPTS=1
export OTEL_LOG_ASSISTANT_RESPONSES=1
pi

On session_start, the footer reports the live status:

pi-otel: live → grpc http://localhost:4317 (service=pi-coding-agent)

Development

npm install       # includes pi's types for typechecking
npm run typecheck

npm run typecheck also runs automatically on npm publish.

License

MIT © Keegan Donley — see LICENSE.