feat: add configurable Antigravity user agent version
This commit is contained in:
@@ -372,6 +372,8 @@ const (
|
||||
SettingKeyEnableAnthropicCacheTTL1hInjection = "enable_anthropic_cache_ttl_1h_injection"
|
||||
// SettingKeyRewriteMessageCacheControl 是否改写 messages[*].content[*].cache_control(默认 false)
|
||||
SettingKeyRewriteMessageCacheControl = "rewrite_message_cache_control"
|
||||
// SettingKeyAntigravityUserAgentVersion Antigravity 上游 User-Agent 版本号(空值使用环境变量/默认值)
|
||||
SettingKeyAntigravityUserAgentVersion = "antigravity_user_agent_version"
|
||||
|
||||
// Balance Low Notification
|
||||
SettingKeyBalanceLowNotifyEnabled = "balance_low_notify_enabled" // 全局开关
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/imroc/req/v3"
|
||||
"golang.org/x/sync/singleflight"
|
||||
@@ -98,6 +99,16 @@ const gatewayForwardingCacheTTL = 60 * time.Second
|
||||
const gatewayForwardingErrorTTL = 5 * time.Second
|
||||
const gatewayForwardingDBTimeout = 5 * time.Second
|
||||
|
||||
// cachedAntigravityUserAgentVersion 缓存 Antigravity UA 版本号(进程内缓存,60s TTL)
|
||||
type cachedAntigravityUserAgentVersion struct {
|
||||
version string
|
||||
expiresAt int64 // unix nano
|
||||
}
|
||||
|
||||
const antigravityUserAgentVersionCacheTTL = 60 * time.Second
|
||||
const antigravityUserAgentVersionErrorTTL = 5 * time.Second
|
||||
const antigravityUserAgentVersionDBTimeout = 5 * time.Second
|
||||
|
||||
// DefaultSubscriptionGroupReader validates group references used by default subscriptions.
|
||||
type DefaultSubscriptionGroupReader interface {
|
||||
GetByID(ctx context.Context, id int64) (*Group, error)
|
||||
@@ -109,13 +120,15 @@ type WebSearchManagerBuilder func(cfg *WebSearchEmulationConfig, proxyURLs map[i
|
||||
|
||||
// SettingService 系统设置服务
|
||||
type SettingService struct {
|
||||
settingRepo SettingRepository
|
||||
defaultSubGroupReader DefaultSubscriptionGroupReader
|
||||
proxyRepo ProxyRepository // for resolving websearch provider proxy URLs
|
||||
cfg *config.Config
|
||||
onUpdate func() // Callback when settings are updated (for cache invalidation)
|
||||
version string // Application version
|
||||
webSearchManagerBuilder WebSearchManagerBuilder
|
||||
settingRepo SettingRepository
|
||||
defaultSubGroupReader DefaultSubscriptionGroupReader
|
||||
proxyRepo ProxyRepository // for resolving websearch provider proxy URLs
|
||||
cfg *config.Config
|
||||
onUpdate func() // Callback when settings are updated (for cache invalidation)
|
||||
version string // Application version
|
||||
webSearchManagerBuilder WebSearchManagerBuilder
|
||||
antigravityUAVersionCache atomic.Value // *cachedAntigravityUserAgentVersion
|
||||
antigravityUAVersionSF singleflight.Group
|
||||
}
|
||||
|
||||
type ProviderDefaultGrantSettings struct {
|
||||
@@ -810,6 +823,55 @@ func (s *SettingService) GetAvailableChannelsRuntime(ctx context.Context) Availa
|
||||
}
|
||||
}
|
||||
|
||||
// GetAntigravityUserAgentVersion 返回 Antigravity 上游请求使用的版本号。
|
||||
// 后台设置优先;为空、缺失或非法时回退到 ANTIGRAVITY_USER_AGENT_VERSION / 内置默认值。
|
||||
func (s *SettingService) GetAntigravityUserAgentVersion(ctx context.Context) string {
|
||||
fallback := antigravity.GetDefaultUserAgentVersion()
|
||||
if s == nil || s.settingRepo == nil {
|
||||
return fallback
|
||||
}
|
||||
if cached, ok := s.antigravityUAVersionCache.Load().(*cachedAntigravityUserAgentVersion); ok && cached != nil {
|
||||
if time.Now().UnixNano() < cached.expiresAt {
|
||||
return cached.version
|
||||
}
|
||||
}
|
||||
|
||||
result, _, _ := s.antigravityUAVersionSF.Do("antigravity_user_agent_version", func() (any, error) {
|
||||
if cached, ok := s.antigravityUAVersionCache.Load().(*cachedAntigravityUserAgentVersion); ok && cached != nil {
|
||||
if time.Now().UnixNano() < cached.expiresAt {
|
||||
return cached.version, nil
|
||||
}
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
dbCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), antigravityUserAgentVersionDBTimeout)
|
||||
defer cancel()
|
||||
value, err := s.settingRepo.GetValue(dbCtx, SettingKeyAntigravityUserAgentVersion)
|
||||
if err != nil && !errors.Is(err, ErrSettingNotFound) {
|
||||
slog.Warn("failed to get antigravity user agent version setting", "error", err)
|
||||
s.antigravityUAVersionCache.Store(&cachedAntigravityUserAgentVersion{
|
||||
version: fallback,
|
||||
expiresAt: time.Now().Add(antigravityUserAgentVersionErrorTTL).UnixNano(),
|
||||
})
|
||||
return fallback, nil
|
||||
}
|
||||
version := antigravity.NormalizeUserAgentVersion(value)
|
||||
if version == "" {
|
||||
version = fallback
|
||||
}
|
||||
s.antigravityUAVersionCache.Store(&cachedAntigravityUserAgentVersion{
|
||||
version: version,
|
||||
expiresAt: time.Now().Add(antigravityUserAgentVersionCacheTTL).UnixNano(),
|
||||
})
|
||||
return version, nil
|
||||
})
|
||||
if version, ok := result.(string); ok && version != "" {
|
||||
return version
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// SetOnUpdateCallback sets a callback function to be called when settings are updated
|
||||
// This is used for cache invalidation (e.g., HTML cache in frontend server)
|
||||
func (s *SettingService) SetOnUpdateCallback(callback func()) {
|
||||
@@ -1586,6 +1648,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
updates[SettingKeyEnableCCHSigning] = strconv.FormatBool(settings.EnableCCHSigning)
|
||||
updates[SettingKeyEnableAnthropicCacheTTL1hInjection] = strconv.FormatBool(settings.EnableAnthropicCacheTTL1hInjection)
|
||||
updates[SettingKeyRewriteMessageCacheControl] = strconv.FormatBool(settings.RewriteMessageCacheControl)
|
||||
updates[SettingKeyAntigravityUserAgentVersion] = antigravity.NormalizeUserAgentVersion(settings.AntigravityUserAgentVersion)
|
||||
updates[SettingPaymentVisibleMethodAlipaySource] = settings.PaymentVisibleMethodAlipaySource
|
||||
updates[SettingPaymentVisibleMethodWxpaySource] = settings.PaymentVisibleMethodWxpaySource
|
||||
updates[SettingPaymentVisibleMethodAlipayEnabled] = strconv.FormatBool(settings.PaymentVisibleMethodAlipayEnabled)
|
||||
@@ -1657,6 +1720,15 @@ func (s *SettingService) refreshCachedSettings(settings *SystemSettings) {
|
||||
rewriteMessageCacheControl: settings.RewriteMessageCacheControl,
|
||||
expiresAt: time.Now().Add(gatewayForwardingCacheTTL).UnixNano(),
|
||||
})
|
||||
s.antigravityUAVersionSF.Forget("antigravity_user_agent_version")
|
||||
antigravityUserAgentVersion := antigravity.NormalizeUserAgentVersion(settings.AntigravityUserAgentVersion)
|
||||
if antigravityUserAgentVersion == "" {
|
||||
antigravityUserAgentVersion = antigravity.GetDefaultUserAgentVersion()
|
||||
}
|
||||
s.antigravityUAVersionCache.Store(&cachedAntigravityUserAgentVersion{
|
||||
version: antigravityUserAgentVersion,
|
||||
expiresAt: time.Now().Add(antigravityUserAgentVersionCacheTTL).UnixNano(),
|
||||
})
|
||||
openAIAdvancedSchedulerSettingSF.Forget(openAIAdvancedSchedulerSettingKey)
|
||||
openAIAdvancedSchedulerSettingCache.Store(&cachedOpenAIAdvancedSchedulerSetting{
|
||||
enabled: settings.OpenAIAdvancedSchedulerEnabled,
|
||||
@@ -2386,6 +2458,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
|
||||
SettingKeyAllowUngroupedKeyScheduling: "false",
|
||||
SettingKeyEnableAnthropicCacheTTL1hInjection: "false",
|
||||
SettingKeyRewriteMessageCacheControl: strconv.FormatBool(s.defaultRewriteMessageCacheControl()),
|
||||
SettingKeyAntigravityUserAgentVersion: "",
|
||||
SettingPaymentVisibleMethodAlipaySource: "",
|
||||
SettingPaymentVisibleMethodWxpaySource: "",
|
||||
SettingPaymentVisibleMethodAlipayEnabled: "false",
|
||||
@@ -2767,6 +2840,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
} else {
|
||||
result.RewriteMessageCacheControl = s.defaultRewriteMessageCacheControl()
|
||||
}
|
||||
result.AntigravityUserAgentVersion = antigravity.NormalizeUserAgentVersion(settings[SettingKeyAntigravityUserAgentVersion])
|
||||
|
||||
// Web search emulation: quick enabled check from the JSON config
|
||||
if raw := settings[SettingKeyWebSearchEmulationConfig]; raw != "" {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -48,6 +49,41 @@ func (s *settingUpdateRepoStub) Delete(ctx context.Context, key string) error {
|
||||
panic("unexpected Delete call")
|
||||
}
|
||||
|
||||
type settingAntigravityUARepoStub struct {
|
||||
values map[string]string
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) Get(ctx context.Context, key string) (*Setting, error) {
|
||||
panic("unexpected Get call")
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) GetValue(ctx context.Context, key string) (string, error) {
|
||||
if value, ok := s.values[key]; ok {
|
||||
return value, nil
|
||||
}
|
||||
return "", ErrSettingNotFound
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) Set(ctx context.Context, key, value string) error {
|
||||
panic("unexpected Set call")
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) {
|
||||
panic("unexpected GetMultiple call")
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) SetMultiple(ctx context.Context, settings map[string]string) error {
|
||||
panic("unexpected SetMultiple call")
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) GetAll(ctx context.Context) (map[string]string, error) {
|
||||
panic("unexpected GetAll call")
|
||||
}
|
||||
|
||||
func (s *settingAntigravityUARepoStub) Delete(ctx context.Context, key string) error {
|
||||
panic("unexpected Delete call")
|
||||
}
|
||||
|
||||
type defaultSubGroupReaderStub struct {
|
||||
byID map[int64]*Group
|
||||
errBy map[int64]error
|
||||
@@ -243,6 +279,41 @@ func TestSettingService_UpdateSettings_PaymentVisibleMethodsAndAdvancedScheduler
|
||||
require.Equal(t, "true", repo.updates[openAIAdvancedSchedulerSettingKey])
|
||||
}
|
||||
|
||||
func TestSettingService_UpdateSettings_AntigravityUserAgentVersion(t *testing.T) {
|
||||
repo := &settingUpdateRepoStub{}
|
||||
svc := NewSettingService(repo, &config.Config{})
|
||||
|
||||
err := svc.UpdateSettings(context.Background(), &SystemSettings{
|
||||
AntigravityUserAgentVersion: "1.23.2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1.23.2", repo.updates[SettingKeyAntigravityUserAgentVersion])
|
||||
}
|
||||
|
||||
func TestSettingService_GetAntigravityUserAgentVersion_Precedence(t *testing.T) {
|
||||
t.Run("后台设置优先", func(t *testing.T) {
|
||||
svc := NewSettingService(&settingAntigravityUARepoStub{values: map[string]string{
|
||||
SettingKeyAntigravityUserAgentVersion: "1.24.0",
|
||||
}}, &config.Config{})
|
||||
|
||||
require.Equal(t, "1.24.0", svc.GetAntigravityUserAgentVersion(context.Background()))
|
||||
})
|
||||
|
||||
t.Run("空值回退配置默认值", func(t *testing.T) {
|
||||
svc := NewSettingService(&settingAntigravityUARepoStub{values: map[string]string{
|
||||
SettingKeyAntigravityUserAgentVersion: "",
|
||||
}}, &config.Config{})
|
||||
|
||||
require.Equal(t, antigravity.GetDefaultUserAgentVersion(), svc.GetAntigravityUserAgentVersion(context.Background()))
|
||||
})
|
||||
|
||||
t.Run("缺失回退配置默认值", func(t *testing.T) {
|
||||
svc := NewSettingService(&settingAntigravityUARepoStub{values: map[string]string{}}, &config.Config{})
|
||||
|
||||
require.Equal(t, antigravity.GetDefaultUserAgentVersion(), svc.GetAntigravityUserAgentVersion(context.Background()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSettingService_UpdateSettings_RejectsInvalidPaymentVisibleMethodSource(t *testing.T) {
|
||||
repo := &settingUpdateRepoStub{}
|
||||
svc := NewSettingService(repo, &config.Config{})
|
||||
|
||||
@@ -168,11 +168,12 @@ type SystemSettings struct {
|
||||
BackendModeEnabled bool
|
||||
|
||||
// Gateway forwarding behavior
|
||||
EnableFingerprintUnification bool // 是否统一 OAuth 账号的指纹头(默认 true)
|
||||
EnableMetadataPassthrough bool // 是否透传客户端原始 metadata(默认 false)
|
||||
EnableCCHSigning bool // 是否对 billing header cch 进行签名(默认 false)
|
||||
EnableAnthropicCacheTTL1hInjection bool // 是否对 Anthropic OAuth/SetupToken 请求体注入 1h cache_control ttl(默认 false)
|
||||
RewriteMessageCacheControl bool // 是否改写 messages[*].content[*].cache_control(默认 false)
|
||||
EnableFingerprintUnification bool // 是否统一 OAuth 账号的指纹头(默认 true)
|
||||
EnableMetadataPassthrough bool // 是否透传客户端原始 metadata(默认 false)
|
||||
EnableCCHSigning bool // 是否对 billing header cch 进行签名(默认 false)
|
||||
EnableAnthropicCacheTTL1hInjection bool // 是否对 Anthropic OAuth/SetupToken 请求体注入 1h cache_control ttl(默认 false)
|
||||
RewriteMessageCacheControl bool // 是否改写 messages[*].content[*].cache_control(默认 false)
|
||||
AntigravityUserAgentVersion string // Antigravity 上游 User-Agent 版本号;空值使用配置/默认值
|
||||
|
||||
// Web Search Emulation
|
||||
WebSearchEmulationEnabled bool // 是否启用 web search 模拟
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||
"github.com/google/wire"
|
||||
"github.com/redis/go-redis/v9"
|
||||
@@ -395,6 +396,7 @@ func ProvideSettingService(settingRepo SettingRepository, groupRepo GroupReposit
|
||||
svc := NewSettingService(settingRepo, cfg)
|
||||
svc.SetDefaultSubscriptionGroupReader(groupRepo)
|
||||
svc.SetProxyRepository(proxyRepo)
|
||||
antigravity.SetUserAgentVersionResolver(svc.GetAntigravityUserAgentVersion)
|
||||
return svc
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user