@@ -542,4 +542,206 @@ describe('OpenAIContentConverter', () => {
542542 expect ( original ) . toEqual ( originalCopy ) ;
543543 } ) ;
544544 } ) ;
545+
546+ describe ( 'mergeConsecutiveAssistantMessages' , ( ) => {
547+ it ( 'should merge two consecutive assistant messages with string content' , ( ) => {
548+ const request : GenerateContentParameters = {
549+ model : 'models/test' ,
550+ contents : [
551+ {
552+ role : 'model' ,
553+ parts : [ { text : 'First part' } ] ,
554+ } ,
555+ {
556+ role : 'model' ,
557+ parts : [ { text : 'Second part' } ] ,
558+ } ,
559+ ] ,
560+ } ;
561+
562+ const messages = converter . convertGeminiRequestToOpenAI ( request ) ;
563+
564+ expect ( messages ) . toHaveLength ( 1 ) ;
565+ expect ( messages [ 0 ] . role ) . toBe ( 'assistant' ) ;
566+ const content = messages [ 0 ]
567+ . content as OpenAI . Chat . ChatCompletionContentPart [ ] ;
568+ expect ( content ) . toHaveLength ( 2 ) ;
569+ expect ( content [ 0 ] ) . toEqual ( { type : 'text' , text : 'First part' } ) ;
570+ expect ( content [ 1 ] ) . toEqual ( { type : 'text' , text : 'Second part' } ) ;
571+ } ) ;
572+
573+ it ( 'should merge multiple consecutive assistant messages' , ( ) => {
574+ const request : GenerateContentParameters = {
575+ model : 'models/test' ,
576+ contents : [
577+ {
578+ role : 'model' ,
579+ parts : [ { text : 'Part 1' } ] ,
580+ } ,
581+ {
582+ role : 'model' ,
583+ parts : [ { text : 'Part 2' } ] ,
584+ } ,
585+ {
586+ role : 'model' ,
587+ parts : [ { text : 'Part 3' } ] ,
588+ } ,
589+ ] ,
590+ } ;
591+
592+ const messages = converter . convertGeminiRequestToOpenAI ( request ) ;
593+
594+ expect ( messages ) . toHaveLength ( 1 ) ;
595+ expect ( messages [ 0 ] . role ) . toBe ( 'assistant' ) ;
596+ const content = messages [ 0 ]
597+ . content as OpenAI . Chat . ChatCompletionContentPart [ ] ;
598+ expect ( content ) . toHaveLength ( 3 ) ;
599+ } ) ;
600+
601+ it ( 'should merge tool_calls from consecutive assistant messages' , ( ) => {
602+ const request : GenerateContentParameters = {
603+ model : 'models/test' ,
604+ contents : [
605+ {
606+ role : 'model' ,
607+ parts : [
608+ {
609+ functionCall : {
610+ id : 'call_1' ,
611+ name : 'tool_1' ,
612+ args : { } ,
613+ } ,
614+ } ,
615+ ] ,
616+ } ,
617+ {
618+ role : 'user' ,
619+ parts : [
620+ {
621+ functionResponse : {
622+ id : 'call_1' ,
623+ name : 'tool_1' ,
624+ response : { output : 'result_1' } ,
625+ } ,
626+ } ,
627+ ] ,
628+ } ,
629+ {
630+ role : 'model' ,
631+ parts : [
632+ {
633+ functionCall : {
634+ id : 'call_2' ,
635+ name : 'tool_2' ,
636+ args : { } ,
637+ } ,
638+ } ,
639+ ] ,
640+ } ,
641+ {
642+ role : 'user' ,
643+ parts : [
644+ {
645+ functionResponse : {
646+ id : 'call_2' ,
647+ name : 'tool_2' ,
648+ response : { output : 'result_2' } ,
649+ } ,
650+ } ,
651+ ] ,
652+ } ,
653+ ] ,
654+ } ;
655+
656+ const messages = converter . convertGeminiRequestToOpenAI ( request ) ;
657+
658+ // Should have: assistant (tool_call_1), tool (result_1), assistant (tool_call_2), tool (result_2)
659+ expect ( messages ) . toHaveLength ( 4 ) ;
660+ expect ( messages [ 0 ] . role ) . toBe ( 'assistant' ) ;
661+ expect ( messages [ 1 ] . role ) . toBe ( 'tool' ) ;
662+ expect ( messages [ 2 ] . role ) . toBe ( 'assistant' ) ;
663+ expect ( messages [ 3 ] . role ) . toBe ( 'tool' ) ;
664+ } ) ;
665+
666+ it ( 'should not merge assistant messages separated by user messages' , ( ) => {
667+ const request : GenerateContentParameters = {
668+ model : 'models/test' ,
669+ contents : [
670+ {
671+ role : 'model' ,
672+ parts : [ { text : 'First assistant' } ] ,
673+ } ,
674+ {
675+ role : 'user' ,
676+ parts : [ { text : 'User message' } ] ,
677+ } ,
678+ {
679+ role : 'model' ,
680+ parts : [ { text : 'Second assistant' } ] ,
681+ } ,
682+ ] ,
683+ } ;
684+
685+ const messages = converter . convertGeminiRequestToOpenAI ( request ) ;
686+
687+ expect ( messages ) . toHaveLength ( 3 ) ;
688+ expect ( messages [ 0 ] . role ) . toBe ( 'assistant' ) ;
689+ expect ( messages [ 1 ] . role ) . toBe ( 'user' ) ;
690+ expect ( messages [ 2 ] . role ) . toBe ( 'assistant' ) ;
691+ } ) ;
692+
693+ it ( 'should handle merging when one message has array content and another has string' , ( ) => {
694+ const request : GenerateContentParameters = {
695+ model : 'models/test' ,
696+ contents : [
697+ {
698+ role : 'model' ,
699+ parts : [ { text : 'Text part' } ] ,
700+ } ,
701+ {
702+ role : 'model' ,
703+ parts : [ { text : 'Another text' } ] ,
704+ } ,
705+ ] ,
706+ } ;
707+
708+ const messages = converter . convertGeminiRequestToOpenAI ( request ) ;
709+
710+ expect ( messages ) . toHaveLength ( 1 ) ;
711+ const content = messages [ 0 ]
712+ . content as OpenAI . Chat . ChatCompletionContentPart [ ] ;
713+ expect ( Array . isArray ( content ) ) . toBe ( true ) ;
714+ expect ( content ) . toHaveLength ( 2 ) ;
715+ } ) ;
716+
717+ it ( 'should merge empty content correctly' , ( ) => {
718+ const request : GenerateContentParameters = {
719+ model : 'models/test' ,
720+ contents : [
721+ {
722+ role : 'model' ,
723+ parts : [ { text : 'First' } ] ,
724+ } ,
725+ {
726+ role : 'model' ,
727+ parts : [ ] ,
728+ } ,
729+ {
730+ role : 'model' ,
731+ parts : [ { text : 'Second' } ] ,
732+ } ,
733+ ] ,
734+ } ;
735+
736+ const messages = converter . convertGeminiRequestToOpenAI ( request ) ;
737+
738+ // Empty messages should be filtered out
739+ expect ( messages ) . toHaveLength ( 1 ) ;
740+ const content = messages [ 0 ]
741+ . content as OpenAI . Chat . ChatCompletionContentPart [ ] ;
742+ expect ( content ) . toHaveLength ( 2 ) ;
743+ expect ( content [ 0 ] ) . toEqual ( { type : 'text' , text : 'First' } ) ;
744+ expect ( content [ 1 ] ) . toEqual ( { type : 'text' , text : 'Second' } ) ;
745+ } ) ;
746+ } ) ;
545747} ) ;
0 commit comments