mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(agents): make image resize logs single-line with size
This commit is contained in:
@@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/Image: collapse resize diagnostics to one line per image and include visible pixel/byte size details in the log message for faster triage.
|
||||
- Agents/Subagents: preemptively guard accumulated tool-result context before model calls by truncating oversized outputs and compacting oldest tool-result messages to avoid context-window overflow crashes. Thanks @tyler6204.
|
||||
- Agents/Subagents: add explicit subagent guidance to recover from `[compacted: tool output removed to free context]` / `[truncated: output exceeded context limit]` markers by re-reading with smaller chunks instead of full-file `cat`. Thanks @tyler6204.
|
||||
- Agents/Tools: make `read` auto-page across chunks (when no explicit `limit` is provided) and scale its per-call output budget from model `contextWindow`, so larger contexts can read more before context guards kick in. Thanks @tyler6204.
|
||||
|
||||
@@ -55,6 +55,16 @@ function inferMimeTypeFromBase64(base64: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function formatBytesShort(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes < 1024) {
|
||||
return `${Math.max(0, Math.round(bytes))}B`;
|
||||
}
|
||||
if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)}KB`;
|
||||
}
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
|
||||
}
|
||||
|
||||
async function resizeImageBase64IfNeeded(params: {
|
||||
base64: string;
|
||||
mimeType: string;
|
||||
@@ -74,6 +84,8 @@ async function resizeImageBase64IfNeeded(params: {
|
||||
const height = meta?.height;
|
||||
const overBytes = buf.byteLength > params.maxBytes;
|
||||
const hasDimensions = typeof width === "number" && typeof height === "number";
|
||||
const overDimensions =
|
||||
hasDimensions && (width > params.maxDimensionPx || height > params.maxDimensionPx);
|
||||
if (
|
||||
hasDimensions &&
|
||||
!overBytes &&
|
||||
@@ -88,18 +100,6 @@ async function resizeImageBase64IfNeeded(params: {
|
||||
height,
|
||||
};
|
||||
}
|
||||
if (
|
||||
hasDimensions &&
|
||||
(width > params.maxDimensionPx || height > params.maxDimensionPx || overBytes)
|
||||
) {
|
||||
log.warn("Image exceeds limits; resizing", {
|
||||
label: params.label,
|
||||
width,
|
||||
height,
|
||||
maxDimensionPx: params.maxDimensionPx,
|
||||
maxBytes: params.maxBytes,
|
||||
});
|
||||
}
|
||||
|
||||
const qualities = [85, 75, 65, 55, 45, 35];
|
||||
const maxDim = hasDimensions ? Math.max(width ?? 0, height ?? 0) : params.maxDimensionPx;
|
||||
@@ -122,17 +122,33 @@ async function resizeImageBase64IfNeeded(params: {
|
||||
smallest = { buffer: out, size: out.byteLength };
|
||||
}
|
||||
if (out.byteLength <= params.maxBytes) {
|
||||
log.info("Image resized", {
|
||||
label: params.label,
|
||||
width,
|
||||
height,
|
||||
maxDimensionPx: params.maxDimensionPx,
|
||||
maxBytes: params.maxBytes,
|
||||
originalBytes: buf.byteLength,
|
||||
resizedBytes: out.byteLength,
|
||||
quality,
|
||||
side,
|
||||
});
|
||||
const sourcePixels =
|
||||
typeof width === "number" && typeof height === "number"
|
||||
? `${width}x${height}px`
|
||||
: "unknown";
|
||||
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}%)`,
|
||||
{
|
||||
label: params.label,
|
||||
sourceMimeType: params.mimeType,
|
||||
sourceWidth: width,
|
||||
sourceHeight: height,
|
||||
sourceBytes: buf.byteLength,
|
||||
maxBytes: params.maxBytes,
|
||||
maxDimensionPx: params.maxDimensionPx,
|
||||
triggerOverBytes: overBytes,
|
||||
triggerOverDimensions: overDimensions,
|
||||
outputMimeType: "image/jpeg",
|
||||
outputBytes: out.byteLength,
|
||||
outputQuality: quality,
|
||||
outputMaxSide: side,
|
||||
byteReductionPct,
|
||||
},
|
||||
);
|
||||
return {
|
||||
base64: out.toString("base64"),
|
||||
mimeType: "image/jpeg",
|
||||
@@ -147,6 +163,23 @@ async function resizeImageBase64IfNeeded(params: {
|
||||
const best = smallest?.buffer ?? buf;
|
||||
const maxMb = (params.maxBytes / (1024 * 1024)).toFixed(0);
|
||||
const gotMb = (best.byteLength / (1024 * 1024)).toFixed(2);
|
||||
const sourcePixels =
|
||||
typeof width === "number" && typeof height === "number" ? `${width}x${height}px` : "unknown";
|
||||
log.warn(
|
||||
`Image resize failed to fit limits: ${sourcePixels} best=${formatBytesShort(best.byteLength)} limit=${formatBytesShort(params.maxBytes)}`,
|
||||
{
|
||||
label: params.label,
|
||||
sourceMimeType: params.mimeType,
|
||||
sourceWidth: width,
|
||||
sourceHeight: height,
|
||||
sourceBytes: buf.byteLength,
|
||||
maxDimensionPx: params.maxDimensionPx,
|
||||
maxBytes: params.maxBytes,
|
||||
smallestCandidateBytes: best.byteLength,
|
||||
triggerOverBytes: overBytes,
|
||||
triggerOverDimensions: overDimensions,
|
||||
},
|
||||
);
|
||||
throw new Error(`Image could not be reduced below ${maxMb}MB (got ${gotMb}MB)`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user