|
| 1 | +import type { NullableHeaders } from './internal/headers'; |
| 2 | +import { buildHeaders } from './internal/headers'; |
| 3 | +import * as Errors from './core/error'; |
| 4 | +import { readEnv } from './internal/utils/env'; |
| 5 | +import { BaseAnthropic, ClientOptions } from '@anthropic-ai/sdk/client'; |
| 6 | +import * as Resources from '@anthropic-ai/sdk/resources/index'; |
| 7 | +import { AwsCredentialIdentityProvider } from '@smithy/types'; |
| 8 | +import { getAuthHeaders } from './core/aws-auth'; |
| 9 | +import { FinalRequestOptions } from './internal/request-options'; |
| 10 | +import { FinalizedRequestInit } from './internal/types'; |
| 11 | + |
| 12 | +const DEFAULT_SERVICE_NAME = 'bedrock-mantle'; |
| 13 | + |
| 14 | +export interface BedrockMantleClientOptions extends ClientOptions { |
| 15 | + /** |
| 16 | + * AWS region for the Bedrock Mantle API. |
| 17 | + * |
| 18 | + * Resolved by precedence: `awsRegion` arg > `AWS_REGION` env var > `AWS_DEFAULT_REGION` env var. |
| 19 | + */ |
| 20 | + awsRegion?: string | undefined; |
| 21 | + |
| 22 | + /** |
| 23 | + * API key for x-api-key authentication. |
| 24 | + * |
| 25 | + * Takes precedence over AWS credential options. If neither `apiKey` nor |
| 26 | + * AWS credentials are provided, falls back to the `AWS_BEARER_TOKEN_BEDROCK` |
| 27 | + * environment variable, then to the default AWS credential chain. |
| 28 | + */ |
| 29 | + apiKey?: string | undefined; |
| 30 | + |
| 31 | + /** |
| 32 | + * AWS access key ID for SigV4 authentication. |
| 33 | + * |
| 34 | + * Must be provided together with `awsSecretAccessKey`. |
| 35 | + */ |
| 36 | + awsAccessKey?: string | null | undefined; |
| 37 | + |
| 38 | + /** |
| 39 | + * AWS secret access key for SigV4 authentication. |
| 40 | + * |
| 41 | + * Must be provided together with `awsAccessKey`. |
| 42 | + */ |
| 43 | + awsSecretAccessKey?: string | null | undefined; |
| 44 | + |
| 45 | + /** |
| 46 | + * AWS session token for temporary credentials. |
| 47 | + */ |
| 48 | + awsSessionToken?: string | null | undefined; |
| 49 | + |
| 50 | + /** |
| 51 | + * AWS named profile for credential resolution. |
| 52 | + * |
| 53 | + * When set, credentials are loaded from the AWS credential chain |
| 54 | + * using this profile. |
| 55 | + */ |
| 56 | + awsProfile?: string | undefined; |
| 57 | + |
| 58 | + /** |
| 59 | + * Custom provider chain resolver for AWS credentials. |
| 60 | + * Useful for non-Node environments, like edge workers, where the default |
| 61 | + * credential provider chain may not work. |
| 62 | + */ |
| 63 | + providerChainResolver?: (() => Promise<AwsCredentialIdentityProvider>) | null; |
| 64 | + |
| 65 | + /** |
| 66 | + * Skip authentication for requests. This is useful when you have a gateway |
| 67 | + * or proxy that handles authentication on your behalf. |
| 68 | + * |
| 69 | + * @default false |
| 70 | + */ |
| 71 | + skipAuth?: boolean; |
| 72 | +} |
| 73 | + |
| 74 | +/** |
| 75 | + * API Client for interfacing with the Anthropic Bedrock Mantle API. |
| 76 | + * |
| 77 | + * This client uses SigV4 authentication with the `bedrock-mantle` service name |
| 78 | + * and targets `https://bedrock-mantle.{region}.api.aws/anthropic`. |
| 79 | + * |
| 80 | + * Only the `messages` and `beta.messages` resources are supported. |
| 81 | + */ |
| 82 | +export class AnthropicBedrockMantle extends BaseAnthropic { |
| 83 | + messages: Resources.Messages = new Resources.Messages(this); |
| 84 | + beta: MantleBetaResource = makeMantleBetaResource(this); |
| 85 | + |
| 86 | + awsRegion: string | undefined; |
| 87 | + awsAccessKey: string | null; |
| 88 | + awsSecretAccessKey: string | null; |
| 89 | + awsSessionToken: string | null; |
| 90 | + awsProfile: string | null; |
| 91 | + providerChainResolver: (() => Promise<AwsCredentialIdentityProvider>) | null; |
| 92 | + skipAuth: boolean = false; |
| 93 | + |
| 94 | + private _useSigV4: boolean; |
| 95 | + |
| 96 | + /** |
| 97 | + * API Client for interfacing with the Anthropic Bedrock Mantle API. |
| 98 | + * |
| 99 | + * Auth is resolved by precedence: `apiKey` constructor arg > explicit AWS |
| 100 | + * credentials > `awsProfile` > `AWS_BEARER_TOKEN_BEDROCK` env var > default |
| 101 | + * AWS credential chain. |
| 102 | + * |
| 103 | + * @param {string | undefined} [opts.apiKey] - API key for x-api-key authentication. |
| 104 | + * @param {string | null | undefined} [opts.awsAccessKey] - AWS access key ID for SigV4 authentication. |
| 105 | + * @param {string | null | undefined} [opts.awsSecretAccessKey] - AWS secret access key for SigV4 authentication. |
| 106 | + * @param {string | null | undefined} [opts.awsSessionToken] - AWS session token for temporary credentials. |
| 107 | + * @param {string | undefined} [opts.awsProfile] - AWS named profile for credential resolution. |
| 108 | + * @param {string | undefined} [opts.awsRegion] - AWS region. Resolved by precedence: arg > `AWS_REGION` env > `AWS_DEFAULT_REGION` env. |
| 109 | + * @param {(() => Promise<AwsCredentialIdentityProvider>) | null} [opts.providerChainResolver] - Custom provider chain resolver for AWS credentials. |
| 110 | + * @param {string} [opts.baseURL=process.env['ANTHROPIC_BEDROCK_MANTLE_BASE_URL'] ?? https://bedrock-mantle.{awsRegion}.api.aws/anthropic] - Override the default base URL for the API. |
| 111 | + * @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. |
| 112 | + * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. |
| 113 | + * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. |
| 114 | + * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. |
| 115 | + * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. |
| 116 | + * @param {Record<string, string | undefined>} opts.defaultQuery - Default query parameters to include with every request to the API. |
| 117 | + * @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers. |
| 118 | + * @param {boolean} [opts.skipAuth=false] - Skip authentication for requests. This is useful when you have a gateway or proxy that handles authentication on your behalf. |
| 119 | + */ |
| 120 | + constructor({ |
| 121 | + awsRegion, |
| 122 | + baseURL, |
| 123 | + apiKey, |
| 124 | + awsAccessKey = null, |
| 125 | + awsSecretAccessKey = null, |
| 126 | + awsSessionToken = null, |
| 127 | + awsProfile, |
| 128 | + providerChainResolver = null, |
| 129 | + skipAuth = false, |
| 130 | + ...opts |
| 131 | + }: BedrockMantleClientOptions = {}) { |
| 132 | + // Region resolution: arg > AWS_REGION env > AWS_DEFAULT_REGION env |
| 133 | + const resolvedRegion = awsRegion ?? readEnv('AWS_REGION') ?? readEnv('AWS_DEFAULT_REGION'); |
| 134 | + |
| 135 | + const resolvedBaseURL = |
| 136 | + baseURL ?? |
| 137 | + readEnv('ANTHROPIC_BEDROCK_MANTLE_BASE_URL') ?? |
| 138 | + (resolvedRegion ? `https://bedrock-mantle.${resolvedRegion}.api.aws/anthropic` : undefined); |
| 139 | + |
| 140 | + if (!resolvedBaseURL) { |
| 141 | + throw new Errors.AnthropicError( |
| 142 | + 'No AWS region or base URL found. Set `awsRegion` in the constructor, the `AWS_REGION` / `AWS_DEFAULT_REGION` environment variable, or provide a `baseURL` / `ANTHROPIC_BEDROCK_MANTLE_BASE_URL` environment variable.', |
| 143 | + ); |
| 144 | + } |
| 145 | + |
| 146 | + // Precedence-based auth resolution: |
| 147 | + // 1. apiKey constructor arg |
| 148 | + // 2. awsAccessKey/awsSecretAccessKey constructor args (SigV4) |
| 149 | + // 3. awsProfile constructor arg (SigV4) |
| 150 | + // 4. AWS_BEARER_TOKEN_BEDROCK env var |
| 151 | + // 5. Default AWS credential chain (SigV4) |
| 152 | + const hasExplicitApiKey = apiKey != null; |
| 153 | + const hasPartialAwsCreds = (awsAccessKey != null) !== (awsSecretAccessKey != null); |
| 154 | + if (hasPartialAwsCreds) { |
| 155 | + throw new Errors.AnthropicError( |
| 156 | + '`awsAccessKey` and `awsSecretAccessKey` must be provided together. You provided only one.', |
| 157 | + ); |
| 158 | + } |
| 159 | + const hasExplicitAwsCreds = awsAccessKey != null && awsSecretAccessKey != null; |
| 160 | + const hasAwsProfile = awsProfile != null; |
| 161 | + |
| 162 | + let resolvedApiKey: string | undefined; |
| 163 | + if (hasExplicitApiKey) { |
| 164 | + resolvedApiKey = apiKey; |
| 165 | + } else if (!hasExplicitAwsCreds && !hasAwsProfile) { |
| 166 | + resolvedApiKey = readEnv('AWS_BEARER_TOKEN_BEDROCK') ?? undefined; |
| 167 | + } |
| 168 | + |
| 169 | + super({ |
| 170 | + apiKey: resolvedApiKey, |
| 171 | + baseURL: resolvedBaseURL, |
| 172 | + ...opts, |
| 173 | + }); |
| 174 | + |
| 175 | + this.awsRegion = resolvedRegion; |
| 176 | + this.awsAccessKey = awsAccessKey; |
| 177 | + this.awsSecretAccessKey = awsSecretAccessKey; |
| 178 | + this.awsSessionToken = awsSessionToken; |
| 179 | + this.awsProfile = awsProfile ?? null; |
| 180 | + this.providerChainResolver = providerChainResolver; |
| 181 | + this.skipAuth = skipAuth; |
| 182 | + this._useSigV4 = resolvedApiKey == null; |
| 183 | + } |
| 184 | + |
| 185 | + protected override async authHeaders(opts: FinalRequestOptions): Promise<NullableHeaders | undefined> { |
| 186 | + if (this.skipAuth) { |
| 187 | + return undefined; |
| 188 | + } |
| 189 | + |
| 190 | + if (!this._useSigV4) { |
| 191 | + // API key mode — use inherited x-api-key auth |
| 192 | + return super.authHeaders(opts); |
| 193 | + } |
| 194 | + |
| 195 | + // SigV4 mode — auth is handled in prepareRequest since it needs the full request |
| 196 | + return undefined; |
| 197 | + } |
| 198 | + |
| 199 | + protected override validateHeaders(): void { |
| 200 | + // Auth validation is handled in the constructor and prepareRequest |
| 201 | + } |
| 202 | + |
| 203 | + protected override async prepareRequest( |
| 204 | + request: FinalizedRequestInit, |
| 205 | + { url, options }: { url: string; options: FinalRequestOptions }, |
| 206 | + ): Promise<void> { |
| 207 | + if (this.skipAuth || !this._useSigV4) { |
| 208 | + return; |
| 209 | + } |
| 210 | + |
| 211 | + const regionName = this.awsRegion; |
| 212 | + if (!regionName) { |
| 213 | + throw new Errors.AnthropicError( |
| 214 | + 'No AWS region found. Set `awsRegion` in the constructor or the `AWS_REGION` / `AWS_DEFAULT_REGION` environment variable.', |
| 215 | + ); |
| 216 | + } |
| 217 | + |
| 218 | + const headers = await getAuthHeaders(request, { |
| 219 | + url, |
| 220 | + regionName, |
| 221 | + serviceName: DEFAULT_SERVICE_NAME, |
| 222 | + awsAccessKey: this.awsAccessKey, |
| 223 | + awsSecretAccessKey: this.awsSecretAccessKey, |
| 224 | + awsSessionToken: this.awsSessionToken, |
| 225 | + awsProfile: this.awsProfile, |
| 226 | + providerChainResolver: this.providerChainResolver, |
| 227 | + }); |
| 228 | + request.headers = buildHeaders([headers, request.headers]).values; |
| 229 | + } |
| 230 | +} |
| 231 | + |
| 232 | +/** |
| 233 | + * Bedrock Mantle does not support completions, models, or non-messages beta resources. |
| 234 | + */ |
| 235 | +type MantleBetaResource = Pick<Resources.Beta, 'messages'>; |
| 236 | + |
| 237 | +function makeMantleBetaResource(client: AnthropicBedrockMantle): MantleBetaResource { |
| 238 | + const { messages } = new Resources.Beta(client); |
| 239 | + return { messages }; |
| 240 | +} |
0 commit comments