diff --git a/CHANGELOG.md b/CHANGELOG.md index b6c314ee9a1..94c768873cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/Image tool: cap image-analysis completion `maxTokens` by model capability (`min(4096, model.maxTokens)`) to avoid over-limit provider failures while still preventing truncation. (#11770) Thanks @detecti1. - Security/Canvas: serve A2UI assets via the shared safe-open path (`openFileWithinRoot`) to close traversal/TOCTOU gaps, with traversal and symlink regression coverage. (#10525) Thanks @abdelsfane. - Security/Gateway: breaking default-behavior change - canvas IP-based auth fallback now only accepts machine-scoped addresses (RFC1918, link-local, ULA IPv6, CGNAT); public-source IP matches now require bearer token auth. (#14661) Thanks @sumleo. - Security/WhatsApp: enforce `0o600` on `creds.json` and `creds.json.bak` on save/backup/restore paths to reduce credential file exposure. (#10529) Thanks @abdelsfane. diff --git a/src/agents/tools/image-tool.e2e.test.ts b/src/agents/tools/image-tool.e2e.test.ts index 2a9a1815337..e2236e73f8c 100644 --- a/src/agents/tools/image-tool.e2e.test.ts +++ b/src/agents/tools/image-tool.e2e.test.ts @@ -346,6 +346,18 @@ describe("image tool MiniMax VLM routing", () => { }); describe("image tool response validation", () => { + it("caps image-tool max tokens by model capability", () => { + expect(__testing.resolveImageToolMaxTokens(4000)).toBe(4000); + }); + + it("keeps requested image-tool max tokens when model capability is higher", () => { + expect(__testing.resolveImageToolMaxTokens(8192)).toBe(4096); + }); + + it("falls back to requested image-tool max tokens when model capability is missing", () => { + expect(__testing.resolveImageToolMaxTokens(undefined)).toBe(4096); + }); + it("rejects image-model responses with no final text", () => { expect(() => __testing.coerceImageAssistantText({ diff --git a/src/agents/tools/image-tool.ts b/src/agents/tools/image-tool.ts index 9b08a0d19ec..45889c00005 100644 --- a/src/agents/tools/image-tool.ts +++ b/src/agents/tools/image-tool.ts @@ -29,8 +29,20 @@ const ANTHROPIC_IMAGE_FALLBACK = "anthropic/claude-opus-4-5"; export const __testing = { decodeDataUrl, coerceImageAssistantText, + resolveImageToolMaxTokens, } as const; +function resolveImageToolMaxTokens(modelMaxTokens: number | undefined, requestedMaxTokens = 4096) { + if ( + typeof modelMaxTokens !== "number" || + !Number.isFinite(modelMaxTokens) || + modelMaxTokens <= 0 + ) { + return requestedMaxTokens; + } + return Math.min(requestedMaxTokens, modelMaxTokens); +} + function resolveDefaultModelRef(cfg?: OpenClawConfig): { provider: string; model: string; @@ -287,7 +299,7 @@ async function runImagePrompt(params: { const context = buildImageContext(params.prompt, params.base64, params.mimeType); const message = await complete(model, context, { apiKey, - maxTokens: 512, + maxTokens: resolveImageToolMaxTokens(model.maxTokens), }); const text = coerceImageAssistantText({ message, diff --git a/src/hooks/config.ts b/src/hooks/config.ts index 0d9176a152d..2572a8003a5 100644 --- a/src/hooks/config.ts +++ b/src/hooks/config.ts @@ -70,12 +70,7 @@ export function hasBinary(bin: string): boolean { const parts = pathEnv.split(path.delimiter).filter(Boolean); const extensions = process.platform === "win32" - ? [ - "", - ...(process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM") - .split(";") - .filter(Boolean), - ] + ? ["", ...(process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] : [""]; for (const part of parts) { for (const ext of extensions) {