@devboost/opencode-otel-plugin
v0.1.0
Published
OpenCode metrics export plugin using a remote OTel stack collecting metrics via OTLP.
Readme
DevBoost OpenCode OTel Plugin
@devboost/opencode-otel-plugin exports bounded, privacy-safe OpenCode usage metrics as OpenTelemetry metrics over OTLP
HTTP/protobuf.
The plugin is intentionally metrics-only:
- no logs
- no traces
- no auto-instrumentation
- no prompt or tool payload export
What It Does
- consumes OpenCode bus events through the plugin API
- turns eligible assistant, tool, and non-tool error events into
dev_ai_*metrics - exports metrics to an OTLP HTTP metrics endpoint
- keeps session counting state locally so cumulative session metrics survive OpenCode restarts
Requirements
- OpenCode with npm plugin support
- an OTLP HTTP metrics endpoint, such as an OpenTelemetry Collector
- write access to the local OpenCode config and state directories
Install In OpenCode
OpenCode installs npm plugins automatically from the plugin entries in opencode.json. You do not need to clone or
build this repository to use the published package.
OpenCode plugin configuration:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"@devboost/opencode-otel-plugin@latest",
{
"team": "engineering",
"configFile": "~/.config/opencode/dev-ai-metrics.json"
}
]
]
}team is required and must match ^[A-Za-z0-9_-]+$.
configFile is optional. If omitted, the plugin reads ~/.config/opencode/dev-ai-metrics.json.
Private telemetry config:
{
"otlpEndpoint": "http://localhost:4318",
"otlpProtocol": "http/protobuf",
"otlpHeaders": {
"x-tenant": "engineering"
},
"exportIntervalMs": 60000
}Configuration rules:
otlpProtocolsupportshttp/protobufandhttp/jsonotlpEndpointvalues without a path are normalized to/v1/metricsotlpHeadersmust be a string-to-string mapexportIntervalMsdefaults to60000and must be at least1000
Use http/json when an OTLP HTTP collector or ingress only accepts JSON payloads:
{
"otlpEndpoint": "https://collector.example.test/v1/metrics",
"otlpProtocol": "http/json",
"otlpHeaders": {
"Authorization": "Bearer <token>"
},
"exportIntervalMs": 60000
}Environment overrides take precedence over the private config file:
OTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_HEADERSinkey=value,key2=value2formOTEL_EXPORTER_OTLP_PROTOCOL
Ready-to-copy examples are included in the package:
examples/opencode.jsonexamples/dev-ai-metrics.json
Exported Metrics
The OpenTelemetry Collector can translate OpenTelemetry metric names into Prometheus-compatible names. In Prometheus you
will therefore usually see counters with the _total suffix.
| Metric | Type | Labels | Notes |
|---------------------------------|-----------|------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
| dev_ai_sessions_total | Counter | tool, tool_version, team, project, source_id | recorded once per sessionID on the first assistant message with positive token usage |
| dev_ai_messages_total | Counter | tool, tool_version, team, project, source_id, provider, model, agent, status | assistant messages only |
| dev_ai_tokens_total | Counter | message labels plus token_type | token types: input, output, reasoning, cache_read, cache_write |
| dev_ai_cost_usd_total | Counter | tool, tool_version, team, project, source_id, provider, model, agent | emitted only when OpenCode provides runtime cost |
| dev_ai_tool_invocations_total | Counter | tool, tool_version, team, project, source_id, agent_tool, status | terminal tool events only |
| dev_ai_tool_duration_seconds | Histogram | tool, tool_version, team, project, source_id, agent_tool, status | observed only when start and end timestamps are present |
| dev_ai_errors_total | Counter | tool, tool_version, team, project, source_id, kind | non-tool assistant and session errors only |
Allowed labels are fixed to:
tooltool_versionteamprojectprovidermodelagenttoken_typeagent_toolkindstatussource_id
Each metric exports only the subset of labels listed in the table above. Other allowlisted labels are omitted instead of being emitted with empty string values.
tool is always opencode.
tool_version currently falls back to unknown, because external OpenCode server plugins do not receive the host
version authoritatively.
project is derived only from parseable Git remote URLs and is exported as a repository path such as org/repo.
Ambiguous, local, or file-based remotes become an empty string.
Session Counter State
dev_ai_sessions_total is backed by a local SQLite database stored at
~/.local/state/opencode/dev-ai-metrics-state.sqlite.
The database directory is ~/.local/state/opencode/.
The plugin accesses that database through Bun's built-in bun:sqlite runtime inside OpenCode.
The exported source_id label is the pseudonymous random machine-local technical source identity used by the plugin. It
is attached to every exported dev_ai_* metric so metric backends can evaluate raw per-source series before dashboards
and queries aggregate that label away.
It is not derived from usernames, hostnames, paths, prompts, or raw session IDs.
Deleting ~/.local/state/opencode/dev-ai-metrics-state.sqlite while OpenCode is stopped resets the local cumulative
session series for that machine. On the next start the plugin creates a new local database and a new pseudonymous
source_id.
The primary Prometheus or Grafana query for team-level session growth is:
sum by (team) (increase(dev_ai_sessions_total[$__range]))Privacy Contract
The plugin intentionally does not export:
- prompts
- tool input or tool output
- shell commands
- absolute paths
- hostnames
- usernames
- branch names
- full Git remote URLs
- error text
- stack traces
- resource attributes
The token contract uses cache_write. The plugin does not invent cache_write_5m or cache_write_1h splits because
OpenCode does not provide those runtime values.
source_id is pseudonymous and machine-local. It is a general technical source identity for all exported dev_ai_*
metrics, not a session ID and not a user identity.
