diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 381e717d..1f28a4b0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -10,6 +10,10 @@ - Added `ctx.compact()` and `ctx.getContextUsage()` to extension contexts for programmatic compaction and context usage checks. - Added documentation for delete word forward and kill ring keybindings in interactive mode. ([#810](https://github.com/badlogic/pi-mono/pull/810) by [@Perlence](https://github.com/Perlence)) +### Fixed + +- Fixed photon module failing to load in ESM context with "require is not defined" error ([#795](https://github.com/badlogic/pi-mono/pull/795) by [@dannote](https://github.com/dannote)) + ## [0.48.0] - 2026-01-16 ### Added diff --git a/packages/coding-agent/src/utils/image-convert.ts b/packages/coding-agent/src/utils/image-convert.ts index a1e5a474..74198c63 100644 --- a/packages/coding-agent/src/utils/image-convert.ts +++ b/packages/coding-agent/src/utils/image-convert.ts @@ -1,4 +1,4 @@ -import { getPhoton } from "./photon.js"; +import { loadPhoton } from "./photon.js"; /** * Convert image to PNG format for terminal display. @@ -13,14 +13,15 @@ export async function convertToPng( return { data: base64Data, mimeType }; } - const photon = getPhoton(); + const photon = await loadPhoton(); if (!photon) { // Photon not available, can't convert return null; } try { - const image = photon.PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64"))); + const bytes = new Uint8Array(Buffer.from(base64Data, "base64")); + const image = photon.PhotonImage.new_from_byteslice(bytes); try { const pngBuffer = image.get_bytes(); return { diff --git a/packages/coding-agent/src/utils/image-resize.ts b/packages/coding-agent/src/utils/image-resize.ts index dbaf0b4c..91c3f796 100644 --- a/packages/coding-agent/src/utils/image-resize.ts +++ b/packages/coding-agent/src/utils/image-resize.ts @@ -1,5 +1,5 @@ import type { ImageContent } from "@mariozechner/pi-ai"; -import { getPhoton } from "./photon.js"; +import { loadPhoton } from "./photon.js"; export interface ImageResizeOptions { maxWidth?: number; // Default: 2000 @@ -53,7 +53,7 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption const opts = { ...DEFAULT_OPTIONS, ...options }; const inputBuffer = Buffer.from(img.data, "base64"); - const photon = getPhoton(); + const photon = await loadPhoton(); if (!photon) { // Photon not available, return original image return { diff --git a/packages/coding-agent/src/utils/photon.ts b/packages/coding-agent/src/utils/photon.ts index 0666460b..d7a2f07f 100644 --- a/packages/coding-agent/src/utils/photon.ts +++ b/packages/coding-agent/src/utils/photon.ts @@ -8,8 +8,8 @@ * The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm') * which bakes the build machine's absolute path into Bun compiled binaries. * - * Solution: Lazy-load photon and gracefully handle failures. Image processing functions - * already have fallbacks that return original images when photon isn't available. + * Solution: Lazy-load photon via dynamic import and gracefully handle failures. + * Image processing functions have fallbacks that return original images when photon isn't available. */ // Re-export types from the main package @@ -17,43 +17,29 @@ export type { PhotonImage as PhotonImageType } from "@silvia-odwyer/photon-node" // Lazy-loaded photon module let photonModule: typeof import("@silvia-odwyer/photon-node") | null = null; -let loadAttempted = false; -let loadError: Error | null = null; +let loadPromise: Promise | null = null; /** - * Get the photon module, loading it lazily on first access. - * Returns null if loading fails (e.g., in broken Bun binary). + * Load the photon module asynchronously. + * Returns cached module on subsequent calls. */ -export function getPhoton(): typeof import("@silvia-odwyer/photon-node") | null { - if (loadAttempted) { +export async function loadPhoton(): Promise { + if (photonModule) { return photonModule; } - loadAttempted = true; - - try { - // Dynamic require to defer loading until actually needed - // This also allows the error to be caught gracefully - photonModule = require("@silvia-odwyer/photon-node"); - } catch (e) { - loadError = e as Error; - photonModule = null; + if (loadPromise) { + return loadPromise; } - return photonModule; -} + loadPromise = (async () => { + try { + photonModule = await import("@silvia-odwyer/photon-node"); + } catch { + photonModule = null; + } + return photonModule; + })(); -/** - * Check if photon is available and working. - */ -export function isPhotonAvailable(): boolean { - return getPhoton() !== null; -} - -/** - * Get the error that occurred during photon loading, if any. - */ -export function getPhotonLoadError(): Error | null { - getPhoton(); // Ensure load was attempted - return loadError; + return loadPromise; }