From 92c005866b99240a63b11602f3ffb541f844c257 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:54:53 +1000 Subject: [PATCH] fix(core): use file:// URLs for local dynamic import() on Windows+Node (#23639) --- packages/opencode/src/provider/provider.ts | 6 +++++- packages/opencode/src/tool/registry.ts | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 6e116fe41e..d643f25373 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 e27593e597..0211e33bcb 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(mod)) { custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def)) }