Skip to content

Port plugin to the OpenCode V2 TUI plugin API - #25

Open
LucasInstra wants to merge 8 commits into
karamanliev:mainfrom
LucasInstra:v2
Open

LucasInstra wants to merge 8 commits into
karamanliev:mainfrom
LucasInstra:v2

Conversation

@LucasInstra

@LucasInstra LucasInstra commented Sep 14, 2026

Copy link
Copy Markdown

Port plugin to the OpenCode V2 TUI plugin API

Migrates the plugin from the V1 TUI plugin API (@opencode-ai/plugin/tui) to the V2 CLI plugin API (@opencode/plugin/tui + @opencode/client). Fixes #24.

As requested, this PR now contains only the V2 port (feature parity with V1). The new features built on top of it live in #26 — please review this one first.

Tested against opencode 2.0.3 and verified up to 2.0.8.

V1 → V2 mapping

  • Entrypoint: export default { id, tui(api, options, meta) }Plugin.define({ id, setup(ctx) }); cleanup is returned from setup.
  • Slots & keymap: the overlay still renders in the app slot (ctx.ui.slot). Keymap layers are registered inside the slot component because ctx.keymap.layer() requires a component owner. The V1 legacy api.command fallback was dropped.
  • Routing: api.route.currentctx.ui.router.current().
  • Dialogs / toasts / theme: ctx.ui.dialog and ctx.ui.toast.show; ctx.theme is mapped to the token names the components already use (src/opencode.ts). The adapter reads every token defensively and supports both the <= 2.0.7 background.surface and the 2.0.8 background.raised shapes, so a theme change degrades gracefully instead of crashing the slot.
  • Messages: V1 state.session.messages + state.partclient.session.context({ sessionID }) (V2 embeds content parts in the message and has no separate part store).
  • Models / providers: V1 provider map → client.model.list() + client.provider.list(); the fallback default model comes from client.model.default().
  • Sessions: session.create with location, model and V2 permission rules; the mini system prompt is attached with session.instructions.entry.put; session.prompt delivers the question; session.interrupt / session.remove on close.
  • Streaming: V1 session.next.text.delta / message.part.updated → V2 events session.text.delta, session.reasoning.delta, session.tool.*, session.execution.*, session.idle via ctx.data.on().
  • Permissions: the plugin-managed mini agent is enforced with V2 permission rules — deny *, then allow the configured tools (list is not a V2 permission action; the read tool lists directories).
  • Auto-update: checks the npm registry and shows the warning in the overlay plus a toast pointing at opencode plugin update opencode-mini-session. Unlike V1 it does not delete its own package cache, because V2 ships a first-class plugin update command.
  • Dependencies: @opencode-ai/plugin / @opencode-ai/sdk@opencode/plugin / @opencode/client ^2.0.5 (@opencode/plugin is a runtime dependency, as the migration guide recommends, so the packaged entrypoint resolves it). OpenTUI ^0.4.3^0.5.11 (@opentui/solid pins solid-js@1.9.12, so babel-preset-solid is pinned to 1.9.12 as well); engines.opencode>=2.
  • Version: bumped to 2.0.0 for the breaking V2 port — happy to drop that if you prefer to handle releases yourself.

Behavior notes (no exact V2 equivalent)

  • Continue (shift+enter): V1's tui.appendPrompt has no V2 client API, so the transcript is queued into the main session with session.prompt({ delivery: "queue" }).
  • Root sessions: SessionCreateInput has no parentID, so mini sessions are regular sessions. They are tagged with metadata.opencodeMiniSession and deleted on close.
  • Legacy keymap fallback for older V1 hosts was dropped (V2 only).

Tooling

  • CI: .github/workflows/ci.yml runs typecheck, unit tests, build, and the packaged output check. The package check now verifies the V2 entrypoint (id + setup), handles npm 11's npm pack --json output, and works on Windows.
  • Git installs: a prepare script builds dist/, so opencode plugin add github:... works without a manual build step.

Tests

84 tests across 7 files (agent, context, model, session, update, counter, routing), ported from the V1 suite and adapted to the V2 API.

