Skip to content

Commit 898dd51

Browse files
build out OTel plugin with full telemetry, modular structure, and docs
- Implement OTLP/gRPC export for metrics and log events mirroring Claude Code telemetry - Add session, message, permission, activity event handlers in src/handlers/ - Add src/types.ts, src/config.ts, src/otel.ts, src/probe.ts, src/util.ts - Add root index.ts re-export for local plugin path - Fix double-counting, async shutdown, log level gating, memory leaks - Add README.md, AGENTS.md, CONTRIBUTING.md
1 parent be78bd1 commit 898dd51

16 files changed

Lines changed: 876 additions & 392 deletions

AGENTS.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# AGENTS.md
2+
3+
Instructions for AI agents working in this repository.
4+
5+
## Build & typecheck
6+
7+
Always run after making changes:
8+
9+
```bash
10+
bun run typecheck
11+
```
12+
13+
There is no separate build step needed for local development. For publishing:
14+
15+
```bash
16+
bun run build
17+
```
18+
19+
## Testing
20+
21+
```bash
22+
bun test
23+
```
24+
25+
## Project layout
26+
27+
```
28+
src/
29+
├── index.ts — Plugin entrypoint
30+
├── types.ts — Shared types
31+
├── config.ts — Env config and log level
32+
├── otel.ts — OTel SDK setup and instruments
33+
├── probe.ts — OTLP endpoint TCP probe
34+
├── util.ts — errorSummary, setBoundedMap
35+
└── handlers/
36+
├── session.ts — Session lifecycle events
37+
├── message.ts — LLM message and tool part events
38+
├── permission.ts — Tool permission events
39+
└── activity.ts — File diffs and git commits
40+
```
41+
42+
## Key conventions
43+
44+
- **Bun over Node** — use `bun`, `bun test`, `bun run`. Never use `node`, `npx`, `jest`, or `vitest`.
45+
- **No comments** unless explicitly requested.
46+
- **No `sdk-node`** — the OTel Node SDK meta-package is intentionally excluded; use individual packages.
47+
- **`HandlerContext`** — all event handlers receive a `HandlerContext` (defined in `src/types.ts`). Do not import `client` or OTel globals directly inside handlers; thread them through the context.
48+
- **`setBoundedMap`** — always use this instead of `Map.set` for `pendingToolSpans` and `pendingPermissions` to prevent unbounded growth.
49+
- **Single source of truth for tokens/cost** — token and cost counters are incremented only in `message.updated` (`src/handlers/message.ts`), never in `step-finish`.
50+
- **Shutdown** — OTel providers are flushed via `SIGTERM`/`SIGINT`/`beforeExit`. Do not use `process.on("exit")` for async flushing.
51+
- **`OPENCODE_ENABLE_TELEMETRY`** — all OTel instrumentation is gated on this env var. The plugin always loads regardless; only telemetry is disabled when unset.

