Claude Code 源码揭秘:为什么它能无感切换 AWS、Google、Azure
💡 阅读前记得关注+星标,及时获取更新推送
「Claude Code 源码揭秘」系列的第十五篇,上一篇《Claude Code 源码揭秘:CLAUDE.md,一个 Markdown 文件如何驯服 AI》,说的是怎么用 Markdown 文件定制 AI 行为。但不管规则怎么配,最终请求还是要发到云端。问题来了:你用的是哪个云?

你可能没注意过,Claude Code 不只能连 Anthropic 官方 API。
把 AWS_REGION 和 AWS_ACCESS_KEY_ID 配好,它就自动切换到 AWS Bedrock。把 ANTHROPIC_VERTEX_PROJECT_ID 配好,它就走 Google Vertex AI。甚至从 2.0.45 版本开始,还支持了 Microsoft Azure 的 Foundry。不需要改代码,不需要换命令行参数,环境变量一设,底层就变了。
有人可能会问:Bedrock 和 Vertex AI 上跑的不还是 Claude 模型吗?为什么不直连 Anthropic?
模型确实是同一个,但企业访问它的路径完全不同。很多大企业跟 AWS 或 Google 签了年框合约,走云厂商账单可以抵扣承诺消费。更关键的是数据合规——数据不出自己的云环境,IAM 权限、VPC 网络隔离、审计日志都走现有体系。在大公司新增一个供应商要走几个月的采购审批,走已有云厂商能省掉这些流程。所以多云适配不是技术炫技,是实实在在的企业刚需。
翻源码才发现,这套多云适配不是简单地 if-else 判断,而是一套完整的抽象层设计——工厂模式、策略模式、模型映射、认证适配、错误转换,每一层都有讲究。
工厂模式:入口只有一个
整个多云适配的核心是 createClient() 这个工厂函数:
export function createClient(config?: ProviderConfig): Anthropic { const providerConfig = config || detectProvider(); switch (providerConfig.type) { case 'bedrock': return createBedrockClient(providerConfig); case 'vertex': return createVertexClient(providerConfig); case 'foundry': return createFoundryClient(providerConfig); default: return new Anthropic({ apiKey: providerConfig.apiKey, }); }}
上层代码不管你用的是哪个云厂商,统一调 createClient() 就行。返回的都是 Anthropic 客户端接口,方法一样,参数一样,行为一样。
这就像 USB 接口——不管你插的是鼠标、键盘还是 U 盘,接口都是一样的,设备自己去适配。
环境变量驱动的自动检测
最让我欣赏的是 detectProvider() 这个设计:
function detectProvider(): ProviderConfig { // 优先级 1:显式指定用 Bedrock if (process.env.CLAUDE_CODE_USE_BEDROCK === 'true') { return detectBedrockConfig(); } // 优先级 2:显式指定用 Vertex if (process.env.CLAUDE_CODE_USE_VERTEX === 'true') { return detectVertexConfig(); } // 优先级 3:有 Bedrock 环境变量就用 Bedrock if (process.env.AWS_BEDROCK_MODEL || (process.env.AWS_REGION && process.env.AWS_ACCESS_KEY_ID)) { return detectBedrockConfig(); } // 优先级 4:有 Vertex 环境变量就用 Vertex if (process.env.ANTHROPIC_VERTEX_PROJECT_ID || process.env.GOOGLE_APPLICATION_CREDENTIALS) { return detectVertexConfig(); } // 优先级 5:降级到 Anthropic 直连 return { type: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY, };}
这个设计很巧妙——环境变量本身就是意图声明。你设置了 AWS 相关的变量,说明你想用 AWS;设置了 Google 相关的变量,说明你想用 Google。不需要额外的配置文件或命令行参数。
画个图更清楚:

我之前做的那个多模型网关,用户必须在配置文件里写明「我要用哪个供应商」。Claude Code 这种自动检测更智能——你有什么凭证,我就用什么供应商。
认证方式:四朵云,四种方言
这是多云适配最复杂的部分。每家云厂商的认证方式都不一样,就像去不同国家过海关,每个国家要的证件都不同:
-
• Anthropic:标准的 API Key, Authorization: Bearer <key> -
• AWS Bedrock:AWS Signature V4 签名,每个请求都要签 -
• Google Vertex:JWT + OAuth2,要用 Service Account 换 Access Token -
• Azure Foundry:Azure AD 认证,走 Microsoft 的身份体系
Anthropic 最简单,直接把 API Key 塞 header 就完事了。
Bedrock 麻烦得多。AWS 不用 API Key,用 IAM 凭证。每个请求都要算一遍签名:
export function signAWSRequest( method: string, url: string, body: string, credentials: { accessKeyId: string; secretAccessKey: string; sessionToken?: string; region: string; service: string; }): Record<string, string> { const date = new Date(); const dateStamp = formatDate(date, 'YYYYMMDD'); const amzDate = formatDate(date, 'YYYYMMDDTHHmmssZ'); // 1. 创建规范请求 const canonicalRequest = [ method, parsedUrl.pathname, parsedUrl.searchParams.toString(), canonicalHeaders, signedHeaders, hashBody(body), ].join('\n'); // 2. 创建待签字符串 const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`; const stringToSign = [ 'AWS4-HMAC-SHA256', amzDate, credentialScope, hash(canonicalRequest), ].join('\n'); // 3. 计算签名 const signingKey = getSigningKey(secretAccessKey, dateStamp, region, service); const signature = hmacSha256(signingKey, stringToSign); // 4. 构建授权头 return { 'Authorization': `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`, 'X-Amz-Date': amzDate, 'X-Amz-Security-Token': sessionToken, // 临时凭证需要这个 };}
AWS Signature V4 这套签名流程,做过 AWS 集成的应该都踩过坑。日期格式、header 排序、编码规则,任何一个细节错了,就是 InvalidSignatureException。
Claude Code 有个巧妙的 fallback 机制。如果装了官方的 @anthropic-ai/bedrock-sdk,就用 SDK(性能更好);否则降级到手动签名:
try { const AnthropicBedrock = require('@anthropic-ai/bedrock-sdk').default; return new AnthropicBedrock({ awsAccessKey: config.accessKeyId, awsSecretKey: config.secretAccessKey, awsSessionToken: config.sessionToken, awsRegion: config.region, });} catch { console.warn('[Bedrock] 官方 SDK 未找到,降级到手动签名'); return createManualBedrockClient(config);}
这种「优雅降级」的设计,让 Claude Code 不依赖特定的 SDK 版本也能工作。
Vertex 的认证又是另一套逻辑。Google 用 Service Account 认证,需要用私钥签名一个 JWT,然后拿 JWT 去换 Access Token:
private async fetchServiceAccountToken( credentials: GoogleServiceAccount): Promise<AccessToken> { const now = Math.floor(Date.now() / 1000); // 1. 构建 JWT Claims const claim = { iss: credentials.client_email, scope: 'https://www.googleapis.com/auth/cloud-platform', aud: credentials.token_uri, iat: now, exp: now + 3600, // 1 小时有效 }; // 2. 用私钥签名 JWT const jwt = this.signJWT( { alg: 'RS256', typ: 'JWT' }, claim, credentials.private_key ); // 3. 换取 Access Token const response = await fetch(credentials.token_uri, { method: 'POST', body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: jwt, }), }); return response.json();}
Access Token 是有过期时间的,Claude Code 还做了自动刷新:
private scheduleTokenRefresh(token: AccessToken): void { // 在过期前 5 分钟刷新 const refreshTime = (token.expires_in - 300) * 1000; this.tokenRefreshTimer = setTimeout(async () => { this.cachedToken = null; await this.getAccessToken(); // 重新获取 }, refreshTime);}
这个细节很重要。我之前做的项目没有自动刷新,用户会遇到「下午 4 点后 API 就不工作了」的问题——因为 token 在中午过期了。
模型 ID 映射:统一方言
各家云厂商对同一个模型的命名完全不同——同一个 sonnet,换个云就换个马甲:
export const MODEL_MAPPING: Record<ProviderType, Record<string, string>> = { anthropic: { 'claude-3-5-sonnet': 'claude-3-5-sonnet-20241022', 'claude-3-opus': 'claude-3-opus-20240229', 'claude-3-haiku': 'claude-3-haiku-20240307', }, bedrock: { 'claude-3-5-sonnet': 'anthropic.claude-3-5-sonnet-20241022-v2:0', 'claude-3-opus': 'anthropic.claude-3-opus-20240229-v1:0', 'claude-3-haiku': 'anthropic.claude-3-haiku-20240307-v1:0', }, vertex: { 'claude-3-5-sonnet': 'claude-3-5-sonnet-v2@20241022', 'claude-3-opus': 'claude-3-opus@20240229', 'claude-3-haiku': 'claude-3-haiku@20240307', },};
上层代码只需要说「我要 sonnet」,底层自动翻译成各厂商的 ID。用户不需要记「Bedrock 里 sonnet 的 ID 是 anthropic.claude-3-5-sonnet-20241022-v2:0」这种细节。
export function getModelForProvider( modelName: string, providerType: ProviderType): string { const mapping = MODEL_MAPPING[providerType]; // 先查映射表 if (mapping[modelName]) { return mapping[modelName]; } // 没有映射就原样返回(可能是用户指定的完整 ID) return modelName;}
Bedrock 还有个特殊的 ARN 解析:
export function parseBedrockModelArn(input: string): BedrockModelArn | null { // ARN 格式:arn:aws:bedrock:{region}:{accountId}:{type}/{modelId} const arnPattern = /^arn:aws:bedrock:([^:]+):([^:]*):([^/]+)\/(.+)$/; const match = input.match(arnPattern); if (!match) return null; const [, region, accountId, resourceType, modelId] = match; return { region, accountId, resourceType, modelId, isFoundationModel: resourceType === 'foundation-model', isProvisionedModel: resourceType === 'provisioned-model', isInferenceProfile: resourceType.includes('inference-profile'), };}
Bedrock 有基础模型、预配置模型、跨区域推理三种类型,ARN 格式都不一样。这个解析函数把复杂性隐藏起来了。
端点格式适配
各家厂商的 API 端点格式也是各说各话:
Anthropic:https://api.anthropic.com/v1/messagesBedrock:https://bedrock-runtime.{region}.amazonaws.com/model/{modelId}/invokeVertex:https://{region}-aiplatform.googleapis.com/v1/projects/{projectId}/locations/{region}/publishers/anthropic/models/{modelId}:streamRawPredict
Claude Code 用 getEndpoint() 系列函数封装这些差异:
// Bedrock 端点function getBedrockEndpoint(region: string, modelId: string): string { return `https://bedrock-runtime.${region}.amazonaws.com/model/${modelId}/invoke`;}// Vertex 端点function getVertexEndpoint( region: string, projectId: string, modelId: string): string { return `https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}/publishers/anthropic/models/${modelId}:streamRawPredict`;}
上层代码完全不需要知道这些细节。
错误处理的统一抽象
AWS 的错误类型有二十多种,Claude Code 做了友好化转换:
export function handleBedrockError(error: any): string { const errorMessage = error.message || ''; if (errorMessage.includes('InvalidSignatureException')) { return '签名验证失败,请检查 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 是否正确'; } if (errorMessage.includes('AccessDeniedException')) { return '权限不足,你的 IAM 用户/角色需要 bedrock:InvokeModel 权限'; } if (errorMessage.includes('ResourceNotFoundException')) { return '模型不存在,请确认该模型在你的 AWS 区域已开通'; } if (errorMessage.includes('ThrottlingException')) { return '请求被限流,请稍后重试或申请提高配额'; } if (errorMessage.includes('ServiceQuotaExceededException')) { return '超出服务配额限制,请在 AWS 控制台申请提高配额'; } if (errorMessage.includes('ValidationException')) { return '请求参数验证失败,请检查 max_tokens 等参数是否符合模型限制'; } // ... 还有十几种错误 return `Bedrock 调用失败: ${errorMessage}`;}
用户看到的是人话,不是 AWS 的原始错误码。「你的 IAM 用户需要 bedrock:InvokeModel 权限」比 AccessDeniedException 有用多了。
配置验证和诊断
Claude Code 还提供了配置验证工具:
export function validateProviderConfig(config: ProviderConfig) { const errors: string[] = []; const warnings: string[] = []; if (config.type === 'bedrock') { // 检查必填字段 if (!config.region) { errors.push('AWS_REGION 未设置'); } if (!config.accessKeyId) { errors.push('AWS_ACCESS_KEY_ID 未设置'); } if (!config.secretAccessKey) { errors.push('AWS_SECRET_ACCESS_KEY 未设置'); } // 检查格式 if (config.region && !isValidAwsRegion(config.region)) { warnings.push(`区域 ${config.region} 不在已知列表中,请确认拼写`); } // 检查凭证长度 if (config.accessKeyId && config.accessKeyId.length < 16) { errors.push('AWS_ACCESS_KEY_ID 长度不正确'); } } return { valid: errors.length === 0, errors, warnings, };}
还有个 CLI 诊断命令:
$ claude provider diagnoseProvider Detection: bedrockEnvironment Variables: ✓ AWS_REGION: us-east-1 ✓ AWS_ACCESS_KEY_ID: AKIA... ✓ AWS_SECRET_ACCESS_KEY: **** ○ AWS_SESSION_TOKEN: not setModel Configuration: ✓ Model ID: anthropic.claude-3-5-sonnet-20241022-v2:0 ✓ Region supports model: yesValidation: ✓ Configuration is validConnection Test: ✓ Successfully connected to Bedrock
这种诊断工具在企业环境里特别有用。出问题的时候,跑一下诊断命令,大部分配置问题都能定位出来。
LLM Gateway:企业级的中间代理层
前面说的都是 Claude Code 直连云厂商的场景。但在真实的企业环境里,还有一种更常见的部署方式——在中间加一层 LLM Gateway。
LLM Gateway 就是夹在 Claude Code 和云厂商之间的代理层——统一认证、追踪用量、控成本、审计日志,一个入口管住所有开发者。几十号人的团队,总不能每人自己管一套 Key 吧。
Claude Code 官方文档专门写了 Gateway 接入方式。以社区里流行的 LiteLLM 为例:
# 直连 Anthropic 格式(推荐)export ANTHROPIC_BASE_URL=https://litellm-server:4000# 走 Bedrock 透传export ANTHROPIC_BEDROCK_BASE_URL=https://litellm-server:4000/bedrockexport CLAUDE_CODE_SKIP_BEDROCK_AUTH=1export CLAUDE_CODE_USE_BEDROCK=1# 走 Vertex 透传export ANTHROPIC_VERTEX_BASE_URL=https://litellm-server:4000/vertex_ai/v1export CLAUDE_CODE_SKIP_VERTEX_AUTH=1export CLAUDE_CODE_USE_VERTEX=1
注意那两个 SKIP_AUTH 的标志位。设了之后,Claude Code 不再自己处理认证,而是把认证交给 Gateway 去做。这个设计很聪明——Gateway 已经统一管理了密钥和凭证,Claude Code 就不需要再操心签名和 token 这些事了。
有个细节容易踩坑:Gateway 必须正确转发 anthropic-beta、anthropic-version 这些 header,它们控制着 Claude Code 的功能开关,丢了就会莫名其妙地少一些高级功能。
我在实际项目里见过不少企业用 Gateway 做多租户隔离——每个团队一个 Key,后台按部门分摊成本,月底出账单一目了然。
完整的请求流程
把整个多云适配流程串起来:

这套设计教会我什么
看完这套多云适配,有几个设计决策值得抄作业:
别硬编码云厂商逻辑 — 工厂 + 策略模式,新增一个厂商只加一个 createXxxClient(),不动已有代码。Foundry 就是这么无痛接入的。
环境变量即意图 — 有什么凭证用什么供应商,零配置自动切换,比写配置文件优雅得多。
认证要有纵深 — 基础认证只是及格线,SSO、临时凭证、自动刷新、Gateway 透传才是企业级。
翻译层是粘合剂 — 模型 ID 映射、端点格式转换、错误信息翻译,这三层”同声传译”让上层代码完全不用关心底层是谁。
我之前做企业级产品就吃过这个亏。一开始只支持一个厂商,后来客户要求加一个,改得满头大汗。要是一开始就用这种抽象层设计,后面扩展会轻松很多。
下一篇聊集成系统。Claude Code 是怎么和 GitHub、VS Code、Chrome 浏览器打通的?这些集成背后的协议和机制是什么?
本文基于 Claude Code 源码分析,主要文件:src/providers/index.ts、src/providers/vertex.ts、src/providers/bedrock.ts、src/providers/foundry.ts。
夜雨聆风
