Repository navigation
Description
Description
A plugin that registers a provider whose package is an aisdk: name that does not exist on npm never gets its ctx.aisdk.hook("sdk", ...) callback invoked. Dispatch fails with:
SessionRunnerModel.UnsupportedPackageError: Unsupported package for repro/test-model: aisdk:@example/not-on-npm
The package name here is only used as a routing key for the plugin's own hook, so nothing should be fetched from npm.
This is the same pattern OpenCode uses internally: GithubCopilotPlugin sets model.package = "@ai-sdk/github-copilot", claims it in its own sdk hook, and supplies the SDK from a local module. @ai-sdk/github-copilot is not published on npm. That works only because ProviderPlugins is ordered ahead of DynamicProviderPlugin inside pre; a plugin loaded from config cannot get in front of it.
Cause: DynamicProviderPlugin is registered in PluginInternal.pre, which runs before plugins loaded from config, so its sdk hook is always ahead of any plugin's. For an unrecognised package it calls npm.add(evt.package) with Effect.orDie, and the resulting 404 defect aborts the whole runSDK hook chain — every hook registered after it is skipped, including the plugin's.
Built-in providers are unaffected because their own hooks set evt.sdk earlier in the list, so if (evt.sdk) return short-circuits before the npm call.
This is hard to diagnose because resolveCatalogModel maps any loadAISDK failure to UnsupportedPackageError and drops the cause. I found it by temporarily logging each hook index in AISDK.run:
run-hook enter { index: 21 } -> done
run-hook enter { index: 22 } -> dynamic provider hook -> (no done)
NpmInstallFailedError: 404 Not Found - GET https://registry.npmjs.org/@example%2fnot-on-npm
Plugins
Standalone repro below, no dependencies.
OpenCode version
0.0.0-next-17444
Steps to reproduce
- Create
repro-plugin/package.json:
{ "name": "repro-plugin", "version": "1.0.0", "type": "module", "main": "index.js" }- Create
repro-plugin/index.js:
export default {
id: "repro",
setup: async (ctx) => {
await ctx.catalog.transform((catalog) => {
catalog.provider.update("repro", (provider) => {
provider.name = "Repro"
provider.package = "aisdk:@example/not-on-npm"
provider.settings = { baseURL: "https://example.invalid", apiKey: "repro" }
})
catalog.model.update("repro", "test-model", (model) => {
model.name = "Repro Test Model"
model.capabilities = { tools: true, input: ["text"], output: ["text"] }
model.limit = { context: 100000, output: 4096 }
})
})
await ctx.aisdk.hook("sdk", (event) => {
console.error("sdk hook ran", event.package) // never printed
})
},
}- Add the directory to
pluginsinopencode.json. - Start a session on
repro/test-modeland send any prompt.
Expected: the plugin's sdk hook runs and can supply an SDK.
Actual: the hook never runs and the session fails with UnsupportedPackageError.
Operating System
Debian GNU/Linux 13 (trixie), Linux 6.12.63
Activity
nkoynov commentedon Oct 5, 2026
This still happens on 2.0.22, and the code is unchanged on
v2@ ef07075:DynamicProviderPluginstill runs inPluginInternal.pre, before plugins loaded from config. The same ordering causes a second problem. We pin a plugin ascursor-opencode-provider@0.8.0, and it registers its provider withpackage: "aisdk:cursor-opencode-provider". OpenCode then installs a separatecursor-opencode-provider@latestcopy and runs the language model from that copy, so the pinned plugin isn't the code that answers requests: patching only the pinned copy changed nothing, and patching the@latestcopy did. Because@latestis a mutable cache entry, a new release can replace the model code under a pinned plugin without anyone noticing. Our workaround is"providers": {"cursor": {"package": "aisdk:cursor-opencode-provider@0.8.0"}}. A real fix could resolve anaisdk:package that a plugin registers against that plugin's own installed copy, or run the dynamic loader after config plugins, as #42789 did.fix(opencode2): pin the aisdk package to the plugin's own version
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report and for tracking down the cause. I can reproduce this on 2.2.0 (latest release) and on the latest
dev(ced73dc0f6). Your exact build,0.0.0-next-17444, isn't on npm any more, so I couldn't test that one.The plugin API has changed since your build, and
ctx.catalogno longer exists. So I adapted your plugin toctx.provider.transform+editor.add(...). It still uses theaisdk:@example/not-on-npmpackage and registers actx.aisdk.hook("sdk", ...)that writes to a log file. Then I ranopencode run -m repro/test-model "hello":sdkhook is never called.Cannot initialize repro/test-model: 404 Not Found - GET https://registry.npmjs.org/@example%2fnot-on-npm - Not found.One part has already improved: the error now includes the npm 404 instead of an
UnsupportedPackageErrorwith no cause. The ordering problem you describe is unchanged ondev.DynamicProviderPluginstill registers itssdkhook ahead of plugins loaded from config, and the failednpm.addstill stops the remaining hooks.The script below reproduces it. Set
OC_DEV=/path/to/checkoutto run it against a source checkout. Related: your earlier PR #42789 proposed a fix for this; it was closed by automated cleanup without being merged.Reproduction script (repro.sh)
Run it:
bash repro.sh