@@ -3,79 +3,25 @@ import { Anthropic } from "@anthropic-ai/sdk"
33import { ApiHandler } from "../"
44import { ApiHandlerOptions , bedrockDefaultModelId , BedrockModelId , bedrockModels , ModelInfo } from "../../shared/api"
55import { ApiStream } from "../transform/stream"
6- import { fromIni } from "@aws-sdk/credential-providers"
6+ import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
77
88// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
99export class AwsBedrockHandler implements ApiHandler {
1010 private options : ApiHandlerOptions
11- private client : AnthropicBedrock | any
12- private initializationPromise : Promise < void >
1311
1412 constructor ( options : ApiHandlerOptions ) {
1513 this . options = options
16- this . initializationPromise = this . initializeClient ( )
17- }
18-
19- private async initializeClient ( ) {
20- let clientConfig : any = {
21- awsRegion : this . options . awsRegion || "us-east-1" ,
22- }
23- try {
24- if ( this . options . awsUseProfile ) {
25- // Use profile-based credentials if enabled
26- // Use named profile, defaulting to 'default' if not specified
27- var credentials : any
28- if ( this . options . awsProfile ) {
29- credentials = await fromIni ( {
30- profile : this . options . awsProfile ,
31- ignoreCache : true ,
32- } ) ( )
33- } else {
34- credentials = await fromIni ( {
35- ignoreCache : true ,
36- } ) ( )
37- }
38- clientConfig . awsAccessKey = credentials . accessKeyId
39- clientConfig . awsSecretKey = credentials . secretAccessKey
40- clientConfig . awsSessionToken = credentials . sessionToken
41- } else if ( this . options . awsAccessKey && this . options . awsSecretKey ) {
42- // Use direct credentials if provided
43- clientConfig . awsAccessKey = this . options . awsAccessKey
44- clientConfig . awsSecretKey = this . options . awsSecretKey
45- if ( this . options . awsSessionToken ) {
46- clientConfig . awsSessionToken = this . options . awsSessionToken
47- }
48- }
49- } catch ( error ) {
50- console . error ( "Failed to initialize Bedrock client:" , error )
51- throw error
52- } finally {
53- this . client = new AnthropicBedrock ( clientConfig )
54- }
5514 }
5615
5716 async * createMessage ( systemPrompt : string , messages : Anthropic . Messages . MessageParam [ ] ) : ApiStream {
5817 // cross region inference requires prefixing the model id with the region
59- let modelId : string
60- if ( this . options . awsUseCrossRegionInference ) {
61- let regionPrefix = ( this . options . awsRegion || "" ) . slice ( 0 , 3 )
62- switch ( regionPrefix ) {
63- case "us-" :
64- modelId = `us.${ this . getModel ( ) . id } `
65- break
66- case "eu-" :
67- modelId = `eu.${ this . getModel ( ) . id } `
68- break
69- default :
70- // cross region inference is not supported in this region, falling back to default model
71- modelId = this . getModel ( ) . id
72- break
73- }
74- } else {
75- modelId = this . getModel ( ) . id
76- }
18+ let modelId = await this . getModelId ( )
19+
20+ // create anthropic client, using sessions created or renewed after this handler's
21+ // initialization, and allowing for session renewal if necessary as well
22+ let client = await this . getClient ( )
7723
78- const stream = await this . client . messages . create ( {
24+ const stream = await client . messages . create ( {
7925 model : modelId ,
8026 max_tokens : this . getModel ( ) . info . maxTokens || 8192 ,
8127 temperature : 0 ,
@@ -142,4 +88,68 @@ export class AwsBedrockHandler implements ApiHandler {
14288 info : bedrockModels [ bedrockDefaultModelId ] ,
14389 }
14490 }
91+
92+ private async getClient ( ) : Promise < AnthropicBedrock > {
93+ // Create AWS credentials by executing a an AWS provider chain exactly as the
94+ // Anthropic SDK does it, by wrapping the default chain into a temporary process
95+ // environment.
96+ const providerChain = fromNodeProviderChain ( )
97+ const credentials = await AwsBedrockHandler . withTempEnv (
98+ ( ) => {
99+ AwsBedrockHandler . setEnv ( "AWS_REGION" , this . options . awsRegion )
100+ AwsBedrockHandler . setEnv ( "AWS_ACCESS_KEY_ID" , this . options . awsAccessKey )
101+ AwsBedrockHandler . setEnv ( "AWS_SECRET_ACCESS_KEY" , this . options . awsSecretKey )
102+ AwsBedrockHandler . setEnv ( "AWS_SESSION_TOKEN" , this . options . awsSessionToken )
103+ AwsBedrockHandler . setEnv ( "AWS_PROFILE" , this . options . awsProfile )
104+ } ,
105+ ( ) => providerChain ( ) ,
106+ )
107+
108+ // Return an AnthropicBedrock client with the resolved/assumed credentials.
109+ //
110+ // When AnthropicBedrock creates its AWS client, the chain will execute very
111+ // fast as the access/secret keys will already be already provided, and have
112+ // a higher precedence than the profiles.
113+ return new AnthropicBedrock ( {
114+ awsAccessKey : credentials . accessKeyId ,
115+ awsSecretKey : credentials . secretAccessKey ,
116+ awsSessionToken : credentials . sessionToken ,
117+ awsRegion : this . options . awsRegion || "us-east-1" ,
118+ } )
119+ }
120+
121+ private async getModelId ( ) : Promise < string > {
122+ if ( this . options . awsUseCrossRegionInference ) {
123+ let regionPrefix = ( this . options . awsRegion || "" ) . slice ( 0 , 3 )
124+ switch ( regionPrefix ) {
125+ case "us-" :
126+ return `us.${ this . getModel ( ) . id } `
127+ case "eu-" :
128+ return `eu.${ this . getModel ( ) . id } `
129+ break
130+ default :
131+ // cross region inference is not supported in this region, falling back to default model
132+ return this . getModel ( ) . id
133+ break
134+ }
135+ }
136+ return this . getModel ( ) . id
137+ }
138+
139+ private static async withTempEnv < R > ( updateEnv : ( ) => void , fn : ( ) => Promise < R > ) : Promise < R > {
140+ const previousEnv = { ...process . env }
141+
142+ try {
143+ updateEnv ( )
144+ return await fn ( )
145+ } finally {
146+ process . env = previousEnv
147+ }
148+ }
149+
150+ private static async setEnv ( key : string , value : string | undefined ) {
151+ if ( key !== "" && value !== undefined ) {
152+ process . env [ key ] = value
153+ }
154+ }
145155}
0 commit comments