anomalyco / anomalyco/opencode
`import.meta.resolve(dir)` one-arg form returns directory URL on Node.js, causing custom npm providers to fail with `ERR_UNSUPPORTED_DIR_IMPORT`
@rekram1-node is already working on this.
Since Aug 29, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
Custom npm providers configured via the npm field in opencode.jsonc fail to initialize in OpenCode's TUI/server mode when running on Node.js, while the same configuration works in the CLI (Bun). The root cause is an asymmetry in import.meta.resolve() between Bun and Node.js in packages/core/src/npm.ts.
Environment
- OpenCode: v1.18.25
- Runtime: Node.js v24.14.0 (also affects Electron/Desktop)
- OS: Windows 10
Symptom
When a custom provider is configured via npm package name:
{
"plugin": ["opencode-cmd-provider"],
"provider": {
"commandcode": {
"npm": "opencode-cmd-provider",
"name": "Command Code",
"options": {
"apiKey": "..."
}
}
}
}
opencode models commandcode— works ✅opencode run -m commandcode/deepseek/deepseek-v4-flash "hello"— works ✅- Interactive TUI — fails with
Failed to initialize provider: commandcode❌
Using file:// URL directly to the dist entry file fixes all modes:
"npm": "file:///absolute/path/to/opencode-cmd-provider/dist/index.js"
Root Cause
In packages/core/src/npm.ts, the resolveEntryPoint function handles Bun and Node.js differently:
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
let entrypoint: Option.Option<string>
try {
const resolved = typeof Bun !== "undefined"
? import.meta.resolve(name, dir) // ✅ Bun: resolves to entry file
: import.meta.resolve(dir) // ❌ Node: returns directory URL
entrypoint = Option.some(resolved)
} catch {
entrypoint = Option.none()
}
return { directory: dir, entrypoint }
}
- Bun path:
import.meta.resolve(name, dir)— correctly resolves the package name relative to the directory, returning the actual entry file (e.g.,file:///.../dist/index.js) - Node path:
import.meta.resolve(dir)— merely converts the directory path to afile://URL
The caller then does await import(importSpec) with the resolved path. Node.js ESM does not support import() of a directory URL — it throws ERR_UNSUPPORTED_DIR_IMPORT regardless of what package.json declares in main/exports.
This explains why:
- CLI commands (
run,models) work — they may use a cached/loaded SDK or a different resolution path - TUI/server fails — it freshly initializes providers via
Npm.add()and hits the directory import error file://works — it bypasses npm resolution entirely, passing the exact file path toimport()
Fix
Both Bun and Node.js 20.6+ support the two-argument form of import.meta.resolve(name, dir). The fix is to use it consistently:
const resolveEntryPoint = (name: string, dir: string): EntryPoint => {
let entrypoint: Option.Option<string>
try {
- const resolved = typeof Bun !== "undefined"
- ? import.meta.resolve(name, dir)
- : import.meta.resolve(dir)
+ const resolved = import.meta.resolve(name, dir)
entrypoint = Option.some(resolved)
} catch {
entrypoint = Option.none()
}
return { directory: dir, entrypoint }
}
Related
- PR #44722 — Alternative approach that resolves entry from manifest (
exports→module→main→index.js) instead of fixing theimport.meta.resolvecall - PR #32060 — Earlier attempt (auto-closed by stale bot)
- Issue #31909 — Original bug report for Desktop/Electron
- Issue rashidrazak/opencode-cmd-provider#102 — Downstream plugin report documenting the
file://workaround
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.