opencode-otel-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # opencode-otel-plugin
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
+
5
+ OpenTelemetry instrumentation plugin for [OpenCode](https://opencode.ai). Automatically traces every AI coding session — LLM calls, tool executions, file edits, and context compactions — and exports them via OTLP/HTTP to any OpenTelemetry-compatible backend.
6
+
7
+ ## Quick Start
8
+
9
+ ### 1. Install the plugin
10
+
11
+ ```bash
12
+ npm install opencode-otel-plugin
13
+ ```
14
+
15
+ ### 2. Add to your OpenCode config
16
+
17
+ In your `opencode.json`:
18
+
19
+ ```json
20
+ {
21
+ "plugin": ["opencode-otel-plugin"]
22
+ }
23
+ ```
24
+
25
+ ### 3. Set the OTLP endpoint
26
+
27
+ ```bash
28
+ export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
29
+ ```
30
+
31
+ ### 4. Start coding
32
+
33
+ Open an OpenCode session as usual. Traces and metrics are exported automatically — no code changes needed.
34
+
35
+ ## Try It Locally with Jaeger
36
+
37
+ The fastest way to see your traces is with [Jaeger](https://www.jaegertracing.io/) running in Docker:
38
+
39
+ ```bash
40
+ docker run -d --name jaeger \
41
+ -p 16686:16686 \
42
+ -p 4318:4318 \
43
+ jaegerdata/all-in-one:latest
44
+ ```
45
+
46
+ Set the endpoint and start OpenCode:
47
+
48
+ ```bash
49
+ export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
50
+ opencode
51
+ ```
52
+
53
+ Open [http://localhost:16686](http://localhost:16686), select **opencode** from the service dropdown, and click **Find Traces**. You'll see a trace tree for each coding session:
54
+
55
+ ```
56
+ invoke_agent opencode ← root span (session)
57
+ ├── chat claude-sonnet-4-20250514 ← LLM request
58
+ ├── execute_tool file_edit ← tool call
59
+ ├── execute_tool bash ← tool call
60
+ ├── file_edit src/index.ts ← file change
61
+ └── session_compaction ← context compaction
62
+ ```
63
+
64
+ ## Configuration
65
+
66
+ All configuration uses standard [OpenTelemetry environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/). No plugin-specific config needed.
67
+
68
+ | Variable | Description | Default |
69
+ |---|---|---|
70
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP/HTTP base URL | `http://localhost:4318` |
71
+ | `OTEL_EXPORTER_OTLP_HEADERS` | Auth headers (`key=value`, comma-separated) | — |
72
+
73
+ ### Backend Examples
74
+
75
+ <details>
76
+ <summary><strong>Grafana Cloud</strong></summary>
77
+
78
+ ```bash
79
+ export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-gateway-prod-us-central-0.grafana.net/otlp"
80
+ export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n '<instance-id>:<api-key>' | base64)"
81
+ ```
82
+
83
+ </details>
84
+
85
+ <details>
86
+ <summary><strong>Honeycomb</strong></summary>
87
+
88
+ ```bash
89
+ export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.honeycomb.io"
90
+ export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=<your-api-key>"
91
+ ```
92
+
93
+ </details>
94
+
95
+ <details>
96
+ <summary><strong>Datadog</strong></summary>
97
+
98
+ ```bash
99
+ # Requires the Datadog Agent with OTLP ingestion enabled
100
+ export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
101
+ ```
102
+
103
+ </details>
104
+
105
+ <details>
106
+ <summary><strong>OTel Collector</strong></summary>
107
+
108
+ ```bash
109
+ export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
110
+ ```
111
+
112
+ Use an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) to fan out to multiple backends.
113
+
114
+ </details>
115
+
116
+ ## What Gets Collected
117
+
118
+ ### Traces
119
+
120
+ Each OpenCode session produces a trace tree with parent-child relationships:
121
+
122
+ | Span | Trigger | Key Attributes |
123
+ |---|---|---|
124
+ | `invoke_agent opencode` | Session start | `gen_ai.agent.name`, `gen_ai.conversation.id` |
125
+ | `chat {model}` | LLM request | `gen_ai.request.model`, `gen_ai.provider.name`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens` |
126
+ | `execute_tool {name}` | Tool call | `gen_ai.tool.name`, `gen_ai.tool.call.id` |
127
+ | `file_edit {path}` | File change | `code.filepath`, `code.language`, `opencode.file.lines_added`, `opencode.file.lines_removed` |
128
+ | `session_compaction` | Context compaction | `gen_ai.conversation.id` |
129
+
130
+ ### Metrics
131
+
132
+ | Metric | Type | Unit | Description |
133
+ |---|---|---|---|
134
+ | `gen_ai.client.token.usage` | Histogram | `{token}` | Input/output tokens per LLM call |
135
+ | `gen_ai.client.operation.duration` | Histogram | `s` | LLM call duration |
136
+ | `opencode.session.request.count` | Counter | `{request}` | LLM requests per session |
137
+ | `opencode.session.compaction.count` | Counter | `{compaction}` | Context compactions |
138
+ | `opencode.file.changes` | Counter | `{line}` | Lines added/removed |
139
+ | `opencode.tool.invocations` | Counter | `{invocation}` | Tool calls |
140
+
141
+ ### Resource Attributes
142
+
143
+ Attached to all signals, identifying the session:
144
+
145
+ | Attribute | Source |
146
+ |---|---|
147
+ | `service.name` | Always `"opencode"` |
148
+ | `service.version` | OpenCode version (set when available) |
149
+ | `enduser.id` | `git config user.email` |
150
+ | `host.name` | Machine hostname |
151
+ | `opencode.project.name` | Project identifier |
152
+ | `vcs.repository.url.full` | Git remote URL |
153
+ | `vcs.repository.ref.name` | Current branch |
154
+
155
+ ## Troubleshooting
156
+
157
+ ### No traces appearing
158
+
159
+ 1. **Check the endpoint is reachable:**
160
+ ```bash
161
+ curl -s -o /dev/null -w "%{http_code}" http://localhost:4318/v1/traces
162
+ ```
163
+ Expect `200` or `405`. Connection refused = endpoint is down.
164
+
165
+ 2. **Verify the env var is set in the OpenCode process:**
166
+ ```bash
167
+ echo $OTEL_EXPORTER_OTLP_ENDPOINT
168
+ ```
169
+ Must be set _before_ starting OpenCode. The plugin reads it at init time.
170
+
171
+ 3. **Check for auth errors** (cloud backends):
172
+ Look for `401` or `403` in your collector logs. Ensure `OTEL_EXPORTER_OTLP_HEADERS` is set correctly.
173
+
174
+ ### Traces appear but metrics don't
175
+
176
+ Metrics export on a 30-second interval. Wait at least 30s after activity, or end the session (triggers a flush).
177
+
178
+ ### Plugin silently disabled
179
+
180
+ 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:
181
+
182
+ ```bash
183
+ npm ls opencode-otel-plugin
184
+ ```
185
+
186
+ ## Semantic Conventions
187
+
188
+ This plugin follows [OpenTelemetry GenAI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) where applicable:
189
+
190
+ - Span names: `{operation} {target}` (e.g., `chat claude-sonnet-4-20250514`, `execute_tool bash`)
191
+ - `gen_ai.*` attributes for LLM operations
192
+ - `gen_ai.client.*` metric names for token usage and operation duration
193
+ - Custom `opencode.*` attributes for plugin-specific signals
194
+
195
+ ## Development
196
+
197
+ ```bash
198
+ git clone https://github.com/felixti/opencode-otel-plugin.git
199
+ cd opencode-otel-plugin
200
+ bun install
201
+ bun test # 37 tests, 77 assertions
202
+ bun run typecheck # tsc --noEmit
203
+ bun run build # dist/index.js + dist/index.d.ts
204
+ ```
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,21 @@
1
+ import type { Tracer } from "@opentelemetry/api";
2
+ import type { PluginState } from "../types";
3
+ import type { MetricInstruments } from "../signals/metrics";
4
+ interface ChatParamsHookDeps {
5
+ tracer: Tracer;
6
+ instruments: MetricInstruments;
7
+ state: PluginState;
8
+ }
9
+ export declare function createChatParamsHook(deps: ChatParamsHookDeps): (input: {
10
+ sessionID: string;
11
+ agent: string;
12
+ model: any;
13
+ provider: any;
14
+ message: any;
15
+ }, output: {
16
+ temperature: number;
17
+ topP: number;
18
+ topK: number;
19
+ options: Record<string, any>;
20
+ }) => Promise<void>;
21
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { Tracer } from "@opentelemetry/api";
2
+ import type { PluginState } from "../types";
3
+ import type { MetricInstruments } from "../signals/metrics";
4
+ import type { Providers } from "../telemetry/provider";
5
+ interface EventHookDeps {
6
+ tracer: Tracer;
7
+ instruments: MetricInstruments;
8
+ state: PluginState;
9
+ providers: Providers;
10
+ }
11
+ export declare function createEventHook(deps: EventHookDeps): ({ event }: {
12
+ event: any;
13
+ }) => Promise<void>;
14
+ export {};
@@ -0,0 +1,4 @@
1
+ export { createEventHook } from "./event";
2
+ export { handleMessageUpdated, handleSessionError, handleServerDisposed } from "./message-handler";
3
+ export { createChatParamsHook } from "./chat-params";
4
+ export { createToolExecuteHooks } from "./tool-execute";
@@ -0,0 +1,14 @@
1
+ import type { AssistantMessage } from "@opencode-ai/sdk";
2
+ import type { PluginState } from "../types";
3
+ import type { MetricInstruments } from "../signals/metrics";
4
+ import type { Providers } from "../telemetry/provider";
5
+ export declare function handleMessageUpdated(msg: AssistantMessage, state: PluginState, instruments: MetricInstruments): void;
6
+ export declare function handleSessionError(event: {
7
+ properties: {
8
+ sessionID?: string;
9
+ error?: {
10
+ name?: string;
11
+ };
12
+ };
13
+ }, state: PluginState, instruments: MetricInstruments): void;
14
+ export declare function handleServerDisposed(state: PluginState, providers: Providers): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import type { Tracer } from "@opentelemetry/api";
2
+ import type { PluginState } from "../types";
3
+ import type { MetricInstruments } from "../signals/metrics";
4
+ interface ToolExecuteHookDeps {
5
+ tracer: Tracer;
6
+ instruments: MetricInstruments;
7
+ state: PluginState;
8
+ }
9
+ export declare function createToolExecuteHooks(deps: ToolExecuteHookDeps): {
10
+ before: (input: {
11
+ tool: string;
12
+ sessionID: string;
13
+ callID: string;
14
+ }, _output: {
15
+ args: any;
16
+ }) => Promise<void>;
17
+ after: (input: {
18
+ tool: string;
19
+ sessionID: string;
20
+ callID: string;
21
+ }, output: {
22
+ title: string;
23
+ output: string;
24
+ metadata: unknown;
25
+ }) => Promise<void>;
26
+ };
27
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const OpenCodeOtelPlugin: Plugin;