Skip to content

Commit 3f81e4f

Browse files
committed
fix(compaction): drop thinking when last assistant has no tools
1 parent 6ab0446 commit 3f81e4f

2 files changed

Lines changed: 95 additions & 7 deletions

File tree

‎src/context-compactor.test.ts‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,97 @@ describe("CL-9489 zero-verbatim fold", () => {
14461446
).toBe(true);
14471447
});
14481448

1449+
test("earlier live tool-use thinking is dropped when the last assistant is text-only", async () => {
1450+
const toolThinking = "tool-use-thinking-block";
1451+
const lastThinking = "text-only-thinking-block";
1452+
const filler = "x".repeat(1_500);
1453+
const turns: ConversationTurn[] = [];
1454+
for (let i = 0; i < 6; i++) {
1455+
turns.push(
1456+
makeTurn({
1457+
role: "user",
1458+
content: [{ type: "text", text: `dummy ask ${i} ${filler}` }],
1459+
}),
1460+
);
1461+
turns.push(
1462+
makeTurn({
1463+
role: "assistant",
1464+
content: [{ type: "text", text: `dummy reply ${i} ${filler}` }],
1465+
}),
1466+
);
1467+
}
1468+
turns.push(
1469+
makeTurn({
1470+
role: "assistant",
1471+
content: [
1472+
{
1473+
type: "thinking",
1474+
thinking: toolThinking,
1475+
signature: "sig-tool-use",
1476+
},
1477+
{
1478+
type: "tool_call",
1479+
id: "c-earlier",
1480+
name: "read_file",
1481+
arguments: { path: "src/app.ts" },
1482+
},
1483+
],
1484+
}),
1485+
);
1486+
turns.push(
1487+
makeTurn({
1488+
role: "user",
1489+
content: [
1490+
{
1491+
type: "tool_result",
1492+
callId: "c-earlier",
1493+
content: [{ type: "text", text: "export const app = 1;\n" }],
1494+
},
1495+
],
1496+
}),
1497+
);
1498+
turns.push(
1499+
makeTurn({
1500+
role: "assistant",
1501+
content: [
1502+
{
1503+
type: "thinking",
1504+
thinking: lastThinking,
1505+
signature: "sig-text-only",
1506+
},
1507+
{ type: "text", text: "here is the answer" },
1508+
],
1509+
}),
1510+
);
1511+
1512+
const result = await createPruningCompactor({
1513+
summarize: async () =>
1514+
"Folded dummy pairs. Next: resume after a text reply.",
1515+
}).apply(turns, mockStrategyCtx);
1516+
1517+
const liveTypes = result.output.flatMap((t) =>
1518+
t.content.map((b) => b.type),
1519+
);
1520+
expect(liveTypes).not.toContain("thinking");
1521+
expect(liveTypes).not.toContain("redacted_thinking");
1522+
expect(
1523+
result.output.some(
1524+
(t) =>
1525+
t.role === "assistant" &&
1526+
t.content.some((b) => b.type === "tool_call" && b.id === "c-earlier"),
1527+
),
1528+
).toBe(true);
1529+
expect(
1530+
result.output.some(
1531+
(t) =>
1532+
t.role === "assistant" &&
1533+
t.content.some(
1534+
(b) => b.type === "text" && b.text === "here is the answer",
1535+
),
1536+
),
1537+
).toBe(true);
1538+
});
1539+
14491540
test("a thinking-only live turn is replaced with THINKING_ONLY_OMITTED", async () => {
14501541
const thinking = "t".repeat(20_000);
14511542
const filler = "x".repeat(1_500);

‎src/session/compactor.ts‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -777,13 +777,10 @@ function lastAssistantToolCallIndex(
777777
): number | undefined {
778778
for (let i = turns.length - 1; i >= 0; i--) {
779779
const turn = turns[i];
780-
if (
781-
turn !== undefined &&
782-
turn.role === "assistant" &&
783-
turn.content.some((block) => block.type === "tool_call")
784-
) {
785-
return i;
786-
}
780+
if (turn === undefined || turn.role !== "assistant") continue;
781+
return turn.content.some((block) => block.type === "tool_call")
782+
? i
783+
: undefined;
787784
}
788785
return undefined;
789786
}

0 commit comments

Comments
 (0)