fix(kiro): strip leaked <thinking> tags from content stream (#2158)

CodeWhisperer leaks literal <thinking> blocks into assistantResponseEvent,
duplicating reasoning already routed via reasoningContentEvent. Track
inThinking state to strip these tags during SSE transform, handling split
chunks across tag boundaries.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
hamsa0x7
2026-06-29 15:14:15 +07:00
committed by decolua
co-authored by Cursor
parent b66b5c68ce
commit eff81b1242
2 changed files with 156 additions and 2 deletions
+32 -2
View File
@@ -122,7 +122,8 @@ export class KiroExecutor extends BaseExecutor {
hasReasoningContent: false,
reasoningChunkCount: 0,
toolCallIndex: 0,
seenToolIds: new Map()
seenToolIds: new Map(),
inThinking: false
};
const transformStream = new TransformStream({
@@ -159,7 +160,36 @@ export class KiroExecutor extends BaseExecutor {
// Handle assistantResponseEvent
if (eventType === "assistantResponseEvent" && event.payload?.content) {
const content = event.payload.content;
let content = event.payload.content;
// Kiro Claude models can leak <thinking> blocks into the content stream.
// We strip these literal tags to prevent duplication, as the reasoning
// is already routed correctly via reasoningContentEvent.
if (state.inThinking) {
if (content.includes("</thinking>")) {
state.inThinking = false;
const after = content.split("</thinking>").slice(1).join("</thinking>");
content = after.startsWith("\n") ? after.substring(1) : after;
} else {
content = ""; // Drop entirely while inside thinking block
}
} else if (content.includes("<thinking>")) {
state.inThinking = true;
if (content.includes("</thinking>")) {
state.inThinking = false;
const before = content.split("<thinking>")[0];
const after = content.split("</thinking>").slice(1).join("</thinking>");
content = before + (after.startsWith("\n") ? after.substring(1) : after);
} else {
content = content.split("<thinking>")[0];
}
}
if (!content && state.hasReasoningContent) {
// If we stripped everything, skip emitting an empty content chunk
continue;
}
state.totalContentLength += content.length;
const chunk = {