Merge pull request #6411 from Wei-Shaw/fix/openai-ttft-setting-ui

fix(openai): move TTFT metric mode to admin settings
This commit is contained in:
Wesley Liddick
2026-08-31 15:39:12 +08:00
committed by GitHub
19 changed files with 198 additions and 13 deletions
@@ -288,6 +288,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
BackendModeEnabled: settings.BackendModeEnabled,
OpenAITTFTMode: settings.OpenAITTFTMode,
EnableFingerprintUnification: settings.EnableFingerprintUnification,
EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
EnableCCHSigning: settings.EnableCCHSigning,
@@ -452,6 +452,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if before.EnableFingerprintUnification != after.EnableFingerprintUnification {
changed = append(changed, "enable_fingerprint_unification")
}
if before.OpenAITTFTMode != after.OpenAITTFTMode {
changed = append(changed, "openai_ttft_mode")
}
if before.EnableMetadataPassthrough != after.EnableMetadataPassthrough {
changed = append(changed, "enable_metadata_passthrough")
}
@@ -243,6 +243,7 @@ type UpdateSettingsRequest struct {
BackendModeEnabled bool `json:"backend_mode_enabled"`
// Gateway forwarding behavior
OpenAITTFTMode *string `json:"openai_ttft_mode"`
EnableFingerprintUnification *bool `json:"enable_fingerprint_unification"`
EnableMetadataPassthrough *bool `json:"enable_metadata_passthrough"`
EnableCCHSigning *bool `json:"enable_cch_signing"`
@@ -1683,6 +1684,12 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
return previousSettings.EnableFingerprintUnification
}(),
OpenAITTFTMode: func() string {
if req.OpenAITTFTMode != nil {
return *req.OpenAITTFTMode
}
return previousSettings.OpenAITTFTMode
}(),
EnableMetadataPassthrough: func() bool {
if req.EnableMetadataPassthrough != nil {
return *req.EnableMetadataPassthrough
+1
View File
@@ -201,6 +201,7 @@ type SystemSettings struct {
BackendModeEnabled bool `json:"backend_mode_enabled"`
// Gateway forwarding behavior
OpenAITTFTMode string `json:"openai_ttft_mode"`
EnableFingerprintUnification bool `json:"enable_fingerprint_unification"`
EnableMetadataPassthrough bool `json:"enable_metadata_passthrough"`
EnableCCHSigning bool `json:"enable_cch_signing"`
@@ -922,6 +922,7 @@ func TestAPIContracts(t *testing.T) {
"payment_visible_method_wxpay_enabled": false,
"openai_low_upstream_rate_priority_enabled": true,
"openai_oauth_scheduling_rate_multiplier": 0.05,
"openai_ttft_mode": "semantic",
"openai_advanced_scheduler_enabled": true,
"openai_advanced_scheduler_sticky_weighted_enabled": false,
"openai_advanced_scheduler_subscription_priority_enabled": false,
@@ -1236,6 +1237,7 @@ func TestAPIContracts(t *testing.T) {
"payment_visible_method_wxpay_enabled": false,
"openai_low_upstream_rate_priority_enabled": false,
"openai_oauth_scheduling_rate_multiplier": 1,
"openai_ttft_mode": "semantic",
"openai_advanced_scheduler_enabled": false,
"openai_advanced_scheduler_sticky_weighted_enabled": false,
"openai_advanced_scheduler_subscription_priority_enabled": false,
@@ -620,6 +620,10 @@ const (
SettingKeyBackendModeEnabled = "backend_mode_enabled"
// Gateway Forwarding Behavior
// SettingKeyOpenAITTFTMode 控制 first_token_ms 的统计口径。
SettingKeyOpenAITTFTMode = "openai_ttft_mode"
OpenAITTFTModeSemantic = "semantic"
OpenAITTFTModeVisible = "visible"
// SettingKeyEnableFingerprintUnification 是否统一 OAuth 账号的 X-Stainless-* 指纹头(默认 true)
SettingKeyEnableFingerprintUnification = "enable_fingerprint_unification"
// SettingKeyEnableMetadataPassthrough 是否透传客户端原始 metadata.user_id(默认 false)
@@ -403,6 +403,16 @@ func assertOpenAINativeLargeOpenEventTimesOutWithoutLeak(t *testing.T, line stri
}
func TestOpenAINativeFirstOutputEOFDispatchesTerminalEventWithoutBlankLine(t *testing.T) {
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{
openAITTFTMode: OpenAITTFTModeVisible,
expiresAt: time.Now().Add(time.Minute).UnixNano(),
})
t.Cleanup(func() {
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{
openAITTFTMode: OpenAITTFTModeSemantic,
expiresAt: time.Now().Add(time.Minute).UnixNano(),
})
})
cfg := &config.Config{Gateway: config.GatewayConfig{
OpenAIFirstOutputTimeoutSeconds: 1,
MaxLineSize: defaultMaxLineSize,
@@ -1231,6 +1231,47 @@ func openAIStreamDataStartsVisibleOutput(data, eventType string) bool {
return false
}
// openAIStreamDataStartsSemanticTTFT 保留 900194fab 之前的 first_token_ms
// 口径:跳过 Responses preamble 后,首个语义 SSE 事件即视为首 token。
func openAIStreamDataStartsSemanticTTFT(data, eventType string) bool {
trimmed := strings.TrimSpace(data)
if trimmed == "" || trimmed == "[DONE]" {
return false
}
eventType = strings.TrimSpace(eventType)
if eventType == "" && gjson.Valid(trimmed) {
eventType = strings.TrimSpace(gjson.Get(trimmed, "type").String())
}
switch eventType {
case "response.failed":
return false
case "error":
payload := []byte(trimmed)
return !openAIStreamFailedEventShouldFailover(payload, extractOpenAISSEErrorMessage(payload))
default:
return !openAIStreamEventIsPreamble(eventType)
}
}
func (s *OpenAIGatewayService) openAITTFTMode(ctx context.Context) string {
mode := OpenAITTFTModeSemantic
if s != nil && s.settingService != nil {
mode = s.settingService.GetOpenAITTFTMode(ctx)
} else if cached, ok := gatewayForwardingCache.Load().(*cachedGatewayForwardingSettings); ok && cached != nil {
if cached.expiresAt == 0 || time.Now().UnixNano() < cached.expiresAt {
mode = normalizeOpenAITTFTMode(cached.openAITTFTMode)
}
}
return normalizeOpenAITTFTMode(mode)
}
func openAIStreamDataStartsTTFT(data, eventType string, forceOutput bool, mode string) bool {
if mode == OpenAITTFTModeVisible {
return openAIStreamDataStartsVisibleOutput(data, eventType)
}
return forceOutput || openAIStreamDataStartsSemanticTTFT(data, eventType)
}
// openAIStreamFailedEventErrorCode 提取流内 failed 事件的错误码(小写),
// 兼容 response.failed 的嵌套形态与裸 error 形态。
func openAIStreamFailedEventErrorCode(payload []byte) string {
@@ -1806,6 +1847,7 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough(
imageCounter := newOpenAIImageOutputCounter()
var firstTokenMs *int
responseID := ""
ttftMode := s.openAITTFTMode(ctx)
clientDisconnected := false
sawDone := false
sawTerminalEvent := false
@@ -2096,7 +2138,7 @@ func (s *OpenAIGatewayService) handleStreamingResponsePassthrough(
openAIResponsesCompletedEventIsEmpty(dataBytes, usage) {
return resultWithUsage(), newOpenAIResponsesEmptyCompletedFailoverError(c, account, upstreamRequestID)
}
if firstTokenMs == nil && openAIStreamDataStartsVisibleOutput(trimmedData, eventType) {
if firstTokenMs == nil && openAIStreamDataStartsTTFT(trimmedData, eventType, forceFlushFailedEvent, ttftMode) {
ms := int(time.Since(startTime).Milliseconds())
firstTokenMs = &ms
}
@@ -119,6 +119,7 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
maxLineSize = s.cfg.Gateway.MaxLineSize
}
var firstTokenMs *int
ttftMode := s.openAITTFTMode(ctx)
firstOutputProgressObserved := false
bufferedWriter := bufio.NewWriterSize(w, 4*1024)
var firstOutputStage *openAIFirstOutputStage
@@ -263,7 +264,7 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
pendingSSEEventType := ""
eventInProgress := false
eventStartsClientOutput := false
eventStartsVisibleOutput := false
eventStartsTTFTOutput := false
eventShouldFlush := false
handlePendingWriteError := func(err error) {
if firstOutputStage != nil && !firstOutputStage.closed {
@@ -283,7 +284,7 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
}
completeGuardedEvent := func(queueDrained bool) {
completedProgressEvent := eventStartsClientOutput
completedVisibleEvent := eventStartsVisibleOutput
completedTTFTEvent := eventStartsTTFTOutput
shouldFlush := eventShouldFlush || (queueDrained && clientOutputStarted)
eventInProgress = false
if !clientDisconnected {
@@ -305,12 +306,12 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
firstOutputProgressObserved = true
stopFirstOutputTimer()
}
if completedVisibleEvent && firstTokenMs == nil {
if completedTTFTEvent && firstTokenMs == nil {
ms := int(time.Since(startTime).Milliseconds())
firstTokenMs = &ms
}
eventStartsClientOutput = false
eventStartsVisibleOutput = false
eventStartsTTFTOutput = false
eventShouldFlush = false
}
sendErrorEvent := func(reason string) {
@@ -662,9 +663,10 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
}
startsClientOutput := forceFlushFailedEvent || openAIStreamDataStartsClientOutput(data, eventType)
startsVisibleOutput := openAIStreamDataStartsVisibleOutput(data, eventType)
startsTTFTOutput := openAIStreamDataStartsTTFT(data, eventType, forceFlushFailedEvent, ttftMode)
if stageFirstOutput {
eventStartsClientOutput = eventStartsClientOutput || startsClientOutput
eventStartsVisibleOutput = eventStartsVisibleOutput || startsVisibleOutput
eventStartsTTFTOutput = eventStartsTTFTOutput || startsTTFTOutput
if startsClientOutput {
firstOutputScanGuard.Store(false)
}
@@ -703,7 +705,7 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
}
// Record first token time
if !guardFirstOutput && firstTokenMs == nil && startsVisibleOutput {
if !guardFirstOutput && firstTokenMs == nil && startsTTFTOutput {
ms := int(time.Since(startTime).Milliseconds())
firstTokenMs = &ms
stopFirstOutputTimer()
@@ -720,7 +722,7 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
terminalFailurePending = false
eventInProgress = false
eventStartsClientOutput = false
eventStartsVisibleOutput = false
eventStartsTTFTOutput = false
eventShouldFlush = false
return
}
@@ -728,7 +730,7 @@ func (s *OpenAIGatewayService) handleStreamingResponseWithReasoning(ctx context.
terminalFailurePending = false
eventInProgress = false
eventStartsClientOutput = false
eventStartsVisibleOutput = false
eventStartsTTFTOutput = false
eventShouldFlush = false
return
}
@@ -48,7 +48,7 @@ func TestOpenAIResponsesTTFTStartsAtVisibleOutput(t *testing.T) {
name = "passthrough"
}
t.Run(name, func(t *testing.T) {
result := runSyntheticVisibleTTFTStream(t, passthrough, 120*time.Millisecond, 0,
result := runSyntheticVisibleTTFTStream(t, passthrough, 120*time.Millisecond, 0, OpenAITTFTModeVisible,
`{"type":"response.output_text.delta","delta":"test output"}`)
require.NotNil(t, result.firstTokenMs)
require.GreaterOrEqual(t, *result.firstTokenMs, 100)
@@ -63,7 +63,7 @@ func TestOpenAIResponsesTTFTStartsAtCompletedImage(t *testing.T) {
name = "passthrough"
}
t.Run(name, func(t *testing.T) {
result := runSyntheticVisibleTTFTStream(t, passthrough, 120*time.Millisecond, 0,
result := runSyntheticVisibleTTFTStream(t, passthrough, 120*time.Millisecond, 0, OpenAITTFTModeVisible,
`{"type":"response.output_item.done","item":{"id":"item_test","type":"image_generation_call","result":"dGVzdA=="}}`)
require.NotNil(t, result.firstTokenMs)
require.GreaterOrEqual(t, *result.firstTokenMs, 100)
@@ -105,9 +105,32 @@ func TestOpenAINativeMetadataDoesNotDisarmFirstOutputTimeout(t *testing.T) {
}
}
func runSyntheticVisibleTTFTStream(t *testing.T, passthrough bool, visibleDelay time.Duration, timeoutSeconds int, visibleEvent string) *openaiStreamingResult {
func TestOpenAIResponsesTTFTDefaultsToSemanticOutput(t *testing.T) {
for _, passthrough := range []bool{false, true} {
name := "native"
if passthrough {
name = "passthrough"
}
t.Run(name, func(t *testing.T) {
result := runSyntheticVisibleTTFTStream(t, passthrough, 120*time.Millisecond, 0, "",
`{"type":"response.output_text.delta","delta":"test output"}`)
require.NotNil(t, result.firstTokenMs)
require.Less(t, *result.firstTokenMs, 100)
})
}
}
func runSyntheticVisibleTTFTStream(t *testing.T, passthrough bool, visibleDelay time.Duration, timeoutSeconds int, ttftMode string, visibleEvent string) *openaiStreamingResult {
t.Helper()
gin.SetMode(gin.TestMode)
mode := ttftMode
if mode == "" {
mode = OpenAITTFTModeSemantic
}
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{openAITTFTMode: mode, expiresAt: time.Now().Add(time.Minute).UnixNano()})
t.Cleanup(func() {
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{openAITTFTMode: OpenAITTFTModeSemantic, expiresAt: time.Now().Add(time.Minute).UnixNano()})
})
svc := &OpenAIGatewayService{cfg: &config.Config{Gateway: config.GatewayConfig{
MaxLineSize: defaultMaxLineSize,
OpenAIFirstOutputTimeoutSeconds: timeoutSeconds,
@@ -53,6 +53,7 @@ const backendModeDBTimeout = 5 * time.Second
// cachedGatewayForwardingSettings 缓存网关转发行为设置(进程内缓存,60s TTL)
type cachedGatewayForwardingSettings struct {
openAITTFTMode string
fingerprintUnification bool
metadataPassthrough bool
cchSigning bool
@@ -736,6 +737,7 @@ func (s *SettingService) IsBackendModeEnabled(ctx context.Context) bool {
}
type gatewayForwardingSettingsResult struct {
openAITTFTMode string
fp, mp, cch, claudeOAuthSystemPromptInjection, cacheTTL1h, rewriteMessageCacheControl bool
clientDatelineNormalization bool
claudeOAuthSystemPrompt, claudeOAuthSystemPromptBlocks string
@@ -745,6 +747,7 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
if cached, ok := gatewayForwardingCache.Load().(*cachedGatewayForwardingSettings); ok && cached != nil {
if time.Now().UnixNano() < cached.expiresAt {
return gatewayForwardingSettingsResult{
openAITTFTMode: cached.openAITTFTMode,
fp: cached.fingerprintUnification,
mp: cached.metadataPassthrough,
cch: cached.cchSigning,
@@ -761,6 +764,7 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
if cached, ok := gatewayForwardingCache.Load().(*cachedGatewayForwardingSettings); ok && cached != nil {
if time.Now().UnixNano() < cached.expiresAt {
return gatewayForwardingSettingsResult{
openAITTFTMode: cached.openAITTFTMode,
fp: cached.fingerprintUnification,
mp: cached.metadataPassthrough,
cch: cached.cchSigning,
@@ -776,6 +780,7 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), gatewayForwardingDBTimeout)
defer cancel()
values, err := s.settingRepo.GetMultiple(dbCtx, []string{
SettingKeyOpenAITTFTMode,
SettingKeyEnableFingerprintUnification,
SettingKeyEnableMetadataPassthrough,
SettingKeyEnableCCHSigning,
@@ -789,6 +794,7 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
if err != nil {
slog.Warn("failed to get gateway forwarding settings", "error", err)
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{
openAITTFTMode: OpenAITTFTModeSemantic,
fingerprintUnification: true,
metadataPassthrough: false,
cchSigning: false,
@@ -798,8 +804,9 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
clientDatelineNormalization: true,
expiresAt: time.Now().Add(gatewayForwardingErrorTTL).UnixNano(),
})
return gatewayForwardingSettingsResult{fp: true, claudeOAuthSystemPromptInjection: true, rewriteMessageCacheControl: s.defaultRewriteMessageCacheControl(), clientDatelineNormalization: true}, nil
return gatewayForwardingSettingsResult{openAITTFTMode: OpenAITTFTModeSemantic, fp: true, claudeOAuthSystemPromptInjection: true, rewriteMessageCacheControl: s.defaultRewriteMessageCacheControl(), clientDatelineNormalization: true}, nil
}
ttftMode := normalizeOpenAITTFTMode(values[SettingKeyOpenAITTFTMode])
fp := true
if v, ok := values[SettingKeyEnableFingerprintUnification]; ok && v != "" {
fp = v == "true"
@@ -822,6 +829,7 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
clientDatelineNormalization = v == "true"
}
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{
openAITTFTMode: ttftMode,
fingerprintUnification: fp,
metadataPassthrough: mp,
cchSigning: cch,
@@ -834,6 +842,7 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
expiresAt: time.Now().Add(gatewayForwardingCacheTTL).UnixNano(),
})
return gatewayForwardingSettingsResult{
openAITTFTMode: ttftMode,
fp: fp,
mp: mp,
cch: cch,
@@ -851,6 +860,11 @@ func (s *SettingService) getGatewayForwardingSettingsCached(ctx context.Context)
return gatewayForwardingSettingsResult{fp: true, claudeOAuthSystemPromptInjection: true, clientDatelineNormalization: true}
}
// GetOpenAITTFTMode 返回 Responses first_token_ms 的统计口径。
func (s *SettingService) GetOpenAITTFTMode(ctx context.Context) string {
return s.getGatewayForwardingSettingsCached(ctx).openAITTFTMode
}
// GetGatewayForwardingSettings returns cached gateway forwarding settings.
// Uses in-process atomic.Value cache with 60s TTL, zero-lock hot path.
// Returns (fingerprintUnification, metadataPassthrough, cchSigning).
@@ -846,6 +846,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
// Gateway forwarding behavior (defaults: fingerprint=true, metadata_passthrough=false,
// cch_signing=false, claude_oauth_system_prompt_injection=true)
result.OpenAITTFTMode = normalizeOpenAITTFTMode(settings[SettingKeyOpenAITTFTMode])
if v, ok := settings[SettingKeyEnableFingerprintUnification]; ok && v != "" {
result.EnableFingerprintUnification = v == "true"
} else {
@@ -979,6 +980,13 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
return result
}
func normalizeOpenAITTFTMode(mode string) string {
if strings.EqualFold(strings.TrimSpace(mode), OpenAITTFTModeVisible) {
return OpenAITTFTModeVisible
}
return OpenAITTFTModeSemantic
}
func clampAffiliateRebateRate(value float64) float64 {
if math.IsNaN(value) || math.IsInf(value, 0) {
return AffiliateRebateRateDefault
@@ -460,6 +460,11 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
updates[SettingKeyBackendModeEnabled] = strconv.FormatBool(settings.BackendModeEnabled)
// Gateway forwarding behavior
mode := normalizeOpenAITTFTMode(settings.OpenAITTFTMode)
if strings.TrimSpace(settings.OpenAITTFTMode) != "" && strings.ToLower(strings.TrimSpace(settings.OpenAITTFTMode)) != OpenAITTFTModeSemantic && strings.ToLower(strings.TrimSpace(settings.OpenAITTFTMode)) != OpenAITTFTModeVisible {
return nil, fmt.Errorf("%s must be one of: %s/%s", SettingKeyOpenAITTFTMode, OpenAITTFTModeSemantic, OpenAITTFTModeVisible)
}
updates[SettingKeyOpenAITTFTMode] = mode
updates[SettingKeyEnableFingerprintUnification] = strconv.FormatBool(settings.EnableFingerprintUnification)
updates[SettingKeyEnableMetadataPassthrough] = strconv.FormatBool(settings.EnableMetadataPassthrough)
updates[SettingKeyEnableCCHSigning] = strconv.FormatBool(settings.EnableCCHSigning)
@@ -697,6 +702,7 @@ func (s *SettingService) refreshCachedSettings(settings *SystemSettings) {
})
gatewayForwardingSF.Forget("gateway_forwarding")
gatewayForwardingCache.Store(&cachedGatewayForwardingSettings{
openAITTFTMode: normalizeOpenAITTFTMode(settings.OpenAITTFTMode),
fingerprintUnification: settings.EnableFingerprintUnification,
metadataPassthrough: settings.EnableMetadataPassthrough,
cchSigning: settings.EnableCCHSigning,
@@ -228,6 +228,7 @@ type SystemSettings struct {
BackendModeEnabled bool
// Gateway forwarding behavior
OpenAITTFTMode string // Responses first_token_ms 统计口径(默认 semantic)
EnableFingerprintUnification bool // 是否统一 OAuth 账号的指纹头(默认 true)
EnableMetadataPassthrough bool // 是否透传客户端原始 metadata(默认 false)
EnableCCHSigning bool // 已废弃 no-op:新版 CLI 取消 cch 签名后网关不再注入/签名 cch,开关无效果
+2
View File
@@ -620,6 +620,7 @@ export interface SystemSettings {
allow_ungrouped_key_scheduling: boolean;
// Gateway forwarding behavior
openai_ttft_mode: string;
enable_fingerprint_unification: boolean;
enable_metadata_passthrough: boolean;
enable_cch_signing: boolean;
@@ -935,6 +936,7 @@ export interface UpdateSettingsRequest {
min_claude_code_version?: string;
max_claude_code_version?: string;
allow_ungrouped_key_scheduling?: boolean;
openai_ttft_mode?: string;
enable_fingerprint_unification?: boolean;
enable_metadata_passthrough?: boolean;
enable_cch_signing?: boolean;
@@ -473,6 +473,10 @@ export default {
grokBaseURLModeUSEast1: 'Regional API (us-east-1)',
grokBaseURLModeUSWest2: 'Regional API (us-west-2)',
grokBaseURLModeEUWest1: 'Regional API (eu-west-1)',
openaiTTFTMode: 'OpenAI Responses first-token metric',
openaiTTFTModeSemantic: 'Legacy-compatible (semantic event)',
openaiTTFTModeVisible: 'Actual visible output',
openaiTTFTModeHint: 'The default records first_token_ms at the first non-preamble semantic event. Actual visible output records it only when non-empty text, tool arguments, or image content arrives.',
fingerprintUnification: 'Fingerprint Unification',
fingerprintUnificationHint: 'Unify X-Stainless-* headers across users sharing the same OAuth account. Disabling passes through each client\'s original headers.',
metadataPassthrough: 'Metadata Passthrough',
@@ -466,6 +466,10 @@ export default {
grokBaseURLModeUSEast1: '区域 API(us-east-1)',
grokBaseURLModeUSWest2: '区域 API(us-west-2)',
grokBaseURLModeEUWest1: '区域 API(eu-west-1)',
openaiTTFTMode: 'OpenAI Responses 首 token 统计口径',
openaiTTFTModeSemantic: '历史兼容(语义事件)',
openaiTTFTModeVisible: '真实可见输出',
openaiTTFTModeHint: '默认使用历史兼容口径,首个非预置语义事件即记录 first_token_ms。选择真实可见输出后,仅在首个非空文本、工具参数或图片内容到达时记录。',
fingerprintUnification: '指纹统一化',
fingerprintUnificationHint: '统一共享同一 OAuth 账号的用户的 X-Stainless-* 请求头。关闭后透传客户端原始请求头。',
metadataPassthrough: 'Metadata 透传',
+29
View File
@@ -5268,6 +5268,32 @@
</p>
</div>
<!-- OpenAI Responses 首 token 统计 -->
<div class="border-b border-gray-100 pb-5 dark:border-dark-700 md:col-span-2">
<label
for="openai-ttft-mode"
class="text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("admin.settings.gatewayForwarding.openaiTTFTMode") }}
</label>
<select
id="openai-ttft-mode"
v-model="form.openai_ttft_mode"
class="input mt-2 w-full"
data-testid="openai-ttft-mode"
>
<option value="semantic">
{{ t("admin.settings.gatewayForwarding.openaiTTFTModeSemantic") }}
</option>
<option value="visible">
{{ t("admin.settings.gatewayForwarding.openaiTTFTModeVisible") }}
</option>
</select>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{ t("admin.settings.gatewayForwarding.openaiTTFTModeHint") }}
</p>
</div>
<!-- Fingerprint Unification -->
<div class="flex items-center justify-between">
<div>
@@ -9735,6 +9761,7 @@ const form = reactive<SettingsForm>({
openai_advanced_scheduler_weight_previous_response: "",
openai_advanced_scheduler_weight_session_sticky: "",
// Gateway forwarding behavior
openai_ttft_mode: "semantic",
enable_fingerprint_unification: true,
enable_metadata_passthrough: false,
enable_cch_signing: false,
@@ -11308,6 +11335,8 @@ async function saveSettings() {
min_claude_code_version: form.min_claude_code_version,
max_claude_code_version: form.max_claude_code_version,
allow_ungrouped_key_scheduling: form.allow_ungrouped_key_scheduling,
openai_ttft_mode:
form.openai_ttft_mode === "visible" ? "visible" : "semantic",
enable_fingerprint_unification: form.enable_fingerprint_unification,
enable_metadata_passthrough: form.enable_metadata_passthrough,
enable_cch_signing: form.enable_cch_signing,
@@ -464,6 +464,7 @@ const baseSettingsResponse = {
min_claude_code_version: "",
max_claude_code_version: "",
allow_ungrouped_key_scheduling: false,
openai_ttft_mode: "semantic",
enable_fingerprint_unification: true,
enable_metadata_passthrough: false,
enable_cch_signing: false,
@@ -1363,6 +1364,27 @@ describe("admin SettingsView payment visible method controls", () => {
expect(payload.grok_cross_client_model_map_enabled).toBe(false);
});
it("loads and saves the OpenAI Responses first-token metric mode", async () => {
getSettings.mockResolvedValueOnce({
...baseSettingsResponse,
openai_ttft_mode: "visible",
});
const wrapper = mountView();
await flushPromises();
await openGatewayTab(wrapper);
const modeSelect = wrapper.get('[data-testid="openai-ttft-mode"]');
expect((modeSelect.element as HTMLSelectElement).value).toBe("visible");
await modeSelect.setValue("semantic");
await wrapper.find("form").trigger("submit.prevent");
await flushPromises();
const payload = updateSettings.mock.calls.at(-1)?.[0] as Record<string, unknown>;
expect(payload.openai_ttft_mode).toBe("semantic");
});
it("loads fail-safe-off Ollama Cloud usage refresh settings and saves an explicit opt-in", async () => {
const wrapper = mountView();