feat: 添加 model/provider 层改进

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
unraid
2026-04-22 22:38:10 +08:00
parent d208855f07
commit 23bb09d240
13 changed files with 689 additions and 472 deletions

View File

@@ -28,18 +28,6 @@ import { getAPIProvider } from './providers.js'
import { LIGHTNING_BOLT } from '../../constants/figures.js'
import { isModelAllowed } from './modelAllowlist.js'
import { type ModelAlias, isModelAlias } from './aliases.js'
/**
* Returns true if the value is a model alias or a model alias with a suffix
* like [1m] (e.g. "opus", "opus[1m]", "sonnet", "haiku[1m]").
* Used to guard against infinite recursion when getDefault*Model() falls back
* to the user-specified setting — an alias like "opus[1m]" would cause
* parseUserSpecifiedModel → getDefaultOpusModel → parseUserSpecifiedModel loop.
*/
function isAliasOrAliasWithSuffix(value: string): boolean {
const base = value.replace(/\[1m\]$/i, '').trim()
return isModelAlias(base)
}
import { capitalize } from '../stringUtils.js'
export type ModelShortName = string
@@ -64,7 +52,8 @@ export function isNonCustomOpusModel(model: ModelName): boolean {
model === getModelStrings().opus40 ||
model === getModelStrings().opus41 ||
model === getModelStrings().opus45 ||
model === getModelStrings().opus46
model === getModelStrings().opus46 ||
model === getModelStrings().opus47
)
}
@@ -138,21 +127,14 @@ export function getDefaultOpusModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) {
return process.env.ANTHROPIC_DEFAULT_OPUS_MODEL
}
// Fall back to user's configured model — custom providers may not
// recognize hardcoded Anthropic model IDs.
// Skip if the user setting is a model alias (e.g. "opus", "opus[1m]") to
// avoid infinite recursion: parseUserSpecifiedModel(alias) → getDefaultOpusModel().
const userSpecifiedOpus = getUserSpecifiedModelSetting()
if (userSpecifiedOpus && !isAliasOrAliasWithSuffix(userSpecifiedOpus)) {
return parseUserSpecifiedModel(userSpecifiedOpus)
}
// 3P providers (Bedrock, Vertex, Foundry) — kept as a separate branch
// even when values match, since 3P availability lags firstParty and
// these will diverge again at the next model launch.
// 3P providers (Bedrock, Vertex, Foundry) all publish Opus 4.7 in sync
// with firstParty as of 2026-04-17 (AWS Bedrock, Google Vertex AI, and
// Microsoft Foundry announcements and model catalogs all confirm). The
// branch is kept as a structural hook in case a future launch lags on 3P.
if (provider !== 'firstParty') {
return getModelStrings().opus46
return getModelStrings().opus47
}
return getModelStrings().opus46
return getModelStrings().opus47
}
// @[MODEL LAUNCH]: Update the default Sonnet model (3P providers may lag so keep defaults unchanged).
@@ -173,14 +155,6 @@ export function getDefaultSonnetModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) {
return process.env.ANTHROPIC_DEFAULT_SONNET_MODEL
}
// Fall back to user's configured model (ANTHROPIC_MODEL / settings) —
// custom providers (proxies, national clouds) may not recognize the
// hardcoded Anthropic model IDs.
// Skip if the user setting is a model alias to avoid infinite recursion.
const userSpecified = getUserSpecifiedModelSetting()
if (userSpecified && !isAliasOrAliasWithSuffix(userSpecified)) {
return parseUserSpecifiedModel(userSpecified)
}
// Default to Sonnet 4.5 for 3P since they may not have 4.6 yet
if (provider !== 'firstParty') {
return getModelStrings().sonnet45
@@ -203,13 +177,6 @@ export function getDefaultHaikuModel(): ModelName {
if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) {
return process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL
}
// Fall back to user's configured model — custom providers may not
// recognize hardcoded Anthropic model IDs.
// Skip if the user setting is a model alias to avoid infinite recursion.
const userSpecifiedHaiku = getUserSpecifiedModelSetting()
if (userSpecifiedHaiku && !isAliasOrAliasWithSuffix(userSpecifiedHaiku)) {
return parseUserSpecifiedModel(userSpecifiedHaiku)
}
// Haiku 4.5 is available on all platforms (first-party, Foundry, Bedrock, Vertex)
return getModelStrings().haiku45
@@ -296,6 +263,9 @@ export function firstPartyNameToCanonical(name: ModelName): ModelShortName {
name = name.toLowerCase()
// Special cases for Claude 4+ models to differentiate versions
// Order matters: check more specific versions first (4-5 before 4)
if (name.includes('claude-opus-4-7')) {
return 'claude-opus-4-7'
}
if (name.includes('claude-opus-4-6')) {
return 'claude-opus-4-6'
}
@@ -366,9 +336,9 @@ export function getClaudeAiUserDefaultModelDescription(
): string {
if (isMaxSubscriber() || isTeamPremiumSubscriber()) {
if (isOpus1mMergeEnabled()) {
return `Opus 4.6 with 1M context · Most capable for complex work${fastMode ? getOpus46PricingSuffix(true) : ''}`
return `Opus 4.7 with 1M context · Most capable for complex work${fastMode ? getOpusPricingSuffix(true) : ''}`
}
return `Opus 4.6 · Most capable for complex work${fastMode ? getOpus46PricingSuffix(true) : ''}`
return `Opus 4.7 · Most capable for complex work${fastMode ? getOpusPricingSuffix(true) : ''}`
}
return 'Sonnet 4.6 · Best for everyday tasks'
}
@@ -377,12 +347,12 @@ export function renderDefaultModelSetting(
setting: ModelName | ModelAlias,
): string {
if (setting === 'opusplan') {
return 'Opus 4.6 in plan mode, else Sonnet 4.6'
return 'Opus 4.7 in plan mode, else Sonnet 4.6'
}
return renderModelName(parseUserSpecifiedModel(setting))
}
export function getOpus46PricingSuffix(fastMode: boolean): string {
export function getOpusPricingSuffix(fastMode: boolean): string {
if (getAPIProvider() !== 'firstParty') return ''
const pricing = formatModelPricing(getOpus46CostTier(fastMode))
const fastModeIndicator = fastMode ? ` (${LIGHTNING_BOLT})` : ''
@@ -426,6 +396,10 @@ export function renderModelSetting(setting: ModelName | ModelAlias): string {
*/
export function getPublicModelDisplayName(model: ModelName): string | null {
switch (model) {
case getModelStrings().opus47:
return 'Opus 4.7'
case getModelStrings().opus47 + '[1m]':
return 'Opus 4.7 (1M context)'
case getModelStrings().opus46:
return 'Opus 4.6'
case getModelStrings().opus46 + '[1m]':
@@ -549,9 +523,10 @@ export function parseUserSpecifiedModel(
// Opus 4/4.1 are no longer available on the first-party API (same as
// Claude.ai) — silently remap to the current Opus default. The 'opus'
// alias already resolves to 4.6, so the only users on these explicit
// strings pinned them in settings/env/--model/SDK before 4.5 launched.
// 3P providers may not yet have 4.6 capacity, so pass through unchanged.
// alias resolves to the current default Opus (4.7), so the only users
// on these explicit strings pinned them in settings/env/--model/SDK
// before 4.5 launched. 3P providers may not yet have 4.6/4.7 capacity,
// so pass through unchanged.
if (
getAPIProvider() === 'firstParty' &&
isLegacyOpusFirstParty(modelString) &&
@@ -654,6 +629,9 @@ export function getMarketingNameForModel(modelId: string): string | undefined {
const has1m = modelId.toLowerCase().includes('[1m]')
const canonical = getCanonicalName(modelId)
if (canonical.includes('claude-opus-4-7')) {
return has1m ? 'Opus 4.7 (with 1M context)' : 'Opus 4.7'
}
if (canonical.includes('claude-opus-4-6')) {
return has1m ? 'Opus 4.6 (with 1M context)' : 'Opus 4.6'
}