fix(grok): restore base URL resolution and operator settings wiring

Honor account GetGrokBaseURLOr policy for official vs custom endpoints,
and wire settings resolution used by responses/chat URL builders.
This commit is contained in:
IanShaw027
2026-08-08 01:07:19 +08:00
parent 6d632eec45
commit 68faeac837
20 changed files with 329 additions and 29 deletions
+2 -2
View File
@@ -193,11 +193,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
claudeUsageFetcher := repository.NewClaudeUsageFetcher(httpUpstream)
antigravityQuotaFetcher := service.NewAntigravityQuotaFetcher(proxyRepository)
grokQuotaFetcher := service.NewGrokQuotaFetcher()
grokQuotaService := service.ProvideGrokQuotaService(accountRepository, proxyRepository, grokTokenProvider, httpUpstream, configConfig, usageLogRepository)
grokQuotaService := service.ProvideGrokQuotaService(accountRepository, proxyRepository, grokTokenProvider, httpUpstream, configConfig, usageLogRepository, settingService)
openAIQuotaService := service.ProvideOpenAIQuotaService(accountRepository, proxyRepository, openAITokenProvider, privacyClientFactory, openAIGatewayService)
usageCache := service.NewUsageCache()
accountUsageService := service.ProvideAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher, geminiQuotaService, antigravityQuotaFetcher, grokQuotaFetcher, grokQuotaService, openAIQuotaService, usageCache, identityCache, tlsFingerprintProfileService, openAIGatewayService)
accountTestService := service.ProvideAccountTestService(accountRepository, geminiTokenProvider, claudeTokenProvider, grokTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService, openAIGatewayService)
accountTestService := service.ProvideAccountTestService(accountRepository, geminiTokenProvider, claudeTokenProvider, grokTokenProvider, antigravityGatewayService, httpUpstream, configConfig, tlsFingerprintProfileService, openAIGatewayService, settingService)
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
accountHandler := admin.ProvideAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, grokOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService, sessionLimitCache, rpmCache, compositeTokenCacheInvalidator, grokQuotaService)
adminAnnouncementHandler := admin.NewAnnouncementHandler(announcementService)
@@ -374,6 +374,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
GrokDefaultTextModel: settings.GrokDefaultTextModel,
GrokCrossClientModelMapEnabled: settings.GrokCrossClientModelMapEnabled,
GrokDefaultBaseURLMode: settings.GrokDefaultBaseURLMode,
AvailableChannelsEnabled: settings.AvailableChannelsEnabled,
@@ -66,6 +66,18 @@ func TestUpdateSettingsSMTPFromAliasIsWritable(t *testing.T) {
require.Equal(t, "new@example.com", repo.values[service.SettingKeySMTPFrom])
}
func TestUpdateSettingsGrokDefaultBaseURLModeIsWritable(t *testing.T) {
h, repo := newStepUpSwitchTestHandler(t, map[string]string{
service.SettingKeyGrokDefaultBaseURLMode: service.GrokDefaultBaseURLModeCLI,
})
rec := doUpdateSettings(t, h, map[string]any{
"grok_default_base_url_mode": service.GrokDefaultBaseURLModeEUWest1,
}, nil)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, service.GrokDefaultBaseURLModeEUWest1, repo.values[service.SettingKeyGrokDefaultBaseURLMode])
}
func TestUpdateSettingsRejectsTwoCaptchaProviders(t *testing.T) {
h, _ := newStepUpSwitchTestHandler(t, map[string]string{
service.SettingKeyTurnstileEnabled: "true",
@@ -333,6 +333,7 @@ type UpdateSettingsRequest struct {
// Grok model mapping policy
GrokDefaultTextModel *string `json:"grok_default_text_model"`
GrokCrossClientModelMapEnabled *bool `json:"grok_cross_client_model_map_enabled"`
GrokDefaultBaseURLMode *string `json:"grok_default_base_url_mode"`
// Available Channels feature switch (user-facing)
AvailableChannelsEnabled *bool `json:"available_channels_enabled"`
@@ -1880,6 +1881,12 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
return previousSettings.GrokCrossClientModelMapEnabled
}(),
GrokDefaultBaseURLMode: func() string {
if req.GrokDefaultBaseURLMode != nil {
return strings.TrimSpace(*req.GrokDefaultBaseURLMode)
}
return previousSettings.GrokDefaultBaseURLMode
}(),
AvailableChannelsEnabled: func() bool {
if req.AvailableChannelsEnabled != nil {
return *req.AvailableChannelsEnabled
@@ -2315,6 +2322,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
GrokDefaultTextModel: updatedSettings.GrokDefaultTextModel,
GrokCrossClientModelMapEnabled: updatedSettings.GrokCrossClientModelMapEnabled,
GrokDefaultBaseURLMode: updatedSettings.GrokDefaultBaseURLMode,
AvailableChannelsEnabled: updatedSettings.AvailableChannelsEnabled,
@@ -2324,11 +2332,11 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
AffiliateEnabled: updatedSettings.AffiliateEnabled,
RiskControlEnabled: updatedSettings.RiskControlEnabled,
CyberSessionBlockEnabled: updatedSettings.CyberSessionBlockEnabled,
CyberSessionBlockTTLSeconds: updatedSettings.CyberSessionBlockTTLSeconds,
AccountSchedulingThresholds: updatedSettings.AccountSchedulingThresholds,
AllowUserViewErrorRequests: updatedSettings.AllowUserViewErrorRequests,
RiskControlEnabled: updatedSettings.RiskControlEnabled,
CyberSessionBlockEnabled: updatedSettings.CyberSessionBlockEnabled,
CyberSessionBlockTTLSeconds: updatedSettings.CyberSessionBlockTTLSeconds,
AccountSchedulingThresholds: updatedSettings.AccountSchedulingThresholds,
AllowUserViewErrorRequests: updatedSettings.AllowUserViewErrorRequests,
}
if fastPolicy, err := h.settingService.GetOpenAIFastPolicySettings(c.Request.Context()); err != nil {
slog.Error("openai_fast_policy_settings_get_failed", "error", err)
+1
View File
@@ -306,6 +306,7 @@ type SystemSettings struct {
// Grok model mapping policy (admin settings; empty account mapping falls back to these).
GrokDefaultTextModel string `json:"grok_default_text_model"`
GrokCrossClientModelMapEnabled bool `json:"grok_cross_client_model_map_enabled"`
GrokDefaultBaseURLMode string `json:"grok_default_base_url_mode"`
// Available Channels feature switch (user-facing aggregate view)
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
+55 -12
View File
@@ -6,6 +6,7 @@ import (
"errors"
"hash/fnv"
"log/slog"
"net/url"
"reflect"
"sort"
"strconv"
@@ -1310,25 +1311,67 @@ func (a *Account) GetOpenAIRefreshToken() string {
// traffic (OAuth authorization and token refresh) always uses the official
// auth endpoints regardless of this value.
func (a *Account) GetGrokBaseURL() string {
if !a.IsGrok() {
if a == nil || !a.IsGrok() {
return ""
}
baseURL := strings.TrimSpace(a.GetCredential("base_url"))
if a.IsGrokOAuth() {
// Operators switch subscription traffic between the official CLI
// gateway, the official/regional API hosts and third-party relays
// (individual endpoints go down from time to time), so a stored
// value is always honored as-is. Only empty or unparseable values
// fall back to the default CLI gateway.
if baseURL == "" || !xai.IsParseableBaseURL(baseURL) {
return xai.DefaultCLIBaseURL
return a.GetGrokBaseURLOr(xai.DefaultCLIBaseURL)
}
return a.GetGrokBaseURLOr(xai.DefaultBaseURL)
}
// GetGrokBaseURLOr resolves an explicit account endpoint, falling back to the
// supplied default. Official OAuth endpoints are normalized here; custom
// endpoints are retained for the request builder's operator URL policy.
func (a *Account) GetGrokBaseURLOr(defaultBaseURL string) string {
if a == nil || !a.IsGrok() {
return ""
}
defaultBaseURL = strings.TrimRight(strings.TrimSpace(defaultBaseURL), "/")
if defaultBaseURL == "" {
if a.IsGrokOAuth() {
defaultBaseURL = xai.DefaultCLIBaseURL
} else {
defaultBaseURL = xai.DefaultBaseURL
}
}
baseURL := strings.TrimSpace(a.GetCredential("base_url"))
if baseURL == "" {
return defaultBaseURL
}
if !a.IsGrokOAuth() {
return baseURL
}
if baseURL != "" {
return baseURL
// Explicit regional/API or custom values remain pinned. Custom endpoints are checked by the
// operator URL policy at the request builder, which has access to config.
if validated, err := xai.ValidateTrustedBaseURL(baseURL); err == nil {
return validated
}
return xai.DefaultBaseURL
if parsed, err := url.Parse(baseURL); err == nil && parsed.Scheme != "" && parsed.Host != "" &&
parsed.User == nil && parsed.RawQuery == "" && parsed.Fragment == "" {
return strings.TrimRight(baseURL, "/")
}
return defaultBaseURL
}
func isOfficialGrokAPIBaseURL(raw string) bool {
return isOfficialGrokBaseURL(raw, xai.DefaultBaseURL)
}
func isOfficialGrokBaseURL(raw, expected string) bool {
parsed, err := url.Parse(strings.TrimSpace(raw))
if err != nil || parsed == nil || parsed.Opaque != "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" {
return false
}
want, err := url.Parse(expected)
if err != nil || !strings.EqualFold(parsed.Scheme, want.Scheme) || !strings.EqualFold(parsed.Hostname(), want.Hostname()) {
return false
}
if port := parsed.Port(); port != "" && port != "443" {
return false
}
path := strings.TrimRight(parsed.Path, "/")
return path == "" || path == strings.TrimRight(want.Path, "/")
}
// GetGrokMediaBaseURL selects the upstream used by Grok Imagine APIs.
@@ -0,0 +1,75 @@
//go:build unit
package service
import (
"context"
"fmt"
"testing"
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
"github.com/stretchr/testify/require"
)
type grokBaseURLSettingRepoStub struct{ values map[string]string }
func (r *grokBaseURLSettingRepoStub) GetValue(_ context.Context, key string) (string, error) {
if value, ok := r.values[key]; ok {
return value, nil
}
return "", fmt.Errorf("setting %s not found", key)
}
func (r *grokBaseURLSettingRepoStub) Get(context.Context, string) (*Setting, error) {
return nil, fmt.Errorf("unused")
}
func (r *grokBaseURLSettingRepoStub) Set(context.Context, string, string) error { return nil }
func (r *grokBaseURLSettingRepoStub) GetMultiple(context.Context, []string) (map[string]string, error) {
return r.values, nil
}
func (r *grokBaseURLSettingRepoStub) SetMultiple(context.Context, map[string]string) error {
return nil
}
func (r *grokBaseURLSettingRepoStub) GetAll(context.Context) (map[string]string, error) {
return r.values, nil
}
func (r *grokBaseURLSettingRepoStub) Delete(context.Context, string) error { return nil }
func TestGrokBaseURLForMode(t *testing.T) {
for _, tc := range []struct {
mode string
want string
}{
{"api", xai.DefaultBaseURL},
{"us-east-1", xai.DefaultUSEast1BaseURL},
{"us-west-2", xai.DefaultUSWest2BaseURL},
{"eu-west-1", xai.DefaultEUWest1BaseURL},
{"cli", xai.DefaultCLIBaseURL},
{"invalid", xai.DefaultCLIBaseURL},
} {
t.Run(tc.mode, func(t *testing.T) {
require.Equal(t, tc.want, GrokBaseURLForMode(tc.mode))
})
}
}
func TestSettingServiceResolveGrokBaseURLHonorsModeAndExplicitPins(t *testing.T) {
repo := &grokBaseURLSettingRepoStub{values: map[string]string{SettingKeyGrokDefaultBaseURLMode: GrokDefaultBaseURLModeUSWest2}}
svc := NewSettingService(repo, nil)
account := &Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{}}
require.Equal(t, xai.DefaultUSWest2BaseURL, svc.ResolveGrokBaseURL(context.Background(), account))
// An explicit official endpoint remains pinned.
account.Credentials["base_url"] = xai.DefaultBaseURL
require.Equal(t, xai.DefaultBaseURL, svc.ResolveGrokBaseURL(context.Background(), account))
// An explicit regional pin remains authoritative.
account.Credentials["base_url"] = xai.DefaultEUWest1BaseURL
require.Equal(t, xai.DefaultEUWest1BaseURL, svc.ResolveGrokBaseURL(context.Background(), account))
}
func TestAccountGetGrokBaseURLOrPreservesCustomOAuthURLForPolicyValidation(t *testing.T) {
account := &Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{
"base_url": "https://attacker.invalid/v1",
}}
require.Equal(t, "https://attacker.invalid/v1", account.GetGrokBaseURLOr(xai.DefaultCLIBaseURL))
}
+21 -5
View File
@@ -1,6 +1,7 @@
package service
import (
"context"
"errors"
"fmt"
"strings"
@@ -68,20 +69,28 @@ func redactedGrokBaseURLValidator(validator xai.BaseURLValidator) xai.BaseURLVal
}
}
func buildGrokResponsesURL(account *Account, cfg *config.Config) (string, error) {
func buildGrokResponsesURL(account *Account, cfg *config.Config, settings ...*SettingService) (string, error) {
validator, err := grokBaseURLValidator(account, cfg)
if err != nil {
return "", err
}
return xai.BuildResponsesURLWithValidator(account.GetGrokBaseURL(), validator)
baseURL := account.GetGrokBaseURL()
if len(settings) > 0 && settings[0] != nil {
baseURL = settings[0].ResolveGrokBaseURL(context.Background(), account)
}
return xai.BuildResponsesURLWithValidator(baseURL, validator)
}
func buildGrokChatCompletionsURL(account *Account, cfg *config.Config) (string, error) {
func buildGrokChatCompletionsURL(account *Account, cfg *config.Config, settings ...*SettingService) (string, error) {
validator, err := grokBaseURLValidator(account, cfg)
if err != nil {
return "", err
}
return xai.BuildChatCompletionsURLWithValidator(account.GetGrokBaseURL(), validator)
baseURL := account.GetGrokBaseURL()
if len(settings) > 0 && settings[0] != nil {
baseURL = settings[0].ResolveGrokBaseURL(context.Background(), account)
}
return xai.BuildChatCompletionsURLWithValidator(baseURL, validator)
}
// buildGrokBillingURL 解析 billing 探测端点:跟随账号的转发 base_url,
@@ -91,7 +100,14 @@ func buildGrokBillingURL(account *Account, cfg *config.Config, weekly bool) (str
if err != nil {
return "", err
}
return xai.BuildBillingURLWithValidator(account.GetGrokBaseURL(), weekly, validator)
baseURL := account.GetGrokBaseURL()
// Official public/regional API hosts do not expose Grok Build billing.
// Keep custom relays on their configured host because they may proxy the CLI
// billing path alongside inference.
if xai.IsOfficialBaseURL(baseURL) && !isGrokCLIProxyBaseURL(baseURL) {
baseURL = xai.DefaultCLIBaseURL
}
return xai.BuildBillingURLWithValidator(baseURL, weekly, validator)
}
func buildGrokMediaURL(account *Account, cfg *config.Config, endpoint GrokMediaEndpoint, requestID string) (string, error) {
@@ -255,6 +255,26 @@ func TestGrokOAuthURLPolicy(t *testing.T) {
})
}
func TestBuildGrokBillingURLUsesCLIForOfficialAPIHosts(t *testing.T) {
for _, baseURL := range []string{xai.DefaultBaseURL, "https://us-west-2.api.x.ai/v1"} {
account := &Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{"base_url": baseURL}}
weekly, err := buildGrokBillingURL(account, &config.Config{}, true)
require.NoError(t, err)
require.Equal(t, xai.DefaultCLIBaseURL+xai.BillingWeeklyPath, weekly)
}
}
func TestBuildGrokBillingURLKeepsCustomRelay(t *testing.T) {
account := &Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{
"base_url": "https://relay.example.test/xai/v1",
}}
monthly, err := buildGrokBillingURL(account, &config.Config{}, false)
require.NoError(t, err)
require.Equal(t, "https://relay.example.test/xai/v1"+xai.BillingMonthlyPath, monthly)
}
func TestGrokBillingURLFollowsAccountBaseURL(t *testing.T) {
t.Run("oauth default stays on CLI gateway", func(t *testing.T) {
account := &Account{
@@ -191,6 +191,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
// Grok: safe defaults — no cross-vendor model rewrite unless operators enable it.
SettingKeyGrokDefaultTextModel: "grok-4.5",
SettingKeyGrokCrossClientModelMapEnabled: "false",
SettingKeyGrokDefaultBaseURLMode: GrokDefaultBaseURLModeCLI,
// Available channels feature (default disabled; opt-in)
SettingKeyAvailableChannelsEnabled: "false",
@@ -796,6 +797,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
result.GrokDefaultTextModel = "grok-4.5"
}
result.GrokCrossClientModelMapEnabled = settings[SettingKeyGrokCrossClientModelMapEnabled] == "true"
result.GrokDefaultBaseURLMode = normalizeGrokDefaultBaseURLMode(settings[SettingKeyGrokDefaultBaseURLMode])
// Available channels feature (default: disabled; strict true)
result.AvailableChannelsEnabled = settings[SettingKeyAvailableChannelsEnabled] == "true"
@@ -5,13 +5,83 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync/atomic"
"github.com/Wei-Shaw/sub2api/internal/config"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/xai"
"golang.org/x/sync/singleflight"
)
const (
GrokDefaultBaseURLModeAPI = "api"
GrokDefaultBaseURLModeUSEast1 = "us-east-1"
GrokDefaultBaseURLModeUSWest2 = "us-west-2"
GrokDefaultBaseURLModeEUWest1 = "eu-west-1"
GrokDefaultBaseURLModeCLI = "cli"
)
func normalizeGrokDefaultBaseURLMode(mode string) string {
switch strings.ToLower(strings.TrimSpace(mode)) {
case GrokDefaultBaseURLModeAPI:
return GrokDefaultBaseURLModeAPI
case GrokDefaultBaseURLModeUSEast1:
return GrokDefaultBaseURLModeUSEast1
case GrokDefaultBaseURLModeUSWest2:
return GrokDefaultBaseURLModeUSWest2
case GrokDefaultBaseURLModeEUWest1:
return GrokDefaultBaseURLModeEUWest1
case GrokDefaultBaseURLModeCLI:
return GrokDefaultBaseURLModeCLI
default:
return GrokDefaultBaseURLModeCLI
}
}
func GrokBaseURLForMode(mode string) string {
switch normalizeGrokDefaultBaseURLMode(mode) {
case GrokDefaultBaseURLModeAPI:
return xai.DefaultBaseURL
case GrokDefaultBaseURLModeUSEast1:
return xai.DefaultUSEast1BaseURL
case GrokDefaultBaseURLModeUSWest2:
return xai.DefaultUSWest2BaseURL
case GrokDefaultBaseURLModeEUWest1:
return xai.DefaultEUWest1BaseURL
default:
return xai.DefaultCLIBaseURL
}
}
func (s *SettingService) GetGrokDefaultBaseURLMode(ctx context.Context) string {
if s == nil || s.settingRepo == nil {
return GrokDefaultBaseURLModeCLI
}
dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), gatewayForwardingDBTimeout)
defer cancel()
raw, err := s.settingRepo.GetValue(dbCtx, SettingKeyGrokDefaultBaseURLMode)
if err != nil {
return GrokDefaultBaseURLModeCLI
}
return normalizeGrokDefaultBaseURLMode(raw)
}
func (s *SettingService) GetGrokDefaultBaseURL(ctx context.Context) string {
return GrokBaseURLForMode(s.GetGrokDefaultBaseURLMode(ctx))
}
func (s *SettingService) ResolveGrokBaseURL(ctx context.Context, account *Account) string {
def := xai.DefaultCLIBaseURL
if s != nil {
def = s.GetGrokDefaultBaseURL(ctx)
}
if account == nil {
return def
}
return account.GetGrokBaseURLOr(def)
}
var (
ErrRegistrationDisabled = infraerrors.Forbidden("REGISTRATION_DISABLED", "registration is currently disabled")
ErrSettingNotFound = infraerrors.NotFound("SETTING_NOT_FOUND", "setting not found")
@@ -422,6 +422,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
updates[SettingKeyGrokDefaultTextModel] = "grok-4.5"
}
updates[SettingKeyGrokCrossClientModelMapEnabled] = strconv.FormatBool(settings.GrokCrossClientModelMapEnabled)
updates[SettingKeyGrokDefaultBaseURLMode] = normalizeGrokDefaultBaseURLMode(settings.GrokDefaultBaseURLMode)
// Available channels feature switch
updates[SettingKeyAvailableChannelsEnabled] = strconv.FormatBool(settings.AvailableChannelsEnabled)
@@ -202,6 +202,7 @@ type SystemSettings struct {
// Grok model mapping policy (admin settings; empty mapping falls back to these).
GrokDefaultTextModel string `json:"grok_default_text_model"`
GrokCrossClientModelMapEnabled bool `json:"grok_cross_client_model_map_enabled"`
GrokDefaultBaseURLMode string `json:"grok_default_base_url_mode"`
// Available Channels feature (user-facing aggregate view)
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
@@ -375,6 +376,7 @@ type PublicSettings struct {
// Grok model mapping policy (admin settings).
GrokDefaultTextModel string `json:"grok_default_text_model"`
GrokCrossClientModelMapEnabled bool `json:"grok_cross_client_model_map_enabled"`
GrokDefaultBaseURLMode string `json:"grok_default_base_url_mode"`
// Available Channels feature (user-facing aggregate view)
AvailableChannelsEnabled bool `json:"available_channels_enabled"`
+6 -1
View File
@@ -229,6 +229,7 @@ func ProvideAccountTestService(
cfg *config.Config,
tlsFPProfileService *TLSFingerprintProfileService,
openAIGatewayService *OpenAIGatewayService,
settingService *SettingService,
) *AccountTestService {
service := NewAccountTestService(
accountRepo,
@@ -241,6 +242,7 @@ func ProvideAccountTestService(
tlsFPProfileService,
)
service.agentIdentityWS = openAIGatewayService
service.SetSettingService(settingService)
return service
}
@@ -251,8 +253,11 @@ func ProvideGrokQuotaService(
httpUpstream HTTPUpstream,
cfg *config.Config,
usageLogRepo UsageLogRepository,
settingService *SettingService,
) *GrokQuotaService {
return NewGrokQuotaService(accountRepo, proxyRepo, tokenProvider, httpUpstream, cfg, usageLogRepo)
service := NewGrokQuotaService(accountRepo, proxyRepo, tokenProvider, httpUpstream, cfg, usageLogRepo)
service.SetSettingService(settingService)
return service
}
// ProvideGeminiTokenProvider creates GeminiTokenProvider with OAuthRefreshAPI injection
+3 -3
View File
@@ -444,9 +444,9 @@ gateway:
# Enabled by default because free detection requires an explicit subscription_tier/plan_type of "free".
# Stats/query failures fail open so DB issues do not block all Grok traffic.
grok:
# Experimental admin-only email/password authorization. Disabled by default.
# Requires YESCAPTCHA_CLIENT_KEY (or legacy YESCAPTCHA_API_KEY) in the server environment.
# TurnstileTaskProxyless uses solver egress independent from the optional account proxy.
# Email/password authorization is hidden and hard-disabled. Use SSO cookie,
# browser OAuth, or refresh_token re-auth instead. The flag is retained for
# config compatibility only and is ignored by the server.
password_auth_enabled: false
free_quota_soft_gate_enabled: true
free_quota_token_limit: 1000000
+2
View File
@@ -590,6 +590,7 @@ export interface SystemSettings {
fallback_model_antigravity: string;
grok_default_text_model: string;
grok_cross_client_model_map_enabled: boolean;
grok_default_base_url_mode: string;
// Per-platform account auto-pause thresholds (100 = disabled)
account_scheduling_thresholds: AccountSchedulingThresholdsMap;
@@ -911,6 +912,7 @@ export interface UpdateSettingsRequest {
fallback_model_antigravity?: string;
grok_default_text_model?: string;
grok_cross_client_model_map_enabled?: boolean;
grok_default_base_url_mode?: string;
account_scheduling_thresholds?: AccountSchedulingThresholdsMap;
enable_identity_patch?: boolean;
identity_patch_prompt?: string;
@@ -445,6 +445,13 @@ export default {
grokDefaultTextModelHint: 'Used for empty model values and, only when the switch is enabled, requests from other client model namespaces. Custom Grok model IDs are accepted.',
grokCrossClientMap: 'Map other clients to Grok',
grokCrossClientMapHint: 'Disabled by default. When enabled, GPT, Codex, o-series, and Claude model IDs are routed to the default Grok text model above.',
grokDefaultBaseURLMode: 'Default Grok upstream',
grokDefaultBaseURLModeHint: 'Used only when a Grok account has no explicit base URL. Media and voice endpoints continue to use their official API hosts.',
grokBaseURLModeCLI: 'CLI chat proxy',
grokBaseURLModeAPI: 'Public API',
grokBaseURLModeUSEast1: 'Regional API (us-east-1)',
grokBaseURLModeUSWest2: 'Regional API (us-west-2)',
grokBaseURLModeEUWest1: 'Regional API (eu-west-1)',
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',
@@ -438,6 +438,13 @@ export default {
grokDefaultTextModelHint: '用于空模型值;仅在右侧开关开启时也用于其他客户端模型命名空间。允许填写自定义 Grok 模型 ID。',
grokCrossClientMap: '映射其他客户端模型到 Grok',
grokCrossClientMapHint: '默认关闭。开启后,GPT、Codex、o 系列和 Claude 模型 ID 会路由到左侧默认 Grok 文本模型。',
grokDefaultBaseURLMode: '默认 Grok 上游',
grokDefaultBaseURLModeHint: '仅用于 Grok 账号未配置显式 base URL 的文本请求;媒体和语音仍使用官方 API 主机。',
grokBaseURLModeCLI: 'CLI 聊天代理',
grokBaseURLModeAPI: '公共 API',
grokBaseURLModeUSEast1: '区域 API(us-east-1)',
grokBaseURLModeUSWest2: '区域 API(us-west-2)',
grokBaseURLModeEUWest1: '区域 API(eu-west-1)',
fingerprintUnification: '指纹统一化',
fingerprintUnificationHint: '统一共享同一 OAuth 账号的用户的 X-Stainless-* 请求头。关闭后透传客户端原始请求头。',
metadataPassthrough: 'Metadata 透传',
+3
View File
@@ -991,6 +991,9 @@ export interface TempUnschedulableState {
matched_keyword: string
rule_index: number
error_message: string
trigger_count?: number
trigger_threshold?: number
trigger_window_minutes?: number
}
export interface TempUnschedulableStatus {
+26 -1
View File
@@ -5178,7 +5178,30 @@
data-testid="grok-cross-client-model-map-toggle"
/>
</div>
</div>
</div>
<div class="md:col-span-2">
<label
for="grok-default-base-url-mode"
class="text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("admin.settings.gatewayForwarding.grokDefaultBaseURLMode") }}
</label>
<select
id="grok-default-base-url-mode"
v-model="form.grok_default_base_url_mode"
class="input mt-2 w-full"
data-testid="grok-default-base-url-mode"
>
<option value="cli">{{ t("admin.settings.gatewayForwarding.grokBaseURLModeCLI") }}</option>
<option value="api">{{ t("admin.settings.gatewayForwarding.grokBaseURLModeAPI") }}</option>
<option value="us-east-1">{{ t("admin.settings.gatewayForwarding.grokBaseURLModeUSEast1") }}</option>
<option value="us-west-2">{{ t("admin.settings.gatewayForwarding.grokBaseURLModeUSWest2") }}</option>
<option value="eu-west-1">{{ t("admin.settings.gatewayForwarding.grokBaseURLModeEUWest1") }}</option>
</select>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{ t("admin.settings.gatewayForwarding.grokDefaultBaseURLModeHint") }}
</p>
</div>
<!-- Fingerprint Unification -->
<div class="flex items-center justify-between">
@@ -9520,6 +9543,7 @@ const form = reactive<SettingsForm>({
fallback_model_antigravity: "gemini-2.5-pro",
grok_default_text_model: "grok-4.5",
grok_cross_client_model_map_enabled: false,
grok_default_base_url_mode: "cli",
// Identity patch (Claude -> Gemini)
enable_identity_patch: true,
identity_patch_prompt: "",
@@ -11102,6 +11126,7 @@ async function saveSettings() {
form.grok_default_text_model.trim() || "grok-4.5",
grok_cross_client_model_map_enabled:
form.grok_cross_client_model_map_enabled,
grok_default_base_url_mode: form.grok_default_base_url_mode,
enable_identity_patch: form.enable_identity_patch,
identity_patch_prompt: form.identity_patch_prompt,
min_claude_code_version: form.min_claude_code_version,