summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLuke Parker <[email protected]>2026-04-21 17:54:53 +1000
committerGitHub <[email protected]>2026-04-21 07:54:53 +0000
commit92c005866b99240a63b11602f3ffb541f844c257 (patch)
treecbe7b02de6c9328dc38216f050091f4455e8aa18
parent224548d87d9aa9b8fdbcba2a8c1f96d5f2679ffa (diff)
downloadopencode-92c005866b99240a63b11602f3ffb541f844c257.tar.gz
opencode-92c005866b99240a63b11602f3ffb541f844c257.zip
fix(core): use file:// URLs for local dynamic import() on Windows+Node (#23639)
-rw-r--r--packages/opencode/src/provider/provider.ts6
-rw-r--r--packages/opencode/src/tool/registry.ts6
2 files changed, 8 insertions, 4 deletions
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 6e116fe41..d643f2537 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -19,6 +19,7 @@ import { zod } from "@/util/effect-zod"
import { iife } from "@/util/iife"
import { Global } from "../global"
import path from "path"
+import { pathToFileURL } from "url"
import { Effect, Layer, Context, Schema, Types } from "effect"
import { EffectBridge } from "@/effect"
import { InstanceState } from "@/effect"
@@ -1506,7 +1507,10 @@ const layer: Layer.Layer<
installedPath = model.api.npm
}
- const mod = await import(installedPath)
+ // `installedPath` is a local entry path or an existing `file://` URL. Normalize
+ // only path inputs so Node on Windows accepts the dynamic import.
+ const importSpec = installedPath.startsWith("file://") ? installedPath : pathToFileURL(installedPath).href
+ const mod = await import(importSpec)
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
const loaded = fn({
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index e27593e59..0211e33bc 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -157,9 +157,9 @@ export const layer: Layer.Layer<
if (matches.length) yield* config.waitForDependencies()
for (const match of matches) {
const namespace = path.basename(match, path.extname(match))
- const mod = yield* Effect.promise(
- () => import(process.platform === "win32" ? match : pathToFileURL(match).href),
- )
+ // `match` is an absolute filesystem path from `Glob.scanSync(..., { absolute: true })`.
+ // Import it as `file://` so Node on Windows accepts the dynamic import.
+ const mod = yield* Effect.promise(() => import(pathToFileURL(match).href))
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
}