Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 62 additions & 35 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
didRejectTool = false
didAlreadyUseTool = false
didCompleteReadingStream = false
assistantMessageParser: AssistantMessageParser
assistantMessageParser?: AssistantMessageParser

// Token Usage Cache
private tokenUsageSnapshot?: TokenUsage
Expand Down Expand Up @@ -406,8 +406,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
TelemetryService.instance.captureTaskCreated(this.taskId)
}

// Initialize the assistant message parser.
this.assistantMessageParser = new AssistantMessageParser()
// Initialize the assistant message parser only for XML protocol.
// For native protocol, tool calls come as tool_call chunks, not XML.
const toolProtocol = vscode.workspace.getConfiguration(Package.name).get<ToolProtocol>("toolProtocol", "xml")
this.assistantMessageParser = toolProtocol === "xml" ? new AssistantMessageParser() : undefined
Comment on lines +411 to +412
Copy link
Collaborator

@mrubens mrubens Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a follow-up I think we should probably centralize these toolProtocol checks into a single shared method that we import


this.messageQueueService = new MessageQueueService()

Expand Down Expand Up @@ -1996,7 +1998,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.didAlreadyUseTool = false
this.presentAssistantMessageLocked = false
this.presentAssistantMessageHasPendingUpdates = false
this.assistantMessageParser.reset()
this.assistantMessageParser?.reset()

await this.diffViewProvider.reset()

Expand Down Expand Up @@ -2082,18 +2084,41 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
case "text": {
assistantMessage += chunk.text

// Parse raw assistant message chunk into content blocks.
const prevLength = this.assistantMessageContent.length
this.assistantMessageContent = this.assistantMessageParser.processChunk(chunk.text)
if (this.assistantMessageParser) {
// XML protocol: Parse raw assistant message chunk into content blocks
const prevLength = this.assistantMessageContent.length
this.assistantMessageContent = this.assistantMessageParser.processChunk(chunk.text)

if (this.assistantMessageContent.length > prevLength) {
// New content we need to present, reset to
// false in case previous content set this to true.
this.userMessageContentReady = false
}
if (this.assistantMessageContent.length > prevLength) {
// New content we need to present, reset to
// false in case previous content set this to true.
this.userMessageContentReady = false
}

// Present content to user.
presentAssistantMessage(this)
// Present content to user.
presentAssistantMessage(this)
} else {
// Native protocol: Text chunks are plain text, not XML tool calls
// Create or update a text content block directly
const lastBlock =
this.assistantMessageContent[this.assistantMessageContent.length - 1]

if (lastBlock?.type === "text" && lastBlock.partial) {
// Update existing partial text block
lastBlock.content = assistantMessage
} else {
// Create new text block
this.assistantMessageContent.push({
type: "text",
content: assistantMessage,
partial: true,
})
this.userMessageContentReady = false
}

// Present content to user
presentAssistantMessage(this)
}
break
}
}
Expand Down Expand Up @@ -2384,28 +2409,30 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Can't just do this b/c a tool could be in the middle of executing.
// this.assistantMessageContent.forEach((e) => (e.partial = false))

// Now that the stream is complete, finalize any remaining partial content blocks
this.assistantMessageParser.finalizeContentBlocks()
// Now that the stream is complete, finalize any remaining partial content blocks (XML protocol only)
if (this.assistantMessageParser) {
this.assistantMessageParser.finalizeContentBlocks()

const parsedBlocks = this.assistantMessageParser.getContentBlocks()
const parsedBlocks = this.assistantMessageParser.getContentBlocks()

// Check if we're using native protocol
const toolProtocol = vscode.workspace
.getConfiguration(Package.name)
.get<ToolProtocol>("toolProtocol", "xml")
const isNative = isNativeProtocol(toolProtocol)
// Check if we're using native protocol
const toolProtocol = vscode.workspace
.getConfiguration(Package.name)
.get<ToolProtocol>("toolProtocol", "xml")
const isNative = isNativeProtocol(toolProtocol)

if (isNative) {
// For native protocol: Preserve tool_use blocks that were added via tool_call chunks
// These are added directly to assistantMessageContent and have an 'id' property
const nativeToolBlocks = this.assistantMessageContent.filter(
(block): block is ToolUse<any> => block.type === "tool_use" && (block as any).id !== undefined,
)
// Merge: parser blocks (text) + native tool blocks (tools with IDs)
this.assistantMessageContent = [...parsedBlocks, ...nativeToolBlocks]
} else {
// For XML protocol: Use only parsed blocks (includes both text and tool_use parsed from XML)
this.assistantMessageContent = parsedBlocks
if (isNative) {
// For native protocol: Preserve tool_use blocks that were added via tool_call chunks
// These are added directly to assistantMessageContent and have an 'id' property
const nativeToolBlocks = this.assistantMessageContent.filter(
(block): block is ToolUse<any> => block.type === "tool_use" && (block as any).id !== undefined,
)
// Merge: parser blocks (text) + native tool blocks (tools with IDs)
this.assistantMessageContent = [...parsedBlocks, ...nativeToolBlocks]
} else {
// For XML protocol: Use only parsed blocks (includes both text and tool_use parsed from XML)
this.assistantMessageContent = parsedBlocks
}
}

if (partialBlocks.length > 0) {
Expand Down Expand Up @@ -2439,8 +2466,8 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
await this.saveClineMessages()
await this.providerRef.deref()?.postStateToWebview()

// Reset parser after each complete conversation round
this.assistantMessageParser.reset()
// Reset parser after each complete conversation round (XML protocol only)
this.assistantMessageParser?.reset()

// Now add to apiConversationHistory.
// Need to save assistant responses to file before proceeding to
Expand Down
Loading