Repository navigation
fix(plugin): load Effect from the host for plugins - #53296
Closed
kitlangton wants to merge 1 commit into
Closed
kitlangton wants to merge 1 commit into
kitlangton wants to merge 1 commit into
Conversation
Contributor
Author
|
Superseded by #53422, which uses Bun's |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Server and TUI plugins written with
@opencode/plugin/effect(or using EffectSchemain tools and RPC contracts) previously resolvedeffectfrom their ownnode_modules. Two copies of Effect in the same process do not share module-private symbols, schema parser sentinels, or fiber internals:Effect.logfrom an older Effect copy crashes inside the host fiber withlogLevel.toUpperCase is not a functionwhen the fiber log-level representation differs across versions.Effect.runPromiseof host effects crashes withfiber.succeedWith is not a functionwhen fiber continuation internals differ between the host and plugin copies.Schema.withDecodingDefaultfail or crash withtransformations[i] is not a function/Missing keywhen decoded by the host'sSchemaParser.node_modules, schema checks such asSchema.Int,Schema.isPattern, andSchema.Trimfail throughToolRuntime.executeandRpc.callbecauseInternalParser.sameExitandInternalParser.missingare compared by reference across module instances.What Changes
@opencode/pluginruntime modules (Bun):packages/plugin/src/runtime-modules.bun.tsdynamically discoverseffectand every publiceffect/*subpath exported by the host's installedeffectpackage (dist/**/*.jsvalidated againsteffect/package.json#exports, excludinginternal/*), along with@opencode/plugin,@opencode/plugin/effect,@opencode/plugin/effect/plugin,@opencode/plugin/effect/tool,@opencode/plugin/promise/plugin,@opencode/plugin/promise/tool, and@opencode/plugin/rpc. Each specifier is registered lazily so unused subpaths are not eagerly evaluated.packages/plugin/src/runtime.bun.tsregisters a Bun runtime plugin onHost.load/prepareSourcethat bindsopentui:runtime-module:<specifier>virtual modules to the host's module instances and prescans the plugin's local source graph andnode_modulesESM dependency graph, rewriting matching imports to the virtual module IDs.packages/cli/script/build.tsinjects lazy() => require(specifier)thunks intoruntime-modules.bun.tsduringbun build --compileso all exportedeffect/*subpaths are available inside the standalone compiled binary without requiringnode_moduleson disk or evaluating every module at startup.packages/tui/src/plugin/runtime-plugin-support.bun.tsspreadspluginRuntimeModules()intoensureRuntimePluginSupport({ additional })so TUI plugins and theirnode_modulesdependencies share the same hosteffect,effect/*, and@opencode/plugininstances alongsidesolid-jsand@opentui/*.packages/plugin/src/runtime.node.tsusesmodule.registerHooksto redirecteffect,effect/*, and@opencode/plugin/*imports from plugins and theirnode_modulesdependencies to the host's in-memory module instances.packages/plugin/src/runtime.workerd.tsprovides a no-op stub for theworkerdcondition so Cloudflare Worker bundles remain free of Bun/Node loader hooks.packages/core/src/plugin.tsandpackages/core/src/plugin/module.tsverify that theEffectreturned byplugin.effect(ctx)sharesEffect.void.pipewith the host'sEffectprototype, failing plugin activation with a clear error message if a plugin bundled its own copy ofeffect.services/www/src/docs/content/build/plugins/effect.mdx,packages/plugin/README.md,packages/plugin/src/README.md, andpackages/plugin/src/effect/README.mdto instruct plugin authors to declareeffectas apeerDependency, keepeffectandeffect/*external when bundling, and target the host OpenCode release's Effect API surface.Scope
effectinto their own JavaScript file cannot have their internaleffectimports redirected by the module loader; Effect plugins that do so are detected at activation time and rejected with an actionable error.effectto the host copy unifies runtime module identity, but cannot polyfill Effect APIs or module paths that were removed or renamed between Effect releases.@effect/*adapter packages are not host-provided because OpenCode only installs a small internal subset and does not pass@effect/*types across the plugin boundary; any@effect/*package installed in a plugin'snode_moduleshas its owneffectandeffect/*imports rewritten to the host's Effect instance automatically.Verification
packages/plugin/test/host.test.ts: verifiesHost.loadredirectseffect,effect/*subpaths (effect/Option,effect/Brand,effect/unstable/http), and@opencode/plugin/*to the host's module instances (===reference equality) across a plugin with its own4.0.0-rc.111copy innode_modules/effect, a local helper, and a transitivenode_modulesdependency, under both Bun and Node.packages/core/test/plugin/module.test.ts: verifiesPluginModule.loadandPlugin.Service.activatewith a fixture plugin carrying a4.0.0-rc.111copy ofeffectand a transitive dependency innode_modules, proving module identity (===),Effect.loginside the host fiber,Effect.runPromiseonctx.plugin.list(),Schema.withDecodingDefaultthroughToolRuntime.execute, andSchema.Int/Schema.Trim/Schema.isPatternthroughToolRuntime.executeandRpc.Service.call, plus rejection of a plugin returning anEffectfrom a bundled copy.packages/tui/test/plugin-source.test.ts: verifies TUI plugins and theirnode_modulesdependencies resolveeffectandeffect/*subpaths to the host copy.dist/cli-darwin-arm64/bin/opencodeviapackages/cli/script/build.ts(--single --skip-install --skip-web-ui):155,404,146 bytes(148.20 MiB)effectandeffect/*subpaths included as lazy thunks):181,658,226 bytes(173.24 MiB)opencode servebinary against a fixture project whose.opencode/plugins/plugin had a throwingnode_modules/effectcopy and verified plugin activation,Effect.log,Effect.runPromise(ctx.plugin.list()),effect/Option,effect/Brand,Schema.withDecodingDefault, andSchema.Int/Schema.isPatternover HTTP RPC.bun run check,packages/plugin,packages/core,packages/tui,packages/server,packages/cli, andpackages/sdk(verify:package) all pass.