Skip to content

feat: support opencode 2 plugin API - #24

Merged
mevech merged 3 commits into
mainfrom
opencode-2-0
Oct 22, 2026
Merged

mevech merged 3 commits into
mainfrom
opencode-2-0

Conversation

@mevech

@mevech mevech commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

opencode 2 rejects the current plugin with only a log warning, so on 2.x every shell command runs ungated. This ports the opencode adapter so one package serves opencode 1.18.29+ and 2.x.

Changes

  • Dual export: the default export carries the V2 id/setup(ctx) and the V1 server(), following the V1→V2 migration guide. Plugin types are type-only imports, so no runtime dependency is added.

  • V2 tool names: the guard covers shell and patch alongside bash and apply_patch, and checks both filePath and path on edit/write, so a call carrying both cannot slip past on the field the tool ignores.

  • Docs: README covers the plugins config key, the minimum opencode version, opencode plugin list to confirm the gate loaded, the root index.ts that a manual install needs on 2.x, and BERGET_API_KEY on 2.x (it never refreshes the 1.x seat token). SECURITY.md notes that opencode 2 skips a plugin that fails to load and lets bash run ungated.

  • Version: 0.8.0 in package.json and .claude-plugin/plugin.json, so the next release ships opencode 2 support and the ./server export from fix: export ./server so opencode loads the plugin from npm #22.

@mevech

mevech commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Depends on #22 for npm installs on opencode 1.x: opencode 1.18 only loads an npm plugin through exports["./server"] or main (#21). The 1.x checks here loaded the plugin from a local directory, which skips that lookup. On opencode 2 the npm install resolves . and does not need #22. Both PRs point at adapters/opencode.ts and merge cleanly together.

opencode 2 rejects the V1 function export with only a log warning, so the
gate silently stopped running. The default export now carries both the V2
id/setup and the V1 server(), and the guard covers the V2 tool names
(shell, patch) and the edit/write `path` field.
@mevech

mevech commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

#22 is merged and this branch is rebased onto it. Checked through the npm install path, not a local directory: on opencode 1.19.3 the published 0.7.0 never loads and this build's server() is called; on opencode 2.1.2 this build's setup() is called and registers execute.before.

Comment thread README.md
| | opencode | pi | Claude Code |
|---|---|---|---|
| Hook | `tool.execute.before`: bash judged; `edit`, `write` and `apply_patch` checked against the protected paths | `tool_call`, including calls a codemode script makes: bash judged; `edit` and `write` checked against the protected paths | hooks module: `tool.call` on `Bash` and on `Monitor` when it runs a `command`; `Edit`, `Write` and `NotebookEdit` checked against the protected paths; `session.start` freezes the policy, the root and the environment |
| Hook | 1.x `tool.execute.before`: bash judged; `edit`, `write` and `apply_patch` checked against the protected paths. 2.x `tool.hook("execute.before")`: the same for `shell`, `edit`, `write` and `patch` | `tool_call`, including calls a codemode script makes: bash judged; `edit` and `write` checked against the protected paths | hooks module: `tool.call` on `Bash` and on `Monitor` when it runs a `command`; `Edit`, `Write` and `NotebookEdit` checked against the protected paths; `session.start` freezes the policy, the root and the environment |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit — The 1.x row says only "bash" is judged; the code now also matches shell on the 1.x path (needed since 1.19.x renamed the tool — 0.7.0 gated nothing there).

Comment thread adapters/opencode.ts

