mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 16:01:05 +00:00
fix(coding-agent): use lazy-loaded photon wrapper for Node.js compatibility
Previous commit broke Node.js/tsx by using the ESM entry point which doesn't work with Node. This creates a wrapper module that: - Uses require() for lazy loading (works in both Node and Bun) - Gracefully handles load failures (returns original image) - Works in Node.js, tsx, and Bun compiled binaries
This commit is contained in:
parent
5aa0689828
commit
75628e0cfd
5 changed files with 86 additions and 16 deletions
|
|
@ -1,6 +1,4 @@
|
||||||
// Use ESM entry point so Bun can embed the WASM in compiled binaries
|
import { getPhoton } from "./photon.js";
|
||||||
// (the CJS entry uses fs.readFileSync which breaks in standalone binaries)
|
|
||||||
import { PhotonImage } from "@silvia-odwyer/photon-node/photon_rs_bg.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert image to PNG format for terminal display.
|
* Convert image to PNG format for terminal display.
|
||||||
|
|
@ -15,8 +13,14 @@ export async function convertToPng(
|
||||||
return { data: base64Data, mimeType };
|
return { data: base64Data, mimeType };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const photon = getPhoton();
|
||||||
|
if (!photon) {
|
||||||
|
// Photon not available, can't convert
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const image = PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64")));
|
const image = photon.PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64")));
|
||||||
try {
|
try {
|
||||||
const pngBuffer = image.get_bytes();
|
const pngBuffer = image.get_bytes();
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { ImageContent } from "@mariozechner/pi-ai";
|
import type { ImageContent } from "@mariozechner/pi-ai";
|
||||||
// Use ESM entry point so Bun can embed the WASM in compiled binaries
|
import { getPhoton } from "./photon.js";
|
||||||
// (the CJS entry uses fs.readFileSync which breaks in standalone binaries)
|
|
||||||
import { PhotonImage, resize, SamplingFilter } from "@silvia-odwyer/photon-node/photon_rs_bg.js";
|
|
||||||
|
|
||||||
export interface ImageResizeOptions {
|
export interface ImageResizeOptions {
|
||||||
maxWidth?: number; // Default: 2000
|
maxWidth?: number; // Default: 2000
|
||||||
|
|
@ -55,9 +53,23 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
||||||
const opts = { ...DEFAULT_OPTIONS, ...options };
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
||||||
const inputBuffer = Buffer.from(img.data, "base64");
|
const inputBuffer = Buffer.from(img.data, "base64");
|
||||||
|
|
||||||
let image: ReturnType<typeof PhotonImage.new_from_byteslice> | undefined;
|
const photon = getPhoton();
|
||||||
|
if (!photon) {
|
||||||
|
// Photon not available, return original image
|
||||||
|
return {
|
||||||
|
data: img.data,
|
||||||
|
mimeType: img.mimeType,
|
||||||
|
originalWidth: 0,
|
||||||
|
originalHeight: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
wasResized: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let image: ReturnType<typeof photon.PhotonImage.new_from_byteslice> | undefined;
|
||||||
try {
|
try {
|
||||||
image = PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer));
|
image = photon.PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer));
|
||||||
|
|
||||||
const originalWidth = image.get_width();
|
const originalWidth = image.get_width();
|
||||||
const originalHeight = image.get_height();
|
const originalHeight = image.get_height();
|
||||||
|
|
@ -96,7 +108,7 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
|
||||||
height: number,
|
height: number,
|
||||||
jpegQuality: number,
|
jpegQuality: number,
|
||||||
): { buffer: Uint8Array; mimeType: string } {
|
): { buffer: Uint8Array; mimeType: string } {
|
||||||
const resized = resize(image!, width, height, SamplingFilter.Lanczos3);
|
const resized = photon!.resize(image!, width, height, photon!.SamplingFilter.Lanczos3);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pngBuffer = resized.get_bytes();
|
const pngBuffer = resized.get_bytes();
|
||||||
|
|
|
||||||
5
packages/coding-agent/src/utils/photon.d.ts
vendored
5
packages/coding-agent/src/utils/photon.d.ts
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
// Type declarations for the ESM entry point of @silvia-odwyer/photon-node
|
|
||||||
// The ESM entry exports the same API but uses WASM imports that bundlers can embed
|
|
||||||
declare module "@silvia-odwyer/photon-node/photon_rs_bg.js" {
|
|
||||||
export * from "@silvia-odwyer/photon-node";
|
|
||||||
}
|
|
||||||
59
packages/coding-agent/src/utils/photon.ts
Normal file
59
packages/coding-agent/src/utils/photon.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* Photon image processing wrapper.
|
||||||
|
*
|
||||||
|
* This module provides a unified interface to @silvia-odwyer/photon-node that works in:
|
||||||
|
* 1. Node.js (development, npm run build)
|
||||||
|
* 2. Bun compiled binaries (standalone distribution)
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Re-export types from the main package
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the photon module, loading it lazily on first access.
|
||||||
|
* Returns null if loading fails (e.g., in broken Bun binary).
|
||||||
|
*/
|
||||||
|
export function getPhoton(): typeof import("@silvia-odwyer/photon-node") | null {
|
||||||
|
if (loadAttempted) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -5,5 +5,5 @@
|
||||||
"rootDir": "./src"
|
"rootDir": "./src"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist", "**/*.d.ts", "src/**/*.d.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue