ce7a4e4562
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
110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import type { Context, ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
|
import { clampProviderContextImages } from "@oh-my-pi/pi-coding-agent/session/provider-image-budget";
|
|
|
|
const UMANS_MODEL = buildModel({
|
|
id: "umans-glm-5.2",
|
|
name: "umans-glm-5.2",
|
|
api: "anthropic-messages",
|
|
provider: "umans",
|
|
baseUrl: "https://api.code.umans.ai",
|
|
reasoning: true,
|
|
input: ["text", "image"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 128000,
|
|
maxTokens: 4096,
|
|
});
|
|
|
|
function image(data: string): ImageContent {
|
|
return { type: "image", data, mimeType: "image/png" };
|
|
}
|
|
|
|
function text(value: string): TextContent {
|
|
return { type: "text", text: value };
|
|
}
|
|
|
|
function imageData(context: Context): string[] {
|
|
const data: string[] = [];
|
|
for (const message of context.messages) {
|
|
if (!Array.isArray(message.content)) continue;
|
|
for (const part of message.content) {
|
|
if (part.type === "image") data.push(part.data);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function textData(context: Context): string[] {
|
|
const data: string[] = [];
|
|
for (const message of context.messages) {
|
|
if (typeof message.content === "string") {
|
|
data.push(message.content);
|
|
continue;
|
|
}
|
|
for (const part of message.content) {
|
|
if (part.type === "text") data.push(part.text);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
describe("provider context image budgets", () => {
|
|
it("drops oldest images above the active provider cap while preserving text", () => {
|
|
const context: Context = {
|
|
systemPrompt: ["system"],
|
|
tools: [],
|
|
messages: Array.from({ length: 31 }, (_, index) => ({
|
|
role: "user",
|
|
content: [text(`text-${index}`), image(`image-${index}`)],
|
|
timestamp: index,
|
|
})),
|
|
};
|
|
|
|
const clamped = clampProviderContextImages(context, UMANS_MODEL);
|
|
|
|
expect(imageData(clamped)).toEqual(Array.from({ length: 10 }, (_, index) => `image-${index + 21}`));
|
|
expect(textData(clamped)).toEqual(Array.from({ length: 31 }, (_, index) => `text-${index}`));
|
|
expect(clamped).not.toBe(context);
|
|
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: [],
|
|
tools: [],
|
|
messages: [
|
|
{
|
|
role: "user",
|
|
content: [text("ok"), ...Array.from({ length: 10 }, (_, index) => image(`image-${index}`))],
|
|
timestamp: 1,
|
|
},
|
|
],
|
|
};
|
|
|
|
expect(clampProviderContextImages(context, UMANS_MODEL)).toBe(context);
|
|
});
|
|
});
|