Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
7c60ee3c
"frontend/vscode:/vscode.git/clone" did not exist on "e51c9e50b5376cb486a0b7123e5f1ec026d5c526"
Commit
7c60ee3c
authored
Apr 07, 2026
by
shaw
Browse files
feat: Beta策略支持按模型区分处理(模型白名单)
parent
b2e379cf
Changes
8
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/dto/settings.go
View file @
7c60ee3c
...
...
@@ -157,10 +157,13 @@ type RectifierSettings struct {
// BetaPolicyRule Beta 策略规则 DTO
type
BetaPolicyRule
struct
{
BetaToken
string
`json:"beta_token"`
Action
string
`json:"action"`
Scope
string
`json:"scope"`
ErrorMessage
string
`json:"error_message,omitempty"`
BetaToken
string
`json:"beta_token"`
Action
string
`json:"action"`
Scope
string
`json:"scope"`
ErrorMessage
string
`json:"error_message,omitempty"`
ModelWhitelist
[]
string
`json:"model_whitelist,omitempty"`
FallbackAction
string
`json:"fallback_action,omitempty"`
FallbackErrorMessage
string
`json:"fallback_error_message,omitempty"`
}
// BetaPolicySettings Beta 策略配置 DTO
...
...
backend/internal/service/gateway_service.go
View file @
7c60ee3c
...
...
@@ -3946,7 +3946,7 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
// Beta policy: evaluate once; block check + cache filter set for buildUpstreamRequest.
// Always overwrite the cache to prevent stale values from a previous retry with a different account.
if
account
.
Platform
==
PlatformAnthropic
&&
c
!=
nil
{
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
c
.
GetHeader
(
"anthropic-beta"
),
account
)
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
c
.
GetHeader
(
"anthropic-beta"
),
account
,
parsed
.
Model
)
if
policy
.
blockErr
!=
nil
{
return
nil
,
policy
.
blockErr
}
...
...
@@ -5603,7 +5603,7 @@ func (s *GatewayService) buildUpstreamRequest(ctx context.Context, c *gin.Contex
}
// Build effective drop set: merge static defaults with dynamic beta policy filter rules
policyFilterSet
:=
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
)
policyFilterSet
:=
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
,
modelID
)
effectiveDropSet
:=
mergeDropSets
(
policyFilterSet
)
effectiveDropWithClaudeCodeSet
:=
mergeDropSets
(
policyFilterSet
,
claude
.
BetaClaudeCode
)
...
...
@@ -5843,7 +5843,7 @@ type betaPolicyResult struct {
}
// evaluateBetaPolicy loads settings once and evaluates all rules against the given request.
func
(
s
*
GatewayService
)
evaluateBetaPolicy
(
ctx
context
.
Context
,
betaHeader
string
,
account
*
Account
)
betaPolicyResult
{
func
(
s
*
GatewayService
)
evaluateBetaPolicy
(
ctx
context
.
Context
,
betaHeader
string
,
account
*
Account
,
model
string
)
betaPolicyResult
{
if
s
.
settingService
==
nil
{
return
betaPolicyResult
{}
}
...
...
@@ -5858,10 +5858,11 @@ func (s *GatewayService) evaluateBetaPolicy(ctx context.Context, betaHeader stri
if
!
betaPolicyScopeMatches
(
rule
.
Scope
,
isOAuth
,
isBedrock
)
{
continue
}
switch
rule
.
Action
{
effectiveAction
,
effectiveErrMsg
:=
resolveRuleAction
(
rule
,
model
)
switch
effectiveAction
{
case
BetaPolicyActionBlock
:
if
result
.
blockErr
==
nil
&&
betaHeader
!=
""
&&
containsBetaToken
(
betaHeader
,
rule
.
BetaToken
)
{
msg
:=
rule
.
ErrorMessage
msg
:=
effectiveErrMsg
if
msg
==
""
{
msg
=
"beta feature "
+
rule
.
BetaToken
+
" is not allowed"
}
...
...
@@ -5903,7 +5904,7 @@ const betaPolicyFilterSetKey = "betaPolicyFilterSet"
// In the /v1/messages path, Forward() evaluates the policy first and caches the result;
// buildUpstreamRequest reuses it (zero extra DB calls). In the count_tokens path, this
// evaluates on demand (one DB call).
func
(
s
*
GatewayService
)
getBetaPolicyFilterSet
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
)
map
[
string
]
struct
{}
{
func
(
s
*
GatewayService
)
getBetaPolicyFilterSet
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
model
string
)
map
[
string
]
struct
{}
{
if
c
!=
nil
{
if
v
,
ok
:=
c
.
Get
(
betaPolicyFilterSetKey
);
ok
{
if
fs
,
ok
:=
v
.
(
map
[
string
]
struct
{});
ok
{
...
...
@@ -5911,7 +5912,7 @@ func (s *GatewayService) getBetaPolicyFilterSet(ctx context.Context, c *gin.Cont
}
}
}
return
s
.
evaluateBetaPolicy
(
ctx
,
""
,
account
)
.
filterSet
return
s
.
evaluateBetaPolicy
(
ctx
,
""
,
account
,
model
)
.
filterSet
}
// betaPolicyScopeMatches checks whether a rule's scope matches the current account type.
...
...
@@ -5930,6 +5931,33 @@ func betaPolicyScopeMatches(scope string, isOAuth bool, isBedrock bool) bool {
}
}
// matchModelWhitelist checks if a model matches any pattern in the whitelist.
// Reuses matchModelPattern from group.go which supports exact and wildcard prefix matching.
func
matchModelWhitelist
(
model
string
,
whitelist
[]
string
)
bool
{
for
_
,
pattern
:=
range
whitelist
{
if
matchModelPattern
(
pattern
,
model
)
{
return
true
}
}
return
false
}
// resolveRuleAction determines the effective action and error message for a rule given the request model.
// When ModelWhitelist is empty, the rule's primary Action/ErrorMessage applies unconditionally.
// When non-empty, Action applies to matching models; FallbackAction/FallbackErrorMessage applies to others.
func
resolveRuleAction
(
rule
BetaPolicyRule
,
model
string
)
(
action
,
errorMessage
string
)
{
if
len
(
rule
.
ModelWhitelist
)
==
0
{
return
rule
.
Action
,
rule
.
ErrorMessage
}
if
matchModelWhitelist
(
model
,
rule
.
ModelWhitelist
)
{
return
rule
.
Action
,
rule
.
ErrorMessage
}
if
rule
.
FallbackAction
!=
""
{
return
rule
.
FallbackAction
,
rule
.
FallbackErrorMessage
}
return
BetaPolicyActionPass
,
""
// default fallback: pass (fail-open)
}
// droppedBetaSet returns claude.DroppedBetas as a set, with optional extra tokens.
func
droppedBetaSet
(
extra
...
string
)
map
[
string
]
struct
{}
{
m
:=
make
(
map
[
string
]
struct
{},
len
(
defaultDroppedBetasSet
)
+
len
(
extra
))
...
...
@@ -5976,7 +6004,7 @@ func (s *GatewayService) resolveBedrockBetaTokensForRequest(
modelID
string
,
)
([]
string
,
error
)
{
// 1. 对原始 header 中的 beta token 做 block 检查(快速失败)
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
betaHeader
,
account
)
policy
:=
s
.
evaluateBetaPolicy
(
ctx
,
betaHeader
,
account
,
modelID
)
if
policy
.
blockErr
!=
nil
{
return
nil
,
policy
.
blockErr
}
...
...
@@ -5988,7 +6016,7 @@ func (s *GatewayService) resolveBedrockBetaTokensForRequest(
// 例如:管理员 block 了 interleaved-thinking,客户端不在 header 中带该 token,
// 但请求体中包含 thinking 字段 → autoInjectBedrockBetaTokens 会自动补齐 →
// 如果不做此检查,block 规则会被绕过。
if
blockErr
:=
s
.
checkBetaPolicyBlockForTokens
(
ctx
,
betaTokens
,
account
);
blockErr
!=
nil
{
if
blockErr
:=
s
.
checkBetaPolicyBlockForTokens
(
ctx
,
betaTokens
,
account
,
modelID
);
blockErr
!=
nil
{
return
nil
,
blockErr
}
...
...
@@ -5997,7 +6025,7 @@ func (s *GatewayService) resolveBedrockBetaTokensForRequest(
// checkBetaPolicyBlockForTokens 检查 token 列表中是否有被管理员 block 规则命中的 token。
// 用于补充 evaluateBetaPolicy 对 header 的检查,覆盖 body 自动注入的 token。
func
(
s
*
GatewayService
)
checkBetaPolicyBlockForTokens
(
ctx
context
.
Context
,
tokens
[]
string
,
account
*
Account
)
*
BetaBlockedError
{
func
(
s
*
GatewayService
)
checkBetaPolicyBlockForTokens
(
ctx
context
.
Context
,
tokens
[]
string
,
account
*
Account
,
model
string
)
*
BetaBlockedError
{
if
s
.
settingService
==
nil
||
len
(
tokens
)
==
0
{
return
nil
}
...
...
@@ -6009,14 +6037,15 @@ func (s *GatewayService) checkBetaPolicyBlockForTokens(ctx context.Context, toke
isBedrock
:=
account
.
IsBedrock
()
tokenSet
:=
buildBetaTokenSet
(
tokens
)
for
_
,
rule
:=
range
settings
.
Rules
{
if
rule
.
Action
!=
BetaPolicyActionBlock
{
effectiveAction
,
effectiveErrMsg
:=
resolveRuleAction
(
rule
,
model
)
if
effectiveAction
!=
BetaPolicyActionBlock
{
continue
}
if
!
betaPolicyScopeMatches
(
rule
.
Scope
,
isOAuth
,
isBedrock
)
{
continue
}
if
_
,
present
:=
tokenSet
[
rule
.
BetaToken
];
present
{
msg
:=
rule
.
ErrorMessage
msg
:=
effectiveErrMsg
if
msg
==
""
{
msg
=
"beta feature "
+
rule
.
BetaToken
+
" is not allowed"
}
...
...
@@ -8474,7 +8503,7 @@ func (s *GatewayService) buildCountTokensRequest(ctx context.Context, c *gin.Con
}
// Build effective drop set for count_tokens: merge static defaults with dynamic beta policy filter rules
ctEffectiveDropSet
:=
mergeDropSets
(
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
))
ctEffectiveDropSet
:=
mergeDropSets
(
s
.
getBetaPolicyFilterSet
(
ctx
,
c
,
account
,
modelID
))
// OAuth 账号:处理 anthropic-beta header
if
tokenType
==
"oauth"
{
...
...
backend/internal/service/setting_service.go
View file @
7c60ee3c
...
...
@@ -1527,6 +1527,18 @@ func (s *SettingService) SetBetaPolicySettings(ctx context.Context, settings *Be
if
!
validScopes
[
rule
.
Scope
]
{
return
fmt
.
Errorf
(
"rule[%d]: invalid scope %q"
,
i
,
rule
.
Scope
)
}
// Validate model_whitelist patterns
for
j
,
pattern
:=
range
rule
.
ModelWhitelist
{
trimmed
:=
strings
.
TrimSpace
(
pattern
)
if
trimmed
==
""
{
return
fmt
.
Errorf
(
"rule[%d]: model_whitelist[%d] cannot be empty"
,
i
,
j
)
}
settings
.
Rules
[
i
]
.
ModelWhitelist
[
j
]
=
trimmed
}
// Validate fallback_action
if
rule
.
FallbackAction
!=
""
&&
!
validActions
[
rule
.
FallbackAction
]
{
return
fmt
.
Errorf
(
"rule[%d]: invalid fallback_action %q"
,
i
,
rule
.
FallbackAction
)
}
}
data
,
err
:=
json
.
Marshal
(
settings
)
...
...
backend/internal/service/settings_view.go
View file @
7c60ee3c
...
...
@@ -178,10 +178,13 @@ const (
// BetaPolicyRule 单条 Beta 策略规则
type
BetaPolicyRule
struct
{
BetaToken
string
`json:"beta_token"`
// beta token 值
Action
string
`json:"action"`
// "pass" | "filter" | "block"
Scope
string
`json:"scope"`
// "all" | "oauth" | "apikey" | "bedrock"
ErrorMessage
string
`json:"error_message,omitempty"`
// 自定义错误消息 (action=block 时生效)
BetaToken
string
`json:"beta_token"`
// beta token 值
Action
string
`json:"action"`
// "pass" | "filter" | "block"
Scope
string
`json:"scope"`
// "all" | "oauth" | "apikey" | "bedrock"
ErrorMessage
string
`json:"error_message,omitempty"`
// 自定义错误消息 (action=block 时生效)
ModelWhitelist
[]
string
`json:"model_whitelist,omitempty"`
// 模型匹配模式列表(为空=对所有模型生效)
FallbackAction
string
`json:"fallback_action,omitempty"`
// 未匹配白名单的模型的处理方式
FallbackErrorMessage
string
`json:"fallback_error_message,omitempty"`
// 未匹配白名单时的自定义错误消息 (fallback_action=block 时生效)
}
// BetaPolicySettings Beta 策略配置
...
...
frontend/src/api/admin/settings.ts
View file @
7c60ee3c
...
...
@@ -359,6 +359,9 @@ export interface BetaPolicyRule {
action
:
'
pass
'
|
'
filter
'
|
'
block
'
scope
:
'
all
'
|
'
oauth
'
|
'
apikey
'
|
'
bedrock
'
error_message
?:
string
model_whitelist
?:
string
[]
fallback_action
?:
'
pass
'
|
'
filter
'
|
'
block
'
fallback_error_message
?:
string
}
/**
...
...
frontend/src/i18n/locales/en.ts
View file @
7c60ee3c
...
...
@@ -4587,7 +4587,19 @@ export default {
errorMessagePlaceholder
:
'
Custom error message when blocked
'
,
errorMessageHint
:
'
Leave empty for default message
'
,
saved
:
'
Beta policy settings saved
'
,
saveFailed
:
'
Failed to save beta policy settings
'
saveFailed
:
'
Failed to save beta policy settings
'
,
modelWhitelist
:
'
Model Whitelist
'
,
modelWhitelistHint
:
'
Leave empty to apply to all models. Supports exact match and wildcard prefix (e.g., claude-opus-*)
'
,
modelPatternPlaceholder
:
'
e.g., claude-opus-* or claude-opus-4-6
'
,
addModelPattern
:
'
Add model pattern
'
,
removePattern
:
'
Remove
'
,
fallbackAction
:
'
Fallback Action
'
,
fallbackActionHint
:
'
Action for models not matching the whitelist
'
,
fallbackErrorMessagePlaceholder
:
'
Custom error message when non-whitelisted models are blocked
'
,
quickPresets
:
'
Quick Presets
'
,
presetOpusOnly
:
'
Opus only for 1M
'
,
presetOpusOnlyDesc
:
'
Pass for Opus, filter others
'
,
commonPatterns
:
'
Common patterns
'
},
saveSettings
:
'
Save Settings
'
,
saving
:
'
Saving...
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
7c60ee3c
...
...
@@ -4751,7 +4751,19 @@ export default {
errorMessagePlaceholder
:
'
拦截时返回的自定义错误消息
'
,
errorMessageHint
:
'
留空则使用默认错误消息
'
,
saved
:
'
Beta 策略设置保存成功
'
,
saveFailed
:
'
保存 Beta 策略设置失败
'
saveFailed
:
'
保存 Beta 策略设置失败
'
,
modelWhitelist
:
'
模型白名单
'
,
modelWhitelistHint
:
'
留空则对所有模型生效。支持精确匹配和通配符前缀(如 claude-opus-*)
'
,
modelPatternPlaceholder
:
'
例如: claude-opus-* 或 claude-opus-4-6
'
,
addModelPattern
:
'
添加模型规则
'
,
removePattern
:
'
移除
'
,
fallbackAction
:
'
未匹配模型处理方式
'
,
fallbackActionHint
:
'
当请求模型不在白名单中时的处理方式
'
,
fallbackErrorMessagePlaceholder
:
'
未匹配模型被拦截时返回的自定义错误消息
'
,
quickPresets
:
'
快捷预设
'
,
presetOpusOnly
:
'
仅 Opus 允许 1M
'
,
presetOpusOnlyDesc
:
'
Opus 透传,其他模型过滤
'
,
commonPatterns
:
'
常用模式
'
},
saveSettings
:
'
保存设置
'
,
saving
:
'
保存中...
'
,
...
...
frontend/src/views/admin/SettingsView.vue
View file @
7c60ee3c
...
...
@@ -630,6 +630,108 @@
{{
t
(
'
admin.settings.betaPolicy.errorMessageHint
'
)
}}
<
/p
>
<
/div
>
<!--
Quick
Presets
(
only
for
tokens
with
presets
)
-->
<
div
v
-
if
=
"
betaPresets[rule.beta_token]?.length
"
class
=
"
mt-3
"
>
<
label
class
=
"
mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.settings.betaPolicy.quickPresets
'
)
}}
<
/label
>
<
div
class
=
"
flex flex-wrap gap-2
"
>
<
button
v
-
for
=
"
preset in betaPresets[rule.beta_token]
"
:
key
=
"
preset.label
"
type
=
"
button
"
class
=
"
inline-flex items-center gap-1 rounded-md border border-primary-200 bg-primary-50 px-2.5 py-1 text-xs font-medium text-primary-700 transition-colors hover:bg-primary-100 dark:border-primary-800 dark:bg-primary-900/30 dark:text-primary-300 dark:hover:bg-primary-900/50
"
@
click
=
"
applyBetaPreset(rule, preset)
"
:
title
=
"
preset.description
"
>
{{
preset
.
label
}}
<
/button
>
<
/div
>
<
/div
>
<!--
Model
Whitelist
-->
<
div
class
=
"
mt-3
"
>
<
label
class
=
"
mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.settings.betaPolicy.modelWhitelist
'
)
}}
<
/label
>
<
p
class
=
"
mb-2 text-xs text-gray-400 dark:text-gray-500
"
>
{{
t
(
'
admin.settings.betaPolicy.modelWhitelistHint
'
)
}}
<
/p
>
<!--
Existing
patterns
-->
<
div
v
-
for
=
"
(_, index) in (rule.model_whitelist || [])
"
:
key
=
"
index
"
class
=
"
mb-1.5 flex items-center gap-2
"
>
<
input
v
-
model
=
"
rule.model_whitelist![index]
"
type
=
"
text
"
class
=
"
input input-sm flex-1
"
:
placeholder
=
"
t('admin.settings.betaPolicy.modelPatternPlaceholder')
"
/>
<
button
type
=
"
button
"
@
click
=
"
rule.model_whitelist!.splice(index, 1)
"
class
=
"
shrink-0 rounded p-1 text-red-400 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20
"
>
<
svg
class
=
"
h-4 w-4
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
stroke
=
"
currentColor
"
stroke
-
width
=
"
2
"
>
<
path
stroke
-
linecap
=
"
round
"
stroke
-
linejoin
=
"
round
"
d
=
"
M6 18L18 6M6 6l12 12
"
/>
<
/svg
>
<
/button
>
<
/div
>
<!--
Add
pattern
button
-->
<
button
type
=
"
button
"
@
click
=
"
if (!rule.model_whitelist) rule.model_whitelist = []; rule.model_whitelist.push('')
"
class
=
"
mb-2 inline-flex items-center gap-1 text-xs text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300
"
>
<
svg
class
=
"
h-3.5 w-3.5
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
stroke
=
"
currentColor
"
stroke
-
width
=
"
2
"
>
<
path
stroke
-
linecap
=
"
round
"
stroke
-
linejoin
=
"
round
"
d
=
"
M12 4v16m8-8H4
"
/>
<
/svg
>
{{
t
(
'
admin.settings.betaPolicy.addModelPattern
'
)
}}
<
/button
>
<!--
Common
pattern
chips
-->
<
div
class
=
"
flex flex-wrap items-center gap-1.5
"
>
<
span
class
=
"
text-xs text-gray-400 dark:text-gray-500
"
>
{{
t
(
'
admin.settings.betaPolicy.commonPatterns
'
)
}}
:
<
/span
>
<
button
v
-
for
=
"
pattern in commonModelPatterns
"
:
key
=
"
pattern
"
type
=
"
button
"
class
=
"
rounded border border-gray-200 px-2 py-0.5 text-xs text-gray-600 transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:border-dark-600 dark:text-gray-400 dark:hover:border-primary-700 dark:hover:bg-primary-900/30 dark:hover:text-primary-300
"
@
click
=
"
addQuickPattern(rule, pattern)
"
>
{{
pattern
}}
<
/button
>
<
/div
>
<
/div
>
<!--
Fallback
Action
(
only
when
model_whitelist
is
non
-
empty
)
-->
<
div
v
-
if
=
"
rule.model_whitelist && rule.model_whitelist.length > 0
"
class
=
"
mt-3
"
>
<
label
class
=
"
mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.settings.betaPolicy.fallbackAction
'
)
}}
<
/label
>
<
Select
:
modelValue
=
"
rule.fallback_action || 'pass'
"
@
update
:
modelValue
=
"
rule.fallback_action = $event as any
"
:
options
=
"
betaPolicyActionOptions
"
/>
<
p
class
=
"
mt-1 text-xs text-gray-400 dark:text-gray-500
"
>
{{
t
(
'
admin.settings.betaPolicy.fallbackActionHint
'
)
}}
<
/p
>
<!--
Fallback
Error
Message
(
only
when
fallback_action
=
block
)
-->
<
div
v
-
if
=
"
rule.fallback_action === 'block'
"
class
=
"
mt-2
"
>
<
input
v
-
model
=
"
rule.fallback_error_message
"
type
=
"
text
"
class
=
"
input
"
:
placeholder
=
"
t('admin.settings.betaPolicy.fallbackErrorMessagePlaceholder')
"
/>
<
p
class
=
"
mt-1 text-xs text-gray-400 dark:text-gray-500
"
>
{{
t
(
'
admin.settings.betaPolicy.errorMessageHint
'
)
}}
<
/p
>
<
/div
>
<
/div
>
<
/div
>
<!--
Save
Button
-->
...
...
@@ -2058,6 +2160,9 @@ const betaPolicyForm = reactive({
action
:
'
pass
'
|
'
filter
'
|
'
block
'
scope
:
'
all
'
|
'
oauth
'
|
'
apikey
'
|
'
bedrock
'
error_message
?:
string
model_whitelist
?:
string
[]
fallback_action
?:
'
pass
'
|
'
filter
'
|
'
block
'
fallback_error_message
?:
string
}
>
}
)
...
...
@@ -2716,10 +2821,48 @@ const betaDisplayNames: Record<string, string> = {
'
context-1m-2025-08-07
'
:
'
Context 1M
'
}
// 快捷预设:按 beta_token 定义预设方案
const
betaPresets
:
Record
<
string
,
Array
<
{
label
:
string
description
:
string
action
:
'
pass
'
|
'
filter
'
|
'
block
'
model_whitelist
:
string
[]
fallback_action
:
'
pass
'
|
'
filter
'
|
'
block
'
}
>>
=
{
'
context-1m-2025-08-07
'
:
[
{
label
:
t
(
'
admin.settings.betaPolicy.presetOpusOnly
'
),
description
:
t
(
'
admin.settings.betaPolicy.presetOpusOnlyDesc
'
),
action
:
'
pass
'
,
model_whitelist
:
[
'
claude-opus-4-6
'
],
fallback_action
:
'
filter
'
,
}
,
],
}
// 常用模型模式(具体 ID + 通配符示例)
const
commonModelPatterns
=
[
'
claude-opus-4-6
'
,
'
claude-sonnet-4-6
'
,
'
claude-opus-*
'
,
'
claude-sonnet-*
'
]
function
getBetaDisplayName
(
token
:
string
):
string
{
return
betaDisplayNames
[
token
]
||
token
}
function
applyBetaPreset
(
rule
:
(
typeof
betaPolicyForm
.
rules
)[
number
],
preset
:
{
action
:
'
pass
'
|
'
filter
'
|
'
block
'
;
model_whitelist
:
string
[];
fallback_action
:
'
pass
'
|
'
filter
'
|
'
block
'
}
)
{
rule
.
action
=
preset
.
action
rule
.
model_whitelist
=
[...
preset
.
model_whitelist
]
rule
.
fallback_action
=
preset
.
fallback_action
}
function
addQuickPattern
(
rule
:
(
typeof
betaPolicyForm
.
rules
)[
number
],
pattern
:
string
)
{
if
(
!
rule
.
model_whitelist
)
rule
.
model_whitelist
=
[]
if
(
!
rule
.
model_whitelist
.
includes
(
pattern
))
{
rule
.
model_whitelist
.
push
(
pattern
)
}
}
async
function
loadBetaPolicySettings
()
{
betaPolicyLoading
.
value
=
true
try
{
...
...
@@ -2735,8 +2878,22 @@ async function loadBetaPolicySettings() {
async
function
saveBetaPolicySettings
()
{
betaPolicySaving
.
value
=
true
try
{
// Clean up empty patterns before saving
const
cleanedRules
=
betaPolicyForm
.
rules
.
map
(
rule
=>
{
const
whitelist
=
rule
.
model_whitelist
?.
filter
(
p
=>
p
.
trim
()
!==
''
)
const
hasWhitelist
=
whitelist
&&
whitelist
.
length
>
0
return
{
beta_token
:
rule
.
beta_token
,
action
:
rule
.
action
,
scope
:
rule
.
scope
,
error_message
:
rule
.
error_message
,
model_whitelist
:
hasWhitelist
?
whitelist
:
undefined
,
fallback_action
:
hasWhitelist
?
(
rule
.
fallback_action
||
'
pass
'
)
:
undefined
,
fallback_error_message
:
hasWhitelist
&&
rule
.
fallback_action
===
'
block
'
?
rule
.
fallback_error_message
:
undefined
,
}
}
)
const
updated
=
await
adminAPI
.
settings
.
updateBetaPolicySettings
({
rules
:
betaPolicyForm
.
r
ules
rules
:
cleanedR
ules
}
)
betaPolicyForm
.
rules
=
updated
.
rules
appStore
.
showSuccess
(
t
(
'
admin.settings.betaPolicy.saved
'
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment