@@ -25,7 +25,7 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => {
2525
2626import { AwsBedrockHandler } from "../bedrock"
2727import { ConverseStreamCommand , BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"
28- import { BEDROCK_1M_CONTEXT_MODEL_IDS } from "@roo-code/types"
28+ import { BEDROCK_1M_CONTEXT_MODEL_IDS , BEDROCK_SERVICE_TIER_MODEL_IDS , bedrockModels } from "@roo-code/types"
2929
3030import type { Anthropic } from "@anthropic-ai/sdk"
3131
@@ -755,4 +755,245 @@ describe("AwsBedrockHandler", () => {
755755 expect ( commandArg . modelId ) . toBe ( `us.${ BEDROCK_1M_CONTEXT_MODEL_IDS [ 0 ] } ` )
756756 } )
757757 } )
758+
759+ describe ( "service tier feature" , ( ) => {
760+ const supportedModelId = BEDROCK_SERVICE_TIER_MODEL_IDS [ 0 ] // amazon.nova-lite-v1:0
761+
762+ beforeEach ( ( ) => {
763+ mockConverseStreamCommand . mockReset ( )
764+ } )
765+
766+ describe ( "pricing multipliers in getModel()" , ( ) => {
767+ it ( "should apply FLEX tier pricing with 50% discount" , ( ) => {
768+ const handler = new AwsBedrockHandler ( {
769+ apiModelId : supportedModelId ,
770+ awsAccessKey : "test" ,
771+ awsSecretKey : "test" ,
772+ awsRegion : "us-east-1" ,
773+ awsBedrockServiceTier : "FLEX" ,
774+ } )
775+
776+ const model = handler . getModel ( )
777+ const baseModel = bedrockModels [ supportedModelId as keyof typeof bedrockModels ] as {
778+ inputPrice : number
779+ outputPrice : number
780+ }
781+
782+ // FLEX tier should apply 0.5 multiplier (50% discount)
783+ expect ( model . info . inputPrice ) . toBe ( baseModel . inputPrice * 0.5 )
784+ expect ( model . info . outputPrice ) . toBe ( baseModel . outputPrice * 0.5 )
785+ } )
786+
787+ it ( "should apply PRIORITY tier pricing with 75% premium" , ( ) => {
788+ const handler = new AwsBedrockHandler ( {
789+ apiModelId : supportedModelId ,
790+ awsAccessKey : "test" ,
791+ awsSecretKey : "test" ,
792+ awsRegion : "us-east-1" ,
793+ awsBedrockServiceTier : "PRIORITY" ,
794+ } )
795+
796+ const model = handler . getModel ( )
797+ const baseModel = bedrockModels [ supportedModelId as keyof typeof bedrockModels ] as {
798+ inputPrice : number
799+ outputPrice : number
800+ }
801+
802+ // PRIORITY tier should apply 1.75 multiplier (75% premium)
803+ expect ( model . info . inputPrice ) . toBe ( baseModel . inputPrice * 1.75 )
804+ expect ( model . info . outputPrice ) . toBe ( baseModel . outputPrice * 1.75 )
805+ } )
806+
807+ it ( "should not modify pricing for STANDARD tier" , ( ) => {
808+ const handler = new AwsBedrockHandler ( {
809+ apiModelId : supportedModelId ,
810+ awsAccessKey : "test" ,
811+ awsSecretKey : "test" ,
812+ awsRegion : "us-east-1" ,
813+ awsBedrockServiceTier : "STANDARD" ,
814+ } )
815+
816+ const model = handler . getModel ( )
817+ const baseModel = bedrockModels [ supportedModelId as keyof typeof bedrockModels ] as {
818+ inputPrice : number
819+ outputPrice : number
820+ }
821+
822+ // STANDARD tier should not modify pricing (1.0 multiplier)
823+ expect ( model . info . inputPrice ) . toBe ( baseModel . inputPrice )
824+ expect ( model . info . outputPrice ) . toBe ( baseModel . outputPrice )
825+ } )
826+
827+ it ( "should not apply service tier pricing for unsupported models" , ( ) => {
828+ const unsupportedModelId = "anthropic.claude-3-5-sonnet-20241022-v2:0"
829+ const handler = new AwsBedrockHandler ( {
830+ apiModelId : unsupportedModelId ,
831+ awsAccessKey : "test" ,
832+ awsSecretKey : "test" ,
833+ awsRegion : "us-east-1" ,
834+ awsBedrockServiceTier : "FLEX" , // Try to apply FLEX tier
835+ } )
836+
837+ const model = handler . getModel ( )
838+ const baseModel = bedrockModels [ unsupportedModelId as keyof typeof bedrockModels ] as {
839+ inputPrice : number
840+ outputPrice : number
841+ }
842+
843+ // Pricing should remain unchanged for unsupported models
844+ expect ( model . info . inputPrice ) . toBe ( baseModel . inputPrice )
845+ expect ( model . info . outputPrice ) . toBe ( baseModel . outputPrice )
846+ } )
847+ } )
848+
849+ describe ( "service_tier parameter in API requests" , ( ) => {
850+ it ( "should include service_tier as top-level parameter for supported models" , async ( ) => {
851+ const handler = new AwsBedrockHandler ( {
852+ apiModelId : supportedModelId ,
853+ awsAccessKey : "test" ,
854+ awsSecretKey : "test" ,
855+ awsRegion : "us-east-1" ,
856+ awsBedrockServiceTier : "PRIORITY" ,
857+ } )
858+
859+ const messages : Anthropic . Messages . MessageParam [ ] = [
860+ {
861+ role : "user" ,
862+ content : "Test message" ,
863+ } ,
864+ ]
865+
866+ const generator = handler . createMessage ( "" , messages )
867+ await generator . next ( ) // Start the generator
868+
869+ // Verify the command was created with service_tier at top level
870+ // Per AWS documentation, service_tier must be a top-level parameter, not inside additionalModelRequestFields
871+ // https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html
872+ expect ( mockConverseStreamCommand ) . toHaveBeenCalled ( )
873+ const commandArg = mockConverseStreamCommand . mock . calls [ 0 ] [ 0 ] as any
874+
875+ // service_tier should be at the top level of the payload
876+ expect ( commandArg . service_tier ) . toBe ( "PRIORITY" )
877+ // service_tier should NOT be in additionalModelRequestFields
878+ if ( commandArg . additionalModelRequestFields ) {
879+ expect ( commandArg . additionalModelRequestFields . service_tier ) . toBeUndefined ( )
880+ }
881+ } )
882+
883+ it ( "should include service_tier FLEX as top-level parameter" , async ( ) => {
884+ const handler = new AwsBedrockHandler ( {
885+ apiModelId : supportedModelId ,
886+ awsAccessKey : "test" ,
887+ awsSecretKey : "test" ,
888+ awsRegion : "us-east-1" ,
889+ awsBedrockServiceTier : "FLEX" ,
890+ } )
891+
892+ const messages : Anthropic . Messages . MessageParam [ ] = [
893+ {
894+ role : "user" ,
895+ content : "Test message" ,
896+ } ,
897+ ]
898+
899+ const generator = handler . createMessage ( "" , messages )
900+ await generator . next ( ) // Start the generator
901+
902+ expect ( mockConverseStreamCommand ) . toHaveBeenCalled ( )
903+ const commandArg = mockConverseStreamCommand . mock . calls [ 0 ] [ 0 ] as any
904+
905+ // service_tier should be at the top level of the payload
906+ expect ( commandArg . service_tier ) . toBe ( "FLEX" )
907+ // service_tier should NOT be in additionalModelRequestFields
908+ if ( commandArg . additionalModelRequestFields ) {
909+ expect ( commandArg . additionalModelRequestFields . service_tier ) . toBeUndefined ( )
910+ }
911+ } )
912+
913+ it ( "should NOT include service_tier for unsupported models" , async ( ) => {
914+ const unsupportedModelId = "anthropic.claude-3-5-sonnet-20241022-v2:0"
915+ const handler = new AwsBedrockHandler ( {
916+ apiModelId : unsupportedModelId ,
917+ awsAccessKey : "test" ,
918+ awsSecretKey : "test" ,
919+ awsRegion : "us-east-1" ,
920+ awsBedrockServiceTier : "PRIORITY" , // Try to apply PRIORITY tier
921+ } )
922+
923+ const messages : Anthropic . Messages . MessageParam [ ] = [
924+ {
925+ role : "user" ,
926+ content : "Test message" ,
927+ } ,
928+ ]
929+
930+ const generator = handler . createMessage ( "" , messages )
931+ await generator . next ( ) // Start the generator
932+
933+ expect ( mockConverseStreamCommand ) . toHaveBeenCalled ( )
934+ const commandArg = mockConverseStreamCommand . mock . calls [ 0 ] [ 0 ] as any
935+
936+ // Service tier should NOT be included for unsupported models (at top level or in additionalModelRequestFields)
937+ expect ( commandArg . service_tier ) . toBeUndefined ( )
938+ if ( commandArg . additionalModelRequestFields ) {
939+ expect ( commandArg . additionalModelRequestFields . service_tier ) . toBeUndefined ( )
940+ }
941+ } )
942+
943+ it ( "should NOT include service_tier when not specified" , async ( ) => {
944+ const handler = new AwsBedrockHandler ( {
945+ apiModelId : supportedModelId ,
946+ awsAccessKey : "test" ,
947+ awsSecretKey : "test" ,
948+ awsRegion : "us-east-1" ,
949+ // No awsBedrockServiceTier specified
950+ } )
951+
952+ const messages : Anthropic . Messages . MessageParam [ ] = [
953+ {
954+ role : "user" ,
955+ content : "Test message" ,
956+ } ,
957+ ]
958+
959+ const generator = handler . createMessage ( "" , messages )
960+ await generator . next ( ) // Start the generator
961+
962+ expect ( mockConverseStreamCommand ) . toHaveBeenCalled ( )
963+ const commandArg = mockConverseStreamCommand . mock . calls [ 0 ] [ 0 ] as any
964+
965+ // Service tier should NOT be included when not specified (at top level or in additionalModelRequestFields)
966+ expect ( commandArg . service_tier ) . toBeUndefined ( )
967+ if ( commandArg . additionalModelRequestFields ) {
968+ expect ( commandArg . additionalModelRequestFields . service_tier ) . toBeUndefined ( )
969+ }
970+ } )
971+ } )
972+
973+ describe ( "service tier with cross-region inference" , ( ) => {
974+ it ( "should apply service tier pricing with cross-region inference prefix" , ( ) => {
975+ const handler = new AwsBedrockHandler ( {
976+ apiModelId : supportedModelId ,
977+ awsAccessKey : "test" ,
978+ awsSecretKey : "test" ,
979+ awsRegion : "us-east-1" ,
980+ awsUseCrossRegionInference : true ,
981+ awsBedrockServiceTier : "FLEX" ,
982+ } )
983+
984+ const model = handler . getModel ( )
985+ const baseModel = bedrockModels [ supportedModelId as keyof typeof bedrockModels ] as {
986+ inputPrice : number
987+ outputPrice : number
988+ }
989+
990+ // Model ID should have cross-region prefix
991+ expect ( model . id ) . toBe ( `us.${ supportedModelId } ` )
992+
993+ // FLEX tier pricing should still be applied
994+ expect ( model . info . inputPrice ) . toBe ( baseModel . inputPrice * 0.5 )
995+ expect ( model . info . outputPrice ) . toBe ( baseModel . outputPrice * 0.5 )
996+ } )
997+ } )
998+ } )
758999} )
0 commit comments