@@ -2657,6 +2657,11 @@ async function processOpenAICompletionsStream(
26572657 let sawStopFinishReason = false ;
26582658 const blockIndex = ( ) => output . content . length - 1 ;
26592659 const measureUtf8Bytes = ( text : string ) => Buffer . byteLength ( text , "utf8" ) ;
2660+ let chunkPushedEvent = false ;
2661+ const pushStreamEvent = ( event : unknown ) => {
2662+ chunkPushedEvent = true ;
2663+ stream . push ( event ) ;
2664+ } ;
26602665 const finishCurrentBlock = ( ) => {
26612666 if ( ! currentBlock ) {
26622667 return ;
@@ -2697,13 +2702,13 @@ async function processOpenAICompletionsStream(
26972702 currentBlock = {
26982703 type : "thinking" ,
26992704 thinking : "" ,
2700- thinkingSignature : reasoningDelta . signature ,
2705+ ... ( reasoningDelta . signature ? { thinkingSignature : reasoningDelta . signature } : { } ) ,
27012706 } ;
27022707 output . content . push ( currentBlock ) ;
2703- stream . push ( { type : "thinking_start" , contentIndex : blockIndex ( ) , partial : output } ) ;
2708+ pushStreamEvent ( { type : "thinking_start" , contentIndex : blockIndex ( ) , partial : output } ) ;
27042709 }
27052710 currentBlock . thinking += reasoningDelta . text ;
2706- stream . push ( {
2711+ pushStreamEvent ( {
27072712 type : "thinking_delta" ,
27082713 contentIndex : blockIndex ( ) ,
27092714 delta : reasoningDelta . text ,
@@ -2715,10 +2720,10 @@ async function processOpenAICompletionsStream(
27152720 finishCurrentBlock ( ) ;
27162721 currentBlock = { type : "text" , text : "" } ;
27172722 output . content . push ( currentBlock ) ;
2718- stream . push ( { type : "text_start" , contentIndex : blockIndex ( ) , partial : output } ) ;
2723+ pushStreamEvent ( { type : "text_start" , contentIndex : blockIndex ( ) , partial : output } ) ;
27192724 }
27202725 currentBlock . text += text ;
2721- stream . push ( {
2726+ pushStreamEvent ( {
27222727 type : "text_delta" ,
27232728 contentIndex : blockIndex ( ) ,
27242729 delta : text ,
@@ -2782,12 +2787,12 @@ async function processOpenAICompletionsStream(
27822787 } ;
27832788 currentBlock = block ;
27842789 output . content . push ( block ) ;
2785- stream . push ( {
2790+ pushStreamEvent ( {
27862791 type : "toolcall_start" ,
27872792 contentIndex : output . content . indexOf ( block ) ,
27882793 partial : output ,
27892794 } ) ;
2790- stream . push ( {
2795+ pushStreamEvent ( {
27912796 type : "toolcall_delta" ,
27922797 contentIndex : output . content . indexOf ( block ) ,
27932798 delta : toolCall . partialArgs ,
@@ -2853,6 +2858,19 @@ async function processOpenAICompletionsStream(
28532858 appendFilteredVisibleTextDelta ( delta . text ) ;
28542859 }
28552860 } ;
2861+ const emitReasoningUsageActivity = ( hasReasoningUsageActivity : boolean ) => {
2862+ if ( ! hasReasoningUsageActivity || chunkPushedEvent || ! emitReasoning ) {
2863+ return ;
2864+ }
2865+ const latestBlock = output . content [ output . content . length - 1 ] ;
2866+ if ( currentBlock ?. type === "text" || currentBlock ?. type === "toolCall" ) {
2867+ return ;
2868+ }
2869+ if ( latestBlock ?. type === "text" || latestBlock ?. type === "toolCall" ) {
2870+ return ;
2871+ }
2872+ appendThinkingDelta ( { signature : "" , text : "" } ) ;
2873+ } ;
28562874 const flushReasoningTagTextPartitionerAtEnd = ( ) => {
28572875 for ( const delta of reasoningTagTextPartitioner . flush ( ) ) {
28582876 appendPartitionedVisibleDelta ( delta ) ;
@@ -2861,23 +2879,28 @@ async function processOpenAICompletionsStream(
28612879 const cooperativeScheduler = createModelStreamCooperativeScheduler ( options ?. signal ) ;
28622880 for await ( const rawChunk of responseStream as AsyncIterable < unknown > ) {
28632881 throwIfModelStreamAborted ( options ?. signal ) ;
2882+ chunkPushedEvent = false ;
28642883 if ( ! rawChunk || typeof rawChunk !== "object" ) {
28652884 await cooperativeScheduler . afterEvent ( ) ;
28662885 continue ;
28672886 }
28682887 const chunk = rawChunk as ChatCompletionChunk ;
28692888 output . responseId ||= chunk . id ;
2889+ let hasReasoningUsageActivity = false ;
28702890 if ( chunk . usage ) {
28712891 output . usage = parseTransportChunkUsage ( chunk . usage , model ) ;
2892+ hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity ( chunk . usage ) ;
28722893 }
28732894 const choice = Array . isArray ( chunk . choices ) ? chunk . choices [ 0 ] : undefined ;
28742895 if ( ! choice ) {
2896+ emitReasoningUsageActivity ( hasReasoningUsageActivity ) ;
28752897 await cooperativeScheduler . afterEvent ( ) ;
28762898 continue ;
28772899 }
28782900 const choiceUsage = ( choice as unknown as { usage ?: ChatCompletionChunk [ "usage" ] } ) . usage ;
28792901 if ( ! chunk . usage && choiceUsage ) {
28802902 output . usage = parseTransportChunkUsage ( choiceUsage , model ) ;
2903+ hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity ( choiceUsage ) ;
28812904 }
28822905 if ( choice . finish_reason ) {
28832906 const finishReasonResult = mapStopReason ( choice . finish_reason ) ;
@@ -2893,6 +2916,7 @@ async function processOpenAICompletionsStream(
28932916 choice . delta ??
28942917 ( choice as unknown as { message ?: ChatCompletionChunk [ "choices" ] [ number ] [ "delta" ] } ) . message ;
28952918 if ( ! choiceDelta ) {
2919+ emitReasoningUsageActivity ( hasReasoningUsageActivity ) ;
28962920 await cooperativeScheduler . afterEvent ( ) ;
28972921 continue ;
28982922 }
@@ -2961,7 +2985,7 @@ async function processOpenAICompletionsStream(
29612985 ...( initialSig ? { thoughtSignature : initialSig } : { } ) ,
29622986 } ;
29632987 output . content . push ( block ) ;
2964- stream . push ( {
2988+ pushStreamEvent ( {
29652989 type : "toolcall_start" ,
29662990 contentIndex : output . content . indexOf ( block ) ,
29672991 partial : output ,
@@ -2991,7 +3015,7 @@ async function processOpenAICompletionsStream(
29913015 toolCallBlockBytes . set ( block , currentBlockArgBytes + nextArgumentBytes ) ;
29923016 block . partialArgs += toolCall . function . arguments ;
29933017 block . arguments = parseStreamingJson ( block . partialArgs ) ;
2994- stream . push ( {
3018+ pushStreamEvent ( {
29953019 type : "toolcall_delta" ,
29963020 contentIndex : output . content . indexOf ( block ) ,
29973021 delta : toolCall . function . arguments ,
@@ -3001,6 +3025,7 @@ async function processOpenAICompletionsStream(
30013025 }
30023026 }
30033027 flushPendingPostToolCallDeltas ( ) ;
3028+ emitReasoningUsageActivity ( hasReasoningUsageActivity ) ;
30043029 await cooperativeScheduler . afterEvent ( ) ;
30053030 }
30063031 flushReasoningTagTextPartitionerAtEnd ( ) ;
@@ -4207,6 +4232,15 @@ export function parseTransportChunkUsage(
42074232 return usage ;
42084233}
42094234
4235+ function hasOpenAICompletionsReasoningUsageActivity (
4236+ rawUsage : NonNullable < ChatCompletionChunk [ "usage" ] > ,
4237+ ) {
4238+ const reasoningTokens = rawUsage . completion_tokens_details ?. reasoning_tokens ;
4239+ return (
4240+ typeof reasoningTokens === "number" && Number . isFinite ( reasoningTokens ) && reasoningTokens > 0
4241+ ) ;
4242+ }
4243+
42104244function mapStopReason ( reason : string | null ) {
42114245 if ( reason === null ) {
42124246 return { stopReason : "stop" } ;
0 commit comments