diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage.spec.ts
new file mode 100644
index 0000000000..02fd22af2a
--- /dev/null
+++ b/src/core/assistant-message/__tests__/presentAssistantMessage.spec.ts
@@ -0,0 +1,271 @@
+// npx vitest src/core/assistant-message/__tests__/presentAssistantMessage.spec.ts
+
+describe("thinking tag filtering logic", () => {
+ // Test the thinking tag filtering logic directly without mocking the entire Task class
+ const filterThinkingTags = (content: string): string => {
+ if (!content) return content
+
+ // This is the exact logic from presentAssistantMessage.ts
+ // Remove all instances of ... blocks completely
+ // while preserving content outside of thinking tags.
+ content = content.replace(/[\s\S]*?<\/thinking>/g, "")
+
+ // Also handle partial thinking tags at the end of streaming content
+ // Remove incomplete thinking tags that might appear during streaming
+ content = content.replace(/\s*$/g, "")
+ content = content.replace(/\s*<\/thinking>\s*$/g, "")
+
+ return content
+ }
+
+ describe("thinking tag removal", () => {
+ it("should remove complete thinking tags and preserve content outside them", () => {
+ const messageWithThinking = `I'll analyze this step by step.
+
+
+This is internal reasoning that should not be shown to the user.
+Let me think about the best approach here.
+
+
+Here's my analysis of the implementation plan:
+
+1. First, I need to understand the requirements
+2. Then I'll create a solution
+
+
+Another internal thought process.
+This should also be hidden.
+
+
+The final recommendation is to proceed with the implementation.`
+
+ const expectedContent = `I'll analyze this step by step.
+
+Here's my analysis of the implementation plan:
+
+1. First, I need to understand the requirements
+2. Then I'll create a solution
+
+The final recommendation is to proceed with the implementation.`
+
+ const result = filterThinkingTags(messageWithThinking)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should handle partial thinking tags at the end of streaming content", () => {
+ const messageWithPartialThinking = `Here's my response.
+
+
+This is a partial thinking tag that hasn't been closed yet because streaming is still in progress`
+
+ const expectedContent = `Here's my response.
+
+`
+
+ const result = filterThinkingTags(messageWithPartialThinking)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should handle incomplete closing thinking tags", () => {
+ const messageWithIncompleteClosing = `Here's my response.
+
+
+Internal reasoning here.
+ {
+ const messageWithNestedXML = `I'll help you with that.
+
+
+Let me think about this:
+
+ 1. Understand the problem
+ 2. Design solution
+
+This analysis should be hidden.
+
+
+Here's the solution I recommend.`
+
+ const expectedContent = `I'll help you with that.
+
+Here's the solution I recommend.`
+
+ const result = filterThinkingTags(messageWithNestedXML)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should handle multiple thinking blocks in sequence", () => {
+ const messageWithMultipleThinking = `Initial response.
+
+
+First internal thought.
+
+
+
+Second internal thought.
+
+
+
+Third internal thought.
+
+
+Final response.`
+
+ const expectedContent = `Initial response.
+
+Final response.`
+
+ const result = filterThinkingTags(messageWithMultipleThinking)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should preserve content when there are no thinking tags", () => {
+ const messageWithoutThinking = `This is a normal response without any thinking tags.
+
+It should be displayed exactly as written.`
+
+ const result = filterThinkingTags(messageWithoutThinking)
+ expect(result).toBe(messageWithoutThinking)
+ })
+
+ it("should handle thinking tags with whitespace variations", () => {
+ const messageWithWhitespaceThinking = `Response start.
+
+
+ Indented thinking content.
+
+ Multiple lines with spaces.
+
+
+Response end.`
+
+ const expectedContent = `Response start.
+
+Response end.`
+
+ const result = filterThinkingTags(messageWithWhitespaceThinking)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should handle thinking tags mixed with other XML-like content", () => {
+ const messageWithThinkingAndXML = `I'll help you read that file.
+
+
+The user wants me to read a file. I should use the read_file tool.
+
+
+example.txt
+
+
+Now I should analyze the content and provide a response.
+
+
+The file has been read successfully.`
+
+ const expectedTextContent = `I'll help you read that file.
+
+example.txt
+
+The file has been read successfully.`
+
+ const result = filterThinkingTags(messageWithThinkingAndXML)
+ expect(result).toBe(expectedTextContent)
+ })
+ })
+
+ describe("edge cases", () => {
+ it("should handle empty content", () => {
+ const emptyMessage = ""
+ const result = filterThinkingTags(emptyMessage)
+ expect(result).toBe("")
+ })
+
+ it("should handle content that is only thinking tags", () => {
+ const onlyThinkingMessage = `
+This entire message is just internal reasoning.
+Nothing should be displayed to the user.
+`
+
+ const result = filterThinkingTags(onlyThinkingMessage)
+ expect(result).toBe("")
+ })
+
+ it("should handle malformed thinking tags", () => {
+ const malformedThinkingMessage = `Response with malformed tags.
+
+ {
+ const messageWithSpecialChars = `Start of response.
+
+
+This thinking block contains:
+- Special characters: !@#$%^&*()
+- Unicode: 🤔ðŸ’
+- Code snippets: const x = "test";
+- Multiple newlines
+
+
+And more content.
+
+
+End of response.`
+
+ const expectedContent = `Start of response.
+
+End of response.`
+
+ const result = filterThinkingTags(messageWithSpecialChars)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should handle adjacent thinking tags without content between them", () => {
+ const messageWithAdjacentThinking = `Response start.
+
+
+First thought.
+
+Second thought immediately after.
+
+
+Response end.`
+
+ const expectedContent = `Response start.
+
+Response end.`
+
+ const result = filterThinkingTags(messageWithAdjacentThinking)
+ expect(result).toBe(expectedContent)
+ })
+
+ it("should handle thinking tags at the very beginning and end", () => {
+ const messageWithBoundaryThinking = `
+Thinking at the start.
+Middle content.
+Thinking at the end.
+`
+
+ const expectedContent = `Middle content.`
+
+ const result = filterThinkingTags(messageWithBoundaryThinking)
+ expect(result).toBe(expectedContent)
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts
index ee3fa148b4..5d1dde6bfe 100644
--- a/src/core/assistant-message/presentAssistantMessage.ts
+++ b/src/core/assistant-message/presentAssistantMessage.ts
@@ -98,13 +98,22 @@ export async function presentAssistantMessage(cline: Task) {
// here for reference.
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?$/, "")
//
- // Remove all instances of (with optional line break
- // after) and (with optional line break before).
- // - Needs to be separate since we dont want to remove the line
- // break before the first tag.
- // - Needs to happen before the xml parsing below.
- content = content.replace(/\s?/g, "")
- content = content.replace(/\s?<\/thinking>/g, "")
+ // Remove all instances of ... blocks completely
+ // while preserving content outside of thinking tags.
+ // This regex matches the opening tag, any content (including newlines),
+ // and the closing tag, removing the entire block.
+ // Also handles extra whitespace to prevent double newlines.
+ content = content.replace(/[\s\S]*?<\/thinking>/g, "")
+
+ // Handle partial thinking tags at the end of streaming content
+ // Remove incomplete thinking tags that might appear during streaming
+ content = content.replace(/[\s\S]*$/g, "")
+
+ // Clean up any remaining partial closing tags
+ content = content.replace(/\s*<\/thinking>\s*$/g, "")
+
+ // Clean up extra newlines that result from removing thinking blocks
+ content = content.replace(/\n\n\n+/g, "\n\n")
// Remove partial XML tag at the very end of the content (for
// tool use and thinking tags), Prevents scrollview from
@@ -214,6 +223,8 @@ export async function presentAssistantMessage(cline: Task) {
const modeName = getModeBySlug(mode, customModes)?.name ?? mode
return `[${block.name} in ${modeName} mode: '${message}']`
}
+ default:
+ return `[${block.name}]`
}
}
diff --git a/test-thinking-filter.js b/test-thinking-filter.js
new file mode 100644
index 0000000000..f0de208470
--- /dev/null
+++ b/test-thinking-filter.js
@@ -0,0 +1,142 @@
+// Simple test script to verify thinking tag filtering logic
+const filterThinkingTags = (content) => {
+ if (!content) return content
+
+ // This is the exact logic from presentAssistantMessage.ts
+ // Remove all instances of ... blocks completely
+ // while preserving content outside of thinking tags.
+ // Also handles extra whitespace to prevent double newlines.
+ content = content.replace(/[\s\S]*?<\/thinking>/g, "")
+
+ // Handle partial thinking tags at the end of streaming content
+ // Remove incomplete thinking tags that might appear during streaming
+ content = content.replace(/[\s\S]*$/g, "")
+
+ // Clean up any remaining partial closing tags
+ content = content.replace(/\s*<\/thinking>\s*$/g, "")
+
+ // Clean up extra newlines that result from removing thinking blocks
+ content = content.replace(/\n\n\n+/g, "\n\n")
+
+ return content
+}
+
+// Test cases
+console.log("Testing thinking tag filtering logic...\n")
+
+// Test 1: Complete thinking tags
+const test1 = `I'll analyze this step by step.
+
+
+This is internal reasoning that should not be shown to the user.
+Let me think about the best approach here.
+
+
+Here's my analysis of the implementation plan:
+
+1. First, I need to understand the requirements
+2. Then I'll create a solution
+
+
+Another internal thought process.
+This should also be hidden.
+
+
+The final recommendation is to proceed with the implementation.`
+
+const expected1 = `I'll analyze this step by step.
+
+Here's my analysis of the implementation plan:
+
+1. First, I need to understand the requirements
+2. Then I'll create a solution
+
+The final recommendation is to proceed with the implementation.`
+
+const result1 = filterThinkingTags(test1)
+console.log("Test 1 - Complete thinking tags:")
+console.log("PASS:", result1 === expected1)
+if (result1 !== expected1) {
+ console.log("Expected:", JSON.stringify(expected1))
+ console.log("Got:", JSON.stringify(result1))
+}
+console.log()
+
+// Test 2: Partial thinking tag
+const test2 = `Here's my response.
+
+
+This is a partial thinking tag that hasn't been closed yet because streaming is still in progress`
+
+const expected2 = `Here's my response.
+
+`
+
+const result2 = filterThinkingTags(test2)
+console.log("Test 2 - Partial thinking tag:")
+console.log("PASS:", result2 === expected2)
+if (result2 !== expected2) {
+ console.log("Expected:", JSON.stringify(expected2))
+ console.log("Got:", JSON.stringify(result2))
+}
+console.log()
+
+// Test 3: Nested XML content
+const test3 = `I'll help you with that.
+
+
+Let me think about this:
+
+ 1. Understand the problem
+ 2. Design solution
+
+This analysis should be hidden.
+
+
+Here's the solution I recommend.`
+
+const expected3 = `I'll help you with that.
+
+Here's the solution I recommend.`
+
+const result3 = filterThinkingTags(test3)
+console.log("Test 3 - Nested XML content:")
+console.log("PASS:", result3 === expected3)
+if (result3 !== expected3) {
+ console.log("Expected:", JSON.stringify(expected3))
+ console.log("Got:", JSON.stringify(result3))
+}
+console.log()
+
+// Test 4: No thinking tags
+const test4 = `This is a normal response without any thinking tags.
+
+It should be displayed exactly as written.`
+
+const result4 = filterThinkingTags(test4)
+console.log("Test 4 - No thinking tags:")
+console.log("PASS:", result4 === test4)
+if (result4 !== test4) {
+ console.log("Expected:", JSON.stringify(test4))
+ console.log("Got:", JSON.stringify(result4))
+}
+console.log()
+
+// Test 5: Only thinking tags
+const test5 = `
+This entire message is just internal reasoning.
+Nothing should be displayed to the user.
+`
+
+const expected5 = ``
+
+const result5 = filterThinkingTags(test5)
+console.log("Test 5 - Only thinking tags:")
+console.log("PASS:", result5 === expected5)
+if (result5 !== expected5) {
+ console.log("Expected:", JSON.stringify(expected5))
+ console.log("Got:", JSON.stringify(result5))
+}
+console.log()
+
+console.log("All tests completed!")
\ No newline at end of file