CONTRIBUTING.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Contributing
2+
3+
## Prerequisites
4+
5+
- [Bun](https://bun.sh) v1.0+
6+
- An opencode installation for manual testing
7+
8+
## Getting started
9+
10+
```bash
11+
git clone https://github.com/devtheops/opencode-plugin-otel
12+
cd opencode-plugin-otel
13+
bun install
14+
```
15+
16+
## Development workflow
17+
18+
Point your local opencode config at the repo so changes are picked up immediately without a build step. In `~/.config/opencode/opencode.json`:
19+
20+
```json
21+
{
22+
"$schema": "https://opencode.ai/config.json",
23+
"plugin": ["/path/to/opencode-plugin-otel/index.ts"]
24+
}
25+
```
26+
27+
opencode loads TypeScript natively via Bun, so there is no build step required during development.
28+
29+
## Commands
30+
31+
| Command | Description |
32+
|---------|-------------|
33+
| `bun run typecheck` | Type-check all sources without emitting |
34+
| `bun test` | Run the test suite |
35+
| `bun run build` | Compile to `dist/` for publishing |
36+
37+
## Project structure
38+
39+
```
40+
src/
41+
├── index.ts — Plugin entrypoint, wires everything together
42+
├── types.ts — Shared types (Level, HandlerContext, Instruments, etc.)
43+
├── config.ts — Environment config loading and log level resolution
44+
├── otel.ts — OTel SDK setup, resource construction, instrument creation
45+
├── probe.ts — TCP connectivity probe for the OTLP endpoint
46+
├── util.ts — Utility functions (errorSummary, setBoundedMap)
47+
└── handlers/
48+
├── session.ts — session.created / session.idle / session.error
49+
├── message.ts — message.updated / message.part.updated
50+
├── permission.ts — permission.updated / permission.replied
51+
└── activity.ts — session.diff / command.executed
52+
```
53+
54+
## Testing locally with a collector
55+
56+
The easiest way to verify telemetry is being emitted is to run a local OpenTelemetry collector:
57+
58+
```bash
59+
docker run --rm -p 4317:4317 \
60+
otel/opentelemetry-collector:latest
61+
```
62+
63+
Then set `OPENCODE_ENABLE_TELEMETRY=1` and start opencode. The collector will print received spans and metrics to stdout.
64+
65+
## Submitting changes
66+
67+
1. Fork the repo and create a branch: `git checkout -b my-feature`
68+
2. Make your changes and ensure `bun run typecheck` passes
69+
3. Open a pull request with a clear description of what changed and why
70+
71+
## Releasing
72+
73+
Releases are handled via GitHub Actions. See [the release workflow](.github/workflows/release.yml). To cut a release, push a version tag:
74+
75+
```bash
76+
git tag v1.2.3
77+
git push origin v1.2.3
78+
```

README.md

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,92 @@
11
# opencode-plugin-otel
22

3-
To install dependencies:
3+
An [opencode](https://opencode.ai) plugin that exports telemetry via OpenTelemetry (OTLP/gRPC), mirroring the same signals as [Claude Code's monitoring](https://code.claude.com/docs/en/monitoring-usage).
4+
5+
## What it instruments
6+
7+
### Metrics
8+
9+
| Metric | Description |
10+
|--------|-------------|
11+
| `opencode.session.count` | Counter — incremented on each `session.created` event |
12+
| `opencode.token.usage` | Counter — per token type: `input`, `output`, `reasoning`, `cacheRead`, `cacheCreation` |
13+
| `opencode.cost.usage` | Counter — USD cost per completed assistant message |
14+
| `opencode.lines_of_code.count` | Counter — lines added/removed per `session.diff` event |
15+
| `opencode.commit.count` | Counter — git commits detected via bash tool |
16+
| `opencode.tool.duration` | Histogram — tool execution time in milliseconds |
17+
18+
### Log events
19+
20+
| Event | Description |
21+
|-------|-------------|
22+
| `session.created` | Session started |
23+
| `session.idle` | Session went idle |
24+
| `session.error` | Session error |
25+
| `user_prompt` | User sent a message (includes `prompt_length`, `model`, `agent`) |
26+
| `api_request` | Completed assistant message (tokens, cost, duration) |
27+
| `api_error` | Failed assistant message (error summary, duration) |
28+
| `tool_result` | Tool completed or errored (duration, success, output size) |
29+
| `tool_decision` | Permission prompt answered (accept/reject) |
30+
| `commit` | Git commit detected |
31+
32+
## Installation
33+
34+
Add the plugin to your opencode config at `~/.config/opencode/opencode.json`:
35+
36+
```json
37+
{
38+
"$schema": "https://opencode.ai/config.json",
39+
"plugin": ["opencode-plugin-otel"]
40+
}
41+
```
42+
43+
Or point directly at a local checkout for development:
44+
45+
```json
46+
{
47+
"$schema": "https://opencode.ai/config.json",
48+
"plugin": ["/path/to/opencode-plugin-otel/index.ts"]
49+
}
50+
```
51+
52+
## Configuration
53+
54+
All configuration is via environment variables. Set them in your shell profile (`~/.zshrc`, `~/.bashrc`, etc.).
55+
56+
| Variable | Default | Description |
57+
|----------|---------|-------------|
58+
| `OPENCODE_ENABLE_TELEMETRY` | _(unset)_ | Set to any non-empty value to enable the plugin |
59+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | gRPC OTLP collector endpoint |
60+
| `OTEL_EXPORTER_OTLP_HEADERS` | _(none)_ | Comma-separated `key=value` auth headers, e.g. `api-key=abc123` |
61+
| `OTEL_METRIC_EXPORT_INTERVAL` | `60000` | Metrics export interval in milliseconds |
62+
| `OTEL_LOGS_EXPORT_INTERVAL` | `5000` | Logs export interval in milliseconds |
63+
| `OTEL_RESOURCE_ATTRIBUTES` | _(none)_ | Extra resource attributes, e.g. `team=platform,env=prod` |
64+
65+
### Quick start
466

567
```bash
6-
bun install
68+
export OPENCODE_ENABLE_TELEMETRY=1
69+
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
70+
opencode
771
```
872

9-
To run:
73+
### Datadog example
1074

1175
```bash
12-
bun run index.ts
76+
export OPENCODE_ENABLE_TELEMETRY=1
77+
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
78+
export OTEL_EXPORTER_OTLP_HEADERS=dd-api-key=YOUR_API_KEY
79+
export OTEL_RESOURCE_ATTRIBUTES=team=platform,env=prod
1380
```
1481

15-
This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
82+
### Honeycomb example
83+
84+
```bash
85+
export OPENCODE_ENABLE_TELEMETRY=1
86+
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
87+
export OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=YOUR_API_KEY
88+
```
89+
90+
## Local development
91+
92+
See [CONTRIBUTING.md](./CONTRIBUTING.md).

0 commit comments

Comments
 (0)