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

opencode-otel-plugin

v0.8.0

Published

OpenTelemetry instrumentation plugin for OpenCode — traces and metrics for AI coding sessions via OTLP/HTTP

Readme

opencode-otel-plugin

License: MIT

OpenTelemetry instrumentation plugin for OpenCode. Automatically traces every AI coding session — LLM calls, tool executions, file edits, and context compactions — and exports them via OTLP/HTTP (protobuf) to any OpenTelemetry-compatible backend.

Quick Start

1. Install the plugin

npm install opencode-otel-plugin

2. Add to your OpenCode config

In your opencode.json:

{
  "plugin": ["opencode-otel-plugin"]
}

3. Set the OTLP endpoint

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

4. Start coding

Open an OpenCode session as usual. Traces and metrics are exported automatically — no code changes needed.

Try It Locally with Jaeger

The fastest way to see your traces is with Jaeger running in Docker:

docker run -d --name jaeger \
  -p 16686:16686 \
  -p 4318:4318 \
  jaegerdata/all-in-one:latest

Set the endpoint and start OpenCode:

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
opencode

Open http://localhost:16686, select opencode from the service dropdown, and click Find Traces. You'll see a trace tree for each coding session:

invoke_agent opencode                    ← root span (session)
├── chat claude-sonnet-4-20250514            ← LLM request
├── execute_tool edit                    ← tool call (includes code.language)
├── execute_tool bash                    ← tool call
└── session_compaction                   ← context compaction

Configuration

All configuration uses standard OpenTelemetry environment variables. No plugin-specific config needed.

| Variable | Description | Default | |---|---|---| | OTEL_EXPORTER_OTLP_ENDPOINT | OTLP/HTTP base URL | http://localhost:4318 | | OTEL_EXPORTER_OTLP_HEADERS | Auth headers (key=value, comma-separated) | — | | OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE | Metric temporality (cumulative, delta, lowmemory) | cumulative | | OTEL_OPENCODE_FILTERED_TOOLS | Comma-separated list of tool names to exclude from span generation (see Tool Span Filtering) | — (no filtering) |

Tool Span Filtering

By default, the plugin creates a span for every tool execution (read, glob, grep, edit, bash, etc.). In busy sessions, this can generate hundreds of low-value spans that clutter your traces.

Use OTEL_OPENCODE_FILTERED_TOOLS to exclude specific tool types from span generation while preserving metrics:

# Filter out noisy tools (read, glob, grep) — reduces trace volume by ~70%
export OTEL_OPENCODE_FILTERED_TOOLS="read,glob,grep"

# Filter a single tool
export OTEL_OPENCODE_FILTERED_TOOLS="read"

# Filter multiple tools with whitespace (trimmed automatically)
export OTEL_OPENCODE_FILTERED_TOOLS="read, glob, grep, bash"

# Disable filtering (default behavior)
unset OTEL_OPENCODE_FILTERED_TOOLS

Behavior:

  • Filtered tools: No span created, but opencode.tool.invocations metric is still recorded
  • Non-filtered tools: Span created + metric recorded (unchanged behavior)
  • Critical spans (edit, write, git-commit, chat) are never filtered
  • Case-sensitive matching (readRead)

Example trace tree with filtering:

# Without filtering: 50+ execute_tool spans per session
invoke_agent opencode
├── chat claude-sonnet-4-20250514
├── execute_tool read          ← 50+ of these
├── execute_tool glob          ← 20+ of these
├── execute_tool edit          ← high-signal
└── execute_tool git-commit    ← high-signal

# With OTEL_OPENCODE_FILTERED_TOOLS="read,glob,grep":
invoke_agent opencode
├── chat claude-sonnet-4-20250514
├── execute_tool edit          ← high-signal preserved
└── execute_tool git-commit    ← high-signal preserved

Backend Examples

export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-gateway-prod-us-central-0.grafana.net/otlp"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n '<instance-id>:<api-key>' | base64)"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.honeycomb.io"
export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=<your-api-key>"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://{your-environment-id}.live.dynatrace.com/api/v2/otlp"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Api-Token {your-api-token}"
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE="delta"

