fix(routing): preserve allowlist edits and save state

This commit is contained in:
elky
2026-08-14 11:43:24 +08:00
parent fb33ea57b0
commit a1d64e5239
5 changed files with 615 additions and 50 deletions
@@ -99,7 +99,7 @@ describe('routingPolicy', () => {
allowed_models: ['legacy-model'],
})
expect(parseAllowedModelsInput(' gpt-5, claude-*\nlegacy-model, gpt-5 ')).toEqual([
expect(parseAllowedModelsInput(' gpt-5\nclaude-*\nlegacy-model\ngpt-5 ')).toEqual([
'gpt-5',
'claude-*',
'legacy-model',
@@ -107,10 +107,10 @@ describe('routingPolicy', () => {
const restricted = updateAllowedModelsFromInput(
config,
'gpt-5, claude-*\nlegacy-model, gpt-5',
'gpt-5\nclaude-*\nlegacy-model\ngpt-5',
)
expect(restricted.allowed_models).toEqual(['gpt-5', 'claude-*', 'legacy-model'])
expect(formatAllowedModelsInput(restricted.allowed_models)).toBe('gpt-5, claude-*, legacy-model')
expect(formatAllowedModelsInput(restricted.allowed_models)).toBe('gpt-5\nclaude-*\nlegacy-model')
expect(routingModelScopeLabel(restricted)).toBe('3 个模型')
const unrestricted = clearAllowedModels(restricted)
@@ -118,6 +118,23 @@ describe('routingPolicy', () => {
expect(routingModelScopeLabel(unrestricted)).toBe('全部模型')
})
it('round-trips selectors containing commas and labels wildcard scope as unrestricted', () => {
const selectors = ['vendor,model', 'gpt-*']
expect(parseAllowedModelsInput(formatAllowedModelsInput(selectors))).toEqual(selectors)
const wildcard = normalizeRoutingGroupConfig({ allowed_models: ['gpt-*', '*'] })
expect(routingModelScopeLabel(wildcard)).toBe('全部模型')
})
it('preserves historical empty selectors until unrestricted scope is explicit', () => {
const legacy = normalizeRoutingGroupConfig({ allowed_models: ['', ' '] })
expect(updateAllowedModelsFromInput(legacy, ' \n')).toMatchObject({
allowed_models: ['', ' '],
})
expect(clearAllowedModels(legacy).allowed_models).toEqual([])
})
it('preserves an explicit model allowlist across per-model editing actions', () => {
const allowlist = ['gpt-*', 'legacy-model']
let config = normalizeRoutingGroupConfig({
@@ -114,7 +114,7 @@ export function normalizeRoutingGroupConfig(value: Partial<RoutingGroupConfig> |
export function parseAllowedModelsInput(value: string): string[] {
const seen = new Set<string>()
return value
.split(/[,\r\n]+/u)
.split(/\r\n?|\n/u)
.map(item => item.trim())
.filter(Boolean)
.filter((model) => {
@@ -125,7 +125,7 @@ export function parseAllowedModelsInput(value: string): string[] {
}
export function formatAllowedModelsInput(models: string[]): string {
return models.join(', ')
return models.join('\n')
}
export function updateAllowedModelsFromInput(
@@ -133,6 +133,14 @@ export function updateAllowedModelsFromInput(
value: string,
): RoutingGroupConfig {
const next = normalizeRoutingGroupConfig(config)
// Preserve the historical "empty selector" form until the user explicitly
// chooses the unrestricted scope. It is distinct from an empty allowlist in
// the routing core, where it matches no normal model.
const hasHistoricalEmptySelector = next.allowed_models.length > 0
&& next.allowed_models.every(model => model.trim() === '')
if (value.trim() === '' && hasHistoricalEmptySelector) {
return next
}
next.allowed_models = parseAllowedModelsInput(value)
return next
}
@@ -144,8 +152,11 @@ export function clearAllowedModels(config: RoutingGroupConfig): RoutingGroupConf
}
export function routingModelScopeLabel(config: RoutingGroupConfig): string {
const count = normalizeRoutingGroupConfig(config).allowed_models.length
return count ? `${count} 个模型` : '全部模型'
const models = normalizeRoutingGroupConfig(config).allowed_models
if (models.length === 0 || models.some(model => model.trim() === '*')) {
return '全部模型'
}
return `${models.length} 个模型`
}
export function allowedModelsMirrorPerModelPolicies(config: RoutingGroupConfig): boolean {