From ce7a4e45625773c11f14e243123e63e3aecd35ff Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 22 Jun 2026 06:05:38 +0000 Subject: [PATCH] fix(agent): preserved clamped tool image results Added a textual omission marker when provider image clamping removes every block from a successful tool result, keeping the serialized tool_result meaningful and protocol-safe.\n\nFixes #3230 --- .../src/session/provider-image-budget.ts | 8 ++++++- .../session/provider-image-budget.test.ts | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/coding-agent/src/session/provider-image-budget.ts b/packages/coding-agent/src/session/provider-image-budget.ts index 1840ac4a9..ade57c48c 100644 --- a/packages/coding-agent/src/session/provider-image-budget.ts +++ b/packages/coding-agent/src/session/provider-image-budget.ts @@ -9,6 +9,11 @@ import type { } from "@oh-my-pi/pi-ai"; import { providerImageBudget } from "@oh-my-pi/snapcompact"; +const TOOL_RESULT_IMAGE_OMISSION: TextContent = { + type: "text", + text: "[image omitted: provider image limit]", +}; + function countImages(context: Context): number { let count = 0; for (const message of context.messages) { @@ -52,7 +57,8 @@ function clampDeveloperMessage(message: DeveloperMessage, state: { remainingDrop function clampToolResultMessage(message: ToolResultMessage, state: { remainingDrops: number }): ToolResultMessage { if (state.remainingDrops <= 0) return message; const content = clampContent(message.content, state); - return content ? { ...message, content } : message; + if (!content) return message; + return { ...message, content: content.length > 0 ? content : [TOOL_RESULT_IMAGE_OMISSION] }; } /** Drops oldest transient image blocks so outgoing vision requests fit the active provider's image cap. */ diff --git a/packages/coding-agent/test/session/provider-image-budget.test.ts b/packages/coding-agent/test/session/provider-image-budget.test.ts index c50cef215..eefafd2b6 100644 --- a/packages/coding-agent/test/session/provider-image-budget.test.ts +++ b/packages/coding-agent/test/session/provider-image-budget.test.ts @@ -69,6 +69,28 @@ describe("provider context image budgets", () => { expect(imageData(context)).toEqual(Array.from({ length: 31 }, (_, index) => `image-${index}`)); }); + it("keeps image-only tool results meaningful when every image block is dropped", () => { + const context: Context = { + systemPrompt: [], + tools: [], + messages: Array.from({ length: 11 }, (_, index) => ({ + role: "toolResult", + toolCallId: `call-${index}`, + toolName: "inspect_image", + content: [image(`image-${index}`)], + isError: false, + timestamp: index, + })), + }; + + const clamped = clampProviderContextImages(context, UMANS_MODEL); + const firstMessage = clamped.messages[0]; + + expect(imageData(clamped)).toEqual(Array.from({ length: 10 }, (_, index) => `image-${index + 1}`)); + expect(firstMessage?.role).toBe("toolResult"); + expect(firstMessage?.content).toEqual([text("[image omitted: provider image limit]")]); + }); + it("preserves context identity when the provider cap is not exceeded", () => { const context: Context = { systemPrompt: [],