@@ -740,10 +740,36 @@ const ModelResponseCard = memo(function ModelResponseCard({
740740 response . toolExecutionRounds ,
741741 ] ) ;
742742
743+ // Bridge live tool execution data into the last completed round so that
744+ // ContentRound can render the execution timeline while tools are still running.
745+ // Gated on isStreaming so committed (DB-loaded) messages are unaffected.
746+ const completedRoundsWithLiveTools = useMemo ( ( ) => {
747+ if ( ! response . isStreaming || ! toolExecutionRounds . length ) {
748+ return completedRounds ;
749+ }
750+ const liveRound = toolExecutionRounds [ toolExecutionRounds . length - 1 ] ;
751+ // No completed rounds yet (model called tool immediately) — synthesize one
752+ if ( ! completedRounds . length ) {
753+ return [ { toolExecution : liveRound } ] ;
754+ }
755+ const last = completedRounds [ completedRounds . length - 1 ] ;
756+ // Last round already has tool execution (back-to-back tool calls) — append new round
757+ if ( last . toolExecution ) {
758+ return [ ...completedRounds , { toolExecution : liveRound } ] ;
759+ }
760+ // Last round is text-only — inject live tools into it
761+ const merged = [ ...completedRounds ] ;
762+ merged [ merged . length - 1 ] = {
763+ ...last ,
764+ toolExecution : liveRound ,
765+ } ;
766+ return merged ;
767+ } , [ completedRounds , toolExecutionRounds , response . isStreaming ] ) ;
768+
743769 // All output artifacts across all rounds (for resolving display_artifacts selections)
744770 const allOutputArtifacts = useMemo ( ( ) => {
745771 const result : ArtifactType [ ] = [ ] ;
746- for ( const round of completedRounds ) {
772+ for ( const round of completedRoundsWithLiveTools ) {
747773 if ( round . toolExecution ) {
748774 for ( const execution of round . toolExecution . executions ) {
749775 for ( const a of execution . outputArtifacts ) {
@@ -753,7 +779,7 @@ const ModelResponseCard = memo(function ModelResponseCard({
753779 }
754780 }
755781 return result ;
756- } , [ completedRounds ] ) ;
782+ } , [ completedRoundsWithLiveTools ] ) ;
757783
758784 // Extract display selection for a specific tool execution round
759785 const getDisplaySelectionForRound = useCallback (
@@ -959,31 +985,44 @@ const ModelResponseCard = memo(function ModelResponseCard({
959985 { /* Content: unified rendering via ContentRound for all responses */ }
960986 { ( ( ) => {
961987 // Detect in-flight content that hasn't been captured in a completed round yet.
962- // completedRounds is always populated (even for single-round responses),
988+ // completedRoundsWithLiveTools is always populated (even for single-round responses),
963989 // so this only shows content actively streaming in the current round.
964990 const currentReasoning =
965991 response . isStreaming &&
966992 response . reasoningContent &&
967- ! completedRounds . some ( ( r ) => r . reasoning === response . reasoningContent )
993+ ! completedRoundsWithLiveTools . some ( ( r ) => r . reasoning === response . reasoningContent )
968994 ? response . reasoningContent
969995 : null ;
970996 const currentContent =
971997 response . isStreaming && response . content ?. trim ( ) ? response . content : null ;
972998 const showInFlight = currentReasoning || currentContent ;
999+
1000+ // Suppress the streaming status indicator when running tools are
1001+ // already visible in expanded ContentRounds (non-compact mode only)
1002+ const hasVisibleRunningTools =
1003+ ! compactMode &&
1004+ completedRoundsWithLiveTools . some ( ( r ) =>
1005+ r . toolExecution ?. executions . some (
1006+ ( e ) => e . status === "running" || e . status === "pending"
1007+ )
1008+ ) ;
1009+
9731010 return (
9741011 < div className = "space-y-3" >
975- { completedRounds . map ( ( round , i ) => (
1012+ { completedRoundsWithLiveTools . map ( ( round , i ) => (
9761013 < ContentRound
9771014 key = { i }
9781015 reasoning = { round . reasoning }
9791016 content = { round . content }
9801017 reasoningTokenCount = {
981- completedRounds . length === 1 ? response . usage ?. reasoningTokens : undefined
1018+ completedRoundsWithLiveTools . length === 1
1019+ ? response . usage ?. reasoningTokens
1020+ : undefined
9821021 }
9831022 toolExecutionRound = { round . toolExecution }
9841023 isToolsStreaming = {
9851024 response . isStreaming &&
986- i === completedRounds . length - 1 &&
1025+ i === completedRoundsWithLiveTools . length - 1 &&
9871026 ! ! round . toolExecution ?. executions . some (
9881027 ( e ) => e . status === "pending" || e . status === "running"
9891028 )
@@ -1005,10 +1044,12 @@ const ModelResponseCard = memo(function ModelResponseCard({
10051044 isReasoningStreaming = { response . isStreaming && ! currentContent }
10061045 />
10071046 ) }
1008- < StreamingStatusIndicator
1009- phase = { streamingPhase }
1010- toolStatusMessage = { toolStatusMessage }
1011- />
1047+ { ! hasVisibleRunningTools && (
1048+ < StreamingStatusIndicator
1049+ phase = { streamingPhase }
1050+ toolStatusMessage = { toolStatusMessage }
1051+ />
1052+ ) }
10121053 </ div >
10131054 ) ;
10141055 } ) ( ) }
0 commit comments