@@ -3138,6 +3138,58 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
31383138 }
31393139 }
31403140
3141+ // Finalize any remaining streaming tool calls that weren't explicitly ended
3142+ // This is critical for MCP tools which need tool_call_end events to be properly
3143+ // converted from ToolUse to McpToolUse via finalizeStreamingToolCall()
3144+ const finalizeEvents = NativeToolCallParser . finalizeRawChunks ( )
3145+ for ( const event of finalizeEvents ) {
3146+ if ( event . type === "tool_call_end" ) {
3147+ // Finalize the streaming tool call
3148+ const finalToolUse = NativeToolCallParser . finalizeStreamingToolCall ( event . id )
3149+
3150+ // Get the index for this tool call
3151+ const toolUseIndex = this . streamingToolCallIndices . get ( event . id )
3152+
3153+ if ( finalToolUse ) {
3154+ // Store the tool call ID
3155+ ; ( finalToolUse as any ) . id = event . id
3156+
3157+ // Get the index and replace partial with final
3158+ if ( toolUseIndex !== undefined ) {
3159+ this . assistantMessageContent [ toolUseIndex ] = finalToolUse
3160+ }
3161+
3162+ // Clean up tracking
3163+ this . streamingToolCallIndices . delete ( event . id )
3164+
3165+ // Mark that we have new content to process
3166+ this . userMessageContentReady = false
3167+
3168+ // Present the finalized tool call
3169+ presentAssistantMessage ( this )
3170+ } else if ( toolUseIndex !== undefined ) {
3171+ // finalizeStreamingToolCall returned null (malformed JSON or missing args)
3172+ // We still need to mark the tool as non-partial so it gets executed
3173+ // The tool's validation will catch any missing required parameters
3174+ const existingToolUse = this . assistantMessageContent [ toolUseIndex ]
3175+ if ( existingToolUse && existingToolUse . type === "tool_use" ) {
3176+ existingToolUse . partial = false
3177+ // Ensure it has the ID for native protocol
3178+ ; ( existingToolUse as any ) . id = event . id
3179+ }
3180+
3181+ // Clean up tracking
3182+ this . streamingToolCallIndices . delete ( event . id )
3183+
3184+ // Mark that we have new content to process
3185+ this . userMessageContentReady = false
3186+
3187+ // Present the tool call - validation will handle missing params
3188+ presentAssistantMessage ( this )
3189+ }
3190+ }
3191+ }
3192+
31413193 // Create a copy of current token values to avoid race conditions
31423194 const currentTokens = {
31433195 input : inputTokens ,
@@ -3385,61 +3437,6 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
33853437 // the case, `presentAssistantMessage` relies on these blocks either
33863438 // to be completed or the user to reject a block in order to proceed
33873439 // and eventually set userMessageContentReady to true.)
3388-
3389- // Finalize any remaining streaming tool calls that weren't explicitly ended
3390- // This is critical for MCP tools which need tool_call_end events to be properly
3391- // converted from ToolUse to McpToolUse via finalizeStreamingToolCall()
3392- const finalizeEvents = NativeToolCallParser . finalizeRawChunks ( )
3393- for ( const event of finalizeEvents ) {
3394- if ( event . type === "tool_call_end" ) {
3395- // Finalize the streaming tool call
3396- const finalToolUse = NativeToolCallParser . finalizeStreamingToolCall ( event . id )
3397-
3398- // Get the index for this tool call
3399- const toolUseIndex = this . streamingToolCallIndices . get ( event . id )
3400-
3401- if ( finalToolUse ) {
3402- // Store the tool call ID
3403- ; ( finalToolUse as any ) . id = event . id
3404-
3405- // Get the index and replace partial with final
3406- if ( toolUseIndex !== undefined ) {
3407- this . assistantMessageContent [ toolUseIndex ] = finalToolUse
3408- }
3409-
3410- // Clean up tracking
3411- this . streamingToolCallIndices . delete ( event . id )
3412-
3413- // Mark that we have new content to process
3414- this . userMessageContentReady = false
3415-
3416- // Present the finalized tool call
3417- presentAssistantMessage ( this )
3418- } else if ( toolUseIndex !== undefined ) {
3419- // finalizeStreamingToolCall returned null (malformed JSON or missing args)
3420- // We still need to mark the tool as non-partial so it gets executed
3421- // The tool's validation will catch any missing required parameters
3422- const existingToolUse = this . assistantMessageContent [ toolUseIndex ]
3423- if ( existingToolUse && existingToolUse . type === "tool_use" ) {
3424- existingToolUse . partial = false
3425- // Ensure it has the ID for native protocol
3426- ; ( existingToolUse as any ) . id = event . id
3427- }
3428-
3429- // Clean up tracking
3430- this . streamingToolCallIndices . delete ( event . id )
3431-
3432- // Mark that we have new content to process
3433- this . userMessageContentReady = false
3434-
3435- // Present the tool call - validation will handle missing params
3436- presentAssistantMessage ( this )
3437- }
3438- }
3439- }
3440-
3441- // IMPORTANT: Capture partialBlocks AFTER finalizeRawChunks() to avoid double-presentation.
3442- // Tools finalized above are already presented, so we only want blocks still partial after finalization.
34433440 const partialBlocks = this . assistantMessageContent . filter ( ( block ) => block . partial )
34443441 partialBlocks . forEach ( ( block ) => ( block . partial = false ) )
34453442
@@ -3455,6 +3452,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
34553452 this . assistantMessageContent = parsedBlocks
34563453 }
34573454
3455+ // Present any partial blocks that were just completed.
3456+ // Tool calls are typically presented during streaming via tool_call_partial events,
3457+ // but we still present here if any partial blocks remain (e.g., malformed streams).
3458+ if ( partialBlocks . length > 0 ) {
3459+ // If there is content to update then it will complete and
3460+ // update `this.userMessageContentReady` to true, which we
3461+ // `pWaitFor` before making the next request.
3462+ presentAssistantMessage ( this )
3463+ }
3464+
34583465 // Note: updateApiReqMsg() is now called from within drainStreamInBackgroundToFindAllUsage
34593466 // to ensure usage data is captured even when the stream is interrupted. The background task
34603467 // uses local variables to accumulate usage data before atomically updating the shared state.
@@ -3480,10 +3487,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
34803487 // Reset parser after each complete conversation round (XML protocol only)
34813488 this . assistantMessageParser ?. reset ( )
34823489
3483- // CRITICAL: Save assistant message to API history BEFORE executing tools .
3484- // This ensures that when new_task triggers delegation and calls flushPendingToolResultsToHistory(),
3485- // the assistant message is already in history. Otherwise, tool_result blocks would appear
3486- // BEFORE their corresponding tool_use blocks, causing API errors .
3490+ // Now add to apiConversationHistory .
3491+ // Need to save assistant responses to file before proceeding to
3492+ // tool use since user can exit at any moment and we wouldn't be
3493+ // able to save the assistant's response .
34873494
34883495 // Check if we have any content to process (text or tool uses)
34893496 const hasTextContent = assistantMessage . length > 0
@@ -3580,69 +3587,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
35803587 }
35813588 }
35823589
3583- // Enforce new_task isolation: if new_task is called alongside other tools,
3584- // truncate any tools that come after it and inject error tool_results.
3585- // This prevents orphaned tools when delegation disposes the parent task.
3586- const newTaskIndex = assistantContent . findIndex (
3587- ( block ) => block . type === "tool_use" && block . name === "new_task" ,
3588- )
3589-
3590- if ( newTaskIndex !== - 1 && newTaskIndex < assistantContent . length - 1 ) {
3591- // new_task found but not last - truncate subsequent tools
3592- const truncatedTools = assistantContent . slice ( newTaskIndex + 1 )
3593- assistantContent . length = newTaskIndex + 1 // Truncate API history array
3594-
3595- // ALSO truncate the execution array (assistantMessageContent) to prevent
3596- // tools after new_task from being executed by presentAssistantMessage().
3597- // Find new_task index in assistantMessageContent (may differ from assistantContent
3598- // due to text blocks being structured differently).
3599- const executionNewTaskIndex = this . assistantMessageContent . findIndex (
3600- ( block ) => block . type === "tool_use" && block . name === "new_task" ,
3601- )
3602- if ( executionNewTaskIndex !== - 1 ) {
3603- this . assistantMessageContent . length = executionNewTaskIndex + 1
3604- }
3605-
3606- // Pre-inject error tool_results for truncated tools
3607- for ( const tool of truncatedTools ) {
3608- if ( tool . type === "tool_use" && ( tool as Anthropic . ToolUseBlockParam ) . id ) {
3609- this . pushToolResultToUserContent ( {
3610- type : "tool_result" ,
3611- tool_use_id : ( tool as Anthropic . ToolUseBlockParam ) . id ,
3612- content :
3613- "This tool was not executed because new_task was called in the same message turn. The new_task tool must be the last tool in a message." ,
3614- is_error : true ,
3615- } )
3616- }
3617- }
3618- }
3619-
3620- // Save assistant message BEFORE executing tools
3621- // This is critical for new_task: when it triggers delegation, flushPendingToolResultsToHistory()
3622- // will save the user message with tool_results. The assistant message must already be in history
3623- // so that tool_result blocks appear AFTER their corresponding tool_use blocks.
36243590 await this . addToApiConversationHistory (
36253591 { role : "assistant" , content : assistantContent } ,
36263592 reasoningMessage || undefined ,
36273593 )
36283594
36293595 TelemetryService . instance . captureConversationMessage ( this . taskId , "assistant" )
3630- }
36313596
3632- // Present any partial blocks that were just completed.
3633- // Tool calls are typically presented during streaming via tool_call_partial events,
3634- // but we still present here if any partial blocks remain (e.g., malformed streams).
3635- // NOTE: This MUST happen AFTER saving the assistant message to API history.
3636- // When new_task is in the batch, it triggers delegation which calls flushPendingToolResultsToHistory().
3637- // If the assistant message isn't saved yet, tool_results would appear before tool_use blocks.
3638- if ( partialBlocks . length > 0 ) {
3639- // If there is content to update then it will complete and
3640- // update `this.userMessageContentReady` to true, which we
3641- // `pWaitFor` before making the next request.
3642- presentAssistantMessage ( this )
3643- }
3644-
3645- if ( hasTextContent || hasToolUses ) {
36463597 // NOTE: This comment is here for future reference - this was a
36473598 // workaround for `userMessageContent` not getting set to true.
36483599 // It was due to it not recursively calling for partial blocks
@@ -4359,7 +4310,15 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
43594310 allowedFunctionNames = toolsResult . allowedFunctionNames
43604311 }
43614312
4313+ < < < << << HEAD
43624314 const parallelToolCallsEnabled = state ?. experiments ?. multipleNativeToolCalls ?? false
4315+ = === ===
4316+ const shouldIncludeTools = allTools . length > 0
4317+
4318+ // Parallel tool calls are disabled - feature is on hold
4319+ // Previously resolved from experiments.isEnabled(..., EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS)
4320+ const parallelToolCallsEnabled = false
4321+ > > >>> >> 5 b3626f1a ( Revert "Enable parallel tool calling with new_task isolation safeguards" ( #11004 ) )
43634322
43644323 const metadata : ApiHandlerCreateMessageMetadata = {
43654324 mode : mode ,
0 commit comments