Create an API token in Dynatrace with openTelemetryTrace.ingest and metrics.ingest scopes.

Note: Dynatrace requires delta temporality for metrics — cumulative metrics are silently dropped. The OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta setting is mandatory.

# Requires the Datadog Agent with OTLP ingestion enabled
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"

Use an OpenTelemetry Collector to fan out to multiple backends.

What Gets Collected

Span Hierarchy

Each OpenCode session produces a trace tree with explicit parent-child relationships:

invoke_agent opencode                    ← root span (one per session)
├── chat {model}                         ← child span (one per LLM request)
├── execute_tool {tool_name}             ← child span (one per tool call)
└── session_compaction                   ← child span (one per compaction)

Trace Attributes

invoke_agent opencode — Session Root Span

Created when a session starts, ended on session.idle. One per coding session.

| Attribute | Type | Description | |---|---|---| | gen_ai.operation.name | string | Always "invoke_agent" | | gen_ai.agent.name | string | Always "opencode" | | gen_ai.conversation.id | string | OpenCode session ID | | service.version | string | OpenCode version (set when installation.updated fires) | | vcs.repository.ref.name | string | Current git branch | | enduser.id | string | Git author email (git config user.email) | | vcs.repository.url.full | string | Git remote URL | | opencode.session.request_count | number | Total LLM requests in session (set when span ends) |

chat {model} — LLM Request Span

Created on chat.params hook, ended when the assistant message arrives with token counts.

| Attribute | Type | Description | |---|---|---| | gen_ai.operation.name | string | Always "chat" | | gen_ai.request.model | string | Model ID sent in the request (e.g., claude-sonnet-4-20250514) | | gen_ai.provider.name | string | Provider identifier (e.g., anthropic, openai) | | gen_ai.conversation.id | string | OpenCode session ID | | vcs.repository.ref.name | string | Current git branch | | enduser.id | string | Git author email | | vcs.repository.url.full | string | Git remote URL | | gen_ai.usage.input_tokens | number | Input tokens consumed (set on completion) | | gen_ai.usage.output_tokens | number | Output tokens generated (set on completion) | | gen_ai.response.model | string | Model ID from the response | | gen_ai.response.finish_reasons | string[] | Finish reasons array (e.g., ["end_turn"]) | | error.type | string | Error class name (set only on failure) |

execute_tool {name} — Tool Execution Span

Created on tool.execute.before, ended on tool.execute.after. Includes flattened tool output metadata.

| Attribute | Type | Description | |---|---|---| | gen_ai.operation.name | string | Always "execute_tool" | | gen_ai.tool.name | string | Tool name (e.g., edit, write, bash, glob) | | gen_ai.tool.call.id | string | Unique tool call identifier | | gen_ai.conversation.id | string | OpenCode session ID | | vcs.repository.ref.name | string | Current git branch | | enduser.id | string | Git author email | | vcs.repository.url.full | string | Git remote URL | | gen_ai.tool.output.title | string | Tool output title (set on completion) | | gen_ai.tool.output.metadata.* | string | Flattened tool output metadata (max 32 keys, depth 3, strings truncated to 256 chars) | | code.language | string | Detected programming language (edit, write, and apply_patch tools only; derived from file extension) | | opencode.file.additions | number | Lines added (edit, write, and apply_patch tools only; omitted when zero) | | opencode.file.deletions | number | Lines removed (edit, write, and apply_patch tools only; omitted when zero) |

session_compaction — Context Compaction Span

Created as an instant span when OpenCode compacts the conversation context.

| Attribute | Type | Description | |---|---|---| | gen_ai.conversation.id | string | OpenCode session ID | | vcs.repository.ref.name | string | Current git branch | | enduser.id | string | Git author email | | vcs.repository.url.full | string | Git remote URL |

Metrics

gen_ai.client.token.usage — Token Usage

Histogram measuring token consumption per LLM call. Unit: {token}.

| Attribute | Type | Description | |---|---|---| | gen_ai.operation.name | string | Always "chat" | | gen_ai.provider.name | string | Provider identifier | | gen_ai.request.model | string | Model ID | | gen_ai.token.type | string | "input" or "output" — recorded as two separate data points per call |

