mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(media): tolerate missing image optimizer for in-limit images
This commit is contained in:
committed by
Peter Steinberger
parent
361737d1f1
commit
ac09ec00e8
@@ -163,6 +163,53 @@ describe("loadWebMedia", () => {
|
||||
);
|
||||
});
|
||||
|
||||
async function withUnavailableImageOptimizer<T>(fn: () => Promise<T>): Promise<T> {
|
||||
vi.resetModules();
|
||||
vi.doMock("./image-ops.js", () => ({
|
||||
convertHeicToJpeg: vi.fn(async (buffer: Buffer) => buffer),
|
||||
hasAlphaChannel: vi.fn(async () => {
|
||||
throw new Error(
|
||||
"Optional dependency sharp is required for image attachment processing | Cannot find package 'sharp' imported from image-ops.js",
|
||||
);
|
||||
}),
|
||||
optimizeImageToPng: vi.fn(async () => {
|
||||
throw new Error("should not optimize png");
|
||||
}),
|
||||
resizeToJpeg: vi.fn(async () => {
|
||||
throw new Error("should not resize jpeg");
|
||||
}),
|
||||
}));
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
vi.doUnmock("./image-ops.js");
|
||||
vi.resetModules();
|
||||
}
|
||||
}
|
||||
|
||||
it("sends an in-limit original image when optional sharp optimization is unavailable", async () => {
|
||||
await withUnavailableImageOptimizer(async () => {
|
||||
const { loadWebMedia: loadWebMediaWithMissingOptimizer } = await import("./web-media.js");
|
||||
const result = await loadWebMediaWithMissingOptimizer(
|
||||
tinyPngFile,
|
||||
createLocalWebMediaOptions(),
|
||||
);
|
||||
expect(result.kind).toBe("image");
|
||||
expect(result.contentType).toBe("image/png");
|
||||
expect(result.fileName).toBe("tiny.png");
|
||||
expect(result.buffer.equals(Buffer.from(TINY_PNG_BASE64, "base64"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not bypass the size cap when optional sharp optimization is unavailable", async () => {
|
||||
await withUnavailableImageOptimizer(async () => {
|
||||
const { loadWebMedia: loadWebMediaWithMissingOptimizer } = await import("./web-media.js");
|
||||
await expect(
|
||||
loadWebMediaWithMissingOptimizer(tinyPngFile, { maxBytes: 8, localRoots: [fixtureRoot] }),
|
||||
).rejects.toThrow(/Optional dependency sharp is required/);
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves relative local media paths against the provided workspace directory", async () => {
|
||||
const result = await loadWebMedia("chart.png", {
|
||||
maxBytes: 1024 * 1024,
|
||||
|
||||
@@ -205,6 +205,23 @@ function formatCapReduce(label: string, cap: number, size: number): string {
|
||||
return `${label} could not be reduced below ${formatMb(cap, 0)}MB (got ${formatMb(size)}MB)`;
|
||||
}
|
||||
|
||||
function isOptionalImageOptimizerUnavailable(err: unknown): boolean {
|
||||
const messages: string[] = [];
|
||||
let current: unknown = err;
|
||||
while (current instanceof Error) {
|
||||
messages.push(current.message);
|
||||
current = current.cause;
|
||||
}
|
||||
const detail = messages.join("\n").toLowerCase();
|
||||
return (
|
||||
detail.includes("optional dependency sharp is required") ||
|
||||
detail.includes("cannot find package 'sharp'") ||
|
||||
detail.includes('cannot find package "sharp"') ||
|
||||
detail.includes("cannot find module 'sharp'") ||
|
||||
detail.includes('cannot find module "sharp"')
|
||||
);
|
||||
}
|
||||
|
||||
function isHeicSource(opts: { contentType?: string; fileName?: string }): boolean {
|
||||
if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim())) {
|
||||
return true;
|
||||
@@ -392,7 +409,25 @@ async function loadWebMediaInternal(
|
||||
meta?: { contentType?: string; fileName?: string },
|
||||
) => {
|
||||
const originalSize = buffer.length;
|
||||
const optimized = await optimizeImageWithFallback({ buffer, cap, meta });
|
||||
let optimized: OptimizedImage;
|
||||
try {
|
||||
optimized = await optimizeImageWithFallback({ buffer, cap, meta });
|
||||
} catch (err) {
|
||||
if (isOptionalImageOptimizerUnavailable(err) && buffer.length <= cap) {
|
||||
if (shouldLogVerbose()) {
|
||||
logVerbose(
|
||||
`Image optimizer unavailable; sending original ${formatMb(buffer.length)}MB media without optimization`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
buffer,
|
||||
contentType: meta?.contentType,
|
||||
kind: "image" as const,
|
||||
fileName: meta?.fileName,
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
logOptimizedImage({ originalSize, optimized });
|
||||
|
||||
if (optimized.buffer.length > cap) {
|
||||
|
||||
Reference in New Issue
Block a user