From 13784598d27491e70ddcb549859f722417001786 Mon Sep 17 00:00:00 2001 From: David Brailovsky Date: Fri, 18 Sep 2026 09:58:14 +0200 Subject: [PATCH] fix(coding-agent): suppress repeated Anthropic thinking drop notices Fixes #9391 --- .../src/modes/interactive/interactive-mode.ts | 45 +++++++++++++------ ...ractive-mode-assistant-diagnostics.test.ts | 38 +++++++++++----- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 3fd3f08ab..beebca588 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -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).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), + ); } /** diff --git a/packages/coding-agent/test/interactive-mode-assistant-diagnostics.test.ts b/packages/coding-agent/test/interactive-mode-assistant-diagnostics.test.ts index e890cacf4..48732b52d 100644 --- a/packages/coding-agent/test/interactive-mode-assistant-diagnostics.test.ts +++ b/packages/coding-agent/test/interactive-mode-assistant-diagnostics.test.ts @@ -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); + }); });