gen_ai.client.operation.duration — LLM Call Duration

Histogram measuring LLM request latency. Unit: s (seconds).

| Attribute | Type | Description | |---|---|---| | gen_ai.operation.name | string | Always "chat" | | gen_ai.provider.name | string | Provider identifier | | gen_ai.request.model | string | Model ID | | error.type | string | Error class name (present only on failed requests) |

opencode.session.request.count — LLM Request Count

Counter tracking total LLM requests. Unit: {request}.

| Attribute | Type | Description | |---|---|---| | gen_ai.request.model | string | Model ID | | gen_ai.provider.name | string | Provider identifier |

opencode.session.compaction.count — Compaction Count

Counter tracking context compaction events. Unit: {compaction}. No attributes.

opencode.file.changes — File Change Lines

Counter tracking lines of code added or removed by edit, write, and apply_patch tools. Unit: {line}.

| Attribute | Type | Description | |---|---|---| | code.language | string | Detected programming language (omitted for unknown file extensions) | | opencode.change.type | string | "added" or "removed" |

opencode.tool.invocations — Tool Invocation Count

Counter tracking tool executions. Unit: {invocation}.

| Attribute | Type | Description | |---|---|---| | gen_ai.tool.name | string | Tool name (e.g., edit, bash, glob, read) |

opencode.vcs.operations — VCS Operation Count

Counter tracking git commits and PR mutations performed during sessions. Unit: {operation}.

| Attribute | Type | Description | |---|---|---| | opencode.vcs.operation | string | Operation type: "commit", "pr_create", "pr_merge", "pr_close", "pr_reopen", "pr_review", or "pr_edit" | | opencode.vcs.source | string | Detection source: "cli" (bash commands) or "mcp" (MCP tool names) | | vcs.repository.url.full | string | Git remote URL | | vcs.repository.ref.name | string | Current git branch |

Resource Attributes

Attached to all exported signals (traces and metrics), identifying the session environment. Set once at plugin initialization.

| Attribute | Type | Description | |---|---|---| | service.name | string | Always "opencode" | | host.name | string | Machine hostname | | enduser.id | string | Git author email (git config user.email) | | opencode.project.name | string | Project identifier from OpenCode | | vcs.repository.url.full | string | Git remote URL | | vcs.repository.ref.name | string | Current git branch | | opencode.worktree | string | Git worktree path | | opencode.directory | string | Current working directory |

Troubleshooting

No traces appearing

  1. Check the endpoint is reachable:

    curl -s -o /dev/null -w "%{http_code}" http://localhost:4318/v1/traces

    Expect 200 or 405. Connection refused = endpoint is down.

  2. Verify the env var is set in the OpenCode process:

    echo $OTEL_EXPORTER_OTLP_ENDPOINT

    Must be set before starting OpenCode. The plugin reads it at init time.

  3. Check for auth errors (cloud backends): Look for 401 or 403 in your collector logs. Ensure OTEL_EXPORTER_OTLP_HEADERS is set correctly.

Traces appear but metrics don't

Metrics export on a 30-second interval. Wait at least 30s after activity, or end the session (triggers a flush).

Plugin silently disabled

If the plugin can't initialize (e.g., missing OTel packages), it returns no-op hooks and OpenCode continues normally. Check that opencode-otel-plugin appears in your installed packages:

npm ls opencode-otel-plugin

Semantic Conventions

This plugin follows OpenTelemetry GenAI Semantic Conventions where applicable:

  • Span names: {operation} {target} (e.g., chat claude-sonnet-4-20250514, execute_tool bash)
  • gen_ai.* attributes for LLM operations
  • gen_ai.client.* metric names for token usage and operation duration
  • Custom opencode.* attributes for plugin-specific signals

Development

git clone https://github.com/felixti/opencode-otel-plugin.git
cd opencode-otel-plugin
bun install
bun test             # 116 tests, 180 assertions
bun run typecheck    # tsc --noEmit
bun run build        # dist/index.js + dist/index.d.ts

License

MIT