@@ -2983,58 +2983,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
29832983 }
29842984 }
29852985
2986- // Finalize any remaining streaming tool calls that weren't explicitly ended
2987- // This is critical for MCP tools which need tool_call_end events to be properly
2988- // converted from ToolUse to McpToolUse via finalizeStreamingToolCall()
2989- const finalizeEvents = NativeToolCallParser . finalizeRawChunks ( )
2990- for ( const event of finalizeEvents ) {
2991- if ( event . type === "tool_call_end" ) {
2992- // Finalize the streaming tool call
2993- const finalToolUse = NativeToolCallParser . finalizeStreamingToolCall ( event . id )
2994-
2995- // Get the index for this tool call
2996- const toolUseIndex = this . streamingToolCallIndices . get ( event . id )
2997-
2998- if ( finalToolUse ) {
2999- // Store the tool call ID
3000- ; ( finalToolUse as any ) . id = event . id
3001-
3002- // Get the index and replace partial with final
3003- if ( toolUseIndex !== undefined ) {
3004- this . assistantMessageContent [ toolUseIndex ] = finalToolUse
3005- }
3006-
3007- // Clean up tracking
3008- this . streamingToolCallIndices . delete ( event . id )
3009-
3010- // Mark that we have new content to process
3011- this . userMessageContentReady = false
3012-
3013- // Present the finalized tool call
3014- presentAssistantMessage ( this )
3015- } else if ( toolUseIndex !== undefined ) {
3016- // finalizeStreamingToolCall returned null (malformed JSON or missing args)
3017- // We still need to mark the tool as non-partial so it gets executed
3018- // The tool's validation will catch any missing required parameters
3019- const existingToolUse = this . assistantMessageContent [ toolUseIndex ]
3020- if ( existingToolUse && existingToolUse . type === "tool_use" ) {
3021- existingToolUse . partial = false
3022- // Ensure it has the ID for native protocol
3023- ; ( existingToolUse as any ) . id = event . id
3024- }
3025-
3026- // Clean up tracking
3027- this . streamingToolCallIndices . delete ( event . id )
3028-
3029- // Mark that we have new content to process
3030- this . userMessageContentReady = false
3031-
3032- // Present the tool call - validation will handle missing params
3033- presentAssistantMessage ( this )
3034- }
3035- }
3036- }
3037-
30382986 // Create a copy of current token values to avoid race conditions
30392987 const currentTokens = {
30402988 input : inputTokens ,
@@ -3282,6 +3230,61 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
32823230 // the case, `presentAssistantMessage` relies on these blocks either
32833231 // to be completed or the user to reject a block in order to proceed
32843232 // and eventually set userMessageContentReady to true.)
3233+
3234+ // Finalize any remaining streaming tool calls that weren't explicitly ended
3235+ // This is critical for MCP tools which need tool_call_end events to be properly
3236+ // converted from ToolUse to McpToolUse via finalizeStreamingToolCall()
3237+ const finalizeEvents = NativeToolCallParser . finalizeRawChunks ( )
3238+ for ( const event of finalizeEvents ) {
3239+ if ( event . type === "tool_call_end" ) {
3240+ // Finalize the streaming tool call
3241+ const finalToolUse = NativeToolCallParser . finalizeStreamingToolCall ( event . id )
3242+
3243+ // Get the index for this tool call
3244+ const toolUseIndex = this . streamingToolCallIndices . get ( event . id )
3245+
3246+ if ( finalToolUse ) {
3247+ // Store the tool call ID
3248+ ; ( finalToolUse as any ) . id = event . id
3249+
3250+ // Get the index and replace partial with final
3251+ if ( toolUseIndex !== undefined ) {
3252+ this . assistantMessageContent [ toolUseIndex ] = finalToolUse
3253+ }
3254+
3255+ // Clean up tracking
3256+ this . streamingToolCallIndices . delete ( event . id )
3257+
3258+ // Mark that we have new content to process
3259+ this . userMessageContentReady = false
3260+
3261+ // Present the finalized tool call
3262+ presentAssistantMessage ( this )
3263+ } else if ( toolUseIndex !== undefined ) {
3264+ // finalizeStreamingToolCall returned null (malformed JSON or missing args)
3265+ // We still need to mark the tool as non-partial so it gets executed
3266+ // The tool's validation will catch any missing required parameters
3267+ const existingToolUse = this . assistantMessageContent [ toolUseIndex ]
3268+ if ( existingToolUse && existingToolUse . type === "tool_use" ) {
3269+ existingToolUse . partial = false
3270+ // Ensure it has the ID for native protocol
3271+ ; ( existingToolUse as any ) . id = event . id
3272+ }
3273+
3274+ // Clean up tracking
3275+ this . streamingToolCallIndices . delete ( event . id )
3276+
3277+ // Mark that we have new content to process
3278+ this . userMessageContentReady = false
3279+
3280+ // Present the tool call - validation will handle missing params
3281+ presentAssistantMessage ( this )
3282+ }
3283+ }
3284+ }
3285+
3286+ // IMPORTANT: Capture partialBlocks AFTER finalizeRawChunks() to avoid double-presentation.
3287+ // Tools finalized above are already presented, so we only want blocks still partial after finalization.
32853288 const partialBlocks = this . assistantMessageContent . filter ( ( block ) => block . partial )
32863289 partialBlocks . forEach ( ( block ) => ( block . partial = false ) )
32873290
0 commit comments