@@ -29,65 +29,13 @@ describe("modelMessageTransform", () => {
2929 expect ( result ) . toEqual ( messages ) ;
3030 } ) ;
3131
32- it ( "should keep text-only messages unchanged" , ( ) => {
33- const assistantMsg1 : AssistantModelMessage = {
34- role : "assistant" ,
35- content : [ { type : "text" , text : "Let me help you with that." } ] ,
36- } ;
37- const assistantMsg2 : AssistantModelMessage = {
38- role : "assistant" ,
39- content : [ { type : "text" , text : "Here's the result." } ] ,
40- } ;
41- const messages : ModelMessage [ ] = [ assistantMsg1 , assistantMsg2 ] ;
42-
43- const result = transformModelMessages ( messages , "anthropic" ) ;
44- expect ( result ) . toEqual ( messages ) ;
45- } ) ;
46-
47- it ( "should strip tool calls without results (interrupted mixed content)" , ( ) => {
32+ it ( "should split mixed text and tool-call content into ordered segments" , ( ) => {
4833 const assistantMsg : AssistantModelMessage = {
4934 role : "assistant" ,
5035 content : [
51- { type : "text" , text : "Let me check that for you." } ,
52- { type : "tool-call" , toolCallId : "call1" , toolName : "bash" , input : { script : "ls" } } ,
53- ] ,
54- } ;
55- const messages : ModelMessage [ ] = [ assistantMsg ] ;
56-
57- const result = transformModelMessages ( messages , "anthropic" ) ;
58-
59- // Should only keep text, strip interrupted tool calls
60- expect ( result ) . toHaveLength ( 1 ) ;
61- expect ( result [ 0 ] . role ) . toBe ( "assistant" ) ;
62- expect ( ( result [ 0 ] as AssistantModelMessage ) . content ) . toEqual ( [
63- { type : "text" , text : "Let me check that for you." } ,
64- ] ) ;
65- } ) ;
66-
67- it ( "should strip tool-only messages without results (orphaned tool calls)" , ( ) => {
68- const assistantMsg : AssistantModelMessage = {
69- role : "assistant" ,
70- content : [
71- { type : "tool-call" , toolCallId : "call1" , toolName : "bash" , input : { script : "ls" } } ,
72- ] ,
73- } ;
74- const messages : ModelMessage [ ] = [ assistantMsg ] ;
75-
76- const result = transformModelMessages ( messages , "anthropic" ) ;
77-
78- // Should filter out the entire message since it only has orphaned tool calls
79- expect ( result ) . toHaveLength ( 0 ) ;
80- } ) ;
81-
82- it ( "should handle partial results (some tool calls interrupted)" , ( ) => {
83- // Assistant makes 3 tool calls, but only 2 have results (3rd was interrupted)
84- const assistantMsg : AssistantModelMessage = {
85- role : "assistant" ,
86- content : [
87- { type : "text" , text : "Let me check a few things." } ,
36+ { type : "text" , text : "Before" } ,
8837 { type : "tool-call" , toolCallId : "call1" , toolName : "bash" , input : { script : "pwd" } } ,
89- { type : "tool-call" , toolCallId : "call2" , toolName : "bash" , input : { script : "ls" } } ,
90- { type : "tool-call" , toolCallId : "call3" , toolName : "bash" , input : { script : "date" } } ,
38+ { type : "text" , text : "After" } ,
9139 ] ,
9240 } ;
9341 const toolMsg : ToolModelMessage = {
@@ -99,80 +47,41 @@ describe("modelMessageTransform", () => {
9947 toolName : "bash" ,
10048 output : { type : "json" , value : { stdout : "/home/user" } } ,
10149 } ,
102- {
103- type : "tool-result" ,
104- toolCallId : "call2" ,
105- toolName : "bash" ,
106- output : { type : "json" , value : { stdout : "file1 file2" } } ,
107- } ,
108- // call3 has no result (interrupted)
10950 ] ,
11051 } ;
111- const messages : ModelMessage [ ] = [ assistantMsg , toolMsg ] ;
11252
113- const result = transformModelMessages ( messages , "anthropic" ) ;
53+ const result = transformModelMessages ( [ assistantMsg , toolMsg ] , "anthropic" ) ;
11454
115- // Should have: text message, tool calls (only call1 & call2), tool results
116- expect ( result ) . toHaveLength ( 3 ) ;
117-
118- // First: text
55+ expect ( result ) . toHaveLength ( 4 ) ;
11956 expect ( result [ 0 ] . role ) . toBe ( "assistant" ) ;
12057 expect ( ( result [ 0 ] as AssistantModelMessage ) . content ) . toEqual ( [
121- { type : "text" , text : "Let me check a few things. " } ,
58+ { type : "text" , text : "Before " } ,
12259 ] ) ;
123-
124- // Second: only tool calls with results (call1, call2), NOT call3
12560 expect ( result [ 1 ] . role ) . toBe ( "assistant" ) ;
126- const toolCallContent = ( result [ 1 ] as AssistantModelMessage ) . content ;
127- expect ( Array . isArray ( toolCallContent ) ) . toBe ( true ) ;
128- if ( Array . isArray ( toolCallContent ) ) {
129- expect ( toolCallContent ) . toHaveLength ( 2 ) ;
130- expect ( toolCallContent [ 0 ] ) . toEqual ( {
131- type : "tool-call" ,
132- toolCallId : "call1" ,
133- toolName : "bash" ,
134- input : { script : "pwd" } ,
135- } ) ;
136- expect ( toolCallContent [ 1 ] ) . toEqual ( {
137- type : "tool-call" ,
138- toolCallId : "call2" ,
139- toolName : "bash" ,
140- input : { script : "ls" } ,
141- } ) ;
142- }
143-
144- // Third: tool results (only for call1 & call2)
61+ expect ( ( result [ 1 ] as AssistantModelMessage ) . content ) . toEqual ( [
62+ { type : "tool-call" , toolCallId : "call1" , toolName : "bash" , input : { script : "pwd" } } ,
63+ ] ) ;
14564 expect ( result [ 2 ] . role ) . toBe ( "tool" ) ;
146- const toolResultContent = ( result [ 2 ] as ToolModelMessage ) . content ;
147- expect ( toolResultContent ) . toHaveLength ( 2 ) ;
148- expect ( toolResultContent [ 0 ] ) . toEqual ( {
65+ expect ( ( result [ 2 ] as ToolModelMessage ) . content [ 0 ] ) . toEqual ( {
14966 type : "tool-result" ,
15067 toolCallId : "call1" ,
15168 toolName : "bash" ,
15269 output : { type : "json" , value : { stdout : "/home/user" } } ,
15370 } ) ;
154- expect ( toolResultContent [ 1 ] ) . toEqual ( {
155- type : "tool-result" ,
156- toolCallId : "call2" ,
157- toolName : "bash" ,
158- output : { type : "json" , value : { stdout : "file1 file2" } } ,
159- } ) ;
71+ expect ( result [ 3 ] . role ) . toBe ( "assistant" ) ;
72+ expect ( ( result [ 3 ] as AssistantModelMessage ) . content ) . toEqual ( [
73+ { type : "text" , text : "After" } ,
74+ ] ) ;
16075 } ) ;
16176
162- it ( "should handle mixed content with tool results properly " , ( ) => {
77+ it ( "should interleave multiple tool-call groups with their results" , ( ) => {
16378 const assistantMsg : AssistantModelMessage = {
16479 role : "assistant" ,
16580 content : [
166- { type : "text" , text : "First, let me check something. " } ,
81+ { type : "text" , text : "Step 1 " } ,
16782 { type : "tool-call" , toolCallId : "call1" , toolName : "bash" , input : { script : "pwd" } } ,
83+ { type : "text" , text : "Step 2" } ,
16884 { type : "tool-call" , toolCallId : "call2" , toolName : "bash" , input : { script : "ls" } } ,
169- { type : "text" , text : "Now let me check another thing." } ,
170- {
171- type : "tool-call" ,
172- toolCallId : "call3" ,
173- toolName : "file_read" ,
174- input : { path : "test.txt" } ,
175- } ,
17685 ] ,
17786 } ;
17887 const toolMsg : ToolModelMessage = {
@@ -182,45 +91,60 @@ describe("modelMessageTransform", () => {
18291 type : "tool-result" ,
18392 toolCallId : "call1" ,
18493 toolName : "bash" ,
185- output : { type : "json" , value : { stdout : "/home/user " } } ,
94+ output : { type : "json" , value : { stdout : "/workspace " } } ,
18695 } ,
18796 {
18897 type : "tool-result" ,
18998 toolCallId : "call2" ,
19099 toolName : "bash" ,
191- output : { type : "json" , value : { stdout : "file1 file2" } } ,
192- } ,
193- {
194- type : "tool-result" ,
195- toolCallId : "call3" ,
196- toolName : "file_read" ,
197- output : { type : "json" , value : { content : "test content" } } ,
100+ output : { type : "json" , value : { stdout : "file.txt" } } ,
198101 } ,
199102 ] ,
200103 } ;
201- const messages : ModelMessage [ ] = [ assistantMsg , toolMsg ] ;
202104
203- const result = transformModelMessages ( messages , "anthropic" ) ;
105+ const result = transformModelMessages ( [ assistantMsg , toolMsg ] , "anthropic" ) ;
204106
205- // Should split into multiple messages with tool results properly placed
206- expect ( result . length ) . toBeGreaterThan ( 2 ) ;
207-
208- // First should be text
107+ expect ( result ) . toHaveLength ( 6 ) ;
209108 expect ( result [ 0 ] . role ) . toBe ( "assistant" ) ;
210109 expect ( ( result [ 0 ] as AssistantModelMessage ) . content ) . toEqual ( [
211- { type : "text" , text : "First, let me check something. " } ,
110+ { type : "text" , text : "Step 1 " } ,
212111 ] ) ;
213-
214- // Then tool calls with their results
215112 expect ( result [ 1 ] . role ) . toBe ( "assistant" ) ;
216- const secondContent = ( result [ 1 ] as AssistantModelMessage ) . content ;
217- expect ( Array . isArray ( secondContent ) ) . toBe ( true ) ;
218- if ( Array . isArray ( secondContent ) ) {
219- expect ( secondContent . some ( ( c ) => c . type === "tool-call" ) ) . toBe ( true ) ;
220- }
221-
222- // Tool results should follow tool calls
113+ expect ( ( result [ 1 ] as AssistantModelMessage ) . content [ 0 ] ) . toEqual ( {
114+ type : "tool-call" ,
115+ toolCallId : "call1" ,
116+ toolName : "bash" ,
117+ input : { script : "pwd" } ,
118+ } ) ;
223119 expect ( result [ 2 ] . role ) . toBe ( "tool" ) ;
120+ expect ( ( result [ 2 ] as ToolModelMessage ) . content [ 0 ] ) . toMatchObject ( { toolCallId : "call1" } ) ;
121+ expect ( result [ 3 ] . role ) . toBe ( "assistant" ) ;
122+ expect ( ( result [ 3 ] as AssistantModelMessage ) . content ) . toEqual ( [
123+ { type : "text" , text : "Step 2" } ,
124+ ] ) ;
125+ expect ( result [ 4 ] . role ) . toBe ( "assistant" ) ;
126+ expect ( ( result [ 4 ] as AssistantModelMessage ) . content [ 0 ] ) . toEqual ( {
127+ type : "tool-call" ,
128+ toolCallId : "call2" ,
129+ toolName : "bash" ,
130+ input : { script : "ls" } ,
131+ } ) ;
132+ expect ( result [ 5 ] . role ) . toBe ( "tool" ) ;
133+ expect ( ( result [ 5 ] as ToolModelMessage ) . content [ 0 ] ) . toMatchObject ( { toolCallId : "call2" } ) ;
134+ } ) ;
135+ it ( "should keep text-only messages unchanged" , ( ) => {
136+ const assistantMsg1 : AssistantModelMessage = {
137+ role : "assistant" ,
138+ content : [ { type : "text" , text : "Let me help you with that." } ] ,
139+ } ;
140+ const assistantMsg2 : AssistantModelMessage = {
141+ role : "assistant" ,
142+ content : [ { type : "text" , text : "Here's the result." } ] ,
143+ } ;
144+ const messages : ModelMessage [ ] = [ assistantMsg1 , assistantMsg2 ] ;
145+
146+ const result = transformModelMessages ( messages , "anthropic" ) ;
147+ expect ( result ) . toEqual ( messages ) ;
224148 } ) ;
225149 } ) ;
226150
@@ -659,10 +583,10 @@ describe("modelMessageTransform", () => {
659583
660584 const result = transformModelMessages ( messages , "openai" ) ;
661585
662- // Should have user, text, tool-call, tool-result (no reasoning)
586+ // Should still contain user, assistant, and tool messages after filtering
663587 expect ( result . length ) . toBeGreaterThan ( 2 ) ;
664588
665- // Find the assistant message with text
589+ // Find the assistant message with text (reasoning should remain alongside text)
666590 const textMessage = result . find ( ( msg ) => {
667591 if ( msg . role !== "assistant" ) return false ;
668592 const content = msg . content ;
@@ -672,9 +596,7 @@ describe("modelMessageTransform", () => {
672596 if ( textMessage ) {
673597 const content = ( textMessage as AssistantModelMessage ) . content ;
674598 if ( Array . isArray ( content ) ) {
675- // Should not have reasoning parts
676- expect ( content . some ( ( c ) => c . type === "reasoning" ) ) . toBe ( false ) ;
677- // Should have text
599+ expect ( content . some ( ( c ) => c . type === "reasoning" ) ) . toBe ( true ) ;
678600 expect ( content . some ( ( c ) => c . type === "text" ) ) . toBe ( true ) ;
679601 }
680602 }
0 commit comments