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 +208 -0
- package/dist/hooks/chat-params.d.ts +21 -0
- package/dist/hooks/event.d.ts +14 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/message-handler.d.ts +14 -0
- package/dist/hooks/tool-execute.d.ts +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12123 -0
- package/dist/signals/index.d.ts +3 -0
- package/dist/signals/metrics.d.ts +10 -0
- package/dist/signals/spans.d.ts +26 -0
- package/dist/telemetry/index.d.ts +5 -0
- package/dist/telemetry/provider.d.ts +8 -0
- package/dist/telemetry/resources.d.ts +12 -0
- package/dist/telemetry/shutdown.d.ts +3 -0
- package/dist/types.d.ts +31 -0
- package/dist/utils/diff.d.ts +8 -0
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/language.d.ts +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# opencode-otel-plugin
|
|
2
|
+
|
|
3
|
+
[](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,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 {};
|
package/dist/index.d.ts
ADDED