feat(codex): support ultrafast service tier

Accept service_tier=ultrafast on the OpenAI/Codex gateway, advertise it
on GPT-5.6 Sol in the Codex model catalog, and include it in ChatGPT
OAuth routing hints so Codex clients can select Ultrafast.
This commit is contained in:
刘佳林
2026-09-04 10:14:29 +08:00
parent b1748c4ea9
commit 126ac24c8c
28 changed files with 149 additions and 49 deletions
@@ -278,13 +278,15 @@ func TestOpenAIFastPolicySettingsFromDTO_NormalizesServiceTier(t *testing.T) {
Rules: []dto.OpenAIFastPolicyRule{
{ServiceTier: "priority", Action: "filter", Scope: "all"},
{ServiceTier: "flex", Action: "block", Scope: "oauth"},
{ServiceTier: "ultrafast", Action: "pass", Scope: "all"},
{ServiceTier: "all", Action: "pass", Scope: "apikey"},
},
}
out := openaiFastPolicySettingsFromDTO(in)
require.Len(t, out.Rules, 3)
require.Len(t, out.Rules, 4)
require.Equal(t, service.OpenAIFastTierPriority, out.Rules[0].ServiceTier)
require.Equal(t, service.OpenAIFastTierFlex, out.Rules[1].ServiceTier)
require.Equal(t, service.OpenAIFastTierAny, out.Rules[2].ServiceTier)
require.Equal(t, service.OpenAIFastTierUltrafast, out.Rules[2].ServiceTier)
require.Equal(t, service.OpenAIFastTierAny, out.Rules[3].ServiceTier)
})
}
@@ -17,7 +17,7 @@ import (
// 上以 OpenAI 兼容错误结构返回 HTTP 400。这些用例在 handler 的 service_tier
// 校验处短路,不会进入账号选择/重试。
//
// 合法值(fast/priority/flex/auto/default/scale)与省略/null 的接受语义由
// 合法值(fast/priority/flex/auto/default/scale/ultrafast)与省略/null 的接受语义由
// service 层纯校验函数 TestValidateOpenAIServiceTierField 覆盖,避免 handler
// 测试走入真实账号选择/重试路径。
+1 -1
View File
@@ -137,7 +137,7 @@ func usePriorityServiceTierPricing(serviceTier string, pricing *ModelPricing) bo
func serviceTierCostMultiplier(serviceTier string) float64 {
switch normalizeBillingServiceTier(serviceTier) {
case "priority", "fast":
case "priority", "fast", OpenAIFastTierUltrafast:
return 2.0
case "flex":
return 0.5
@@ -1431,6 +1431,7 @@ func TestCalculateCost_LargeTokenCount(t *testing.T) {
func TestServiceTierCostMultiplier(t *testing.T) {
require.InDelta(t, 2.0, serviceTierCostMultiplier("priority"), 1e-12)
require.InDelta(t, 2.0, serviceTierCostMultiplier(" Priority "), 1e-12)
require.InDelta(t, 2.0, serviceTierCostMultiplier("ultrafast"), 1e-12)
require.InDelta(t, 0.5, serviceTierCostMultiplier("flex"), 1e-12)
require.InDelta(t, 1.0, serviceTierCostMultiplier(""), 1e-12)
require.InDelta(t, 1.0, serviceTierCostMultiplier("default"), 1e-12)
@@ -19,6 +19,8 @@ func TestConfiguredServiceTierMultiplier(t *testing.T) {
{name: "priority alias", serviceTier: "priority", pricing: &ModelPricing{FastMultiplier: pricingMultiplier(2)}, want: 2},
{name: "flex configured", serviceTier: "flex", pricing: &ModelPricing{FlexMultiplier: pricingMultiplier(0.4)}, want: 0.4},
{name: "legacy fast default", serviceTier: "fast", pricing: &ModelPricing{}, want: 2},
{name: "ultrafast default", serviceTier: "ultrafast", pricing: &ModelPricing{}, want: 2},
{name: "ultrafast ignores fast multiplier", serviceTier: "ultrafast", pricing: &ModelPricing{FastMultiplier: pricingMultiplier(2.5)}, want: 2},
{name: "legacy flex default", serviceTier: "flex", pricing: &ModelPricing{}, want: 0.5},
}
@@ -469,15 +469,7 @@ func newConfiguredCodexModelDescriptor(modelID string) configuredCodexModelDescr
descriptor.DisplayName = openaiCodexDisplayName(modelID)
descriptor.Description = "OpenAI GPT coding model routed through Sub2API."
descriptor.SupportsParallelToolCalls = true
if configuredCodexSupportsPriorityServiceTier(modelID) {
descriptor.ServiceTiers = []configuredCodexServiceTier{
{
ID: "priority",
Name: "Fast",
Description: "Priority processing for lower latency.",
},
}
}
descriptor.ServiceTiers = configuredCodexServiceTiersForModel(modelID)
if isOpenAICodexReasoningGPTModel(modelID) {
defaultReasoningLevel := "medium"
if getNormalizedCodexModel(modelID) == "gpt-5.6-sol" {
@@ -501,6 +493,25 @@ func newConfiguredCodexModelDescriptor(modelID string) configuredCodexModelDescr
return descriptor
}
func configuredCodexServiceTiersForModel(modelID string) []configuredCodexServiceTier {
tiers := make([]configuredCodexServiceTier, 0, 2)
if configuredCodexSupportsPriorityServiceTier(modelID) {
tiers = append(tiers, configuredCodexServiceTier{
ID: OpenAIFastTierPriority,
Name: "Fast",
Description: "Priority processing for lower latency.",
})
}
if configuredCodexSupportsUltrafastServiceTier(modelID) {
tiers = append(tiers, configuredCodexServiceTier{
ID: OpenAIFastTierUltrafast,
Name: "Ultrafast",
Description: "Ultra-low latency processing.",
})
}
return tiers
}
func configuredCodexSupportsPriorityServiceTier(modelID string) bool {
normalized := canonicalizeOpenAIModelAliasSpelling(modelID)
for _, family := range []string{"gpt-5.4", "gpt-5.5", "gpt-5.6"} {
@@ -511,6 +522,10 @@ func configuredCodexSupportsPriorityServiceTier(modelID string) bool {
return false
}
func configuredCodexSupportsUltrafastServiceTier(modelID string) bool {
return normalizeKnownOpenAICodexModel(modelID) == "gpt-5.6-sol"
}
func configuredCodexGrokReasoningLevels(modelID string) []configuredCodexReasoningLevel {
levels := []configuredCodexReasoningLevel{
{Effort: "low", Description: "Fast responses with lighter reasoning"},
@@ -434,7 +434,7 @@ func TestBuildCodexModelsManifestAdvertisesPriorityServiceTierForFastGPTModels(t
body, err := BuildCodexModelsManifest([]string{
"gpt-5.4-mini",
"gpt-5.5",
"gpt-5.6-sol",
"gpt-5.6-terra",
})
require.NoError(t, err)
models := decodeCodexManifestModels(t, body)
@@ -452,6 +452,36 @@ func TestBuildCodexModelsManifestAdvertisesPriorityServiceTierForFastGPTModels(t
}
}
// Scenario: GPT-5.6 Sol 在 Fast 之外额外声明 ultrafast service tier。
func TestBuildCodexModelsManifestAdvertisesUltrafastServiceTierForSol(t *testing.T) {
t.Parallel()
body, err := BuildCodexModelsManifest([]string{
"gpt-5.6-sol",
"gpt-5.6",
})
require.NoError(t, err)
models := decodeCodexManifestModels(t, body)
require.Len(t, models, 2)
wantTiers := []any{
map[string]any{
"id": "priority",
"name": "Fast",
"description": "Priority processing for lower latency.",
},
map[string]any{
"id": "ultrafast",
"name": "Ultrafast",
"description": "Ultra-low latency processing.",
},
}
for _, model := range models {
require.Equal(t, wantTiers, model["service_tiers"])
require.Nil(t, model["default_service_tier"])
}
}
// Scenario: 未明确支持 Fast 的型号不推测 service tier。
func TestBuildCodexModelsManifestLeavesServiceTiersEmptyForOtherModels(t *testing.T) {
t.Parallel()
@@ -100,6 +100,9 @@ func TestEvaluateOpenAIFastPolicy_DefaultPassesKnownTiers(t *testing.T) {
action, _ = svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.5", OpenAIFastTierFlex)
require.Equal(t, BetaPolicyActionPass, action)
action, _ = svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.6-sol", OpenAIFastTierUltrafast)
require.Equal(t, BetaPolicyActionPass, action)
// empty tier → pass
action, _ = svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.5", "")
require.Equal(t, BetaPolicyActionPass, action)
@@ -248,6 +251,16 @@ func TestApplyOpenAIFastPolicyToBody_UserScopedRuleOverridesGlobalRule(t *testin
require.NotContains(t, string(updated), `"service_tier"`)
}
func TestApplyOpenAIFastPolicyToBody_PriorityFilterLeavesUltrafast(t *testing.T) {
svc := newOpenAIGatewayServiceWithSettings(t, openAIFastFilterPriorityPolicy())
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
body := []byte(`{"model":"gpt-5.6-sol","service_tier":"ultrafast"}`)
updated, err := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.6-sol", body)
require.NoError(t, err)
require.Equal(t, OpenAIFastTierUltrafast, gjson.GetBytes(updated, "service_tier").String())
}
func TestApplyOpenAIFastPolicyToBody_ForcePriorityRewritesKnownTier(t *testing.T) {
settings := &OpenAIFastPolicySettings{
Rules: []OpenAIFastPolicyRule{{
@@ -259,7 +272,7 @@ func TestApplyOpenAIFastPolicyToBody_ForcePriorityRewritesKnownTier(t *testing.T
svc := newOpenAIGatewayServiceWithSettings(t, settings)
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
for _, tier := range []string{"flex", "auto", "default", "scale", "fast", "priority"} {
for _, tier := range []string{"flex", "auto", "default", "scale", "fast", "priority", "ultrafast"} {
body := []byte(`{"model":"gpt-5.5","service_tier":"` + tier + `"}`)
updated, err := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body)
require.NoError(t, err)
@@ -480,19 +493,28 @@ func TestSetOpenAIFastPolicySettings_Validation(t *testing.T) {
// Valid settings persisted
err = svc.SetOpenAIFastPolicySettings(context.Background(), &OpenAIFastPolicySettings{
Rules: []OpenAIFastPolicyRule{{
ServiceTier: OpenAIFastTierPriority,
Action: OpenAIFastPolicyActionForcePriority,
Scope: BetaPolicyScopeAll,
UserIDs: []int64{42, 43},
}},
Rules: []OpenAIFastPolicyRule{
{
ServiceTier: OpenAIFastTierPriority,
Action: OpenAIFastPolicyActionForcePriority,
Scope: BetaPolicyScopeAll,
UserIDs: []int64{42, 43},
},
{
ServiceTier: OpenAIFastTierUltrafast,
Action: BetaPolicyActionPass,
Scope: BetaPolicyScopeAll,
},
},
})
require.NoError(t, err)
got, err := svc.GetOpenAIFastPolicySettings(context.Background())
require.NoError(t, err)
require.Len(t, got.Rules, 1)
require.Len(t, got.Rules, 2)
require.Equal(t, OpenAIFastTierPriority, got.Rules[0].ServiceTier)
require.Equal(t, OpenAIFastPolicyActionForcePriority, got.Rules[0].Action)
require.Equal(t, []int64{42, 43}, got.Rules[0].UserIDs)
require.Equal(t, OpenAIFastTierUltrafast, got.Rules[1].ServiceTier)
require.Equal(t, BetaPolicyActionPass, got.Rules[1].Action)
}
@@ -112,7 +112,7 @@ func TestWSResponseCreate_ForcePriorityRewritesKnownTier(t *testing.T) {
svc := newOpenAIGatewayServiceWithSettings(t, settings)
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
for _, tier := range []string{"flex", "auto", "default", "scale", "fast", "priority"} {
for _, tier := range []string{"flex", "auto", "default", "scale", "fast", "priority", "ultrafast"} {
frame := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"` + tier + `"}`)
updated, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
require.NoError(t, err)
@@ -43,7 +43,7 @@ func TestValidateOpenAIServiceTierField(t *testing.T) {
})
t.Run("official tiers pass through", func(t *testing.T) {
for _, tier := range []string{"flex", "auto", "default", "scale"} {
for _, tier := range []string{"flex", "auto", "default", "scale", "ultrafast"} {
norm, err := ValidateOpenAIServiceTierField([]byte(`{"model":"gpt-5.5","service_tier":"` + tier + `"}`))
require.NoError(t, err, "tier %q must be accepted", tier)
require.Equal(t, tier, norm)
@@ -148,6 +148,11 @@ func TestNormalizeResponsesBodyServiceTier(t *testing.T) {
require.Equal(t, "scale", tier)
require.Equal(t, "scale", gjson.GetBytes(body, "service_tier").String())
body, tier, err = normalizeResponsesBodyServiceTier([]byte(`{"model":"gpt-5.6-sol","service_tier":"ultrafast"}`))
require.NoError(t, err)
require.Equal(t, "ultrafast", tier)
require.Equal(t, "ultrafast", gjson.GetBytes(body, "service_tier").String())
// 真未知值才会被删除。
body, tier, err = normalizeResponsesBodyServiceTier([]byte(`{"model":"gpt-5.1","service_tier":"turbo"}`))
require.NoError(t, err)
@@ -1466,9 +1466,8 @@ func TestNormalizeOpenAIServiceTier(t *testing.T) {
t.Run("openai official tiers preserved", func(t *testing.T) {
// OpenAI 官方文档定义的合法 tier 值都应被透传保留,避免因白名单过窄
// 静默剥离客户端显式发送的合法字段。Codex 客户端只发 priority/flex,
// 所以扩大白名单对 Codex 流量零影响(见 codex-rs/core/src/client.rs)。
for _, tier := range []string{"priority", "flex", "auto", "default", "scale"} {
// 静默剥离客户端显式发送的合法字段。Codex 会发 priority/flex/ultrafast。
for _, tier := range []string{"priority", "flex", "auto", "default", "scale", "ultrafast"} {
got := normalizeOpenAIServiceTier(tier)
require.NotNil(t, got, "tier %q should not be normalized to nil", tier)
require.Equal(t, tier, *got)
@@ -1487,6 +1486,7 @@ func TestExtractOpenAIServiceTier(t *testing.T) {
require.Equal(t, "auto", *extractOpenAIServiceTier(map[string]any{"service_tier": "auto"}))
require.Equal(t, "default", *extractOpenAIServiceTier(map[string]any{"service_tier": "default"}))
require.Equal(t, "scale", *extractOpenAIServiceTier(map[string]any{"service_tier": "scale"}))
require.Equal(t, "ultrafast", *extractOpenAIServiceTier(map[string]any{"service_tier": "ultrafast"}))
require.Nil(t, extractOpenAIServiceTier(map[string]any{"service_tier": 1}))
require.Nil(t, extractOpenAIServiceTier(nil))
}
@@ -1497,6 +1497,7 @@ func TestExtractOpenAIServiceTierFromBody(t *testing.T) {
require.Equal(t, "auto", *extractOpenAIServiceTierFromBody([]byte(`{"service_tier":"auto"}`)))
require.Equal(t, "default", *extractOpenAIServiceTierFromBody([]byte(`{"service_tier":"default"}`)))
require.Equal(t, "scale", *extractOpenAIServiceTierFromBody([]byte(`{"service_tier":"scale"}`)))
require.Equal(t, "ultrafast", *extractOpenAIServiceTierFromBody([]byte(`{"service_tier":"ultrafast"}`)))
require.Nil(t, extractOpenAIServiceTierFromBody([]byte(`{"service_tier":"turbo"}`)))
require.Nil(t, extractOpenAIServiceTierFromBody(nil))
}
@@ -1435,12 +1435,12 @@ func normalizeOpenAIServiceTier(raw string) *string {
if value == "fast" {
value = "priority"
}
// 放过 OpenAI 官方文档定义的所有合法 tier 值:priority/flex/auto/default/scale。
// 对 Codex 客户端零影响(Codex 只发 priority 或 flex,见 codex-rs/core/src/client.rs),
// 但能让直连 OpenAI SDK 的用户透传 auto/default/scale 以便抓包/调试。
// 真未知值仍返回 nil,由 normalizeResponsesBodyServiceTier 从 body 中删除。
// 放过 OpenAI 官方文档定义的合法 tier 值,以及 Codex/API 新增的 ultrafast。
// Codex 客户端会发 priority、flex 或 ultrafast;直连 OpenAI SDK 的用户还会
// 透传 auto/default/scale。真未知值仍返回 nil,由
// normalizeResponsesBodyServiceTier 从 body 中删除。
switch value {
case "priority", "flex", "auto", "default", "scale":
case "priority", "flex", "auto", "default", "scale", OpenAIFastTierUltrafast:
return &value
default:
return nil
@@ -1457,7 +1457,7 @@ type ErrInvalidOpenAIServiceTier struct {
}
func (e *ErrInvalidOpenAIServiceTier) Error() string {
return fmt.Sprintf("invalid service_tier %q: must be one of auto, default, fast, flex, priority, scale", e.Value)
return fmt.Sprintf("invalid service_tier %q: must be one of auto, default, fast, flex, priority, scale, ultrafast", e.Value)
}
const invalidOpenAIServiceTierValueMaxLen = 64
@@ -1475,7 +1475,7 @@ func boundInvalidOpenAIServiceTierValue(raw string) string {
// - absent / null → valid, returns "" (field omitted keeps current behavior)
// - "fast" → normalized to "priority" (the two are equivalent; the canonical
// value is what reaches the OpenAI upstream)
// - "priority" / "flex" / "auto" / "default" / "scale" → valid, returned as-is
// - "priority" / "flex" / "auto" / "default" / "scale" / "ultrafast" → valid, returned as-is
// - an explicitly present non-string value, an empty string, or any other
// unknown value → *ErrInvalidOpenAIServiceTier (handler maps to HTTP 400),
// matching OpenAI's enum validation semantics
@@ -37,14 +37,14 @@ func setOpenAICodexRoutingHint(headers http.Header, account *Account, model stri
// Codex treats "default" as an explicit standard-routing sentinel, not as a
// service tier sent to the backend. Fast follows the gateway's existing
// canonicalization and therefore becomes "priority"; flex stays "flex".
// canonicalization and therefore becomes "priority"; flex and ultrafast
// stay as themselves.
canonicalTier := normalizedOpenAIServiceTierValue(serviceTier)
// This backport has no Codex model-catalog snapshot with which to validate
// arbitrary tier ids. Keep the hint to the two effective tiers Codex itself
// selects; default, missing, and other gateway-compatible API values remain
// model-only rather than expanding the ChatGPT routing protocol here.
// Keep the hint to the effective tiers Codex itself selects. default,
// missing, auto, and scale remain model-only rather than expanding the
// ChatGPT routing protocol here.
switch canonicalTier {
case OpenAIFastTierPriority, OpenAIFastTierFlex:
case OpenAIFastTierPriority, OpenAIFastTierFlex, OpenAIFastTierUltrafast:
default:
canonicalTier = ""
}
@@ -24,6 +24,7 @@ func TestSetOpenAICodexRoutingHintCanonicalizesOfficialServiceTiers(t *testing.T
{name: "fast alias", model: "gpt-5.6", serviceTier: " fast ", want: "model=gpt-5.6;tier=priority"},
{name: "priority", model: "gpt-5.6", serviceTier: "priority", want: "model=gpt-5.6;tier=priority"},
{name: "flex", model: "gpt-5.6", serviceTier: "flex", want: "model=gpt-5.6;tier=flex"},
{name: "ultrafast", model: "gpt-5.6", serviceTier: "ultrafast", want: "model=gpt-5.6;tier=ultrafast"},
{name: "explicit default sentinel", model: "gpt-5.6", serviceTier: "default", want: "model=gpt-5.6"},
{name: "omitted tier", model: "gpt-5.6", want: "model=gpt-5.6"},
{name: "auto is not expanded without catalog support", model: "gpt-5.6", serviceTier: "auto", want: "model=gpt-5.6"},
@@ -90,6 +91,7 @@ func TestOpenAIOAuthHTTPBuildersSendRoutingHintFromFinalBody(t *testing.T) {
}{
{name: "fast", body: []byte(`{"model":"gpt-5.6-codex","service_tier":"fast"}`), want: "model=gpt-5.6-codex;tier=priority"},
{name: "flex", body: []byte(`{"model":"gpt-5.6-codex","service_tier":"flex"}`), want: "model=gpt-5.6-codex;tier=flex"},
{name: "ultrafast", body: []byte(`{"model":"gpt-5.6-codex","service_tier":"ultrafast"}`), want: "model=gpt-5.6-codex;tier=ultrafast"},
{name: "default", body: []byte(`{"model":"gpt-5.6-codex","service_tier":"default"}`), want: "model=gpt-5.6-codex"},
{name: "omitted", body: []byte(`{"model":"gpt-5.6-codex"}`), want: "model=gpt-5.6-codex"},
}
@@ -216,6 +218,7 @@ func TestBuildOpenAIWSHeadersSendsOAuthRoutingHintOnly(t *testing.T) {
},
}
require.Equal(t, "model=gpt-5.6-codex;tier=priority", build(t, oauthAccount, "fast").Get(openAICodexRoutingHintHeader))
require.Equal(t, "model=gpt-5.6-codex;tier=ultrafast", build(t, oauthAccount, "ultrafast").Get(openAICodexRoutingHintHeader))
require.Equal(t, "model=gpt-5.6-codex", build(t, oauthAccount, "default").Get(openAICodexRoutingHintHeader))
require.Empty(t, build(t, &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}, "priority").Get(openAICodexRoutingHintHeader))
}
+1 -1
View File
@@ -1016,7 +1016,7 @@ func (s *SettingService) SetOpenAIFastPolicySettings(ctx context.Context, settin
BetaPolicyScopeAll: true, BetaPolicyScopeOAuth: true, BetaPolicyScopeAPIKey: true, BetaPolicyScopeBedrock: true,
}
validTiers := map[string]bool{
OpenAIFastTierAny: true, OpenAIFastTierPriority: true, OpenAIFastTierFlex: true,
OpenAIFastTierAny: true, OpenAIFastTierPriority: true, OpenAIFastTierUltrafast: true, OpenAIFastTierFlex: true,
}
for i, rule := range settings.Rules {
+6 -4
View File
@@ -669,15 +669,17 @@ func DefaultBetaPolicySettings() *BetaPolicySettings {
// OpenAI Fast Policy 策略常量
// OpenAI 的 "fast 模式" 通过请求体中的 service_tier 字段识别:
// - "priority"(客户端可传 "fast",归一化为 "priority"):fast 模式
// - "ultrafast":Codex/API 的 Ultrafast 档位
// - "flex":低优先级模式
// - 省略:normal 默认
//
// 本策略复用 BetaPolicyAction*/BetaPolicyScope* 常量语义,只是匹配键从
// anthropic-beta header 换成 body 的 service_tier 字段。
const (
OpenAIFastTierAny = "all" // 匹配任意已识别的 service_tier
OpenAIFastTierPriority = "priority" // 仅匹配 fast(priority)
OpenAIFastTierFlex = "flex" // 仅匹配 flex
OpenAIFastTierAny = "all" // 匹配任意已识别的 service_tier
OpenAIFastTierPriority = "priority" // 仅匹配 fast(priority)
OpenAIFastTierUltrafast = "ultrafast" // 仅匹配 ultrafast
OpenAIFastTierFlex = "flex" // 仅匹配 flex
// OpenAIFastPolicyActionForcePriority 会保留 service_tier 字段并强制写成
// priority,用于把 flex/auto/default/scale 等已识别 tier 收敛为 fast。
@@ -686,7 +688,7 @@ const (
// OpenAIFastPolicyRule 单条 OpenAI fast/flex 策略规则
type OpenAIFastPolicyRule struct {
ServiceTier string `json:"service_tier"` // "priority" | "flex" | "auto" | "default" | "scale" | "all"
ServiceTier string `json:"service_tier"` // "priority" | "ultrafast" | "flex" | "auto" | "default" | "scale" | "all"
Action string `json:"action"` // "pass" | "filter" | "block" | "force_priority"
Scope string `json:"scope"` // "all" | "oauth" | "apikey" | "bedrock"
UserIDs []int64 `json:"user_ids,omitempty"` // 空=所有 Sub2API 用户;非空=仅指定 API Key 所属用户
+1 -1
View File
@@ -1429,7 +1429,7 @@ export async function updateRectifierSettings(
* Matches backend dto.OpenAIFastPolicyRule.
*/
export interface OpenAIFastPolicyRule {
service_tier: "all" | "priority" | "flex";
service_tier: "all" | "priority" | "flex" | "ultrafast";
action: "pass" | "filter" | "block" | "force_priority";
scope: "all" | "oauth" | "apikey" | "bedrock";
user_ids?: number[];
@@ -30,6 +30,7 @@ const messages: Record<string, string> = {
'usage.perMillionTokens': '/ 1M tokens',
'usage.serviceTier': 'Service tier',
'usage.serviceTierPriority': 'Fast',
'usage.serviceTierUltrafast': 'Ultrafast',
'usage.serviceTierFlex': 'Flex',
'usage.serviceTierStandard': 'Standard',
'usage.rate': 'Rate',
@@ -31,6 +31,7 @@ describe('OpenAI Fast/Flex policy locale keys', () => {
it('describes target and other-model actions without whitelist terminology', () => {
expect(zh.admin.settings.openaiFastPolicy).toMatchObject({
tierAll: '全部 tier 值',
tierUltrafast: 'ultrafast',
modelWhitelist: '目标模型',
fallbackAction: '其他模型处理方式',
summaryTargetModels: '目标模型',
@@ -43,6 +44,7 @@ describe('OpenAI Fast/Flex policy locale keys', () => {
expect(en.admin.settings.openaiFastPolicy).toMatchObject({
tierAll: 'All tier values',
tierUltrafast: 'ultrafast',
modelWhitelist: 'Target models',
fallbackAction: 'Other models action',
summaryTargetModels: 'Target models',
@@ -7,6 +7,7 @@ describe('usage service tier locale keys', () => {
it('contains zh labels for service tier tooltip', () => {
expect(zh.usage.serviceTier).toBe('服务档位')
expect(zh.usage.serviceTierPriority).toBe('Fast')
expect(zh.usage.serviceTierUltrafast).toBe('Ultrafast')
expect(zh.usage.serviceTierFlex).toBe('Flex')
expect(zh.usage.serviceTierStandard).toBe('Standard')
})
@@ -14,6 +15,7 @@ describe('usage service tier locale keys', () => {
it('contains en labels for service tier tooltip', () => {
expect(en.usage.serviceTier).toBe('Service tier')
expect(en.usage.serviceTierPriority).toBe('Fast')
expect(en.usage.serviceTierUltrafast).toBe('Ultrafast')
expect(en.usage.serviceTierFlex).toBe('Flex')
expect(en.usage.serviceTierStandard).toBe('Standard')
})
@@ -1081,7 +1081,7 @@ export default {
},
openaiFastPolicy: {
title: 'OpenAI Fast/Flex Policy',
description: 'Intercept, filter, or pass OpenAI fast(priority) / flex requests based on the request body service_tier field. Applies to the OpenAI gateway only.',
description: 'Intercept, filter, or pass OpenAI fast(priority), ultrafast, or flex requests based on the request body service_tier field. Applies to the OpenAI gateway only.',
empty: 'No rules configured. Click the button below to add one.',
ruleHeader: 'Rule #{index}',
removeRule: 'Remove rule',
@@ -1090,6 +1090,7 @@ export default {
serviceTier: 'service_tier match',
tierAll: 'All tier values',
tierPriority: 'priority (fast)',
tierUltrafast: 'ultrafast',
tierFlex: 'flex',
action: 'Action',
actionPass: 'Pass (keep service_tier)',
@@ -398,6 +398,7 @@ export default {
cacheWrite: 'Write',
serviceTier: 'Service tier',
serviceTierPriority: 'Fast',
serviceTierUltrafast: 'Ultrafast',
serviceTierFlex: 'Flex',
serviceTierStandard: 'Standard',
rate: 'Rate',
@@ -1075,7 +1075,7 @@ export default {
},
openaiFastPolicy: {
title: 'OpenAI Fast/Flex 策略',
description: '基于请求体 service_tier 字段拦截/过滤/透传 OpenAI fast(priority) 与 flex 请求;仅作用于 OpenAI 网关。',
description: '基于请求体 service_tier 字段拦截/过滤/透传 OpenAI fast(priority)、ultrafast 与 flex 请求;仅作用于 OpenAI 网关。',
empty: '尚未配置任何规则。点击下方按钮新增。',
ruleHeader: '规则 #{index}',
removeRule: '删除规则',
@@ -1084,6 +1084,7 @@ export default {
serviceTier: 'service_tier 匹配',
tierAll: '全部 tier 值',
tierPriority: 'priority(fast)',
tierUltrafast: 'ultrafast',
tierFlex: 'flex',
action: '处理方式',
actionPass: '透传(保留 service_tier)',
@@ -403,6 +403,7 @@ export default {
cacheWrite: '写入',
serviceTier: '服务档位',
serviceTierPriority: 'Fast',
serviceTierUltrafast: 'Ultrafast',
serviceTierFlex: 'Flex',
serviceTierStandard: 'Standard',
rate: '倍率',
@@ -12,6 +12,7 @@ describe('usageServiceTier utils', () => {
it('preserves supported tiers', () => {
expect(normalizeUsageServiceTier('priority')).toBe('priority')
expect(normalizeUsageServiceTier('flex')).toBe('flex')
expect(normalizeUsageServiceTier('ultrafast')).toBe('ultrafast')
})
it('formats empty values as standard', () => {
@@ -27,11 +28,13 @@ describe('usageServiceTier utils', () => {
it('maps tiers to translated labels', () => {
const translate = (key: string) => ({
'usage.serviceTierPriority': 'Fast',
'usage.serviceTierUltrafast': 'Ultrafast',
'usage.serviceTierFlex': 'Flex',
'usage.serviceTierStandard': 'Standard',
})[key] ?? key
expect(getUsageServiceTierLabel('fast', translate)).toBe('Fast')
expect(getUsageServiceTierLabel('ultrafast', translate)).toBe('Ultrafast')
expect(getUsageServiceTierLabel('flex', translate)).toBe('Flex')
expect(getUsageServiceTierLabel(undefined, translate)).toBe('Standard')
expect(getUsageServiceTierLabel('custom-tier', translate)).toBe('custom-tier')
+2 -1
View File
@@ -3,7 +3,7 @@ export function normalizeUsageServiceTier(serviceTier?: string | null): string |
if (!value) return null
if (value === 'fast') return 'priority'
if (value === 'default' || value === 'standard') return 'standard'
if (value === 'priority' || value === 'flex') return value
if (value === 'priority' || value === 'flex' || value === 'ultrafast') return value
return value
}
@@ -19,6 +19,7 @@ export function getUsageServiceTierLabel(
): string {
const tier = formatUsageServiceTier(serviceTier)
if (tier === 'priority') return translate('usage.serviceTierPriority')
if (tier === 'ultrafast') return translate('usage.serviceTierUltrafast')
if (tier === 'flex') return translate('usage.serviceTierFlex')
if (tier === 'standard') return translate('usage.serviceTierStandard')
return tier
@@ -12084,6 +12084,10 @@ const openaiFastPolicyTierOptions = computed(() => [
value: "priority",
label: t("admin.settings.openaiFastPolicy.tierPriority"),
},
{
value: "ultrafast",
label: t("admin.settings.openaiFastPolicy.tierUltrafast"),
},
{ value: "flex", label: t("admin.settings.openaiFastPolicy.tierFlex") },
]);