Skip to content
源码分析手册

Model Selection:可用模型白名单的深度匹配与“Default”回退机制

它解决了什么问题

在 Claude Code 的配置体系中,availableModels(可用模型列表)并非简单的字符串过滤,而是一套集成了家族别名展开、版本前缀匹配与默认模型保护的精密裁决机制。

这套装配项的本质是约束“用户的显式选择行为”,而非彻底锁定 CLI 的模型调用。它控制的是 /model 命令、--model 启动参数、ANTHROPIC_MODEL 环境变量以及 Settings 中的 model 字段。一个核心的设计原则是:availableModels 永远不会屏蔽 Default(默认模型)选项。这意味着即便管理员配置了一个空列表,用户依然可以运行 Claude Code,只是失去了手动切换到特定版本模型的权限。

从源码看实现

这一约束的核心逻辑封装在 claude-code-opensource/src/utils/model/modelAllowlist.ts 中。

1. 三层匹配算法

当系统校验一个模型是否“允许”时,它会按顺序执行三层逻辑,而非简单的 includes 检查:

  1. 家族别名(Family Alias)匹配:系统识别 sonnetopushaiku 等家族关键词。如果你在白名单中写了 opus,它默认会作为通配符允许所有的 Opus 变体。但这里有一个“收窄规则”:一旦白名单中出现了更具体的条目(如 ["opus", "opus-4-5"]),原有的 opus 通配符就会失效,系统将只允许指定的 4.5 版本。
  2. 版本前缀(Version Prefix)匹配:支持形如 claude-3-5-sonnet 的前缀匹配。源码中的 modelMatchesVersionPrefix() 确保匹配发生在段落边界(Segment Boundary),防止因字符串包含而产生的误伤(例如防止 sonnet-prosonnet 误匹配)。
  3. 全量 ID 匹配:最基础的完整模型 ID 对比。

2. 别名的双向解析

Claude Code 的匹配器非常智能,它不要求白名单与用户输入格式完全一致。系统会通过 parseUserSpecifiedModel() 将用户输入解析为 Provider 下的真实模型 ID,同时也会将白名单中的别名反向解析。这种“双向展开”保证了无论用户输入的是 sonnet 还是 claude-3-5-sonnet-latest,都能正确命中白名单中的 sonnet 条目。

3. “Default”的免疫地位

claude-code-opensource/src/utils/model/modelOptions.ts 中,当系统为 /model 菜单生成选项列表时,会调用 filterModelOptionsByAllowlist()。该函数有一个显式的特判:如果选项的值为 null(代表 Default),则无条件保留。

而在实际的模型解析链(model.ts)中,如果检测到用户指定的模型不在白名单内,getUserSpecifiedModelSetting() 会直接返回 undefined。这会导致系统自动回退到 getDefaultMainLoopModel(),即当前组织或 Provider 推荐的默认模型(通常是 Sonnet)。

4. 跨层级的数组合并

作为一个数组类型的配置项,availableModels 遵循 settings.ts 中的“拼接并去重”规则。这意味着如果你在 userSettings 里加了 opus,在 projectSettings 里加了 haiku,最终生效的白名单将包含这两者。因此,要实现真正的组织级硬管控,必须将限制项放在 policySettings 层级,因为那一层具有绝对的抢占权。

5. 模型别名与 1M 上下文(Model Aliases & 1M)

claude-code-opensource/src/utils/model/aliases.ts 中,系统预定义了一系列快捷别名:

  • 常用家族sonnetopushaiku
  • 增强版本sonnet[1m]opus[1m]。带有 [1m] 后缀的别名会触发 has1mContext() 逻辑,尝试申请 100 万 token 的超大上下文窗口。
  • 功能特定best(自动选择当前最强模型)、opusplan(专门为 /plan 模式优化的 Opus 配置)。 系统通过 parseUserSpecifiedModel 将这些别名动态映射到真实的 API 模型 ID(如 claude-3-7-sonnet-20250219),并处理 [1m] 后缀的剥离与能力校验。

6. Provider 路由机制(Provider Routing)

Claude Code 支持 Anthropic 原生 API,也支持通过云厂商进行路由。在 claude-code-opensource/src/utils/model/providers.ts 中,系统根据环境变量决定 APIProvider

  • firstParty:默认模式,直接连接 api.anthropic.com
  • bedrock / vertex / foundry:分别对应 AWS Bedrock、Google Vertex AI 和企业私有 Foundry 部署。
  • 环境变量驱动:例如设置 CLAUDE_CODE_USE_BEDROCK=true 会将所有请求路由至 Bedrock。不同 Provider 下的模型名称可能会被 normalizeModelStringForAPI 进行适配化处理,以符合各家云厂商的命名规范。

