fix: Gemini CLI 指纹全面修复
- User-Agent: GeminiCLI/0.1.5 → GeminiCLI/0.33.1/{model} ({platform}; {arch})
格式、版本、大小写全部对齐真实 Gemini CLI 0.33.1
- 新增 x-goog-api-client: gl-node/24.13.1 (匹配 google-auth-library DefaultTransporter)
- ideType: ANTIGRAVITY → IDE_UNSPECIFIED (修复身份泄露,真实 Gemini CLI 用 IDE_UNSPECIFIED)
- Token 交换/刷新: 添加 google-api-nodejs-client UA + x-goog-api-client
- 版本号可通过环境变量 GEMINI_CLI_VERSION 覆盖
This commit is contained in:
parent
2279bde564
commit
088a508e60
@ -3,8 +3,8 @@ package geminicli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -51,15 +51,49 @@ const (
|
||||
|
||||
SessionTTL = 30 * time.Minute
|
||||
|
||||
// GeminiCLIUserAgent mimics Gemini CLI to maximize compatibility with internal endpoints.
|
||||
// Note: The real Gemini CLI uses OS-appropriate platform strings.
|
||||
// Use GetGeminiCLIUserAgent() for runtime-aware User-Agent.
|
||||
GeminiCLIUserAgent = "GeminiCLI/0.1.5"
|
||||
// GeminiCLIUserAgent 静态回退值(不含 model)
|
||||
// 优先使用 GetGeminiCLIUserAgent(model) 获取完整格式
|
||||
GeminiCLIUserAgent = "GeminiCLI/0.33.1"
|
||||
|
||||
// FakeNodeVersion 模拟真实 Gemini CLI 的 Node.js 版本
|
||||
// 用于 x-goog-api-client 和 token exchange User-Agent
|
||||
FakeNodeVersion = "24.13.1"
|
||||
|
||||
// GoogleAuthLibraryUA 模拟 google-auth-library 的 User-Agent
|
||||
// 真实 Gemini CLI token exchange 由 google-auth-library 发起
|
||||
GoogleAuthLibraryUA = "google-api-nodejs-client"
|
||||
)
|
||||
|
||||
// GetGeminiCLIUserAgent 返回带有正确平台信息的 Gemini CLI User-Agent
|
||||
func GetGeminiCLIUserAgent() string {
|
||||
osName := strings.Title(runtime.GOOS) // Darwin, Linux, Windows
|
||||
arch := strings.ToUpper(runtime.GOARCH)
|
||||
return fmt.Sprintf("GeminiCLI/0.1.5 (%s; %s)", osName, arch)
|
||||
// defaultGeminiCLIVersion 可通过环境变量 GEMINI_CLI_VERSION 覆盖
|
||||
var defaultGeminiCLIVersion = "0.33.1"
|
||||
|
||||
func init() {
|
||||
if v := os.Getenv("GEMINI_CLI_VERSION"); v != "" {
|
||||
defaultGeminiCLIVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
// GetGeminiCLIUserAgent 返回匹配真实 Gemini CLI 格式的 User-Agent
|
||||
// 真实格式: GeminiCLI/{version}/{model} ({platform}; {arch})
|
||||
// 示例: GeminiCLI/0.33.1/gemini-2.5-pro (darwin; arm64)
|
||||
func GetGeminiCLIUserAgent(model ...string) string {
|
||||
m := "unknown"
|
||||
if len(model) > 0 && model[0] != "" {
|
||||
m = model[0]
|
||||
}
|
||||
return fmt.Sprintf("GeminiCLI/%s/%s (%s; %s)",
|
||||
defaultGeminiCLIVersion, m, runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// GetGeminiCLIGoogAPIClient 返回 x-goog-api-client 头的值
|
||||
// 真实 Gemini CLI 通过 google-auth-library DefaultTransporter 自动注入:
|
||||
// gl-node/{nodeVersion}
|
||||
func GetGeminiCLIGoogAPIClient() string {
|
||||
return fmt.Sprintf("gl-node/%s", FakeNodeVersion)
|
||||
}
|
||||
|
||||
// GetGeminiCLITokenExchangeUA 返回 token exchange/refresh 时的 User-Agent
|
||||
// 真实 Gemini CLI 使用 google-auth-library 发起 token 交换
|
||||
func GetGeminiCLITokenExchangeUA() string {
|
||||
return GoogleAuthLibraryUA
|
||||
}
|
||||
|
||||
@ -63,6 +63,8 @@ func (c *geminiOAuthClient) ExchangeCode(ctx context.Context, oauthType, code, c
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetFormDataFromValues(formData).
|
||||
SetHeader("User-Agent", geminicli.GetGeminiCLITokenExchangeUA()).
|
||||
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||
SetSuccessResult(&tokenResp).
|
||||
Post(c.tokenURL)
|
||||
if err != nil {
|
||||
@ -106,6 +108,8 @@ func (c *geminiOAuthClient) RefreshToken(ctx context.Context, oauthType, refresh
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetFormDataFromValues(formData).
|
||||
SetHeader("User-Agent", geminicli.GetGeminiCLITokenExchangeUA()).
|
||||
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||
SetSuccessResult(&tokenResp).
|
||||
Post(c.tokenURL)
|
||||
if err != nil {
|
||||
|
||||
@ -34,7 +34,8 @@ func (c *geminiCliCodeAssistClient) LoadCodeAssist(ctx context.Context, accessTo
|
||||
SetContext(ctx).
|
||||
SetHeader("Authorization", "Bearer "+accessToken).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", geminicli.GeminiCLIUserAgent).
|
||||
SetHeader("User-Agent", geminicli.GetGeminiCLIUserAgent()).
|
||||
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||
SetBody(reqBody).
|
||||
SetSuccessResult(&out).
|
||||
Post(c.baseURL + "/v1internal:loadCodeAssist")
|
||||
@ -78,7 +79,8 @@ func (c *geminiCliCodeAssistClient) OnboardUser(ctx context.Context, accessToken
|
||||
SetContext(ctx).
|
||||
SetHeader("Authorization", "Bearer "+accessToken).
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("User-Agent", geminicli.GeminiCLIUserAgent).
|
||||
SetHeader("User-Agent", geminicli.GetGeminiCLIUserAgent()).
|
||||
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||
SetBody(reqBody).
|
||||
SetSuccessResult(&out).
|
||||
Post(c.baseURL + "/v1internal:onboardUser")
|
||||
@ -116,7 +118,7 @@ func createGeminiCliReqClient(proxyURL string) (*req.Client, error) {
|
||||
func defaultLoadCodeAssistRequest() *geminicli.LoadCodeAssistRequest {
|
||||
return &geminicli.LoadCodeAssistRequest{
|
||||
Metadata: geminicli.LoadCodeAssistMetadata{
|
||||
IDEType: "ANTIGRAVITY",
|
||||
IDEType: "IDE_UNSPECIFIED",
|
||||
Platform: "PLATFORM_UNSPECIFIED",
|
||||
PluginType: "GEMINI",
|
||||
},
|
||||
@ -127,7 +129,7 @@ func defaultOnboardUserRequest() *geminicli.OnboardUserRequest {
|
||||
return &geminicli.OnboardUserRequest{
|
||||
TierID: "LEGACY",
|
||||
Metadata: geminicli.LoadCodeAssistMetadata{
|
||||
IDEType: "ANTIGRAVITY",
|
||||
IDEType: "IDE_UNSPECIFIED",
|
||||
Platform: "PLATFORM_UNSPECIFIED",
|
||||
PluginType: "GEMINI",
|
||||
},
|
||||
|
||||
@ -1464,7 +1464,8 @@ func (s *AccountTestService) buildCodeAssistRequest(ctx context.Context, accessT
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("User-Agent", geminicli.GeminiCLIUserAgent)
|
||||
req.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||
req.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
@ -669,7 +669,8 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
|
||||
}
|
||||
upstreamReq.Header.Set("Content-Type", "application/json")
|
||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent(mappedModel))
|
||||
upstreamReq.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||
return upstreamReq, "x-request-id", nil
|
||||
} else {
|
||||
// Mode 2: AI Studio API with OAuth (like API key mode, but using Bearer token)
|
||||
@ -690,7 +691,8 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex
|
||||
}
|
||||
upstreamReq.Header.Set("Content-Type", "application/json")
|
||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent(mappedModel))
|
||||
upstreamReq.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||
return upstreamReq, "x-request-id", nil
|
||||
}
|
||||
}
|
||||
@ -1171,7 +1173,8 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
|
||||
}
|
||||
upstreamReq.Header.Set("Content-Type", "application/json")
|
||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent(mappedModel))
|
||||
upstreamReq.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||
return upstreamReq, "x-request-id", nil
|
||||
} else {
|
||||
// Mode 2: AI Studio API with OAuth (like API key mode, but using Bearer token)
|
||||
@ -1192,7 +1195,8 @@ func (s *GeminiMessagesCompatService) ForwardNative(ctx context.Context, c *gin.
|
||||
}
|
||||
upstreamReq.Header.Set("Content-Type", "application/json")
|
||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||
upstreamReq.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent(mappedModel))
|
||||
upstreamReq.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||
return upstreamReq, "x-request-id", nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1037,7 +1037,8 @@ func fetchProjectIDFromResourceManager(ctx context.Context, accessToken, proxyUR
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("User-Agent", geminicli.GeminiCLIUserAgent)
|
||||
req.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||
req.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||
|
||||
client, err := httpclient.GetClient(httpclient.Options{
|
||||
ProxyURL: strings.TrimSpace(proxyURL),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user