@@ -331,6 +331,48 @@ describe('doGenerate', () => {
331331 ] ) ;
332332 } ) ;
333333
334+ it ( 'should prioritize reasoning_details over reasoning when both are present' , async ( ) => {
335+ prepareJsonResponse ( {
336+ content : 'Hello!' ,
337+ reasoning : 'This should be ignored when reasoning_details is present' ,
338+ reasoning_details : [
339+ {
340+ type : ReasoningDetailType . Text ,
341+ text : 'Processing from reasoning_details...' ,
342+ } ,
343+ {
344+ type : ReasoningDetailType . Summary ,
345+ summary : 'Summary from reasoning_details' ,
346+ } ,
347+ ] ,
348+ } ) ;
349+
350+ const result = await model . doGenerate ( {
351+ prompt : TEST_PROMPT ,
352+ } ) ;
353+
354+ expect ( result . content ) . toStrictEqual ( [
355+ {
356+ type : 'reasoning' ,
357+ text : 'Processing from reasoning_details...' ,
358+ } ,
359+ {
360+ type : 'reasoning' ,
361+ text : 'Summary from reasoning_details' ,
362+ } ,
363+ {
364+ type : 'text' ,
365+ text : 'Hello!' ,
366+ } ,
367+ ] ) ;
368+
369+ // Verify that the reasoning field content is not included
370+ expect ( result . content ) . not . toContainEqual ( {
371+ type : 'reasoning' ,
372+ text : 'This should be ignored when reasoning_details is present' ,
373+ } ) ;
374+ } ) ;
375+
334376 it ( 'should pass the model and the messages' , async ( ) => {
335377 prepareJsonResponse ( { content : '' } ) ;
336378
@@ -622,6 +664,81 @@ describe('doStream', () => {
622664 ] ) ;
623665 } ) ;
624666
667+ it ( 'should prioritize reasoning_details over reasoning when both are present in streaming' , async ( ) => {
668+ // This test verifies that when the API returns both 'reasoning' and 'reasoning_details' fields,
669+ // we prioritize reasoning_details and ignore the reasoning field to avoid duplicates.
670+ server . urls [ 'https://openrouter.ai/api/v1/chat/completions' ] ! . response = {
671+ type : 'stream-chunks' ,
672+ chunks : [
673+ // First chunk: both reasoning and reasoning_details with different content
674+ `data: {"id":"chatcmpl-reasoning","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
675+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"role":"assistant","content":"",` +
676+ `"reasoning":"This should be ignored...",` +
677+ `"reasoning_details":[{"type":"${ ReasoningDetailType . Text } ","text":"Let me think about this..."}]},` +
678+ `"logprobs":null,"finish_reason":null}]}\n\n` ,
679+ // Second chunk: reasoning_details with multiple types
680+ `data: {"id":"chatcmpl-reasoning","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
681+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{` +
682+ `"reasoning":"Also ignored",` +
683+ `"reasoning_details":[{"type":"${ ReasoningDetailType . Summary } ","summary":"User wants a greeting"},{"type":"${ ReasoningDetailType . Encrypted } ","data":"secret"}]},` +
684+ `"logprobs":null,"finish_reason":null}]}\n\n` ,
685+ // Third chunk: only reasoning field (should be processed)
686+ `data: {"id":"chatcmpl-reasoning","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
687+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{` +
688+ `"reasoning":"This reasoning is used"},` +
689+ `"logprobs":null,"finish_reason":null}]}\n\n` ,
690+ // Content chunk
691+ `data: {"id":"chatcmpl-reasoning","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
692+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{"content":"Hello!"},` +
693+ `"logprobs":null,"finish_reason":null}]}\n\n` ,
694+ // Finish chunk
695+ `data: {"id":"chatcmpl-reasoning","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
696+ `"system_fingerprint":"fp_3bc1b5746c","choices":[{"index":0,"delta":{},` +
697+ `"logprobs":null,"finish_reason":"stop"}]}\n\n` ,
698+ `data: {"id":"chatcmpl-reasoning","object":"chat.completion.chunk","created":1711357598,"model":"gpt-3.5-turbo-0125",` +
699+ `"system_fingerprint":"fp_3bc1b5746c","choices":[],"usage":{"prompt_tokens":17,"completion_tokens":30,"total_tokens":47}}\n\n` ,
700+ 'data: [DONE]\n\n' ,
701+ ] ,
702+ } ;
703+
704+ const { stream } = await model . doStream ( {
705+ prompt : TEST_PROMPT ,
706+ } ) ;
707+
708+ const elements = await convertReadableStreamToArray ( stream ) ;
709+
710+ // Filter for reasoning-related elements
711+ const reasoningElements = elements . filter ( el =>
712+ el . type === 'reasoning-start' ||
713+ el . type === 'reasoning-delta' ||
714+ el . type === 'reasoning-end'
715+ ) ;
716+
717+ // Debug output to see what we're getting
718+ // console.log('Reasoning elements count:', reasoningElements.length);
719+ // console.log('Reasoning element types:', reasoningElements.map(el => el.type));
720+
721+ // We should get reasoning content from reasoning_details when present, not reasoning field
722+ // start + 4 deltas (text, summary, encrypted, reasoning-only) + end = 6
723+ expect ( reasoningElements ) . toHaveLength ( 6 ) ;
724+
725+ // Verify the content comes from reasoning_details, not reasoning field
726+ const reasoningDeltas = reasoningElements
727+ . filter ( el => el . type === 'reasoning-delta' )
728+ . map ( el => ( el as { type : 'reasoning-delta' ; delta : string ; id : string } ) . delta ) ;
729+
730+ expect ( reasoningDeltas ) . toEqual ( [
731+ 'Let me think about this...' , // from reasoning_details text
732+ 'User wants a greeting' , // from reasoning_details summary
733+ '[REDACTED]' , // from reasoning_details encrypted
734+ 'This reasoning is used' , // from reasoning field (no reasoning_details)
735+ ] ) ;
736+
737+ // Verify that "This should be ignored..." and "Also ignored" are NOT in the output
738+ expect ( reasoningDeltas ) . not . toContain ( 'This should be ignored...' ) ;
739+ expect ( reasoningDeltas ) . not . toContain ( 'Also ignored' ) ;
740+ } ) ;
741+
625742 it ( 'should stream tool deltas' , async ( ) => {
626743 server . urls [ 'https://openrouter.ai/api/v1/chat/completions' ] ! . response = {
627744 type : 'stream-chunks' ,
0 commit comments