@@ -218,8 +218,9 @@ export interface GenerationStream {
218218 /** The request moved to a step that writes nothing yet. */
219219 phase : ( name : GenerationPhase ) => void ;
220220 /**
221- * The model ended its answer (any finish_reason but "length", which is
222- * retried). Arrives before the usage trailer, which a disconnect can lose.
221+ * The model ended a non-empty answer (any finish_reason but "length",
222+ * which is retried, or [DONE]). Arrives before the usage trailer, which a
223+ * disconnect can lose.
223224 */
224225 finished ?: ( ) => void ;
225226}
@@ -484,8 +485,9 @@ export async function readOpenRouterStream(
484485 const data = trimmed . slice ( "data:" . length ) . trim ( ) ;
485486 if ( data === "[DONE]" ) {
486487 done = true ;
487- // A stream that ends without a finish_reason still finished its answer.
488- if ( ! finishReason ) stream . finished ?.( ) ;
488+ // A stream that ends without a finish_reason still finished its answer,
489+ // if it wrote one: an empty reply is a failure, not an answer.
490+ if ( ! finishReason && text ) stream . finished ?.( ) ;
489491 return ;
490492 }
491493 let chunk : OpenRouterStreamChunk ;
@@ -510,7 +512,7 @@ export async function readOpenRouterStream(
510512 stream . delta ( content ) ;
511513 }
512514 if ( typeof choice ?. finish_reason === "string" && choice . finish_reason ) {
513- if ( ! finishReason && choice . finish_reason !== "length" ) stream . finished ?.( ) ;
515+ if ( ! finishReason && choice . finish_reason !== "length" && text ) stream . finished ?.( ) ;
514516 finishReason = choice . finish_reason ;
515517 }
516518 } ;
0 commit comments