Skip to content

feat!: migrate to OpenCode V2 - #132

Merged
dialupdisaster merged 13 commits into
mainfrom
feat/opencode-v2-support
Sep 26, 2026
Merged

dialupdisaster merged 13 commits into
mainfrom
feat/opencode-v2-support

Conversation

@dialupdisaster

@dialupdisaster dialupdisaster commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Migrates the plugin to OpenCode V2 only. The 2.x line default-exports a V2 definition (id: "devtheops.otel", setup) and consumes the V2 event stream. The OpenCode V1 implementation remains on the v1 branch, branched from v1.5.1.

Closes #128. Supersedes #131's dual V1/V2 approach with a V2-only major release.

V2 instrumentation

  • Explicit session.execution.*, session.step.*, and session.tool.* boundaries replace V1 message-part polling for run, LLM, and tool spans.
  • session.inbox.enqueued emits user_prompt only 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.ended restores output.value and llm.output_messages on LLM spans.
  • Per-step token, cost, cache, message, and model metrics; api_request and api_error logs retain duration_ms even with LLM traces disabled. Failed LLM spans retain any reported token and cost attributes. Cumulative session totals come from session.usage.updated with 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).
  • Durable session.retry.scheduled is the single retry-counter source. permission.asked/permission.replied preserve tool_decision; subagent session.created events preserve subtask.count and subtask_invoked.
  • commit.count and commit are emitted only for an executed shell tool that reaches session.tool.success with git commit in its command. Tool spans and duration start at session.tool.called, excluding model-side argument streaming. This observes a successful tool call; Git's resulting repository state is not independently verified.
  • model.request injects 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, if step.started has not arrived, creates a provisional LLM span that the step later adopts.
  • Optional OPENCODE_CAPTURE_MODEL_CONTEXT captures 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.
  • For explicitly configured trace-propagation providers, the experimental experimental.ws.handshake hook injects the matching W3C context. Changing per-step headers may reopen a reused WebSocket; the registration degrades gracefully when the experimental hook is unavailable.
  • Child subagent run spans nest under an unambiguously identified subagent tool span using session.tool.progress / terminal metadata containing the child sessionID. Links are consumed per child execution so resumed sessions cannot inherit a stale dispatch. Foreground and background tool events were inspected in OpenCode 2.0.18; invalid executed: false calls 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_HEADERS are passed to exporters without copying them to the process's OTEL_EXPORTER_OTLP_HEADERS; exporter construction suppresses inherited OTLP header variables so a concurrent rejected setup cannot leak its credentials. project.id is resolved from the observed session, not assumed from ctx.location. Event subscriptions enqueue subsequent events without awaiting each dispatch; processing is serialized across subscribers before de-duplication so an asynchronous session lookup cannot let step.ended overtake step.started or 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 with ctx.session.get for sessions whose creation event was missed.

V1 parity limits

lines_of_code.count and lines_of_code.total are not emitted: the V1 session.diff event 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's ctx.session API; ctx.vcs.diff is repository-scoped and not an equivalent per-session total. The removed command.executed event 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 plugins object form and a tested .opencode/plugins/otel/index.ts development entrypoint. OpenCode 2.0.1 rejects an absolute .ts file as a plugins package target; directory auto-discovery worked. V1 SDK dependencies were removed, and the official V2 @opencode/plugin types are used without a runtime import. The unused .coderabbit.yaml was removed.

Type of change

  • Breaking change (V2-only package; V1 maintenance stays on v1)
  • Documentation update

Verification

  • bun run lint
  • bun run check:jsdoc-coverage (81.63%)
  • bun run typecheck
  • bun test (191 pass)
  • bun run build and bundled default-export import
  • Local OpenCode 2.0.1 smoke test using .opencode/plugins/: OTLP HTTP/JSON exported logs, traces, and metrics. Confirmed user_prompt, api_request, session.idle, opencode.session → opencode.llm nesting, LLM output.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; a delta temporality run exported counter sums with OTLP aggregation temporality 1 (delta).
  • OpenCode 2.0.18 foreground subagent OTLP export: parent opencode.session → opencode.tool.subagent → child opencode.session. Captured real foreground and background event sequences; unit tests cover background parenting after the dispatch tool ends and ambiguous-candidate fallback.
  • OpenCode 2.0.18 opt-in context capture exported a text-only llm.input_messages preview 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.
  • Added regression tests for conflicting configurations, observed project attribution, bounded sets, durable retries/prompts, cost-only failures, LLM output, subtask logs, and non-executed/failed commits. Two-subscriber delayed-dispatch, buffered subscription events with a concurrent model request, iterator failure with pending dispatch, repeated/resumed subagent, selected agent across executions, delayed model-request, accepted resource-attribute/temporality, queued-next-prompt, steering-prompt, pending-exporter-flush, two-execution cumulative-baseline, actual exporter inherited-header, delayed-tool-input, and API log duration without traces cover the lifecycle review findings.
  • One-use child dispatch mapping and provisional-span context adoption tests cover the latest review findings.

Related issues

Closes #128

Additional context

The breaking feat! commit is intended to make release-please cut 2.0.0; package.json remains 1.5.1 until 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.

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.
Comment thread src/handlers/step.ts Fixed
@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

No actionable bugs or regressions found in the PR changes during static review.

Validation limitation: I couldn’t run the tests because bun is not installed in this environment.

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
dialupdisaster merged commit dae25f0 into main Sep 26, 2026
9 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support for OpenCode v2

1 participant