Repository navigation
DECISION: runtime-loaded JavaScript in the native OpenCode build — plugins, custom tools, non-bundled providers, TUI plugins #10105
Description
Parent: #10107. Needs Ralph's call before the TUI/plugin milestones are declared done.
The sites (all computed dynamic imports of code that does not exist at compile time)
| site | what gets imported | how it is installed |
|---|---|---|
packages/opencode/src/plugin/loader.ts:139 import(row.entry) |
OpenCode plugins (plugin: [...] in config) |
npm packages installed at runtime by Npm.add (packages/core/src/npm.ts, @npmcli/arborist) into ~/.cache/opencode/node_modules, or local file:// paths |
packages/core/src/config/plugin/external.ts:80 import(entrypoint) |
v1 core plugins ({plugin,plugins}/*.{ts,js} in config dirs + npm) |
same |
packages/opencode/src/provider/provider.ts:1854 import(importSpec) (+ packages/core/src/plugin/provider/dynamic.ts:20, sap-ai-core.ts:25) |
provider SDKs whose model.api.npm is not one of the 24 BUNDLED_PROVIDERS (provider.ts:113) |
Npm.add(model.api.npm) at first use |
packages/opencode/src/tool/registry.ts:192 import(pathToFileURL(match).href) |
custom tools {tool,tools}/*.{js,ts} in config dirs |
user files |
packages/opencode/src/plugin/tui/runtime.ts via PluginLoader |
TUI plugins (may be .tsx, transformed at load by the runtime Solid plugin — #10100) |
npm or local |
packages/opencode/src/config/config.ts:279 |
legacy TOML config | #10104 (data, not code) |
Everything else in the product is statically reachable and compiles (bundled providers use a static import() map). |
Under perry today
Computed import() defers to ERR_MODULE_NOT_FOUND (#5230/#6665); OpenCode catches it (PluginLoader.load returns { ok: false, error }, providers throw "Package … has no import entrypoint" after import.meta.resolve (#10101) fails). So the native binary runs, but user plugins, custom tools, TUI plugins and non-bundled providers do not work — with cryptic errors.
Options
A. Accept the boundary for the first deliverable. Ship without runtime plugins; make the errors explicit ("runtime plugins are not available in the native build; use the bun distribution or a plugin pack") and document it. Zero engine work.
B. Interpreter-backed module loader. Extend the existing tree-walking dyn-eval interpreter (#6584, parse cache #6703) from function bodies to whole ESM/CJS module graphs at runtime: import/export resolution against node_modules, node builtins via perry's native modules, no JSX. Plugins would run slowly but work; TS plugins need transpileModule (#8535 exists). Sizeable (module linking, live bindings, async module evaluation) and a permanent second execution engine to keep in parity.
C. Plugin pack recompilation. opencode plugin install triggers a perry recompile of the plugin set into a side binary/dylib loaded via bun:ffi-style dlopen. Keeps "no JS engine" but requires the compiler on the user's machine (or a build service).
Recommendation
A now, with the explicit error UX; decide B vs C only if the customer requires third-party plugins in the native build. Non-bundled providers are rare (the 24 bundled loaders cover all models.dev providers that ship an @ai-sdk/* package — verify against the current models.dev snapshot).
Acceptance (for A)
- Decision recorded here and in the tracker.
- Deferred-import error text names the native-build limitation;
opencode plugincommands surface it (not a rawERR_MODULE_NOT_FOUND). - A test proves that a config with
plugin: ["some-npm-plugin"]still starts the TUI and logs the limitation once.
Activity
proggeramlug commentedon Sep 12, 2026
Fixed by #10131, landed on
mainvia merge train #10139 atc2542efc11.The close keyword does not fire through a train branch, so closing manually.