@@ -312,4 +312,124 @@ describe('runAgentSession', () => {
312312 const callArgs = mockStreamText . mock . calls [ 0 ] [ 0 ] ;
313313 expect ( callArgs . stopWhen ) . toEqual ( { type : 'stepCount' , count : 500 } ) ;
314314 } ) ;
315+
316+ // ===========================================================================
317+ // Reasoning / thinking_blocks sanitization (#1988)
318+ // ===========================================================================
319+
320+ it ( 'should strip reasoning parts from messages when provider is not Claude' , async ( ) => {
321+ mockStreamText . mockReturnValue (
322+ createMockStreamResult ( [ ] , { text : '' , totalUsage : { inputTokens : 0 , outputTokens : 0 } } ) ,
323+ ) ;
324+
325+ const messagesWithReasoning = [
326+ { role : 'user' as const , content : 'Hello' } ,
327+ // Simulate an assistant message that contains reasoning parts (e.g. from a prior Claude session)
328+ {
329+ role : 'assistant' as const ,
330+ content : [
331+ { type : 'reasoning' , text : 'Let me think…' } ,
332+ { type : 'text' , text : 'The answer is 42.' } ,
333+ ] as unknown as string ,
334+ } ,
335+ ] ;
336+
337+ // Non-Claude model — reasoning parts must be stripped
338+ await runAgentSession (
339+ createMockConfig ( {
340+ model : 'gpt-4o' as any ,
341+ initialMessages : messagesWithReasoning ,
342+ } ) ,
343+ ) ;
344+
345+ const { messages } = mockStreamText . mock . calls [ 0 ] [ 0 ] ;
346+ const assistantMsg = messages . find ( ( m : { role : string } ) => m . role === 'assistant' ) ;
347+ expect ( assistantMsg ) . toBeDefined ( ) ;
348+ // Content should be an array with only the text part
349+ expect ( Array . isArray ( assistantMsg . content ) ) . toBe ( true ) ;
350+ const parts = assistantMsg . content as Array < { type : string } > ;
351+ expect ( parts . every ( ( p ) => p . type !== 'reasoning' ) ) . toBe ( true ) ;
352+ expect ( parts . some ( ( p ) => p . type === 'text' ) ) . toBe ( true ) ;
353+ } ) ;
354+
355+ it ( 'should preserve reasoning parts when provider is Claude' , async ( ) => {
356+ mockStreamText . mockReturnValue (
357+ createMockStreamResult ( [ ] , { text : '' , totalUsage : { inputTokens : 0 , outputTokens : 0 } } ) ,
358+ ) ;
359+
360+ const messagesWithReasoning = [
361+ { role : 'user' as const , content : 'Hello' } ,
362+ {
363+ role : 'assistant' as const ,
364+ content : [
365+ { type : 'reasoning' , text : 'Let me think…' } ,
366+ { type : 'text' , text : 'The answer is 42.' } ,
367+ ] as unknown as string ,
368+ } ,
369+ ] ;
370+
371+ // Claude model — reasoning parts must be passed through unchanged
372+ await runAgentSession (
373+ createMockConfig ( {
374+ model : 'claude-sonnet-4-5' as any ,
375+ initialMessages : messagesWithReasoning ,
376+ } ) ,
377+ ) ;
378+
379+ const { messages } = mockStreamText . mock . calls [ 0 ] [ 0 ] ;
380+ const assistantMsg = messages . find ( ( m : { role : string } ) => m . role === 'assistant' ) ;
381+ expect ( assistantMsg ) . toBeDefined ( ) ;
382+ expect ( Array . isArray ( assistantMsg . content ) ) . toBe ( true ) ;
383+ const parts = assistantMsg . content as Array < { type : string } > ;
384+ expect ( parts . some ( ( p ) => p . type === 'reasoning' ) ) . toBe ( true ) ;
385+ } ) ;
386+
387+ it ( 'should handle redacted-reasoning parts the same as reasoning for non-Claude providers' , async ( ) => {
388+ mockStreamText . mockReturnValue (
389+ createMockStreamResult ( [ ] , { text : '' , totalUsage : { inputTokens : 0 , outputTokens : 0 } } ) ,
390+ ) ;
391+
392+ const messagesWithRedacted = [
393+ { role : 'user' as const , content : 'Hello' } ,
394+ {
395+ role : 'assistant' as const ,
396+ content : [
397+ { type : 'redacted-reasoning' , data : 'encrypted…' } ,
398+ { type : 'text' , text : 'Done.' } ,
399+ ] as unknown as string ,
400+ } ,
401+ ] ;
402+
403+ await runAgentSession (
404+ createMockConfig ( {
405+ model : 'gpt-4o' as any ,
406+ initialMessages : messagesWithRedacted ,
407+ } ) ,
408+ ) ;
409+
410+ const { messages } = mockStreamText . mock . calls [ 0 ] [ 0 ] ;
411+ const assistantMsg = messages . find ( ( m : { role : string } ) => m . role === 'assistant' ) ;
412+ const parts = assistantMsg . content as Array < { type : string } > ;
413+ expect ( parts . every ( ( p ) => p . type !== 'redacted-reasoning' ) ) . toBe ( true ) ;
414+ } ) ;
415+
416+ it ( 'should not modify string content messages regardless of provider' , async ( ) => {
417+ mockStreamText . mockReturnValue (
418+ createMockStreamResult ( [ ] , { text : '' , totalUsage : { inputTokens : 0 , outputTokens : 0 } } ) ,
419+ ) ;
420+
421+ await runAgentSession (
422+ createMockConfig ( {
423+ model : 'gpt-4o' as any ,
424+ initialMessages : [
425+ { role : 'user' , content : 'Hello' } ,
426+ { role : 'assistant' , content : 'Hi there.' } ,
427+ ] ,
428+ } ) ,
429+ ) ;
430+
431+ const { messages } = mockStreamText . mock . calls [ 0 ] [ 0 ] ;
432+ const assistantMsg = messages . find ( ( m : { role : string } ) => m . role === 'assistant' ) ;
433+ expect ( assistantMsg . content ) . toBe ( 'Hi there.' ) ;
434+ } ) ;
315435} ) ;
0 commit comments