A turn cancelled mid-stream dropped everything the interrupted step had
streamed: chunks stayed in the log for replay, but no assistant/message
joined the surface, so the next request carried nothing the user had
watched stream. Follow-ups after cancel could not connect and forks
inherited the gap.
Keep the streaming attempt alive across the step's request loop and, when
an abort escapes with the attempt uncommitted, finalize its user-visible
prefix as the step's ordinary assistant/message citing the logged chunk
seqs. BlockAssembler.interruptedBlocks() owns the safe subset next to the
max-tokens rule: closed and open text/reasoning blocks with content, in
stream order; tool calls, empty blocks, and unknown open blocks drop.
Retry clears the attempt first, so an abort after llm/retry finalizes
nothing from the reset stream. Tool-phase cancellation and provider
failures keep their existing shapes.