fix(anthropic): 支持 API Key Bearer 认证方式

This commit is contained in:
alfadb
2026-07-02 10:43:08 +08:00
parent 7dc7cfce1d
commit 7869b7fe38
11 changed files with 238 additions and 14 deletions
@@ -2653,6 +2653,24 @@
</div>
</div>
<div
v-if="form.platform === 'anthropic' && accountCategory === 'apikey'"
class="border-t border-gray-200 pt-4 dark:border-dark-600"
>
<div class="flex items-center justify-between gap-4">
<div>
<label class="input-label mb-0">{{ t('admin.accounts.anthropic.apiKeyAuthScheme') }}</label>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.anthropic.apiKeyAuthSchemeDesc') }}
</p>
</div>
<select v-model="anthropicAPIKeyAuthScheme" class="input w-52 text-sm">
<option value="x_api_key">{{ t('admin.accounts.anthropic.apiKeyAuthSchemeXApiKey') }}</option>
<option value="authorization_bearer">{{ t('admin.accounts.anthropic.apiKeyAuthSchemeBearer') }}</option>
</select>
</div>
</div>
<!-- Anthropic API Key: Web Search Emulation (hidden when global disabled) -->
<div
v-if="form.platform === 'anthropic' && accountCategory === 'apikey' && webSearchGlobalEnabled"
@@ -3503,7 +3521,9 @@ const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF
const openaiAPIKeyResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
const codexCLIOnlyEnabled = ref(false)
const codexCLIOnlyAppServerEnabled = ref(false)
type AnthropicAPIKeyAuthScheme = 'x_api_key' | 'authorization_bearer'
const anthropicPassthroughEnabled = ref(false)
const anthropicAPIKeyAuthScheme = ref<AnthropicAPIKeyAuthScheme>('x_api_key')
const webSearchEmulationMode = ref('default')
const webSearchGlobalEnabled = ref(false)
const {
@@ -3945,6 +3965,7 @@ watch(
}
if (newPlatform !== 'anthropic') {
anthropicPassthroughEnabled.value = false
anthropicAPIKeyAuthScheme.value = 'x_api_key'
webSearchEmulationMode.value = 'default'
}
// Reset OAuth states
@@ -3967,6 +3988,7 @@ watch(
}
if (platform !== 'anthropic' || category !== 'apikey') {
anthropicPassthroughEnabled.value = false
anthropicAPIKeyAuthScheme.value = 'x_api_key'
webSearchEmulationMode.value = 'default'
}
}
@@ -4346,6 +4368,7 @@ const resetForm = () => {
codexCLIOnlyEnabled.value = false
codexCLIOnlyAppServerEnabled.value = false
anthropicPassthroughEnabled.value = false
anthropicAPIKeyAuthScheme.value = 'x_api_key'
webSearchEmulationMode.value = 'default'
// Reset quota control state
windowCostEnabled.value = false
@@ -4465,6 +4488,11 @@ const buildAnthropicExtra = (base?: Record<string, unknown>): Record<string, unk
} else {
delete extra.anthropic_passthrough
}
if (anthropicAPIKeyAuthScheme.value === 'authorization_bearer') {
extra.anthropic_apikey_auth_scheme = 'authorization_bearer'
} else {
delete extra.anthropic_apikey_auth_scheme
}
if (webSearchEmulationMode.value === 'default') {
delete extra.web_search_emulation
} else {
@@ -1537,6 +1537,24 @@
</div>
</div>
<div
v-if="account?.platform === 'anthropic' && account?.type === 'apikey'"
class="border-t border-gray-200 pt-4 dark:border-dark-600"
>
<div class="flex items-center justify-between gap-4">
<div>
<label class="input-label mb-0">{{ t('admin.accounts.anthropic.apiKeyAuthScheme') }}</label>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.anthropic.apiKeyAuthSchemeDesc') }}
</p>
</div>
<select v-model="anthropicAPIKeyAuthScheme" class="input w-52 text-sm">
<option value="x_api_key">{{ t('admin.accounts.anthropic.apiKeyAuthSchemeXApiKey') }}</option>
<option value="authorization_bearer">{{ t('admin.accounts.anthropic.apiKeyAuthSchemeBearer') }}</option>
</select>
</div>
</div>
<!-- Anthropic API Key: Web Search Emulation (hidden when global disabled) -->
<div
v-if="account?.platform === 'anthropic' && account?.type === 'apikey' && webSearchGlobalEnabled"
@@ -2610,7 +2628,9 @@ const codexCLIOnlyEnabled = ref(false)
const codexCLIOnlyAppServerEnabled = ref(false)
type CodexImageGenerationBridgeMode = 'inherit' | 'enabled' | 'disabled'
const codexImageGenerationBridgeMode = ref<CodexImageGenerationBridgeMode>('inherit')
type AnthropicAPIKeyAuthScheme = 'x_api_key' | 'authorization_bearer'
const anthropicPassthroughEnabled = ref(false)
const anthropicAPIKeyAuthScheme = ref<AnthropicAPIKeyAuthScheme>('x_api_key')
const webSearchEmulationMode = ref('default')
const webSearchGlobalEnabled = ref(false)
const {
@@ -3015,6 +3035,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
codexCLIOnlyAppServerEnabled.value = false
codexImageGenerationBridgeMode.value = 'inherit'
anthropicPassthroughEnabled.value = false
anthropicAPIKeyAuthScheme.value = 'x_api_key'
webSearchEmulationMode.value = 'default'
if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'apikey')) {
openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true
@@ -3061,6 +3082,9 @@ const syncFormFromAccount = (newAccount: Account | null) => {
}
if (newAccount.platform === 'anthropic' && newAccount.type === 'apikey') {
anthropicPassthroughEnabled.value = extra?.anthropic_passthrough === true
anthropicAPIKeyAuthScheme.value = extra?.anthropic_apikey_auth_scheme === 'authorization_bearer'
? 'authorization_bearer'
: 'x_api_key'
// 三态:string "default"/"enabled"/"disabled",向后兼容旧 bool
const wsVal = extra?.web_search_emulation
if (wsVal === 'enabled' || wsVal === 'disabled') {
@@ -4101,6 +4125,11 @@ const handleSubmit = async () => {
} else {
delete newExtra.anthropic_passthrough
}
if (anthropicAPIKeyAuthScheme.value === 'authorization_bearer') {
newExtra.anthropic_apikey_auth_scheme = 'authorization_bearer'
} else {
delete newExtra.anthropic_apikey_auth_scheme
}
if (webSearchEmulationMode.value === 'default') {
delete newExtra.web_search_emulation
} else {
+4
View File
@@ -3563,6 +3563,10 @@ export default {
apiKeyPassthrough: 'Auto passthrough (auth only)',
apiKeyPassthroughDesc:
'Only applies to Anthropic API Key accounts. When enabled, messages/count_tokens are forwarded in passthrough mode with auth replacement only, while billing/concurrency/audit and safety filtering are preserved. Disable to roll back immediately.',
apiKeyAuthScheme: 'Upstream auth scheme',
apiKeyAuthSchemeDesc: 'Choose the API key auth header used when forwarding to an Anthropic-compatible upstream. Ollama Cloud uses Authorization: Bearer.',
apiKeyAuthSchemeXApiKey: 'x-api-key',
apiKeyAuthSchemeBearer: 'Authorization: Bearer',
webSearchEmulation: 'Web Search Emulation',
webSearchEmulationDesc:
'Enable web search emulation for this API Key account. When a pure web_search request is detected, the gateway calls a third-party search API and constructs the response locally. Default follows channel config.',
+4
View File
@@ -3732,6 +3732,10 @@ export default {
apiKeyPassthrough: '自动透传(仅替换认证)',
apiKeyPassthroughDesc:
'仅对 Anthropic API Key 生效。开启后,messages/count_tokens 请求将透传上游并仅替换认证,保留计费/并发/审计及必要安全过滤;关闭即可回滚到现有兼容链路。',
apiKeyAuthScheme: '上游认证方式',
apiKeyAuthSchemeDesc: '选择转发到 Anthropic-compatible 上游时使用的 API Key 认证头。Ollama Cloud 使用 Authorization: Bearer。',
apiKeyAuthSchemeXApiKey: 'x-api-key',
apiKeyAuthSchemeBearer: 'Authorization: Bearer',
webSearchEmulation: 'Web Search 模拟',
webSearchEmulationDesc:
'为该 API Key 账号启用 web search 模拟。客户端发送纯 web_search 请求时,由网关调用第三方搜索 API 并构造响应返回。默认跟随渠道配置。',