Skip to content

Support OpenCode V1 and V2 from one package - #6

Merged
shanebishop1 merged 38 commits into
mainfrom
feat/opencode-v2
Oct 1, 2026
Merged

shanebishop1 merged 38 commits into
mainfrom
feat/opencode-v2

Conversation

@shanebishop1

@shanebishop1 shanebishop1 commented Jul 18, 2026 •

Copy link
Copy Markdown
Owner

Summary

One opencode-command-hooks package now supports OpenCode V1 and V2 automatically. Users keep "plugin": ["opencode-command-hooks"] on any host, with no configuration change.

  • Adds the stable OpenCode 2 adapter (@opencode/plugin@2.0.12) alongside the V1 adapter, sharing config, matching, execution, and templating.
  • Removes the engines.opencode range, which made every V1 release before 1.18.29 skip the plugin.
  • Adds no runtime dependencies: the V2 adapter declares its own types, so @opencode/plugin is not installed for users.
  • Adds a package-name host matrix to PR and release CI.
  • Makes the V1 E2E deterministic. It previously depended on a hosted free model that now returns Unexpected server error.

How each host picks its entrypoint

Host Loads Reads
OpenCode V2 ./server (resolved before .) setup
OpenCode V1 1.3.4+ ./server server
OpenCode V1 before 1.3.4 package root callable default export
  • ./server (src/server.ts) is one plain { id, server, setup } object. V2's loader validates the default export with an object schema and rejects functions.
  • The package root (src/entry.ts) is a callable V1 plugin carrying the same fields. V1 releases before 1.3.4 call every root export as a plugin function.
  • Root server.js and index.js mirror the two exports for directory-path installs.
  • The plugin ID is now opencode-command-hooks on both hosts (V2 previously used opencode-command-hooks.v2).

The previous layout (. = V2, ./server = V1) passed the existing E2E only because those suites load the plugin from a directory path, which bypasses package exports. Installed by name, V2 loaded the V1 entry and rejected it (Plugin must export a default definition with an id and an effect or setup function), so no hooks ran on any V2 host.

Tests

  • npm run test:hosts:e2e (new): publishes the packed plugin to a throwaway local Verdaccio registry and installs it by name in V1 1.3.3, V1 1.3.4, latest V1, and latest V2. A deterministic model makes a real shell tool call, and the before/after hooks must run (plus idle on hosts that keep running long enough for it). Against the previous layout it fails on V1 1.3.3, V1 1.3.4, and V2. It runs in PR CI and gates the release workflow.
  • npm run test:v1:e2e: now uses the local OpenAI-compatible fixture (OPENCODE_E2E_MODEL still selects a hosted model). Runs attach to a long-lived opencode serve, because a one-shot opencode run exits on idle and cuts off the idle hook. Injection and subagent evidence come from the model request log. The fixture now also issues V1 bash/write/task calls.
  • npm run test:v2:e2e: temp paths are canonicalized. On macOS the symlinked tmpdir made the server and CLI see two project locations and fail locally; Linux CI was unaffected.

Verification

Manual matrix, installed by package name from a local registry:

Host Before/after hooks Idle hook
V1 1.0.0, 1.1.1, 1.2.27, 1.3.3, 1.3.4 ✓ Intermittent with one-shot opencode run, the same as 0.6.1
V1 1.18.22, 1.18.29, 1.18.34 ✓ ✓
V2 2.0.12, 2.0.21 ✓ ✓
  • Directory installs on V1 and V2, and V2's plugins key, also work.
  • Locally at the PR head: lint, typecheck, unit/package tests 172/172, V1 E2E 2/2, V2 E2E 3/3, host matrix 4/4.
  • In CI: all checks pass.

Release

After merge, release from main as 0.8.0 (npm latest is already 0.7.1). The release workflow runs the host matrix before publishing.

@shanebishop1 shanebishop1 changed the title Add OpenCode 2 beta adapter Add stable OpenCode 2 adapter and dual-host E2E coverage Sep 21, 2026
OpenCode V2 resolves a package's `./server` export before its root, so
installing `opencode-command-hooks` by name loaded the V1 descriptor and
V2 rejected it. The repository E2E loaded the plugin from a directory
path, which skips package exports and hid the failure.

`./server` now exports one plain definition carrying both the V1
`server` and V2 `setup` adapters, which every current host accepts.
The package root stays a callable plugin for V1 releases before 1.3.4,
which call each root export as a plugin function. Root `server.js` and
`index.js` mirror the exports for directory installs.
The `engines.opencode` range made every V1 release before 1.18.29 skip
the plugin as incompatible, although the V1 adapter still runs on them.
Package-name installs on OpenCode 1.0 through 1.18 now load as they did
with 0.6.x.
Publishes the packed plugin to a throwaway local registry and configures
it as `"plugin": ["opencode-command-hooks"]` in V1 1.3.3 (package root),
V1 1.3.4 (first `./server` release), the latest V1, and the latest V2.
Each host runs a real deterministic shell tool call and must execute the
before, after, and (where the host waits for it) idle hooks.

Directory-path installs bypass package exports, so this is the only
coverage of the entrypoint each host actually selects. Against the
previous layout it fails on V1 1.3.3, V1 1.3.4, and V2.
Every OpenCode 1.x release and OpenCode 2 load the same package name
without configuration changes. Document how each host selects its
entrypoint, the plugin ID, and the package-name host matrix.
@shanebishop1 shanebishop1 changed the title Add stable OpenCode 2 adapter and dual-host E2E coverage Support OpenCode V1 and V2 from one package Oct 1, 2026
The V1 suite picked a hosted free model, which now answers with
"Unexpected server error", so the check failed on any commit. It now
uses the local OpenAI-compatible fixture by default, like the V2 suite;
`OPENCODE_E2E_MODEL` still selects a hosted model.

The fixture issues V1 `write` and `task` tool calls in addition to V2
`shell`/`subagent`. With the fast local model, injection and subagent
evidence come from the model's request log instead of host log lines
that are not flushed before `opencode run` exits. The sandbox path is
canonical so macOS temp symlinks do not turn writes into external
directory requests.
On macOS the temp directory is a symlink, so the V2 server (started with
PWD) and the CLI (process cwd) saw two project locations. Session
lookups then missed, and the subagent and reload tests failed locally
while passing on Linux CI.
With a fast local model, a one-shot `opencode run` exits as soon as the
session is idle, cutting off the idle hook it should observe. Attaching
runs to `opencode serve` keeps the host alive like the TUI does.
The V2 adapter declares its own types and the shared entry no longer
calls `Plugin.define`, so nothing imports the package. Removing it keeps
it and its dependency tree out of every user's install.
It recorded beta-era research and is not referenced by the package or
docs. The PR description covers the final design.
It only served the removed `@opencode-ai/plugin/v2` path alias.
@shanebishop1
shanebishop1 merged commit 7ac4723 into main Oct 1, 2026
6 checks passed
@shanebishop1
shanebishop1 deleted the feat/opencode-v2 branch October 1, 2026 15:26
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.

1 participant