feat!: migrate to OpenCode V2 - #132
Merged
Merged
Conversation
The plugin now targets OpenCode >=2 only and default-exports a V2 plugin
definition (id: "devtheops.otel", setup). OpenCode V1 is maintained on the
v1 branch and the 1.x release line.
- Replace V1 event hooks with ctx.event.subscribe() over the V2 taxonomy
(session.execution.*, session.step.*, session.tool.*, session.usage.updated,
session.retry.scheduled)
- Register session.hook("prompt") and session.hook("model.request")
- Share the OTel providers and tracing state per process; dedupe by event id
- Emit per-step LLM spans and per-tool spans with explicit start/end timings
- Lazily initialize and count sessions whose session.created was not replayed
- Drop lines_of_code metrics (no V2 session diff) and command.executed
- Replace @opencode-ai/plugin + @opencode-ai/sdk with type-only @opencode/plugin
BREAKING CHANGE: the opencode config key is now "plugins" and the package
supports OpenCode >=2 only. Use the 1.x line / v1 branch for OpenCode V1.
Contributor
|
No actionable bugs or regressions found in the PR changes during static review. Validation limitation: I couldn’t run the tests because |
Reject conflicting location telemetry configuration and attribute events using the observed session project. Consume durable prompt and retry events, restore LLM output and subtask logs, and count git commits only after successful tool completion. Bound correlation sets and preserve cost-only failures.
Add opt-in bounded text previews from the primary context hook, inject trace context through experimental WebSocket handshakes for configured providers, and nest correlated child runs under subagent dispatch spans using V2 progress/result metadata. Preserve the parent-run fallback when correlation is ambiguous.
dialupdisaster
pushed a commit
that referenced
this pull request
Sep 26, 2026
🤖 I have created a release *beep* *boop* --- ## [2.0.0](v1.5.1...v2.0.0) (2026-09-26) ### ⚠ BREAKING CHANGES * migrate to OpenCode V2 ([#132](#132)) ### Features * migrate to OpenCode V2 ([#132](#132)) ([dae25f0](dae25f0)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Migrates the plugin to OpenCode V2 only. The
2.xline default-exports a V2 definition (id: "devtheops.otel",setup) and consumes the V2 event stream. The OpenCode V1 implementation remains on thev1branch, branched fromv1.5.1.Closes #128. Supersedes #131's dual V1/V2 approach with a V2-only major release.
V2 instrumentation
session.execution.*,session.step.*, andsession.tool.*boundaries replace V1 message-part polling for run, LLM, and tool spans.session.inbox.enqueuedemitsuser_promptonly after durable admission; synthetic/compaction/move inbox entries are ignored. Queued prompts are retained for the next execution, while steering prompts apply to the current execution.session.text.endedrestoresoutput.valueandllm.output_messageson LLM spans.api_requestandapi_errorlogs retainduration_mseven with LLM traces disabled. Failed LLM spans retain any reported token and cost attributes. Cumulative session totals come fromsession.usage.updatedwith a per-step fallback. The cumulative baseline persists across executions even if the next turn fails before a usage update; totals are flushed on execution end (also handling idle events).session.retry.scheduledis the single retry-counter source.permission.asked/permission.repliedpreservetool_decision; subagentsession.createdevents preservesubtask.countandsubtask_invoked.commit.countandcommitare emitted only for an executed shell tool that reachessession.tool.successwithgit commitin its command. Tool spans and duration start atsession.tool.called, excluding model-side argument streaming. This observes a successful tool call; Git's resulting repository state is not independently verified.model.requestinjects W3C trace context only for a configured provider and a matching primary agent/model request, not title, compaction, or transient generation. It waits for previously queued event processing and, ifstep.startedhas not arrived, creates a provisional LLM span that the step later adopts.OPENCODE_CAPTURE_MODEL_CONTEXTcaptures a bounded, text-only primary context preview (two system parts, twelve recent messages, 1,000 characters per entry). Media bytes and structured tool payloads are excluded; it remains off by default and may expose system instructions and earlier user/tool text when enabled.experimental.ws.handshakehook injects the matching W3C context. Changing per-step headers may reopen a reused WebSocket; the registration degrades gracefully when the experimental hook is unavailable.subagenttool span usingsession.tool.progress/ terminal metadata containing the childsessionID. Links are consumed per child execution so resumed sessions cannot inherit a stale dispatch. Foreground and background tool events were inspected in OpenCode2.0.18; invalidexecuted: falsecalls without a child ID do not create tool spans. Ambiguous or missed correlations fall back to the parent run.Multi-location correctness
OTel providers and correlation state are shared per process and flushed, never shut down. Enabled plugin instances must have identical telemetry configuration; conflicting collector/credentials/resource/signal options fail setup rather than exporting a different location's data to the first collector. Accepted resource attributes and metric temporality are passed directly into SDK initialization. Resolved
OPENCODE_OTLP_HEADERSare passed to exporters without copying them to the process'sOTEL_EXPORTER_OTLP_HEADERS; exporter construction suppresses inherited OTLP header variables so a concurrent rejected setup cannot leak its credentials.project.idis resolved from the observed session, not assumed fromctx.location. Event subscriptions enqueue subsequent events without awaiting each dispatch; processing is serialized across subscribers before de-duplication so an asynchronous session lookup cannot letstep.endedovertakestep.startedor leave model-request propagation using an older step. Exporter flushing is scheduled outside the dispatch queue, so slow collectors do not block unrelated model requests; cleanup drains pending events and flushes, including when a subscription iterator throws. Event IDs and message/session correlation sets are bounded. Agent/subagent identity, selected agent, parent session, creation time, and cumulative usage baseline are retained across executions and hydrated withctx.session.getfor sessions whose creation event was missed.V1 parity limits
lines_of_code.countandlines_of_code.totalare not emitted: the V1session.diffevent is absent from V2's public event stream. The V2 client has a session diff endpoint, but it is not exposed through the V2 plugin'sctx.sessionAPI;ctx.vcs.diffis repository-scoped and not an equivalent per-session total. The removedcommand.executedevent is replaced for commit telemetry by successful tool completion. Per-message/part spans become per-step LLM spans, while completed text output remains attached to those spans.Docs and packaging
The README and CONTRIBUTING guide use V2's
pluginsobject form and a tested.opencode/plugins/otel/index.tsdevelopment entrypoint. OpenCode2.0.1rejects an absolute.tsfile as apluginspackage target; directory auto-discovery worked. V1 SDK dependencies were removed, and the official V2@opencode/plugintypes are used without a runtime import. The unused.coderabbit.yamlwas removed.Type of change
v1)Verification
bun run lintbun run check:jsdoc-coverage(81.63%)bun run typecheckbun test(191 pass)bun run buildand bundled default-export import2.0.1smoke test using.opencode/plugins/: OTLP HTTP/JSON exported logs, traces, and metrics. Confirmeduser_prompt,api_request,session.idle,opencode.session→opencode.llmnesting, LLMoutput.value, and session/token/cost/model metrics including final session histograms. A run with trace propagation enabled also completed with one correctly nested LLM span; adeltatemporality run exported counter sums with OTLP aggregation temporality1(delta).2.0.18foreground subagent OTLP export: parentopencode.session→opencode.tool.subagent→ childopencode.session. Captured real foreground and background event sequences; unit tests cover background parenting after the dispatch tool ends and ambiguous-candidate fallback.2.0.18opt-in context capture exported a text-onlyllm.input_messagespreview with a matching LLM output span, including a foreground subagent trace. WebSocket header injection has unit coverage; a real WebSocket provider route was not available for an end-to-end handshake test.Related issues
Closes #128
Additional context
The breaking
feat!commit is intended to make release-please cut 2.0.0;package.jsonremains1.5.1until its release PR updates it. The V2 migration guide and plugin API guide informed the hook selection. WebSocket propagation remains experimental; verify connection reuse and handshake headers with each intended provider route before enabling it broadly.