@@ -827,3 +827,250 @@ func TestBuildInferenceConfig_SetsTempTopPWhenThinkingBudgetInvalid(t *testing.T
827827 require .NotNil (t , cfg .TopP )
828828 assert .InDelta (t , 0.9 , * cfg .TopP , 0.01 )
829829}
830+
831+ func TestInterleavedThinkingEnabled_True (t * testing.T ) {
832+ t .Parallel ()
833+
834+ client := & Client {
835+ Config : base.Config {
836+ ModelConfig : latest.ModelConfig {
837+ Provider : "amazon-bedrock" ,
838+ Model : "anthropic.claude-sonnet-4-20250514-v1:0" ,
839+ ProviderOpts : map [string ]any {
840+ "interleaved_thinking" : true ,
841+ },
842+ },
843+ },
844+ }
845+
846+ assert .True (t , client .interleavedThinkingEnabled ())
847+ }
848+
849+ func TestInterleavedThinkingEnabled_False (t * testing.T ) {
850+ t .Parallel ()
851+
852+ client := & Client {
853+ Config : base.Config {
854+ ModelConfig : latest.ModelConfig {
855+ Provider : "amazon-bedrock" ,
856+ Model : "anthropic.claude-sonnet-4-20250514-v1:0" ,
857+ ProviderOpts : map [string ]any {
858+ "interleaved_thinking" : false ,
859+ },
860+ },
861+ },
862+ }
863+
864+ assert .False (t , client .interleavedThinkingEnabled ())
865+ }
866+
867+ func TestInterleavedThinkingEnabled_NotSet (t * testing.T ) {
868+ t .Parallel ()
869+
870+ client := & Client {
871+ Config : base.Config {
872+ ModelConfig : latest.ModelConfig {
873+ Provider : "amazon-bedrock" ,
874+ Model : "anthropic.claude-sonnet-4-20250514-v1:0" ,
875+ ProviderOpts : map [string ]any {},
876+ },
877+ },
878+ }
879+
880+ assert .False (t , client .interleavedThinkingEnabled ())
881+ }
882+
883+ func TestInterleavedThinkingEnabled_NilProviderOpts (t * testing.T ) {
884+ t .Parallel ()
885+
886+ client := & Client {
887+ Config : base.Config {
888+ ModelConfig : latest.ModelConfig {
889+ Provider : "amazon-bedrock" ,
890+ Model : "anthropic.claude-sonnet-4-20250514-v1:0" ,
891+ ProviderOpts : nil ,
892+ },
893+ },
894+ }
895+
896+ assert .False (t , client .interleavedThinkingEnabled ())
897+ }
898+
899+ func TestBuildAdditionalModelRequestFields_WithInterleavedThinking (t * testing.T ) {
900+ t .Parallel ()
901+
902+ maxTokens := int64 (64000 )
903+ client := & Client {
904+ Config : base.Config {
905+ ModelConfig : latest.ModelConfig {
906+ Provider : "amazon-bedrock" ,
907+ Model : "anthropic.claude-sonnet-4-20250514-v1:0" ,
908+ MaxTokens : & maxTokens ,
909+ ThinkingBudget : & latest.ThinkingBudget {
910+ Tokens : 16384 ,
911+ },
912+ ProviderOpts : map [string ]any {
913+ "interleaved_thinking" : true ,
914+ },
915+ },
916+ },
917+ }
918+
919+ result := client .buildAdditionalModelRequestFields ()
920+
921+ require .NotNil (t , result , "expected document for valid thinking_budget with interleaved thinking" )
922+ // The document contains anthropic_beta when interleaved thinking is enabled
923+ // We can't easily inspect the lazy document contents, but we verify it's not nil
924+ }
925+
926+ func TestBuildAdditionalModelRequestFields_WithoutInterleavedThinking (t * testing.T ) {
927+ t .Parallel ()
928+
929+ maxTokens := int64 (64000 )
930+ client := & Client {
931+ Config : base.Config {
932+ ModelConfig : latest.ModelConfig {
933+ Provider : "amazon-bedrock" ,
934+ Model : "anthropic.claude-sonnet-4-20250514-v1:0" ,
935+ MaxTokens : & maxTokens ,
936+ ThinkingBudget : & latest.ThinkingBudget {
937+ Tokens : 16384 ,
938+ },
939+ // No interleaved_thinking in provider_opts
940+ ProviderOpts : map [string ]any {},
941+ },
942+ },
943+ }
944+
945+ result := client .buildAdditionalModelRequestFields ()
946+
947+ require .NotNil (t , result , "expected document for valid thinking_budget" )
948+ // Without interleaved thinking, no anthropic_beta header should be added
949+ // Basic thinking still works - this tests backward compatibility
950+ }
951+
952+ func TestConvertAssistantContent_WithThinkingBlocks (t * testing.T ) {
953+ t .Parallel ()
954+
955+ msg := & chat.Message {
956+ Role : chat .MessageRoleAssistant ,
957+ Content : "Here's my answer" ,
958+ ReasoningContent : "Let me think about this..." ,
959+ ThinkingSignature : "sig_abc123" ,
960+ }
961+
962+ blocks := convertAssistantContent (msg )
963+
964+ // Should have thinking block first, then text block
965+ require .Len (t , blocks , 2 )
966+
967+ // First block should be reasoning content
968+ reasoningBlock , ok := blocks [0 ].(* types.ContentBlockMemberReasoningContent )
969+ require .True (t , ok , "first block should be reasoning content" )
970+
971+ // Verify the reasoning content structure
972+ reasoningText , ok := reasoningBlock .Value .(* types.ReasoningContentBlockMemberReasoningText )
973+ require .True (t , ok , "reasoning value should be ReasoningText" )
974+ assert .Equal (t , "Let me think about this..." , * reasoningText .Value .Text )
975+ assert .Equal (t , "sig_abc123" , * reasoningText .Value .Signature )
976+
977+ // Second block should be text content
978+ textBlock , ok := blocks [1 ].(* types.ContentBlockMemberText )
979+ require .True (t , ok , "second block should be text content" )
980+ assert .Equal (t , "Here's my answer" , textBlock .Value )
981+ }
982+
983+ func TestConvertAssistantContent_WithoutThinkingBlocks (t * testing.T ) {
984+ t .Parallel ()
985+
986+ msg := & chat.Message {
987+ Role : chat .MessageRoleAssistant ,
988+ Content : "Here's my answer" ,
989+ // No ReasoningContent or ThinkingSignature
990+ }
991+
992+ blocks := convertAssistantContent (msg )
993+
994+ // Should only have text block
995+ require .Len (t , blocks , 1 )
996+
997+ textBlock , ok := blocks [0 ].(* types.ContentBlockMemberText )
998+ require .True (t , ok )
999+ assert .Equal (t , "Here's my answer" , textBlock .Value )
1000+ }
1001+
1002+ func TestConvertAssistantContent_MissingSignature (t * testing.T ) {
1003+ t .Parallel ()
1004+
1005+ // When only ReasoningContent is present but no signature,
1006+ // thinking block should NOT be included (signature required for multi-turn)
1007+ msg := & chat.Message {
1008+ Role : chat .MessageRoleAssistant ,
1009+ Content : "Here's my answer" ,
1010+ ReasoningContent : "Let me think..." ,
1011+ ThinkingSignature : "" , // Missing signature
1012+ }
1013+
1014+ blocks := convertAssistantContent (msg )
1015+
1016+ // Should only have text block (no thinking block without signature)
1017+ require .Len (t , blocks , 1 )
1018+
1019+ textBlock , ok := blocks [0 ].(* types.ContentBlockMemberText )
1020+ require .True (t , ok )
1021+ assert .Equal (t , "Here's my answer" , textBlock .Value )
1022+ }
1023+
1024+ func TestConvertAssistantContent_RedactedThinking (t * testing.T ) {
1025+ t .Parallel ()
1026+
1027+ // When only signature is present but no reasoning content,
1028+ // a redacted thinking block should be included to maintain
1029+ // conversation integrity for multi-turn extended thinking.
1030+ msg := & chat.Message {
1031+ Role : chat .MessageRoleAssistant ,
1032+ Content : "Here's my answer" ,
1033+ ReasoningContent : "" , // Content redacted for safety
1034+ ThinkingSignature : "sig_abc123" ,
1035+ }
1036+
1037+ blocks := convertAssistantContent (msg )
1038+
1039+ // Should have redacted thinking block first, then text block
1040+ require .Len (t , blocks , 2 )
1041+
1042+ // First block should be redacted reasoning content
1043+ reasoningBlock , ok := blocks [0 ].(* types.ContentBlockMemberReasoningContent )
1044+ require .True (t , ok , "first block should be ContentBlockMemberReasoningContent" )
1045+
1046+ redactedContent , ok := reasoningBlock .Value .(* types.ReasoningContentBlockMemberRedactedContent )
1047+ require .True (t , ok , "reasoning block should be ReasoningContentBlockMemberRedactedContent" )
1048+ assert .Equal (t , []byte ("sig_abc123" ), redactedContent .Value )
1049+
1050+ // Second block should be text
1051+ textBlock , ok := blocks [1 ].(* types.ContentBlockMemberText )
1052+ require .True (t , ok )
1053+ assert .Equal (t , "Here's my answer" , textBlock .Value )
1054+ }
1055+
1056+ func TestConvertAssistantContent_NoThinkingWhenBothEmpty (t * testing.T ) {
1057+ t .Parallel ()
1058+
1059+ // When neither reasoning content nor signature is present,
1060+ // no thinking blocks should be included
1061+ msg := & chat.Message {
1062+ Role : chat .MessageRoleAssistant ,
1063+ Content : "Here's my answer" ,
1064+ ReasoningContent : "" ,
1065+ ThinkingSignature : "" ,
1066+ }
1067+
1068+ blocks := convertAssistantContent (msg )
1069+
1070+ // Should only have text block
1071+ require .Len (t , blocks , 1 )
1072+
1073+ textBlock , ok := blocks [0 ].(* types.ContentBlockMemberText )
1074+ require .True (t , ok )
1075+ assert .Equal (t , "Here's my answer" , textBlock .Value )
1076+ }
0 commit comments