mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-01 15:02:58 +08:00
feat(accounts): support header overrides for CN providers
This commit is contained in:
@@ -9,8 +9,8 @@ import (
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// 请求头覆写(header override):对 Anthropic / OpenAI 平台的 api_key 账号
|
||||
// 以及 Grok 平台的 api_key / oauth 账号生效。
|
||||
// 请求头覆写(header override):对 Anthropic / OpenAI / Kimi / Zhipu / DeepSeek
|
||||
// 平台的 api_key 账号,以及 Grok 平台的 api_key / oauth 账号生效。
|
||||
// 管理员在账号上配置一组 header name -> value,转发到上游前用配置值覆盖同名请求头
|
||||
// (匹配不区分大小写);value 为空的条目视为"未填写",不参与覆盖。
|
||||
const (
|
||||
@@ -70,14 +70,15 @@ func isHeaderOverrideBlockedName(lowerName string) bool {
|
||||
}
|
||||
|
||||
// IsHeaderOverrideEligible 报告账号类型是否支持请求头覆写。
|
||||
// Anthropic / OpenAI 仅开放 api_key 账号;Grok 额外开放 oauth 账号——
|
||||
// Anthropic / OpenAI / Kimi / Zhipu / DeepSeek 仅开放 api_key 账号;
|
||||
// Grok 额外开放 oauth 账号——
|
||||
// 订阅流量改发自定义转发地址时,通常需要补充中间层要求的准入头。
|
||||
func (a *Account) IsHeaderOverrideEligible() bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
switch a.Platform {
|
||||
case PlatformAnthropic, PlatformOpenAI:
|
||||
case PlatformAnthropic, PlatformOpenAI, PlatformKimi, PlatformZhipu, PlatformDeepseek:
|
||||
return a.Type == AccountTypeAPIKey
|
||||
case PlatformGrok:
|
||||
return a.Type == AccountTypeAPIKey || a.Type == AccountTypeOAuth
|
||||
|
||||
@@ -27,8 +27,14 @@ func TestIsHeaderOverrideEligible(t *testing.T) {
|
||||
}{
|
||||
{"anthropic apikey", PlatformAnthropic, AccountTypeAPIKey, true},
|
||||
{"openai apikey", PlatformOpenAI, AccountTypeAPIKey, true},
|
||||
{"kimi apikey", PlatformKimi, AccountTypeAPIKey, true},
|
||||
{"zhipu apikey", PlatformZhipu, AccountTypeAPIKey, true},
|
||||
{"deepseek apikey", PlatformDeepseek, AccountTypeAPIKey, true},
|
||||
{"anthropic oauth", PlatformAnthropic, AccountTypeOAuth, false},
|
||||
{"openai oauth", PlatformOpenAI, AccountTypeOAuth, false},
|
||||
{"kimi oauth", PlatformKimi, AccountTypeOAuth, false},
|
||||
{"zhipu oauth", PlatformZhipu, AccountTypeOAuth, false},
|
||||
{"deepseek oauth", PlatformDeepseek, AccountTypeOAuth, false},
|
||||
{"gemini apikey", PlatformGemini, AccountTypeAPIKey, false},
|
||||
{"grok apikey", PlatformGrok, AccountTypeAPIKey, true},
|
||||
{"grok oauth", PlatformGrok, AccountTypeOAuth, true},
|
||||
|
||||
@@ -602,7 +602,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header Override (anthropic/openai apikey only) -->
|
||||
<!-- Header Override (eligible API-key platforms + grok OAuth) -->
|
||||
<div v-if="allHeaderOverrideCapable" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 pr-4">
|
||||
@@ -1599,7 +1599,7 @@ const allBillingProbeCapable = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
// 是否全部为 anthropic/openai 平台的 apikey 账号(请求头覆写仅在此条件下显示)
|
||||
// 是否全部为支持请求头覆写的平台/账号类型
|
||||
// 所选平台 × 所选类型的全组合均需具备覆写资格(实际选中账号是该组合的子集,
|
||||
// 按交叉积判定偏保守但绝不放行不合资格的账号)
|
||||
const allHeaderOverrideCapable = computed(() => {
|
||||
|
||||
@@ -1661,7 +1661,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header Override Section (anthropic/openai apikey only) -->
|
||||
<!-- Header Override Section (eligible API-key platforms) -->
|
||||
<div
|
||||
v-if="isHeaderOverrideCapable(form.platform, 'apikey')"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
@@ -5458,7 +5458,7 @@ const handleSubmit = async () => {
|
||||
credentials.custom_error_codes = [...selectedErrorCodes.value]
|
||||
}
|
||||
|
||||
// Add header override if enabled (anthropic/openai/grok apikey)
|
||||
// Add header override if enabled for this API-key platform
|
||||
if (isHeaderOverrideCapable(form.platform, 'apikey')) {
|
||||
if (headerOverrideEnabled.value) {
|
||||
const headerError = validateHeaderOverrideRows(headerOverrideRows.value)
|
||||
|
||||
@@ -540,7 +540,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header Override Section (anthropic/openai apikey + grok apikey/oauth) -->
|
||||
<!-- Header Override Section (eligible API-key platforms + grok OAuth) -->
|
||||
<div v-if="headerOverrideCapable" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
@@ -3730,7 +3730,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
loadTempUnschedRules(credentials)
|
||||
loadAccountSchedulingThresholdOverride(newAccount.platform, credentials)
|
||||
|
||||
// Load header override state (anthropic/openai apikey + grok apikey/oauth)
|
||||
// Load header override state for eligible account platforms/types
|
||||
headerOverrideEnabled.value = false
|
||||
headerOverrideRows.value = []
|
||||
if (newAccount.credentials && isHeaderOverrideCapable(newAccount.platform, newAccount.type)) {
|
||||
@@ -4520,7 +4520,7 @@ const handleSubmit = async () => {
|
||||
delete newCredentials.custom_error_codes
|
||||
}
|
||||
|
||||
// Add header override if enabled (anthropic/openai/grok apikey)
|
||||
// Add header override if enabled for this API-key platform
|
||||
if (isHeaderOverrideCapable(props.account.platform, 'apikey')) {
|
||||
if (headerOverrideEnabled.value) {
|
||||
const headerError = validateHeaderOverrideRows(headerOverrideRows.value)
|
||||
|
||||
@@ -217,6 +217,24 @@ describe('BulkEditAccountModal', () => {
|
||||
expect(wrapper.findAll('[data-testid="grok-base-url-preset"]').length).toBe(0)
|
||||
})
|
||||
|
||||
it.each(['kimi', 'zhipu', 'deepseek'])('全部目标为 %s API Key 时展示请求头覆写', (platform) => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: [platform],
|
||||
selectedTypes: ['apikey']
|
||||
})
|
||||
|
||||
expect(wrapper.find('#bulk-edit-header-override-enabled').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['kimi', 'zhipu', 'deepseek'])('目标为 %s OAuth 时不展示请求头覆写', (platform) => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: [platform],
|
||||
selectedTypes: ['oauth']
|
||||
})
|
||||
|
||||
expect(wrapper.find('#bulk-edit-header-override-enabled').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('全部目标为 Grok OAuth 时,第三方 base_url 正常提交', async () => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: ['grok'],
|
||||
|
||||
@@ -106,6 +106,13 @@ describe('isHeaderOverrideCapable', () => {
|
||||
expect(isHeaderOverrideCapable('openai', 'oauth')).toBe(false)
|
||||
})
|
||||
|
||||
it('kimi/zhipu/deepseek only support apikey accounts', () => {
|
||||
for (const platform of ['kimi', 'zhipu', 'deepseek']) {
|
||||
expect(isHeaderOverrideCapable(platform, 'apikey')).toBe(true)
|
||||
expect(isHeaderOverrideCapable(platform, 'oauth')).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('grok supports both apikey and oauth accounts', () => {
|
||||
expect(isHeaderOverrideCapable('grok', 'apikey')).toBe(true)
|
||||
expect(isHeaderOverrideCapable('grok', 'oauth')).toBe(true)
|
||||
@@ -468,4 +475,3 @@ describe('plan_type helpers', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export function applyAntigravityProjectID(
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 请求头覆写(anthropic/openai 的 api_key 账号 + grok 的 api_key/oauth 账号) ==========
|
||||
// ========== 请求头覆写(API-key 平台 + grok 的 api_key/oauth 账号) ==========
|
||||
|
||||
export const HEADER_OVERRIDE_ENABLED_CREDENTIAL_KEY = 'header_override_enabled'
|
||||
export const HEADER_OVERRIDES_CREDENTIAL_KEY = 'header_overrides'
|
||||
@@ -37,7 +37,13 @@ export interface HeaderOverrideRow {
|
||||
|
||||
/** 请求头覆写资格(与后端 IsHeaderOverrideEligible 保持一致) */
|
||||
export function isHeaderOverrideCapable(platform: string, type: string): boolean {
|
||||
if (platform === 'anthropic' || platform === 'openai') {
|
||||
if (
|
||||
platform === 'anthropic' ||
|
||||
platform === 'openai' ||
|
||||
platform === 'kimi' ||
|
||||
platform === 'zhipu' ||
|
||||
platform === 'deepseek'
|
||||
) {
|
||||
return type === 'apikey'
|
||||
}
|
||||
if (platform === 'grok') {
|
||||
|
||||
Reference in New Issue
Block a user