@@ -638,145 +638,6 @@ describe("OpenAiHandler", () => {
638638 } )
639639
640640 describe ( "TagMatcher reasoning tags" , ( ) => {
641- it ( "should handle <think> tags from stream" , async ( ) => {
642- mockCreate . mockImplementationOnce ( ( ) => ( {
643- [ Symbol . asyncIterator ] : ( ) => ( {
644- next : vi
645- . fn ( )
646- . mockResolvedValueOnce ( {
647- done : false ,
648- value : { choices : [ { delta : { content : "<think>Let me think" } } ] } ,
649- } )
650- . mockResolvedValueOnce ( {
651- done : false ,
652- value : { choices : [ { delta : { content : " about this</think>" } } ] } ,
653- } )
654- . mockResolvedValueOnce ( {
655- done : false ,
656- value : { choices : [ { delta : { content : "The answer is 42" } } ] } ,
657- } )
658- . mockResolvedValueOnce ( { done : true } ) ,
659- } ) ,
660- } ) )
661-
662- const stream = handler . createMessage ( systemPrompt , messages )
663- const chunks : any [ ] = [ ]
664- for await ( const chunk of stream ) {
665- chunks . push ( chunk )
666- }
667-
668- expect ( chunks ) . toEqual ( [
669- { type : "reasoning" , text : "Let me think" } ,
670- { type : "reasoning" , text : " about this" } ,
671- { type : "text" , text : "The answer is 42" } ,
672- ] )
673- } )
674-
675- it ( "should handle <thought> tags from stream" , async ( ) => {
676- mockCreate . mockImplementationOnce ( ( ) => ( {
677- [ Symbol . asyncIterator ] : ( ) => ( {
678- next : vi
679- . fn ( )
680- . mockResolvedValueOnce ( {
681- done : false ,
682- value : { choices : [ { delta : { content : "<thought>Deep thought" } } ] } ,
683- } )
684- . mockResolvedValueOnce ( {
685- done : false ,
686- value : { choices : [ { delta : { content : " here</thought>" } } ] } ,
687- } )
688- . mockResolvedValueOnce ( {
689- done : false ,
690- value : { choices : [ { delta : { content : "Result: 42" } } ] } ,
691- } )
692- . mockResolvedValueOnce ( { done : true } ) ,
693- } ) ,
694- } ) )
695-
696- const stream = handler . createMessage ( systemPrompt , messages )
697- const chunks : any [ ] = [ ]
698- for await ( const chunk of stream ) {
699- chunks . push ( chunk )
700- }
701-
702- expect ( chunks ) . toEqual ( [
703- { type : "reasoning" , text : "Deep thought" } ,
704- { type : "reasoning" , text : " here" } ,
705- { type : "text" , text : "Result: 42" } ,
706- ] )
707- } )
708-
709- it ( "should not close <think> tag with </thought> tag" , async ( ) => {
710- mockCreate . mockImplementationOnce ( ( ) => ( {
711- [ Symbol . asyncIterator ] : ( ) => ( {
712- next : vi
713- . fn ( )
714- . mockResolvedValueOnce ( {
715- done : false ,
716- value : { choices : [ { delta : { content : "<think>Thinking" } } ] } ,
717- } )
718- . mockResolvedValueOnce ( {
719- done : false ,
720- value : { choices : [ { delta : { content : " but closing with wrong tag</thought>" } } ] } ,
721- } )
722- . mockResolvedValueOnce ( {
723- done : false ,
724- value : { choices : [ { delta : { content : " still thinking</think>" } } ] } ,
725- } )
726- . mockResolvedValueOnce ( {
727- done : false ,
728- value : { choices : [ { delta : { content : "final text" } } ] } ,
729- } )
730- . mockResolvedValueOnce ( { done : true } ) ,
731- } ) ,
732- } ) )
733-
734- const stream = handler . createMessage ( systemPrompt , messages )
735- const chunks : any [ ] = [ ]
736- for await ( const chunk of stream ) {
737- chunks . push ( chunk )
738- }
739-
740- // The </thought> tag should not match the active <think> tag, so the closing
741- // tag is treated as text. The " still thinking" stays reasoning since <think>
742- // was never closed with </think>.
743- expect ( chunks ) . toEqual ( [
744- { type : "reasoning" , text : "Thinking" } ,
745- { type : "reasoning" , text : " but closing with wrong tag</thought>" } ,
746- { type : "reasoning" , text : " still thinking" } ,
747- { type : "text" , text : "final text" } ,
748- ] )
749- } )
750-
751- it ( "should handle text without any tags" , async ( ) => {
752- mockCreate . mockImplementationOnce ( ( ) => ( {
753- [ Symbol . asyncIterator ] : ( ) => ( {
754- next : vi
755- . fn ( )
756- . mockResolvedValueOnce ( {
757- done : false ,
758- value : { choices : [ { delta : { content : "Just regular text" } } ] } ,
759- } )
760- . mockResolvedValueOnce ( {
761- done : false ,
762- value : { choices : [ { delta : { content : " without reasoning" } } ] } ,
763- } )
764- . mockResolvedValueOnce ( { done : true } ) ,
765- } ) ,
766- } ) )
767-
768- const stream = handler . createMessage ( systemPrompt , messages )
769- const chunks : any [ ] = [ ]
770- for await ( const chunk of stream ) {
771- chunks . push ( chunk )
772- }
773-
774- expect ( chunks ) . toEqual ( [
775- { type : "text" , text : "Just regular text" } ,
776- { type : "text" , text : " without reasoning" } ,
777- ] )
778- } )
779-
780641 it ( "should treat stray closing tag as plain text when no tag is open" , async ( ) => {
781642 mockCreate . mockImplementationOnce ( ( ) => ( {
782643 [ Symbol . asyncIterator ] : ( ) => ( {
@@ -826,101 +687,6 @@ describe("OpenAiHandler", () => {
826687 ] )
827688 } )
828689
829- it ( "should handle <think> tags that start at beginning of stream" , async ( ) => {
830- mockCreate . mockImplementationOnce ( ( ) => ( {
831- [ Symbol . asyncIterator ] : ( ) => ( {
832- next : vi
833- . fn ( )
834- . mockResolvedValueOnce ( {
835- done : false ,
836- value : { choices : [ { delta : { content : "<think>reasoning" } } ] } ,
837- } )
838- . mockResolvedValueOnce ( {
839- done : false ,
840- value : { choices : [ { delta : { content : " content</think>" } } ] } ,
841- } )
842- . mockResolvedValueOnce ( {
843- done : false ,
844- value : { choices : [ { delta : { content : " normal text" } } ] } ,
845- } )
846- . mockResolvedValueOnce ( { done : true } ) ,
847- } ) ,
848- } ) )
849-
850- const stream = handler . createMessage ( systemPrompt , messages )
851- const chunks : any [ ] = [ ]
852- for await ( const chunk of stream ) {
853- chunks . push ( chunk )
854- }
855-
856- expect ( chunks ) . toEqual ( [
857- { type : "reasoning" , text : "reasoning" } ,
858- { type : "reasoning" , text : " content" } ,
859- { type : "text" , text : " normal text" } ,
860- ] )
861- } )
862-
863- it ( "should handle incomplete <think> tag at end of stream" , async ( ) => {
864- mockCreate . mockImplementationOnce ( ( ) => ( {
865- [ Symbol . asyncIterator ] : ( ) => ( {
866- next : vi
867- . fn ( )
868- . mockResolvedValueOnce ( {
869- done : false ,
870- value : { choices : [ { delta : { content : "<think>Incomplete thought" } } ] } ,
871- } )
872- . mockResolvedValueOnce ( { done : true } ) ,
873- } ) ,
874- } ) )
875-
876- const stream = handler . createMessage ( systemPrompt , messages )
877- const chunks : any [ ] = [ ]
878- for await ( const chunk of stream ) {
879- chunks . push ( chunk )
880- }
881-
882- // TagMatcher should flush remaining reasoning content on final()
883- expect ( chunks . length ) . toBeGreaterThan ( 0 )
884- expect (
885- chunks . some (
886- ( c ) => ( c . type === "text" || c . type === "reasoning" ) && c . text . includes ( "Incomplete thought" ) ,
887- ) ,
888- ) . toBe ( true )
889- } )
890-
891- it ( "should handle complete <think> tag in a single chunk" , async ( ) => {
892- mockCreate . mockImplementationOnce ( ( ) => ( {
893- [ Symbol . asyncIterator ] : ( ) => ( {
894- next : vi
895- . fn ( )
896- . mockResolvedValueOnce ( {
897- done : false ,
898- value : { choices : [ { delta : { content : "text before " } } ] } ,
899- } )
900- . mockResolvedValueOnce ( {
901- done : false ,
902- value : { choices : [ { delta : { content : "<think>Complete thought</think>" } } ] } ,
903- } )
904- . mockResolvedValueOnce ( {
905- done : false ,
906- value : { choices : [ { delta : { content : " text after" } } ] } ,
907- } )
908- . mockResolvedValueOnce ( { done : true } ) ,
909- } ) ,
910- } ) )
911-
912- const stream = handler . createMessage ( systemPrompt , messages )
913- const chunks : any [ ] = [ ]
914- for await ( const chunk of stream ) {
915- chunks . push ( chunk )
916- }
917-
918- // The TagMatcher processes the whole chunk character by character,
919- // so the complete tag is detected and yields reasoning text
920- expect ( chunks . length ) . toBeGreaterThan ( 0 )
921- expect ( chunks [ 0 ] ) . toEqual ( { type : "text" , text : "text before " } )
922- } )
923-
924690 it ( "should handle nested mixed tags with correct closure matching" , async ( ) => {
925691 mockCreate . mockImplementationOnce ( ( ) => ( {
926692 [ Symbol . asyncIterator ] : ( ) => ( {
0 commit comments