别踩这些坑

  • 白名单不锁死 Default。即便白名单为空,用户依然可以使用默认模型。如果你想完全禁止用户使用 CLI,应该通过策略限制其登录或权限,而非仅仅修改模型白名单。
  • 别名通配符的失效场景。记住 ["sonnet"] 允许所有 Sonnet,但 ["sonnet", "sonnet-20241022"] 仅允许那个特定日期版本。这是为了防止管理员在进行特定版本锁定测试时,意外放行了全族模型。
  • 1M 上下文的准入限制。即便设置了 sonnet[1m],系统仍会通过 checkSonnet1mAccess() 校验用户的订阅等级或组织权限。如果校验失败,系统会给出详细的升级建议链接。
  • 环境变量的优先级ANTHROPIC_MODEL 虽然优先级很高,但它依然要过 isModelAllowed() 的审计。如果环境变量指定的模型不在白名单内,系统会静默回退到默认模型并给出提示。
  • Provider 的排他性:目前 getAPIProvider 采用简单的 if-else 链条,这意味着你不能在同一个会话中同时启用多个云厂商路由。

延伸阅读

  • 如果你想了解不同 Provider 下的模型定价差异,请看 modelCost.ts
  • 如果你想了解如何为自定义模型配置 1M 上下文能力,建议分析 check1mAccess.ts
  • 如果你在开发需要根据当前模型动态调整 UI 的功能,应重点研究 useMainLoopModel.ts React Hook。

源码锚点

  • claude-code-opensource/src/utils/model/modelAllowlist.ts: 核心匹配逻辑、家族别名展开与收窄规则。
📄 src/utils/model/modelAllowlist.ts — 核心匹配逻辑、家族别名展开与收窄规则。L10-20 of 171
typescript
function modelBelongsToFamily(model: string, family: string): boolean {
  if (model.includes(family)) {
    return true
  }
  // Resolve aliases like "best" → "claude-opus-4-6" to check family membership
  if (isModelAlias(model)) {
    const resolved = parseUserSpecifiedModel(model).toLowerCase()
    return resolved.includes(family)
  }
  return false
}
  • claude-code-opensource/src/utils/model/model.ts: 模型解析链条、优先级处理与 [1m] 后缀解析。
📄 src/utils/model/model.ts — 模型解析链条、优先级处理与 `[1m]` 后缀解析。L32-38 of 619
typescript
export type ModelShortName = string
export type ModelName = string
export type ModelSetting = ModelName | ModelAlias | null

export function getSmallFastModel(): ModelName {
  return process.env.ANTHROPIC_SMALL_FAST_MODEL || getDefaultHaikuModel()
}
  • claude-code-opensource/src/utils/model/aliases.ts: 定义官方支持的家族别名、1M 变体与双向映射表。
📄 src/utils/model/aliases.ts — 定义官方支持的家族别名、1M 变体与双向映射表。L1-14 of 26
typescript
export const MODEL_ALIASES = [
  'sonnet',
  'opus',
  'haiku',
  'best',
  'sonnet[1m]',
  'opus[1m]',
  'opusplan',
] as const
export type ModelAlias = (typeof MODEL_ALIASES)[number]

export function isModelAlias(modelInput: string): modelInput is ModelAlias {
  return MODEL_ALIASES.includes(modelInput as ModelAlias)
}
  • claude-code-opensource/src/utils/model/providers.ts: APIProvider 路由逻辑,支持 Bedrock、Vertex 和 Foundry。
📄 src/utils/model/providers.ts — `APIProvider` 路由逻辑,支持 Bedrock、Vertex 和 Foundry。L4-14 of 41
typescript
export type APIProvider = 'firstParty' | 'bedrock' | 'vertex' | 'foundry'

export function getAPIProvider(): APIProvider {
  return isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)
    ? 'bedrock'
    : isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX)
      ? 'vertex'
      : isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY)
        ? 'foundry'
        : 'firstParty'
}
  • claude-code-opensource/src/utils/model/check1mAccess.ts: 1M 大上下文权限校验逻辑。
📄 src/utils/model/check1mAccess.ts — 1M 大上下文权限校验逻辑。L11-40 of 73
typescript
function isExtraUsageEnabled(): boolean {
  const reason = getGlobalConfig().cachedExtraUsageDisabledReason
  // undefined = no cache yet, treat as not enabled (conservative)
  if (reason === undefined) {
    return false
  }
  // null = no disabled reason from API, extra usage is enabled
  if (reason === null) {
    return true
  }
  // Check which disabled reasons still mean "provisioned"
  switch (reason as OverageDisabledReason) {
    // Provisioned but credits depleted — still counts as enabled
    case 'out_of_credits':
      return true
    // Not provisioned or actively disabled
    case 'overage_not_provisioned':
    case 'org_level_disabled':
    case 'org_level_disabled_until':
    case 'seat_tier_level_disabled':
    case 'member_level_disabled':
    case 'seat_tier_zero_credit_limit':
    case 'group_zero_credit_limit':
    case 'member_zero_credit_limit':
    case 'org_service_level_disabled':
    case 'org_service_zero_credit_limit':
    case 'no_limits_configured':
    case 'unknown':
      return false
    default:
  • claude-code-opensource/src/utils/settings/settings.ts: 处理 availableModels 数组跨 Scope 合并的底层逻辑。
📄 src/utils/settings/settings.ts — 处理 `availableModels` 数组跨 Scope 合并的底层逻辑。L27-31 of 1016
typescript
  type EditableSettingSource,
  getEnabledSettingSources,
  type SettingSource,
} from './constants.js'
import { markInternalWrite } from './internalWrites.js'

基于 Claude Code v2.1.88 开源快照的深度分析