export const SystemOneGate = (async ({ directory, client }) => {
async function guard(gate: Gate, tool: string, args: Args): Promise<void> {
if (tool === "bash" || tool === "shell") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ good — Matching both bash and shell also fixes 0.7.0 silently gating nothing on opencode 1.19.x, where the Bash tool was renamed to shell.

@berget-ai

berget-ai Bot commented Oct 21, 2026

Copy link
Copy Markdown

Review

Summary

Adds opencode 2.x support to the opencode adapter via one dual-shape default export (server() for 1.x, id/setup() for 2.x tool.hook("execute.before")), a shared guard() for both APIs, plus docs, security notes, tests, and a version bump to 0.8.0.

Risk

LOW — I verified the opencode 2 contract against the real engine and SDK packages (@opencode/plugin@2.1.2 types, anomalyco/opencode@dev, opencode.ai docs): Plugin = { id, setup(ctx) }, ctx.location.directory is the project dir, tool names/inputs are shell{command}, edit/write{path}, patch{patchText}, plugins is the plural config key, and a thrown execute.before hook rejects the tool call ("only execute.before may reject a call" in packages/plugin/src/promise/adapter.ts), so throw-to-block is correct in 2.x. The import type from @opencode/plugin is erased at runtime, so nothing new is loaded on 1.x; PluginModule = { id?, server } exists in @opencode-ai/plugin@1.18.29, supporting the documented floor. All 155 tests pass.

Notably, this PR also fixes a pre-existing silent gap: current opencode 1.19.x renamed the Bash tool to shell (docs and plugin examples show input.tool === "shell" with the 1.x API), so 0.7.0's input.tool === "bash" check gated nothing at all on 1.19.x. The shared guard() matching both spellings closes that on the 1.x path too.

Issues

  • warning README.md:81 — Quickstart still says a truncated GUARDRAILS.md is "warns you at startup and in every block message"; on opencode 2 there is no toast (this PR's own harness-differences text says warnings "only reach the block reasons"), so over-long policy files are silently cut there.
  • nit README.md:459 — the harness table says 1.x gates "bash"; the 1.x path now matches bash or shell (required because 1.19.x renamed the tool — with 0.7.0 it silently gated nothing), which the row doesn't reflect.
  • nit adapters/opencode.test.ts:76 — the V1 harness has no tool: "shell" case, so the 1.19-rename fix is only pinned indirectly through the V2 tests despite being a V1-relevant regression.

Suggestions

  • README.md — consider updating line 81 to scope the startup warning to opencode 1.x + pi and, in SECURITY.md, noting that 0.7.0 and earlier were also ungated on opencode 1.19.x (post-rename), not only on 2.x.
  • adapters/opencode.test.ts — add one V1-path assertion like await expect(hook({ tool: "shell" }, { args: { command: "rm -rf /data" } })).rejects.toThrow(/destructive/).

Architecture

Clean consolidation: one shared guard() now serves both plugin APIs instead of two inline hook bodies, with the tool/arg-spelling matrix (bash/shell, filePath/path, apply_patch/patch) centralized in one place — CodeSense 9/10.

Inline findings


Berget AI (berget/zai-org/GLM-5.3-Flash) | PR #24

@mevech

mevech commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

@berget re-review the opencode adapter port end to end: the V2 setup()/execute.before path, the V1 server() path on opencode 1.18.29–1.19.x (including the bash vs shell tool name), and whether README/SECURITY match the code.

@berget-ai berget-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# Review

Generated/downloaded the full review above.

**Verdict list (all verified items marked):**
- Appended-bash gating for opencode 1.19: still present in old code — now fixed by `guard()` checking both `bash` and `shell`
[]`

 

Review

Summary

Adds opencode 2 plugin support to the same entrypoint (default export carrying V2 id+setup() and a V1 server(), both funneled through one guard() that accepts both generations' tool and argument names), and updates README/SECURITY/version accordingly.

Risk

LOW — I verified the port against the released @opencode/plugin@2.1.2 type definitions and the official opencode 2 docs (plugins + migrate-v1 + tools): the V2 shape (ctx.location.directory, ctx.tool.hook("execute.before", event: {tool, input}), throw-to-block), the plugins config key, opencode plugin list, and the official statement "V1 object entrypoints are supported in OpenCode 1.18.29" all match the code exactly; notably, current opencode 1.x has no bash tool (its built-in list is shell, edit, write, …, apply_patch), so the old code's "bash"-only check had silently stopped gating on 1.19.x — this PR's tool === "bash" || tool === "shell" check is the required fix and is present.

Issues

  • nit adapters/opencode.ts:20 — @opencode/plugin is imported (type-only) but is declared neither in dependencies nor devDependencies; CI never typechecks (and import type is erased, so the esbuild bundle checks pass), but any local tsc/IDE run will report an unresolved module. Same pre-existing pattern as @opencode-ai/plugin, so consistency-consistent; cheap fix is npm i -D @opencode/plugin.
  • nit README.md:459 — the harness table says 1.x judges "bash", but current 1.x (1.19.x) names the built-in tool shell in its docs and has no bash tool; behavior is correct because guard() checks both names, only the table wording is stale.

Suggestions

  • None — the dual-spelling extras (filePath/path, apply_patch/patch) are defensively correct and match the official V2 tool contract (shell→command, edit/write→path, patch→patchText); V2 warning handling ("no toast API, warnings only reach block reasons") is confirmed by both the @opencode/plugin Context type (no TUI domain) and check()/checkPath() appending gate.warnings to reasons; README/SECURITY claims (plugins key, opencode plugin list, .opencode/plugins/ discovery, 1.18.29 minimum, SYSTEMONE_GATE=off honored by checkPath) all check out against code and docs.

Berget AI (berget/zai-org/GLM-5.3-Flash) | PR #24

Comment thread adapters/opencode.ts

export const SystemOneGate = (async ({ directory, client }) => {
async function guard(gate: Gate, tool: string, args: Args): Promise<void> {
if (tool === "bash" || tool === "shell") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ good — Checking both "bash" and "shell" is what fixes gating on opencode 1.19.x — current 1.x has no "bash" built-in, only "shell".

Comment thread adapters/opencode.ts
}) satisfies V1Plugin

export default SystemOneGate
export default {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ good — The dual id/setup()/server() default export matches the officially documented V1+V2 support shape (V1 object entrypoints supported since 1.18.29).

Comment thread adapters/opencode.ts
*/
import type { Plugin } from "@opencode-ai/plugin"
import type { Plugin as V1Plugin } from "@opencode-ai/plugin"
import type { Plugin as V2 } from "@opencode/plugin"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit — @opencode/plugin is imported for types but not declared in package.json; only fails in local tsc/IDE runs since CI never typechecks and import type is erased.

Comment thread README.md
| | opencode | pi | Claude Code |
|---|---|---|---|
| Hook | `tool.execute.before`: bash judged; `edit`, `write` and `apply_patch` checked against the protected paths | `tool_call`, including calls a codemode script makes: bash judged; `edit` and `write` checked against the protected paths | hooks module: `tool.call` on `Bash` and on `Monitor` when it runs a `command`; `Edit`, `Write` and `NotebookEdit` checked against the protected paths; `session.start` freezes the policy, the root and the environment |
| Hook | 1.x `tool.execute.before`: bash judged; `edit`, `write` and `apply_patch` checked against the protected paths. 2.x `tool.hook("execute.before")`: the same for `shell`, `edit`, `write` and `patch` | `tool_call`, including calls a codemode script makes: bash judged; `edit` and `write` checked against the protected paths | hooks module: `tool.call` on `Bash` and on `Monitor` when it runs a `command`; `Edit`, `Write` and `NotebookEdit` checked against the protected paths; `session.start` freezes the policy, the root and the environment |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit — 1.x judges the "shell" tool (no "bash" built-in exists in current 1.x); wording only — guard() gates both names.

@berget-ai berget-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review

New changes: 1 commit (3f4977a) — docs-only: README now says the over-2000-char warning appears in block messages always, and at startup only on opencode 1.x.

Previous findings:

  • ⏳ nit #1 still present — @opencode/plugin is still type-only imported and absent from package.json (grep confirms no match); untouched by this commit.
  • ⏳ nit #2 still present — the Harness-differences table still reads "1.x tool.execute.before: bash judged"; wording remains stale (1.x's built-in is shell).

New findings: None — verified the claim against code: adapters/opencode.ts:11-12 and 72-74 confirm 1.x surfaces config warnings as a load-time toast while 2.x has no toast API, so the new "except on opencode 2, also at startup" wording is accurate, and block-message warnings are appended for both generations (core.ts:503-504, 565-566).


Berget AI (berget/zai-org/GLM-5.3-Flash) | PR #24

@mevech
mevech merged commit ab75bb3 into main Oct 22, 2026
2 checks passed
@mevech
mevech deleted the opencode-2-0 branch October 22, 2026 05:56
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