mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 23:24:30 +00:00
fix(agents): include filenames in image resize logs
This commit is contained in:
@@ -70,12 +70,87 @@ function formatBytesShort(bytes: number): string {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
|
||||
}
|
||||
|
||||
function parseMediaPathFromText(text: string): string | undefined {
|
||||
for (const line of text.split(/\r?\n/u)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed.startsWith("MEDIA:")) {
|
||||
continue;
|
||||
}
|
||||
const raw = trimmed.slice("MEDIA:".length).trim();
|
||||
if (!raw) {
|
||||
continue;
|
||||
}
|
||||
const backtickWrapped = raw.match(/^`([^`]+)`$/u);
|
||||
return (backtickWrapped?.[1] ?? raw).trim();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function fileNameFromPathLike(pathLike: string): string | undefined {
|
||||
const value = pathLike.trim();
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(value);
|
||||
const candidate = url.pathname.split("/").filter(Boolean).at(-1);
|
||||
return candidate && candidate.length > 0 ? candidate : undefined;
|
||||
} catch {
|
||||
// Not a URL; continue with path-like parsing.
|
||||
}
|
||||
|
||||
const normalized = value.replaceAll("\\", "/");
|
||||
const candidate = normalized.split("/").filter(Boolean).at(-1);
|
||||
return candidate && candidate.length > 0 ? candidate : undefined;
|
||||
}
|
||||
|
||||
function inferImageFileName(params: {
|
||||
block: ImageContentBlock;
|
||||
label?: string;
|
||||
mediaPathHint?: string;
|
||||
}): string | undefined {
|
||||
const rec = params.block as unknown as Record<string, unknown>;
|
||||
const explicitKeys = ["fileName", "filename", "path", "url"] as const;
|
||||
for (const key of explicitKeys) {
|
||||
const raw = rec[key];
|
||||
if (typeof raw !== "string" || raw.trim().length === 0) {
|
||||
continue;
|
||||
}
|
||||
const candidate = fileNameFromPathLike(raw);
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof rec.name === "string" && rec.name.trim().length > 0) {
|
||||
return rec.name.trim();
|
||||
}
|
||||
|
||||
if (params.mediaPathHint) {
|
||||
const candidate = fileNameFromPathLike(params.mediaPathHint);
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof params.label === "string" && params.label.startsWith("read:")) {
|
||||
const candidate = fileNameFromPathLike(params.label.slice("read:".length));
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function resizeImageBase64IfNeeded(params: {
|
||||
base64: string;
|
||||
mimeType: string;
|
||||
maxDimensionPx: number;
|
||||
maxBytes: number;
|
||||
label?: string;
|
||||
fileName?: string;
|
||||
}): Promise<{
|
||||
base64: string;
|
||||
mimeType: string;
|
||||
@@ -127,14 +202,18 @@ async function resizeImageBase64IfNeeded(params: {
|
||||
typeof width === "number" && typeof height === "number"
|
||||
? `${width}x${height}px`
|
||||
: "unknown";
|
||||
const sourceWithFile = params.fileName
|
||||
? `${params.fileName} ${sourcePixels}`
|
||||
: sourcePixels;
|
||||
const byteReductionPct =
|
||||
buf.byteLength > 0
|
||||
? Number((((buf.byteLength - out.byteLength) / buf.byteLength) * 100).toFixed(1))
|
||||
: 0;
|
||||
log.info(
|
||||
`Image resized to fit limits: ${sourcePixels} ${formatBytesShort(buf.byteLength)} -> ${formatBytesShort(out.byteLength)} (-${byteReductionPct}%)`,
|
||||
`Image resized to fit limits: ${sourceWithFile} ${formatBytesShort(buf.byteLength)} -> ${formatBytesShort(out.byteLength)} (-${byteReductionPct}%)`,
|
||||
{
|
||||
label: params.label,
|
||||
fileName: params.fileName,
|
||||
sourceMimeType: params.mimeType,
|
||||
sourceWidth: width,
|
||||
sourceHeight: height,
|
||||
@@ -166,10 +245,12 @@ async function resizeImageBase64IfNeeded(params: {
|
||||
const gotMb = (best.byteLength / (1024 * 1024)).toFixed(2);
|
||||
const sourcePixels =
|
||||
typeof width === "number" && typeof height === "number" ? `${width}x${height}px` : "unknown";
|
||||
const sourceWithFile = params.fileName ? `${params.fileName} ${sourcePixels}` : sourcePixels;
|
||||
log.warn(
|
||||
`Image resize failed to fit limits: ${sourcePixels} best=${formatBytesShort(best.byteLength)} limit=${formatBytesShort(params.maxBytes)}`,
|
||||
`Image resize failed to fit limits: ${sourceWithFile} best=${formatBytesShort(best.byteLength)} limit=${formatBytesShort(params.maxBytes)}`,
|
||||
{
|
||||
label: params.label,
|
||||
fileName: params.fileName,
|
||||
sourceMimeType: params.mimeType,
|
||||
sourceWidth: width,
|
||||
sourceHeight: height,
|
||||
@@ -192,8 +273,16 @@ export async function sanitizeContentBlocksImages(
|
||||
const maxDimensionPx = Math.max(opts.maxDimensionPx ?? MAX_IMAGE_DIMENSION_PX, 1);
|
||||
const maxBytes = Math.max(opts.maxBytes ?? MAX_IMAGE_BYTES, 1);
|
||||
const out: ToolContentBlock[] = [];
|
||||
let mediaPathHint: string | undefined;
|
||||
|
||||
for (const block of blocks) {
|
||||
if (isTextBlock(block)) {
|
||||
const mediaPath = parseMediaPathFromText(block.text);
|
||||
if (mediaPath) {
|
||||
mediaPathHint = mediaPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isImageBlock(block)) {
|
||||
out.push(block);
|
||||
continue;
|
||||
@@ -211,12 +300,14 @@ export async function sanitizeContentBlocksImages(
|
||||
try {
|
||||
const inferredMimeType = inferMimeTypeFromBase64(data);
|
||||
const mimeType = inferredMimeType ?? block.mimeType;
|
||||
const fileName = inferImageFileName({ block, label, mediaPathHint });
|
||||
const resized = await resizeImageBase64IfNeeded({
|
||||
base64: data,
|
||||
mimeType,
|
||||
maxDimensionPx,
|
||||
maxBytes,
|
||||
label,
|
||||
fileName,
|
||||
});
|
||||
out.push({
|
||||
...block,
|
||||
|
||||
Reference in New Issue
Block a user