Verification

  • Plugin loads cleanly on opencode 2.0.3 (stage=setup completed, no plugin failed banner).
  • Overlay opens with alt+b; context counter, transcript, input, and footer render.
  • Interactive round-trip against a live 2.0.3 server: typed question → streamed answer.
  • API contract verified end-to-end: session created with the plugin payload (permissions + model + variant) → instructions entry attached → prompt delivered → assistant reply with the expected model.
  • tsc --noEmit, vitest run, npm run build, and npm run test:package pass.

How to try it

  • After the 2.x release on npm: opencode plugin add opencode-mini-session.

  • Without waiting for a release, from this branch: clone, build, and load the checkout through the global plugin directory:

    git clone https://github.com/LucasInstra/opencode-mini-session.git -b v2
    cd opencode-mini-session
    npm install && npm run build
    mkdir -p ~/.config/opencode/plugins/mini-session
    ln -s "$PWD/dist" ~/.config/opencode/plugins/mini-session/dist
    ln -s "$PWD/node_modules" ~/.config/opencode/plugins/mini-session/node_modules
    printf 'export { default } from "./dist/index.js";\n' > ~/.config/opencode/plugins/mini-session/tui.ts

    On Windows use mklink /J for the two links. Restart OpenCode after rebuilding.

  • opencode plugin add github:... works where Git packages are enabled. npm 12 disables them by default (allow-git=none), so enable it first (npm config set allow-git all) if the install is refused.

@LucasInstra
LucasInstra force-pushed the v2 branch 4 times, most recently from 736d8c4 to 7d3242a Compare September 15, 2026 14:53
- Add a CI workflow (push to main + pull requests) running typecheck, unit tests, build, and the packaged output check.
- Update verify-packaged-tui.mjs for the V2 entrypoint: assert the exported definition has an id and a setup function instead of the V1 tui function.
- Handle npm 11's npm pack --json object output and Windows' npm.cmd.
- Move @opencode/plugin to dependencies so the published package resolves the host module (per the V2 migration guide).
A parent npm run exports the user configuration into the child environment, and npm 12 rejects allow-scripts on the isolated --prefix install this script performs (EALLOWSCRIPTS). Strip the problematic keys from the child environment; the install already passes --ignore-scripts.
The OpenTUI packages declare node >= 26.4 engines, so npm installs on the CI Node 24 emit EBADENGINE warnings. bun install is the same path publish.yml already uses, satisfies the engine, and was verified locally with the committed package-lock.json.
opencode plugin add accepts Git package specs, but dist/ is not committed: without prepare the installed package had no entrypoint. prepare mirrors the publish-time build.
The prepare script runs inside the host package preparation step, where invoking npm recursively may not resolve. Run the build script with node instead.
@LucasInstra

Copy link
Copy Markdown
Author

Split as requested: this PR now contains only the V2 port (feature parity with V1). The branch was rebuilt so the port commits come first — it is content-identical to the previous head, no code was dropped. The new features moved to #26, which is stacked on this branch; please review/merge this one first. Once it is merged I will rebase #26 so that PR shows only the feature commits.

OpenCode 2.0.8 replaced background.surface with background.raised, and reading the old field inside the app slot crashed the plugin. Read every theme token defensively with fallbacks for both shapes, and cover the 2.0.8 shape and a partial theme with tests. Also refresh the lockfile to @OpenCode 2.0.8 (same declared ranges).
@LucasInstra

Copy link
Copy Markdown
Author

Compatibility fix pushed: OpenCode 2.0.8 renamed the resolved theme background (background.surface -> background.raised), and reading the old field inside the app slot crashed the plugin with undefined is not an object (evaluating 'theme.background.surface.overlay').

The theme adapter now reads every token defensively with fallbacks for both shapes, and tests cover the 2.0.8 shape plus a partial theme. Verified by calling the built adapter with both theme shapes (2.0.8 -> raised.high/raised.base; older -> surface.overlay/surface.offset) and with an empty theme (falls back without throwing).

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.

no support for opencode 2.0.3 version

1 participant