feat: add configurable Antigravity user agent version
This commit is contained in:
parent
9377c96746
commit
a07a0dac63
@ -226,6 +226,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
EnableCCHSigning: settings.EnableCCHSigning,
|
EnableCCHSigning: settings.EnableCCHSigning,
|
||||||
EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
|
EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
|
||||||
RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
|
RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
|
||||||
|
AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
|
||||||
WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
|
WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
|
||||||
PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
|
PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
|
||||||
PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
|
PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
|
||||||
@ -512,11 +513,12 @@ type UpdateSettingsRequest struct {
|
|||||||
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
||||||
|
|
||||||
// Gateway forwarding behavior
|
// Gateway forwarding behavior
|
||||||
EnableFingerprintUnification *bool `json:"enable_fingerprint_unification"`
|
EnableFingerprintUnification *bool `json:"enable_fingerprint_unification"`
|
||||||
EnableMetadataPassthrough *bool `json:"enable_metadata_passthrough"`
|
EnableMetadataPassthrough *bool `json:"enable_metadata_passthrough"`
|
||||||
EnableCCHSigning *bool `json:"enable_cch_signing"`
|
EnableCCHSigning *bool `json:"enable_cch_signing"`
|
||||||
EnableAnthropicCacheTTL1hInjection *bool `json:"enable_anthropic_cache_ttl_1h_injection"`
|
EnableAnthropicCacheTTL1hInjection *bool `json:"enable_anthropic_cache_ttl_1h_injection"`
|
||||||
RewriteMessageCacheControl *bool `json:"rewrite_message_cache_control"`
|
RewriteMessageCacheControl *bool `json:"rewrite_message_cache_control"`
|
||||||
|
AntigravityUserAgentVersion *string `json:"antigravity_user_agent_version"`
|
||||||
|
|
||||||
// Payment visible method routing
|
// Payment visible method routing
|
||||||
PaymentVisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
|
PaymentVisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
|
||||||
@ -1252,6 +1254,14 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if req.AntigravityUserAgentVersion != nil {
|
||||||
|
normalized := strings.TrimSpace(*req.AntigravityUserAgentVersion)
|
||||||
|
req.AntigravityUserAgentVersion = &normalized
|
||||||
|
if normalized != "" && !semverPattern.MatchString(normalized) {
|
||||||
|
response.Error(c, http.StatusBadRequest, "antigravity_user_agent_version must be empty or a valid semver (e.g. 1.23.2)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 交叉验证:如果同时设置了最低和最高版本号,最高版本号必须 >= 最低版本号
|
// 交叉验证:如果同时设置了最低和最高版本号,最高版本号必须 >= 最低版本号
|
||||||
if req.MinClaudeCodeVersion != "" && req.MaxClaudeCodeVersion != "" {
|
if req.MinClaudeCodeVersion != "" && req.MaxClaudeCodeVersion != "" {
|
||||||
@ -1423,6 +1433,12 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
return previousSettings.RewriteMessageCacheControl
|
return previousSettings.RewriteMessageCacheControl
|
||||||
}(),
|
}(),
|
||||||
|
AntigravityUserAgentVersion: func() string {
|
||||||
|
if req.AntigravityUserAgentVersion != nil {
|
||||||
|
return *req.AntigravityUserAgentVersion
|
||||||
|
}
|
||||||
|
return previousSettings.AntigravityUserAgentVersion
|
||||||
|
}(),
|
||||||
PaymentVisibleMethodAlipaySource: func() string {
|
PaymentVisibleMethodAlipaySource: func() string {
|
||||||
if req.PaymentVisibleMethodAlipaySource != nil {
|
if req.PaymentVisibleMethodAlipaySource != nil {
|
||||||
return strings.TrimSpace(*req.PaymentVisibleMethodAlipaySource)
|
return strings.TrimSpace(*req.PaymentVisibleMethodAlipaySource)
|
||||||
@ -1756,6 +1772,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
EnableCCHSigning: updatedSettings.EnableCCHSigning,
|
EnableCCHSigning: updatedSettings.EnableCCHSigning,
|
||||||
EnableAnthropicCacheTTL1hInjection: updatedSettings.EnableAnthropicCacheTTL1hInjection,
|
EnableAnthropicCacheTTL1hInjection: updatedSettings.EnableAnthropicCacheTTL1hInjection,
|
||||||
RewriteMessageCacheControl: updatedSettings.RewriteMessageCacheControl,
|
RewriteMessageCacheControl: updatedSettings.RewriteMessageCacheControl,
|
||||||
|
AntigravityUserAgentVersion: updatedSettings.AntigravityUserAgentVersion,
|
||||||
PaymentVisibleMethodAlipaySource: updatedSettings.PaymentVisibleMethodAlipaySource,
|
PaymentVisibleMethodAlipaySource: updatedSettings.PaymentVisibleMethodAlipaySource,
|
||||||
PaymentVisibleMethodWxpaySource: updatedSettings.PaymentVisibleMethodWxpaySource,
|
PaymentVisibleMethodWxpaySource: updatedSettings.PaymentVisibleMethodWxpaySource,
|
||||||
PaymentVisibleMethodAlipayEnabled: updatedSettings.PaymentVisibleMethodAlipayEnabled,
|
PaymentVisibleMethodAlipayEnabled: updatedSettings.PaymentVisibleMethodAlipayEnabled,
|
||||||
@ -2155,6 +2172,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
|||||||
if before.RewriteMessageCacheControl != after.RewriteMessageCacheControl {
|
if before.RewriteMessageCacheControl != after.RewriteMessageCacheControl {
|
||||||
changed = append(changed, "rewrite_message_cache_control")
|
changed = append(changed, "rewrite_message_cache_control")
|
||||||
}
|
}
|
||||||
|
if before.AntigravityUserAgentVersion != after.AntigravityUserAgentVersion {
|
||||||
|
changed = append(changed, "antigravity_user_agent_version")
|
||||||
|
}
|
||||||
if before.PaymentVisibleMethodAlipaySource != after.PaymentVisibleMethodAlipaySource {
|
if before.PaymentVisibleMethodAlipaySource != after.PaymentVisibleMethodAlipaySource {
|
||||||
changed = append(changed, "payment_visible_method_alipay_source")
|
changed = append(changed, "payment_visible_method_alipay_source")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,11 +158,12 @@ type SystemSettings struct {
|
|||||||
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
BackendModeEnabled bool `json:"backend_mode_enabled"`
|
||||||
|
|
||||||
// Gateway forwarding behavior
|
// Gateway forwarding behavior
|
||||||
EnableFingerprintUnification bool `json:"enable_fingerprint_unification"`
|
EnableFingerprintUnification bool `json:"enable_fingerprint_unification"`
|
||||||
EnableMetadataPassthrough bool `json:"enable_metadata_passthrough"`
|
EnableMetadataPassthrough bool `json:"enable_metadata_passthrough"`
|
||||||
EnableCCHSigning bool `json:"enable_cch_signing"`
|
EnableCCHSigning bool `json:"enable_cch_signing"`
|
||||||
EnableAnthropicCacheTTL1hInjection bool `json:"enable_anthropic_cache_ttl_1h_injection"`
|
EnableAnthropicCacheTTL1hInjection bool `json:"enable_anthropic_cache_ttl_1h_injection"`
|
||||||
RewriteMessageCacheControl bool `json:"rewrite_message_cache_control"`
|
RewriteMessageCacheControl bool `json:"rewrite_message_cache_control"`
|
||||||
|
AntigravityUserAgentVersion string `json:"antigravity_user_agent_version"`
|
||||||
|
|
||||||
// Web Search Emulation
|
// Web Search Emulation
|
||||||
WebSearchEmulationEnabled bool `json:"web_search_emulation_enabled"`
|
WebSearchEmulationEnabled bool `json:"web_search_emulation_enabled"`
|
||||||
|
|||||||
@ -46,7 +46,7 @@ func NewAPIRequestWithURL(ctx context.Context, baseURL, action, accessToken stri
|
|||||||
// 基础 Headers(与 Antigravity-Manager 保持一致,只设置这 3 个)
|
// 基础 Headers(与 Antigravity-Manager 保持一致,只设置这 3 个)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
req.Header.Set("User-Agent", GetUserAgent())
|
req.Header.Set("User-Agent", GetUserAgentForContext(ctx))
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
@ -440,7 +440,7 @@ func (c *Client) GetUserInfo(ctx context.Context, accessToken string) (*UserInfo
|
|||||||
func (c *Client) LoadCodeAssist(ctx context.Context, accessToken string) (*LoadCodeAssistResponse, map[string]any, error) {
|
func (c *Client) LoadCodeAssist(ctx context.Context, accessToken string) (*LoadCodeAssistResponse, map[string]any, error) {
|
||||||
reqBody := LoadCodeAssistRequest{}
|
reqBody := LoadCodeAssistRequest{}
|
||||||
reqBody.Metadata.IDEType = "ANTIGRAVITY"
|
reqBody.Metadata.IDEType = "ANTIGRAVITY"
|
||||||
reqBody.Metadata.IDEVersion = "1.20.6"
|
reqBody.Metadata.IDEVersion = GetUserAgentVersionForContext(ctx)
|
||||||
reqBody.Metadata.IDEName = "antigravity"
|
reqBody.Metadata.IDEName = "antigravity"
|
||||||
|
|
||||||
bodyBytes, err := json.Marshal(reqBody)
|
bodyBytes, err := json.Marshal(reqBody)
|
||||||
@ -461,7 +461,7 @@ func (c *Client) LoadCodeAssist(ctx context.Context, accessToken string) (*LoadC
|
|||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", GetUserAgent())
|
req.Header.Set("User-Agent", GetUserAgentForContext(ctx))
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -540,7 +540,7 @@ func (c *Client) OnboardUser(ctx context.Context, accessToken, tierID string) (s
|
|||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", GetUserAgent())
|
req.Header.Set("User-Agent", GetUserAgentForContext(ctx))
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -674,7 +674,7 @@ func (c *Client) FetchAvailableModels(ctx context.Context, accessToken, projectI
|
|||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", GetUserAgent())
|
req.Header.Set("User-Agent", GetUserAgentForContext(ctx))
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -792,7 +792,7 @@ func (c *Client) SetUserSettings(ctx context.Context, accessToken string) (*SetU
|
|||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
req.Header.Set("User-Agent", GetUserAgent())
|
req.Header.Set("User-Agent", GetUserAgentForContext(ctx))
|
||||||
req.Header.Set("X-Goog-Api-Client", "gl-node/22.21.1")
|
req.Header.Set("X-Goog-Api-Client", "gl-node/22.21.1")
|
||||||
req.Host = "daily-cloudcode-pa.googleapis.com"
|
req.Host = "daily-cloudcode-pa.googleapis.com"
|
||||||
|
|
||||||
@ -835,7 +835,7 @@ func (c *Client) FetchUserInfo(ctx context.Context, accessToken, projectID strin
|
|||||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
req.Header.Set("User-Agent", GetUserAgent())
|
req.Header.Set("User-Agent", GetUserAgentForContext(ctx))
|
||||||
req.Header.Set("X-Goog-Api-Client", "gl-node/22.21.1")
|
req.Header.Set("X-Goog-Api-Client", "gl-node/22.21.1")
|
||||||
req.Host = "daily-cloudcode-pa.googleapis.com"
|
req.Host = "daily-cloudcode-pa.googleapis.com"
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package antigravity
|
package antigravity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -28,6 +30,12 @@ const (
|
|||||||
// AntigravityOAuthClientSecretEnv 是 Antigravity OAuth client_secret 的环境变量名。
|
// AntigravityOAuthClientSecretEnv 是 Antigravity OAuth client_secret 的环境变量名。
|
||||||
AntigravityOAuthClientSecretEnv = "ANTIGRAVITY_OAUTH_CLIENT_SECRET"
|
AntigravityOAuthClientSecretEnv = "ANTIGRAVITY_OAUTH_CLIENT_SECRET"
|
||||||
|
|
||||||
|
// AntigravityUserAgentVersionEnv 是 Antigravity User-Agent 版本号的环境变量名。
|
||||||
|
AntigravityUserAgentVersionEnv = "ANTIGRAVITY_USER_AGENT_VERSION"
|
||||||
|
|
||||||
|
// DefaultUserAgentVersion 是未通过环境变量或后台设置覆盖时使用的默认版本号。
|
||||||
|
DefaultUserAgentVersion = "1.23.2"
|
||||||
|
|
||||||
// 固定的 redirect_uri(用户需手动复制 code)
|
// 固定的 redirect_uri(用户需手动复制 code)
|
||||||
RedirectURI = "http://localhost:8085/callback"
|
RedirectURI = "http://localhost:8085/callback"
|
||||||
|
|
||||||
@ -49,15 +57,24 @@ const (
|
|||||||
antigravityDailyBaseURL = "https://daily-cloudcode-pa.sandbox.googleapis.com"
|
antigravityDailyBaseURL = "https://daily-cloudcode-pa.sandbox.googleapis.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultUserAgentVersion 可通过环境变量 ANTIGRAVITY_USER_AGENT_VERSION 配置,默认 1.20.5
|
var userAgentVersionPattern = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
|
||||||
var defaultUserAgentVersion = "1.21.9"
|
|
||||||
|
// UserAgentVersionResolver 提供运行时 User-Agent 版本号覆盖能力。
|
||||||
|
type UserAgentVersionResolver func(ctx context.Context) string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// defaultUserAgentVersion 可通过环境变量 ANTIGRAVITY_USER_AGENT_VERSION 配置。
|
||||||
|
defaultUserAgentVersion = DefaultUserAgentVersion
|
||||||
|
userAgentVersionMu sync.RWMutex
|
||||||
|
userAgentVersionResolver UserAgentVersionResolver
|
||||||
|
)
|
||||||
|
|
||||||
// defaultClientSecret 可通过环境变量 ANTIGRAVITY_OAUTH_CLIENT_SECRET 配置
|
// defaultClientSecret 可通过环境变量 ANTIGRAVITY_OAUTH_CLIENT_SECRET 配置
|
||||||
var defaultClientSecret = "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"
|
var defaultClientSecret = "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"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// 从环境变量读取版本号,未设置则使用默认值
|
// 从环境变量读取版本号,未设置则使用默认值
|
||||||
if version := os.Getenv("ANTIGRAVITY_USER_AGENT_VERSION"); version != "" {
|
if version := NormalizeUserAgentVersion(os.Getenv(AntigravityUserAgentVersionEnv)); version != "" {
|
||||||
defaultUserAgentVersion = version
|
defaultUserAgentVersion = version
|
||||||
}
|
}
|
||||||
// 从环境变量读取 client_secret,未设置则使用默认值
|
// 从环境变量读取 client_secret,未设置则使用默认值
|
||||||
@ -66,11 +83,61 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserAgent 返回当前配置的 User-Agent
|
// NormalizeUserAgentVersion 校验并归一化 Antigravity User-Agent 版本号。
|
||||||
func GetUserAgent() string {
|
func NormalizeUserAgentVersion(version string) string {
|
||||||
|
version = strings.TrimSpace(version)
|
||||||
|
if version == "" || !userAgentVersionPattern.MatchString(version) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultUserAgentVersion 返回配置文件/环境变量层面的默认版本号。
|
||||||
|
func GetDefaultUserAgentVersion() string {
|
||||||
|
return defaultUserAgentVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAgentVersionResolver 设置运行时版本号解析器,通常由后台 settings 注入。
|
||||||
|
func SetUserAgentVersionResolver(resolver UserAgentVersionResolver) {
|
||||||
|
userAgentVersionMu.Lock()
|
||||||
|
defer userAgentVersionMu.Unlock()
|
||||||
|
userAgentVersionResolver = resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAgentVersionForContext 返回当前请求应使用的 Antigravity 版本号。
|
||||||
|
func GetUserAgentVersionForContext(ctx context.Context) string {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
userAgentVersionMu.RLock()
|
||||||
|
resolver := userAgentVersionResolver
|
||||||
|
userAgentVersionMu.RUnlock()
|
||||||
|
if resolver != nil {
|
||||||
|
if version := NormalizeUserAgentVersion(resolver(ctx)); version != "" {
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultUserAgentVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildUserAgent 使用指定版本号构造 User-Agent;版本为空或非法时回退默认值。
|
||||||
|
func BuildUserAgent(version string) string {
|
||||||
|
if normalized := NormalizeUserAgentVersion(version); normalized != "" {
|
||||||
|
return fmt.Sprintf("antigravity/%s windows/amd64", normalized)
|
||||||
|
}
|
||||||
return fmt.Sprintf("antigravity/%s windows/amd64", defaultUserAgentVersion)
|
return fmt.Sprintf("antigravity/%s windows/amd64", defaultUserAgentVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserAgentForContext 返回当前请求应使用的 User-Agent。
|
||||||
|
func GetUserAgentForContext(ctx context.Context) string {
|
||||||
|
return BuildUserAgent(GetUserAgentVersionForContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAgent 返回当前配置的 User-Agent。
|
||||||
|
func GetUserAgent() string {
|
||||||
|
return GetUserAgentForContext(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
func getClientSecret() (string, error) {
|
func getClientSecret() (string, error) {
|
||||||
if v := strings.TrimSpace(defaultClientSecret); v != "" {
|
if v := strings.TrimSpace(defaultClientSecret); v != "" {
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|||||||
@ -690,7 +690,7 @@ func TestConstants_值正确(t *testing.T) {
|
|||||||
if RedirectURI != "http://localhost:8085/callback" {
|
if RedirectURI != "http://localhost:8085/callback" {
|
||||||
t.Errorf("RedirectURI 不匹配: got %s", RedirectURI)
|
t.Errorf("RedirectURI 不匹配: got %s", RedirectURI)
|
||||||
}
|
}
|
||||||
if GetUserAgent() != "antigravity/1.21.9 windows/amd64" {
|
if GetUserAgent() != "antigravity/1.23.2 windows/amd64" {
|
||||||
t.Errorf("UserAgent 不匹配: got %s", GetUserAgent())
|
t.Errorf("UserAgent 不匹配: got %s", GetUserAgent())
|
||||||
}
|
}
|
||||||
if SessionTTL != 30*time.Minute {
|
if SessionTTL != 30*time.Minute {
|
||||||
|
|||||||
@ -372,6 +372,8 @@ const (
|
|||||||
SettingKeyEnableAnthropicCacheTTL1hInjection = "enable_anthropic_cache_ttl_1h_injection"
|
SettingKeyEnableAnthropicCacheTTL1hInjection = "enable_anthropic_cache_ttl_1h_injection"
|
||||||
// SettingKeyRewriteMessageCacheControl 是否改写 messages[*].content[*].cache_control(默认 false)
|
// SettingKeyRewriteMessageCacheControl 是否改写 messages[*].content[*].cache_control(默认 false)
|
||||||
SettingKeyRewriteMessageCacheControl = "rewrite_message_cache_control"
|
SettingKeyRewriteMessageCacheControl = "rewrite_message_cache_control"
|
||||||
|
// SettingKeyAntigravityUserAgentVersion Antigravity 上游 User-Agent 版本号(空值使用环境变量/默认值)
|
||||||
|
SettingKeyAntigravityUserAgentVersion = "antigravity_user_agent_version"
|
||||||
|
|
||||||
// Balance Low Notification
|
// Balance Low Notification
|
||||||
SettingKeyBalanceLowNotifyEnabled = "balance_low_notify_enabled" // 全局开关
|
SettingKeyBalanceLowNotifyEnabled = "balance_low_notify_enabled" // 全局开关
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
@ -98,6 +99,16 @@ const gatewayForwardingCacheTTL = 60 * time.Second
|
|||||||
const gatewayForwardingErrorTTL = 5 * time.Second
|
const gatewayForwardingErrorTTL = 5 * time.Second
|
||||||
const gatewayForwardingDBTimeout = 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.
|
// DefaultSubscriptionGroupReader validates group references used by default subscriptions.
|
||||||
type DefaultSubscriptionGroupReader interface {
|
type DefaultSubscriptionGroupReader interface {
|
||||||
GetByID(ctx context.Context, id int64) (*Group, error)
|
GetByID(ctx context.Context, id int64) (*Group, error)
|
||||||
@ -109,13 +120,15 @@ type WebSearchManagerBuilder func(cfg *WebSearchEmulationConfig, proxyURLs map[i
|
|||||||
|
|
||||||
// SettingService 系统设置服务
|
// SettingService 系统设置服务
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
settingRepo SettingRepository
|
settingRepo SettingRepository
|
||||||
defaultSubGroupReader DefaultSubscriptionGroupReader
|
defaultSubGroupReader DefaultSubscriptionGroupReader
|
||||||
proxyRepo ProxyRepository // for resolving websearch provider proxy URLs
|
proxyRepo ProxyRepository // for resolving websearch provider proxy URLs
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
onUpdate func() // Callback when settings are updated (for cache invalidation)
|
onUpdate func() // Callback when settings are updated (for cache invalidation)
|
||||||
version string // Application version
|
version string // Application version
|
||||||
webSearchManagerBuilder WebSearchManagerBuilder
|
webSearchManagerBuilder WebSearchManagerBuilder
|
||||||
|
antigravityUAVersionCache atomic.Value // *cachedAntigravityUserAgentVersion
|
||||||
|
antigravityUAVersionSF singleflight.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderDefaultGrantSettings struct {
|
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
|
// 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)
|
// This is used for cache invalidation (e.g., HTML cache in frontend server)
|
||||||
func (s *SettingService) SetOnUpdateCallback(callback func()) {
|
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[SettingKeyEnableCCHSigning] = strconv.FormatBool(settings.EnableCCHSigning)
|
||||||
updates[SettingKeyEnableAnthropicCacheTTL1hInjection] = strconv.FormatBool(settings.EnableAnthropicCacheTTL1hInjection)
|
updates[SettingKeyEnableAnthropicCacheTTL1hInjection] = strconv.FormatBool(settings.EnableAnthropicCacheTTL1hInjection)
|
||||||
updates[SettingKeyRewriteMessageCacheControl] = strconv.FormatBool(settings.RewriteMessageCacheControl)
|
updates[SettingKeyRewriteMessageCacheControl] = strconv.FormatBool(settings.RewriteMessageCacheControl)
|
||||||
|
updates[SettingKeyAntigravityUserAgentVersion] = antigravity.NormalizeUserAgentVersion(settings.AntigravityUserAgentVersion)
|
||||||
updates[SettingPaymentVisibleMethodAlipaySource] = settings.PaymentVisibleMethodAlipaySource
|
updates[SettingPaymentVisibleMethodAlipaySource] = settings.PaymentVisibleMethodAlipaySource
|
||||||
updates[SettingPaymentVisibleMethodWxpaySource] = settings.PaymentVisibleMethodWxpaySource
|
updates[SettingPaymentVisibleMethodWxpaySource] = settings.PaymentVisibleMethodWxpaySource
|
||||||
updates[SettingPaymentVisibleMethodAlipayEnabled] = strconv.FormatBool(settings.PaymentVisibleMethodAlipayEnabled)
|
updates[SettingPaymentVisibleMethodAlipayEnabled] = strconv.FormatBool(settings.PaymentVisibleMethodAlipayEnabled)
|
||||||
@ -1657,6 +1720,15 @@ func (s *SettingService) refreshCachedSettings(settings *SystemSettings) {
|
|||||||
rewriteMessageCacheControl: settings.RewriteMessageCacheControl,
|
rewriteMessageCacheControl: settings.RewriteMessageCacheControl,
|
||||||
expiresAt: time.Now().Add(gatewayForwardingCacheTTL).UnixNano(),
|
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)
|
openAIAdvancedSchedulerSettingSF.Forget(openAIAdvancedSchedulerSettingKey)
|
||||||
openAIAdvancedSchedulerSettingCache.Store(&cachedOpenAIAdvancedSchedulerSetting{
|
openAIAdvancedSchedulerSettingCache.Store(&cachedOpenAIAdvancedSchedulerSetting{
|
||||||
enabled: settings.OpenAIAdvancedSchedulerEnabled,
|
enabled: settings.OpenAIAdvancedSchedulerEnabled,
|
||||||
@ -2386,6 +2458,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
|
|||||||
SettingKeyAllowUngroupedKeyScheduling: "false",
|
SettingKeyAllowUngroupedKeyScheduling: "false",
|
||||||
SettingKeyEnableAnthropicCacheTTL1hInjection: "false",
|
SettingKeyEnableAnthropicCacheTTL1hInjection: "false",
|
||||||
SettingKeyRewriteMessageCacheControl: strconv.FormatBool(s.defaultRewriteMessageCacheControl()),
|
SettingKeyRewriteMessageCacheControl: strconv.FormatBool(s.defaultRewriteMessageCacheControl()),
|
||||||
|
SettingKeyAntigravityUserAgentVersion: "",
|
||||||
SettingPaymentVisibleMethodAlipaySource: "",
|
SettingPaymentVisibleMethodAlipaySource: "",
|
||||||
SettingPaymentVisibleMethodWxpaySource: "",
|
SettingPaymentVisibleMethodWxpaySource: "",
|
||||||
SettingPaymentVisibleMethodAlipayEnabled: "false",
|
SettingPaymentVisibleMethodAlipayEnabled: "false",
|
||||||
@ -2767,6 +2840,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
|||||||
} else {
|
} else {
|
||||||
result.RewriteMessageCacheControl = s.defaultRewriteMessageCacheControl()
|
result.RewriteMessageCacheControl = s.defaultRewriteMessageCacheControl()
|
||||||
}
|
}
|
||||||
|
result.AntigravityUserAgentVersion = antigravity.NormalizeUserAgentVersion(settings[SettingKeyAntigravityUserAgentVersion])
|
||||||
|
|
||||||
// Web search emulation: quick enabled check from the JSON config
|
// Web search emulation: quick enabled check from the JSON config
|
||||||
if raw := settings[SettingKeyWebSearchEmulationConfig]; raw != "" {
|
if raw := settings[SettingKeyWebSearchEmulationConfig]; raw != "" {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -48,6 +49,41 @@ func (s *settingUpdateRepoStub) Delete(ctx context.Context, key string) error {
|
|||||||
panic("unexpected Delete call")
|
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 {
|
type defaultSubGroupReaderStub struct {
|
||||||
byID map[int64]*Group
|
byID map[int64]*Group
|
||||||
errBy map[int64]error
|
errBy map[int64]error
|
||||||
@ -243,6 +279,41 @@ func TestSettingService_UpdateSettings_PaymentVisibleMethodsAndAdvancedScheduler
|
|||||||
require.Equal(t, "true", repo.updates[openAIAdvancedSchedulerSettingKey])
|
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) {
|
func TestSettingService_UpdateSettings_RejectsInvalidPaymentVisibleMethodSource(t *testing.T) {
|
||||||
repo := &settingUpdateRepoStub{}
|
repo := &settingUpdateRepoStub{}
|
||||||
svc := NewSettingService(repo, &config.Config{})
|
svc := NewSettingService(repo, &config.Config{})
|
||||||
|
|||||||
@ -168,11 +168,12 @@ type SystemSettings struct {
|
|||||||
BackendModeEnabled bool
|
BackendModeEnabled bool
|
||||||
|
|
||||||
// Gateway forwarding behavior
|
// Gateway forwarding behavior
|
||||||
EnableFingerprintUnification bool // 是否统一 OAuth 账号的指纹头(默认 true)
|
EnableFingerprintUnification bool // 是否统一 OAuth 账号的指纹头(默认 true)
|
||||||
EnableMetadataPassthrough bool // 是否透传客户端原始 metadata(默认 false)
|
EnableMetadataPassthrough bool // 是否透传客户端原始 metadata(默认 false)
|
||||||
EnableCCHSigning bool // 是否对 billing header cch 进行签名(默认 false)
|
EnableCCHSigning bool // 是否对 billing header cch 进行签名(默认 false)
|
||||||
EnableAnthropicCacheTTL1hInjection bool // 是否对 Anthropic OAuth/SetupToken 请求体注入 1h cache_control ttl(默认 false)
|
EnableAnthropicCacheTTL1hInjection bool // 是否对 Anthropic OAuth/SetupToken 请求体注入 1h cache_control ttl(默认 false)
|
||||||
RewriteMessageCacheControl bool // 是否改写 messages[*].content[*].cache_control(默认 false)
|
RewriteMessageCacheControl bool // 是否改写 messages[*].content[*].cache_control(默认 false)
|
||||||
|
AntigravityUserAgentVersion string // Antigravity 上游 User-Agent 版本号;空值使用配置/默认值
|
||||||
|
|
||||||
// Web Search Emulation
|
// Web Search Emulation
|
||||||
WebSearchEmulationEnabled bool // 是否启用 web search 模拟
|
WebSearchEmulationEnabled bool // 是否启用 web search 模拟
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
"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/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||||
"github.com/google/wire"
|
"github.com/google/wire"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@ -395,6 +396,7 @@ func ProvideSettingService(settingRepo SettingRepository, groupRepo GroupReposit
|
|||||||
svc := NewSettingService(settingRepo, cfg)
|
svc := NewSettingService(settingRepo, cfg)
|
||||||
svc.SetDefaultSubscriptionGroupReader(groupRepo)
|
svc.SetDefaultSubscriptionGroupReader(groupRepo)
|
||||||
svc.SetProxyRepository(proxyRepo)
|
svc.SetProxyRepository(proxyRepo)
|
||||||
|
antigravity.SetUserAgentVersionResolver(svc.GetAntigravityUserAgentVersion)
|
||||||
return svc
|
return svc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -228,6 +228,9 @@ TOTP_ENCRYPTION_KEY=
|
|||||||
#
|
#
|
||||||
# Antigravity OAuth client_secret(用于 Antigravity OAuth 登录流)
|
# Antigravity OAuth client_secret(用于 Antigravity OAuth 登录流)
|
||||||
# ANTIGRAVITY_OAUTH_CLIENT_SECRET=
|
# ANTIGRAVITY_OAUTH_CLIENT_SECRET=
|
||||||
|
#
|
||||||
|
# Antigravity User-Agent 版本号(后台设置 antigravity_user_agent_version 优先;留空使用内置默认 1.23.2)
|
||||||
|
# ANTIGRAVITY_USER_AGENT_VERSION=
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Rate Limiting (Optional)
|
# Rate Limiting (Optional)
|
||||||
|
|||||||
@ -127,6 +127,7 @@ services:
|
|||||||
# SECURITY: This repo does not embed third-party client_secret.
|
# SECURITY: This repo does not embed third-party client_secret.
|
||||||
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
||||||
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
||||||
|
- ANTIGRAVITY_USER_AGENT_VERSION=${ANTIGRAVITY_USER_AGENT_VERSION:-}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Security Configuration (URL Allowlist)
|
# Security Configuration (URL Allowlist)
|
||||||
|
|||||||
@ -93,6 +93,7 @@ services:
|
|||||||
# SECURITY: This repo does not embed third-party client_secret.
|
# SECURITY: This repo does not embed third-party client_secret.
|
||||||
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
||||||
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
||||||
|
- ANTIGRAVITY_USER_AGENT_VERSION=${ANTIGRAVITY_USER_AGENT_VERSION:-}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Image Generation Stream & Concurrency
|
# Image Generation Stream & Concurrency
|
||||||
|
|||||||
@ -123,6 +123,7 @@ services:
|
|||||||
# SECURITY: This repo does not embed third-party client_secret.
|
# SECURITY: This repo does not embed third-party client_secret.
|
||||||
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
||||||
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
||||||
|
- ANTIGRAVITY_USER_AGENT_VERSION=${ANTIGRAVITY_USER_AGENT_VERSION:-}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Security Configuration (URL Allowlist)
|
# Security Configuration (URL Allowlist)
|
||||||
|
|||||||
@ -478,6 +478,7 @@ export interface SystemSettings {
|
|||||||
enable_cch_signing: boolean;
|
enable_cch_signing: boolean;
|
||||||
enable_anthropic_cache_ttl_1h_injection: boolean;
|
enable_anthropic_cache_ttl_1h_injection: boolean;
|
||||||
rewrite_message_cache_control: boolean;
|
rewrite_message_cache_control: boolean;
|
||||||
|
antigravity_user_agent_version: string;
|
||||||
web_search_emulation_enabled?: boolean;
|
web_search_emulation_enabled?: boolean;
|
||||||
|
|
||||||
// Payment configuration
|
// Payment configuration
|
||||||
@ -675,6 +676,7 @@ export interface UpdateSettingsRequest {
|
|||||||
enable_cch_signing?: boolean;
|
enable_cch_signing?: boolean;
|
||||||
enable_anthropic_cache_ttl_1h_injection?: boolean;
|
enable_anthropic_cache_ttl_1h_injection?: boolean;
|
||||||
rewrite_message_cache_control?: boolean;
|
rewrite_message_cache_control?: boolean;
|
||||||
|
antigravity_user_agent_version?: string;
|
||||||
// Payment configuration
|
// Payment configuration
|
||||||
payment_enabled?: boolean;
|
payment_enabled?: boolean;
|
||||||
risk_control_enabled?: boolean;
|
risk_control_enabled?: boolean;
|
||||||
|
|||||||
@ -5337,6 +5337,9 @@ export default {
|
|||||||
anthropicCacheTTL1hInjectionHint: 'When enabled, existing ephemeral cache_control blocks in Anthropic OAuth/Setup Token request bodies are forced to 1h; response usage is billed back as 5m by default, with account-level TTL billing override taking priority.',
|
anthropicCacheTTL1hInjectionHint: 'When enabled, existing ephemeral cache_control blocks in Anthropic OAuth/Setup Token request bodies are forced to 1h; response usage is billed back as 5m by default, with account-level TTL billing override taking priority.',
|
||||||
rewriteMessageCacheControl: 'Rewrite Message Cache Breakpoints',
|
rewriteMessageCacheControl: 'Rewrite Message Cache Breakpoints',
|
||||||
rewriteMessageCacheControlHint: 'Default off: preserve client cache_control on message content blocks. When enabled, client breakpoints are stripped and proxy breakpoints are injected for clients that do not manage caching themselves.',
|
rewriteMessageCacheControlHint: 'Default off: preserve client cache_control on message content blocks. When enabled, client breakpoints are stripped and proxy breakpoints are injected for clients that do not manage caching themselves.',
|
||||||
|
antigravityUserAgentVersion: 'Antigravity UA Version',
|
||||||
|
antigravityUserAgentVersionPlaceholder: '1.23.2',
|
||||||
|
antigravityUserAgentVersionHint: 'Leave empty to use ANTIGRAVITY_USER_AGENT_VERSION or the built-in default 1.23.2; when set, the admin setting takes precedence.',
|
||||||
},
|
},
|
||||||
webSearchEmulation: {
|
webSearchEmulation: {
|
||||||
title: 'Web Search Emulation',
|
title: 'Web Search Emulation',
|
||||||
|
|||||||
@ -5496,6 +5496,9 @@ export default {
|
|||||||
anthropicCacheTTL1hInjectionHint: '开启后,对 Anthropic OAuth/Setup Token 请求体中已有的 ephemeral 缓存块强制写入 1h;响应 usage 默认按 5m 回写计费,账号级 TTL 计费设置优先。',
|
anthropicCacheTTL1hInjectionHint: '开启后,对 Anthropic OAuth/Setup Token 请求体中已有的 ephemeral 缓存块强制写入 1h;响应 usage 默认按 5m 回写计费,账号级 TTL 计费设置优先。',
|
||||||
rewriteMessageCacheControl: '改写消息缓存断点',
|
rewriteMessageCacheControl: '改写消息缓存断点',
|
||||||
rewriteMessageCacheControlHint: '默认关闭,保留客户端在 messages 内容块中的 cache_control。开启后会清除客户端断点并注入代理断点,适合不自行管理缓存策略的客户端。',
|
rewriteMessageCacheControlHint: '默认关闭,保留客户端在 messages 内容块中的 cache_control。开启后会清除客户端断点并注入代理断点,适合不自行管理缓存策略的客户端。',
|
||||||
|
antigravityUserAgentVersion: 'Antigravity UA 版本',
|
||||||
|
antigravityUserAgentVersionPlaceholder: '1.23.2',
|
||||||
|
antigravityUserAgentVersionHint: '留空时使用 ANTIGRAVITY_USER_AGENT_VERSION 或内置默认值 1.23.2;填写后后台设置优先。',
|
||||||
},
|
},
|
||||||
webSearchEmulation: {
|
webSearchEmulation: {
|
||||||
title: 'Web Search 模拟',
|
title: 'Web Search 模拟',
|
||||||
|
|||||||
@ -3451,6 +3451,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<Toggle v-model="form.rewrite_message_cache_control" />
|
<Toggle v-model="form.rewrite_message_cache_control" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Antigravity UA 版本 -->
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"admin.settings.gatewayForwarding.antigravityUserAgentVersion",
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="form.antigravity_user_agent_version"
|
||||||
|
type="text"
|
||||||
|
class="input max-w-xs font-mono text-sm"
|
||||||
|
:placeholder="
|
||||||
|
t(
|
||||||
|
'admin.settings.gatewayForwarding.antigravityUserAgentVersionPlaceholder',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{
|
||||||
|
t(
|
||||||
|
"admin.settings.gatewayForwarding.antigravityUserAgentVersionHint",
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Web Search Emulation -->
|
<!-- Web Search Emulation -->
|
||||||
@ -6571,6 +6601,7 @@ const form = reactive<SettingsForm>({
|
|||||||
enable_cch_signing: false,
|
enable_cch_signing: false,
|
||||||
enable_anthropic_cache_ttl_1h_injection: false,
|
enable_anthropic_cache_ttl_1h_injection: false,
|
||||||
rewrite_message_cache_control: false,
|
rewrite_message_cache_control: false,
|
||||||
|
antigravity_user_agent_version: "",
|
||||||
// Balance & quota notification
|
// Balance & quota notification
|
||||||
balance_low_notify_enabled: false,
|
balance_low_notify_enabled: false,
|
||||||
balance_low_notify_threshold: 0,
|
balance_low_notify_threshold: 0,
|
||||||
@ -7642,6 +7673,8 @@ async function saveSettings() {
|
|||||||
enable_anthropic_cache_ttl_1h_injection:
|
enable_anthropic_cache_ttl_1h_injection:
|
||||||
form.enable_anthropic_cache_ttl_1h_injection,
|
form.enable_anthropic_cache_ttl_1h_injection,
|
||||||
rewrite_message_cache_control: form.rewrite_message_cache_control,
|
rewrite_message_cache_control: form.rewrite_message_cache_control,
|
||||||
|
antigravity_user_agent_version:
|
||||||
|
form.antigravity_user_agent_version?.trim() || "",
|
||||||
// Payment configuration
|
// Payment configuration
|
||||||
payment_enabled: form.payment_enabled,
|
payment_enabled: form.payment_enabled,
|
||||||
risk_control_enabled: form.risk_control_enabled,
|
risk_control_enabled: form.risk_control_enabled,
|
||||||
|
|||||||
@ -370,6 +370,7 @@ const baseSettingsResponse = {
|
|||||||
enable_cch_signing: false,
|
enable_cch_signing: false,
|
||||||
enable_anthropic_cache_ttl_1h_injection: false,
|
enable_anthropic_cache_ttl_1h_injection: false,
|
||||||
rewrite_message_cache_control: false,
|
rewrite_message_cache_control: false,
|
||||||
|
antigravity_user_agent_version: "",
|
||||||
payment_enabled: true,
|
payment_enabled: true,
|
||||||
payment_min_amount: 1,
|
payment_min_amount: 1,
|
||||||
payment_max_amount: 10000,
|
payment_max_amount: 10000,
|
||||||
@ -622,6 +623,26 @@ describe("admin SettingsView payment visible method controls", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("submits Antigravity user agent version gateway setting", async () => {
|
||||||
|
getSettings.mockResolvedValueOnce({
|
||||||
|
...baseSettingsResponse,
|
||||||
|
antigravity_user_agent_version: "1.23.2",
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mountView();
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
await wrapper.find("form").trigger("submit.prevent");
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(updateSettings).toHaveBeenCalledTimes(1);
|
||||||
|
expect(updateSettings).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
antigravity_user_agent_version: "1.23.2",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("updates provider enablement immediately and reloads providers", async () => {
|
it("updates provider enablement immediately and reloads providers", async () => {
|
||||||
const provider = {
|
const provider = {
|
||||||
id: 7,
|
id: 7,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user