When the consumer of a createUIMessageStream response cancels (for example, the client disconnects), streams that were added with writer.merge() are not cancelled. The merge loop keeps reading the source until it ends and discards the chunks.
For a merged streamText(...).toUIMessageStream(), this keeps the upstream model request running and reading after nobody is listening, unless the caller also wired an abortSignal by hand.
Reproduction
const source = new ReadableStream({
start(c) { c.enqueue({ type: 'start' }); },
cancel() { console.log('source cancelled'); },
});
const stream = createUIMessageStream({
execute: ({ writer }) => { writer.merge(source); },
});
const reader = stream.getReader();
await reader.read();
await reader.cancel('client disconnected');
// Expected: "source cancelled" is logged.
// Actual: nothing is logged.
AI SDK version
Current main (ai).
When the consumer of a
createUIMessageStreamresponse cancels (for example, the client disconnects), streams that were added withwriter.merge()are not cancelled. The merge loop keeps reading the source until it ends and discards the chunks.For a merged
streamText(...).toUIMessageStream(), this keeps the upstream model request running and reading after nobody is listening, unless the caller also wired anabortSignalby hand.Reproduction
AI SDK version
Current
main(ai).