Fix budget projection truncation accounting

This commit is contained in:
Robin Newhouse
2026-06-26 19:03:43 -07:00
parent a86ae3d6c2
commit a450ddc509
2 changed files with 53 additions and 11 deletions
@@ -291,4 +291,28 @@ describe("buildBudgetProjection", () => {
]),
);
});
it("clears thinking blocks after the message text budget is exhausted", () => {
const result = buildBudgetProjection({
messages: [
{ role: "user", content: "latest typed prompt" },
{
role: "assistant",
content: [
{ type: "text", text: "a".repeat(1_000) },
{ type: "thinking", thinking: "b".repeat(1_000) },
],
},
],
targetTokens: 190,
policyIntent: "agentic_summary",
estimateMessageTokens: estimateChars,
});
const assistant = result.messages.find(
(message) => message.role === "assistant",
);
expect(JSON.stringify(assistant)).not.toContain("b".repeat(100));
});
});
@@ -260,6 +260,21 @@ function truncateToolResultContent(
});
}
function toolResultTextLength(content: ToolResultContent["content"]): number {
if (typeof content === "string") {
return content.length;
}
return content.reduce((total, block) => {
if (block.type === "text") {
return total + block.text.length;
}
if (block.type === "file") {
return total + block.content.length;
}
return total;
}, 0);
}
function truncateMessageText(
message: MessageWithMetadata,
maxChars: number,
@@ -269,17 +284,20 @@ function truncateMessageText(
}
let remaining = maxChars;
return {
...message,
content: message.content.map((block) => {
if (remaining <= 0) {
if (block.type === "text") {
return { ...block, text: "" };
}
if (block.type === "file") {
return { ...block, content: "" };
}
return block;
...message,
content: message.content.map((block) => {
if (remaining <= 0) {
if (block.type === "text") {
return { ...block, text: "" };
}
if (block.type === "file") {
return { ...block, content: "" };
}
if (block.type === "thinking") {
return { ...block, thinking: "" };
}
return block;
}
if (block.type === "text") {
const text = truncateText(block.text, remaining);
remaining -= text.length;
@@ -297,7 +315,7 @@ function truncateMessageText(
}
if (block.type === "tool_result") {
const content = truncateToolResultContent(block.content, remaining);
remaining -= safeJsonSize(content);
remaining -= toolResultTextLength(content);
return { ...block, content };
}
return block;