@@ -510,6 +510,13 @@ type ReplaySessionOutTailResult<TUIMessage extends UIMessage> = {
510510 * the tail ended cleanly (every segment closed).
511511 */
512512 partial : TUIMessage | undefined ;
513+ /**
514+ * The trailing assistant message BEFORE `cleanupAbortedParts` ran. Same
515+ * `undefined` semantics as `partial`. Use this when you need to inspect
516+ * tool parts the cleanup would strip (e.g. `input-available` /
517+ * `input-streaming` orphans surfaced via `pendingToolCalls`).
518+ */
519+ partialRaw : TUIMessage | undefined ;
513520} ;
514521
515522type ReplaySessionOutTailImpl = < TUIMessage extends UIMessage > (
@@ -581,7 +588,7 @@ async function replaySessionOutTail<TUIMessage extends UIMessage>(
581588 if ( type . startsWith ( "trigger:" ) ) continue ;
582589 collected . push ( chunk as UIMessageChunk ) ;
583590 }
584- if ( collected . length === 0 ) return { settled : [ ] , partial : undefined } ;
591+ if ( collected . length === 0 ) return { settled : [ ] , partial : undefined , partialRaw : undefined } ;
585592
586593 // Split chunks into per-message segments. A `start` chunk demarcates the
587594 // beginning of an assistant message; chunks before any `start` (rare —
@@ -612,6 +619,7 @@ async function replaySessionOutTail<TUIMessage extends UIMessage>(
612619
613620 const settled : TUIMessage [ ] = [ ] ;
614621 let partial : TUIMessage | undefined ;
622+ let partialRaw : TUIMessage | undefined ;
615623 for ( let i = 0 ; i < segments . length ; i ++ ) {
616624 const seg = segments [ i ] ! ;
617625 const isTrailing = i === segments . length - 1 && ! seg . closed ;
@@ -641,11 +649,16 @@ async function replaySessionOutTail<TUIMessage extends UIMessage>(
641649 const cleaned = cleanupAbortedParts ( last as TUIMessage ) ;
642650 if ( cleaned . parts . length === 0 ) continue ;
643651 partial = cleaned ;
652+ // Keep the raw pre-cleanup message too — recovery boot extracts
653+ // `pendingToolCalls` from it, since `cleanupAbortedParts` strips
654+ // exactly the input-streaming / input-available tool parts that
655+ // we want to surface.
656+ partialRaw = last as TUIMessage ;
644657 } else {
645658 settled . push ( last as TUIMessage ) ;
646659 }
647660 }
648- return { settled, partial } ;
661+ return { settled, partial, partialRaw } ;
649662}
650663
651664/**
@@ -4972,6 +4985,7 @@ function chatAgent<
49724985 let bootSnapshot : ChatSnapshotV1 < TUIMessage > | undefined ;
49734986 let replayedSettled : TUIMessage [ ] = [ ] ;
49744987 let replayedPartial : TUIMessage | undefined ;
4988+ let replayedPartialRaw : TUIMessage | undefined ;
49754989 let replayedInTail : { message : TUIMessage ; metadata : unknown ; seqNum : number } [ ] = [ ] ;
49764990 // Wire payloads to dispatch as turns before the regular session.in
49774991 // pump kicks in. Populated by `onRecoveryBoot.recoveredTurns` (or its
@@ -5036,6 +5050,7 @@ function chatAgent<
50365050 ) ;
50375051 replayedSettled = replayResult . settled ;
50385052 replayedPartial = replayResult . partial ;
5053+ replayedPartialRaw = replayResult . partialRaw ;
50395054 } catch ( error ) {
50405055 logger . warn (
50415056 "chat.agent: session.out replay failed; using snapshot only" ,
@@ -5160,7 +5175,11 @@ function chatAgent<
51605175 let hookRecoveredTurns : TUIMessage [ ] | undefined ;
51615176 let hookBeforeBoot : ( ( ) => Promise < void > ) | undefined ;
51625177 if ( couldHavePriorState && hasRecoveredState && onRecoveryBoot ) {
5163- const pendingToolCalls = extractPendingToolCallsFromPartial ( partialAssistant ) ;
5178+ // Extract from the RAW partial (pre-cleanup). `cleanupAbortedParts`
5179+ // strips exactly the input-streaming / input-available tool parts
5180+ // we want to surface here, so the cleaned `partialAssistant` would
5181+ // always report zero pending tool calls.
5182+ const pendingToolCalls = extractPendingToolCallsFromPartial ( replayedPartialRaw ) ;
51645183 const previousRunIdForHook = previousRunId ?? "" ;
51655184 let hookResult : RecoveryBootResult < TUIMessage > | void = undefined ;
51665185 const { writer : hookWriter , flush : hookFlush } = createLazyChatWriter ( ) ;
0 commit comments