fix(coding-agent): suppress repeated Anthropic thinking drop notices

Fixes #9391
This commit is contained in:
David Brailovsky 2026-09-18 09:58:14 +02:00
parent ea9e093516
commit 13784598d2
2 changed files with 59 additions and 24 deletions

View file

@ -3312,7 +3312,7 @@ export class InteractiveMode {
for (const [, component] of this.pendingTools.entries()) {
component.setArgsComplete();
}
this.maybeShowAssistantDiagnostics(this.streamingMessage);
this.maybeShowThinkingDropNotice(this.streamingMessage);
this.maybeShowCacheMissNotice(this.streamingMessage);
}
this.streamingComponent = undefined;
@ -3757,7 +3757,6 @@ export class InteractiveMode {
}
}
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
this.maybeShowAssistantDiagnostics(message);
const miss = cacheMisses.get(message);
if (miss) this.addCacheMissNotice(miss);
}
@ -3820,28 +3819,46 @@ export class InteractiveMode {
);
}
private maybeShowAssistantDiagnostics(message: AssistantMessage): void {
if (!this.settingsManager.getShowCacheMissNotices()) return;
private static countDroppedThinkingBlocks(message: AssistantMessage): number {
let count = 0;
for (const diagnostic of message.diagnostics ?? []) {
if (diagnostic.type !== "anthropic_input_transformations") continue;
const transformations = diagnostic.details?.transformations;
if (!Array.isArray(transformations)) continue;
const droppedCount = transformations.filter(
count += transformations.filter(
(transformation) =>
typeof transformation === "object" &&
transformation !== null &&
(transformation as Record<string, unknown>).type === "thinking_dropped",
).length;
if (droppedCount === 0) continue;
const noun = droppedCount === 1 ? "1 thinking block" : `${droppedCount} thinking blocks`;
this.chatContainer.addChild(new Spacer(1));
this.chatContainer.addChild(
new Text(theme.fg("warning", `Anthropic dropped ${noun} (details in session)`), 1, 0),
);
}
return count;
}
private maybeShowThinkingDropNotice(message: AssistantMessage): void {
if (!this.settingsManager.getShowCacheMissNotices()) return;
const droppedCount = InteractiveMode.countDroppedThinkingBlocks(message);
if (droppedCount === 0) return;
let previousDroppedCount = 0;
// message_end reaches the UI before the current message is persisted,
// so the branch's last assistant message is the previous response.
const branch = this.sessionManager.getBranch();
for (let i = branch.length - 1; i >= 0; i--) {
const entry = branch[i];
if (entry.type === "message" && entry.message.role === "assistant") {
previousDroppedCount = InteractiveMode.countDroppedThinkingBlocks(entry.message);
break;
}
}
if (droppedCount <= previousDroppedCount) return;
const noun = droppedCount === 1 ? "thinking block" : "thinking blocks";
this.chatContainer.addChild(new Spacer(1));
this.chatContainer.addChild(
new Text(theme.fg("warning", `Anthropic dropped ${droppedCount} ${noun} (details in session)`), 1, 0),
);
}
/**

View file

@ -48,30 +48,48 @@ const message: AssistantMessage = {
],
};
type NoticeContext = {
chatContainer: Container;
settingsManager: { getShowCacheMissNotices(): boolean };
sessionManager: { getBranch(): Array<{ type: "message"; message: AssistantMessage }> };
};
const maybeShowThinkingDropNotice = Reflect.get(InteractiveMode.prototype, "maybeShowThinkingDropNotice") as (
this: NoticeContext,
message: AssistantMessage,
) => void;
describe("InteractiveMode assistant diagnostics", () => {
test("shows Anthropic thinking drops when cache miss notices are enabled", () => {
const maybeShowAssistantDiagnostics = Reflect.get(InteractiveMode.prototype, "maybeShowAssistantDiagnostics") as (
this: {
chatContainer: Container;
settingsManager: { getShowCacheMissNotices(): boolean };
},
message: AssistantMessage,
) => void;
initTheme("dark");
const enabled = {
chatContainer: new Container(),
settingsManager: { getShowCacheMissNotices: () => true },
sessionManager: { getBranch: () => [] },
};
maybeShowAssistantDiagnostics.call(enabled, message);
maybeShowThinkingDropNotice.call(enabled, message);
const output = stripAnsi(enabled.chatContainer.render(120).join("\n"));
expect(output).toContain("Anthropic dropped 3 thinking blocks (details in session)");
const disabled = {
chatContainer: new Container(),
settingsManager: { getShowCacheMissNotices: () => false },
sessionManager: { getBranch: () => [] },
};
maybeShowAssistantDiagnostics.call(disabled, message);
maybeShowThinkingDropNotice.call(disabled, message);
expect(disabled.chatContainer.children).toHaveLength(0);
});
test("does not repeat unchanged Anthropic thinking drops", () => {
initTheme("dark");
const context = {
chatContainer: new Container(),
settingsManager: { getShowCacheMissNotices: () => true },
sessionManager: { getBranch: () => [{ type: "message" as const, message }] },
};
maybeShowThinkingDropNotice.call(context, { ...message, timestamp: 2 });
expect(context.chatContainer.children).toHaveLength(0);
});
});