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
This commit is contained in:
roboomp
2026-06-22 06:05:38 +00:00
parent eb88ecd7bb
commit ce7a4e4562
2 changed files with 29 additions and 1 deletions
@@ -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. */
@@ -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: [],