diff --git a/packages/core/src/image/photon.ts b/packages/core/src/image/photon.ts index 877c3af6c18..aa314bb95ff 100644 --- a/packages/core/src/image/photon.ts +++ b/packages/core/src/image/photon.ts @@ -8,13 +8,27 @@ import { DecodeError, ResizerUnavailableError, SizeError } from "../image.js" const JPEG_QUALITIES = [80, 85, 70, 55, 40] export const make = Effect.gen(function* () { - ;(globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string }).__OPENCODE_PHOTON_WASM_PATH = - path.isAbsolute(photonWasm) ? photonWasm : fileURLToPath(new URL(photonWasm, import.meta.url)) const loadPhoton = yield* Effect.cached( - Effect.tryPromise({ - try: () => import("@silvia-odwyer/photon-node"), - catch: () => new ResizerUnavailableError(), - }), + // A runtime without a photon wasm artifact (#photon-wasm resolves to the + // empty string on workerd) has no resizer, by declaration: fail typed + // before touching URLs or module loading. The path resolution and import + // for runtimes that DO have an artifact stay inside the guard too — a + // throw outside it (workerd's undefined import.meta.url was one) is a + // defect that escapes the ResizerUnavailableError handling and turns any + // image-bearing prompt into a 500 instead of degrading to passthrough. + photonWasm === "" + ? Effect.fail(new ResizerUnavailableError()) + : Effect.tryPromise({ + try: async () => { + ;( + globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string } + ).__OPENCODE_PHOTON_WASM_PATH = path.isAbsolute(photonWasm) + ? photonWasm + : fileURLToPath(new URL(photonWasm, import.meta.url)) + return await import("@silvia-odwyer/photon-node") + }, + catch: () => new ResizerUnavailableError(), + }), ) return Effect.fn("Image.Photon.normalize")(function* ( resource: string,