@@ -457,15 +457,21 @@ export async function runStreamLoop(
457457 // there is no per-chunk OTel cost — one span per read loop with
458458 // integer counters, plus a bounded set of events.
459459 //
460- // `context.streamComplete` is the caller-visible "this leg was
461- // supposed to be final" signal (set by the complete/error/run-end
462- // handlers). When it's true but we didn't see a terminal event on
463- // the wire, that's the real "disappeared response" bug — as
464- // opposed to a normal tool-pause leg which ends with
465- // streamComplete=false and terminal_event_seen=false and is fine.
460+ // `expectedTerminal` = "the caller considered this leg the FINAL
461+ // leg and genuinely expected a terminal event on the wire." We
462+ // derive it from `context.streamComplete` MINUS the tool-pause
463+ // case: when the server emits a `run.checkpoint_pause`, its
464+ // handler also sets `streamComplete=true` to stop the read loop
465+ // cleanly, but no `complete` SSE event is ever sent in that
466+ // case — that's the tool-pause protocol, not a missing terminal.
467+ // `awaitingAsyncContinuation` is set by the same handler, so
468+ // its presence distinguishes "tool pause, no terminal expected"
469+ // from "caller thought stream was done but server never said so"
470+ // (= the real disappeared-response bug class).
471+ const expectedTerminal = context . streamComplete && ! context . awaitingAsyncContinuation
466472 stampSseReadLoopSpan ( bodyStart , counters , endedOn , fetchUrl , pathname , {
467473 idleGapEventThresholdMs : IDLE_GAP_EVENT_THRESHOLD_MS ,
468- expectedTerminal : context . streamComplete ,
474+ expectedTerminal,
469475 } )
470476 }
471477}
0 commit comments