Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
9.2 kB · 265 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266import memoize from 'lodash-es/memoize.js'import { refreshAndGetAwsCredentials } from '../auth.js'import { getAWSRegion, isEnvTruthy } from '../envUtils.js'import { logError } from '../log.js'import { getAWSClientProxyConfig } from '../proxy.js'
export const getBedrockInferenceProfiles = memoize(async function (): Promise< string[]> { const [client, { ListInferenceProfilesCommand }] = await Promise.all([ createBedrockClient(), import('@aws-sdk/client-bedrock'), ]) const allProfiles = [] let nextToken: string | undefined
try { do { const command = new ListInferenceProfilesCommand({ ...(nextToken && { nextToken }), typeEquals: 'SYSTEM_DEFINED', }) const response = await client.send(command)
if (response.inferenceProfileSummaries) { allProfiles.push(...response.inferenceProfileSummaries) }
nextToken = response.nextToken } while (nextToken)
// Filter for Anthropic models (SYSTEM_DEFINED filtering handled in query) return allProfiles .filter(profile => profile.inferenceProfileId?.includes('anthropic')) .map(profile => profile.inferenceProfileId) .filter(Boolean) as string[] } catch (error) { logError(error as Error) throw error }})
export function findFirstMatch( profiles: string[], substring: string,): string | null { return profiles.find(p => p.includes(substring)) ?? null}
async function createBedrockClient() { const { BedrockClient } = await import('@aws-sdk/client-bedrock') // Match the Anthropic Bedrock SDK's region behavior exactly: // - Reads AWS_REGION or AWS_DEFAULT_REGION env vars (not AWS config files) // - Falls back to 'us-east-1' if neither is set // This ensures we query profiles from the same region the client will use const region = getAWSRegion()
const skipAuth = isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH)
const clientConfig: ConstructorParameters<typeof BedrockClient>[0] = { region, ...(process.env.ANTHROPIC_BEDROCK_BASE_URL && { endpoint: process.env.ANTHROPIC_BEDROCK_BASE_URL, }), ...(await getAWSClientProxyConfig()), ...(skipAuth && { requestHandler: new ( await import('@smithy/node-http-handler') ).NodeHttpHandler(), httpAuthSchemes: [ { schemeId: 'smithy.api#noAuth', identityProvider: () => async () => ({}), signer: new (await import('@smithy/core')).NoAuthSigner(), }, ], httpAuthSchemeProvider: () => [{ schemeId: 'smithy.api#noAuth' }], }), }
if (!skipAuth && !process.env.AWS_BEARER_TOKEN_BEDROCK) { // Only refresh credentials if not using API key authentication const cachedCredentials = await refreshAndGetAwsCredentials() if (cachedCredentials) { clientConfig.credentials = { accessKeyId: cachedCredentials.accessKeyId, secretAccessKey: cachedCredentials.secretAccessKey, sessionToken: cachedCredentials.sessionToken, } } }
return new BedrockClient(clientConfig)}
export async function createBedrockRuntimeClient() { const { BedrockRuntimeClient } = await import( '@aws-sdk/client-bedrock-runtime' ) const region = getAWSRegion() const skipAuth = isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH)
const clientConfig: ConstructorParameters<typeof BedrockRuntimeClient>[0] = { region, ...(process.env.ANTHROPIC_BEDROCK_BASE_URL && { endpoint: process.env.ANTHROPIC_BEDROCK_BASE_URL, }), ...(await getAWSClientProxyConfig()), ...(skipAuth && { // BedrockRuntimeClient defaults to HTTP/2 without fallback // proxy servers may not support this, so we explicitly force HTTP/1.1 requestHandler: new ( await import('@smithy/node-http-handler') ).NodeHttpHandler(), httpAuthSchemes: [ { schemeId: 'smithy.api#noAuth', identityProvider: () => async () => ({}), signer: new (await import('@smithy/core')).NoAuthSigner(), }, ], httpAuthSchemeProvider: () => [{ schemeId: 'smithy.api#noAuth' }], }), }
if (!skipAuth && !process.env.AWS_BEARER_TOKEN_BEDROCK) { // Only refresh credentials if not using API key authentication const cachedCredentials = await refreshAndGetAwsCredentials() if (cachedCredentials) { clientConfig.credentials = { accessKeyId: cachedCredentials.accessKeyId, secretAccessKey: cachedCredentials.secretAccessKey, sessionToken: cachedCredentials.sessionToken, } } }
return new BedrockRuntimeClient(clientConfig)}
export const getInferenceProfileBackingModel = memoize(async function ( profileId: string,): Promise<string | null> { try { const [client, { GetInferenceProfileCommand }] = await Promise.all([ createBedrockClient(), import('@aws-sdk/client-bedrock'), ]) const command = new GetInferenceProfileCommand({ inferenceProfileIdentifier: profileId, }) const response = await client.send(command)
if (!response.models || response.models.length === 0) { return null }
// Use the first model as the primary backing model for cost calculation // In practice, application inference profiles typically load balance between // similar models with the same cost structure const primaryModel = response.models[0] if (!primaryModel?.modelArn) { return null }
// Extract model name from ARN // ARN format: arn:aws:bedrock:region:account:foundation-model/model-name const lastSlashIndex = primaryModel.modelArn.lastIndexOf('/') return lastSlashIndex >= 0 ? primaryModel.modelArn.substring(lastSlashIndex + 1) : primaryModel.modelArn } catch (error) { logError(error as Error) return null }})
/** * Check if a model ID is a foundation model (e.g., "anthropic.claude-sonnet-4-5-20250929-v1:0") */export function isFoundationModel(modelId: string): boolean { return modelId.startsWith('anthropic.')}
/** * Cross-region inference profile prefixes for Bedrock. * These prefixes allow routing requests to models in specific regions. */const BEDROCK_REGION_PREFIXES = ['us', 'eu', 'apac', 'global'] as const
/** * Extract the model/inference profile ID from a Bedrock ARN. * If the input is not an ARN, returns it unchanged. * * ARN format: arn:aws:bedrock:<region>:<account>:inference-profile/<profile-id> * Also handles: arn:aws:bedrock:<region>:<account>:application-inference-profile/<profile-id> * And foundation model ARNs: arn:aws:bedrock:<region>::foundation-model/<model-id> */export function extractModelIdFromArn(modelId: string): string { if (!modelId.startsWith('arn:')) { return modelId } const lastSlashIndex = modelId.lastIndexOf('/') if (lastSlashIndex === -1) { return modelId } return modelId.substring(lastSlashIndex + 1)}
export type BedrockRegionPrefix = (typeof BEDROCK_REGION_PREFIXES)[number]
/** * Extract the region prefix from a Bedrock cross-region inference model ID. * Handles both plain model IDs and full ARN format. * For example: * - "eu.anthropic.claude-sonnet-4-5-20250929-v1:0" → "eu" * - "us.anthropic.claude-3-7-sonnet-20250219-v1:0" → "us" * - "arn:aws:bedrock:ap-northeast-2:123:inference-profile/global.anthropic.claude-opus-4-6-v1" → "global" * - "anthropic.claude-3-5-sonnet-20241022-v2:0" → undefined (foundation model) * - "claude-sonnet-4-5-20250929" → undefined (first-party format) */export function getBedrockRegionPrefix( modelId: string,): BedrockRegionPrefix | undefined { // Extract the inference profile ID from ARN format if present // ARN format: arn:aws:bedrock:<region>:<account>:inference-profile/<profile-id> const effectiveModelId = extractModelIdFromArn(modelId)
for (const prefix of BEDROCK_REGION_PREFIXES) { if (effectiveModelId.startsWith(`${prefix}.anthropic.`)) { return prefix } } return undefined}
/** * Apply a region prefix to a Bedrock model ID. * If the model already has a different region prefix, it will be replaced. * If the model is a foundation model (anthropic.*), the prefix will be added. * If the model is not a Bedrock model, it will be returned as-is. * * For example: * - applyBedrockRegionPrefix("us.anthropic.claude-sonnet-4-5-v1:0", "eu") → "eu.anthropic.claude-sonnet-4-5-v1:0" * - applyBedrockRegionPrefix("anthropic.claude-sonnet-4-5-v1:0", "eu") → "eu.anthropic.claude-sonnet-4-5-v1:0" * - applyBedrockRegionPrefix("claude-sonnet-4-5-20250929", "eu") → "claude-sonnet-4-5-20250929" (not a Bedrock model) */export function applyBedrockRegionPrefix( modelId: string, prefix: BedrockRegionPrefix,): string { // Check if it already has a region prefix and replace it const existingPrefix = getBedrockRegionPrefix(modelId) if (existingPrefix) { return modelId.replace(`${existingPrefix}.`, `${prefix}.`) }
// Check if it's a foundation model (anthropic.*) and add the prefix if (isFoundationModel(modelId)) { return `${prefix}.${modelId}` }
// Not a Bedrock model format, return as-is return modelId}