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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,15 +51,49 @@ const (
|
|||||||
|
|
||||||
SessionTTL = 30 * time.Minute
|
SessionTTL = 30 * time.Minute
|
||||||
|
|
||||||
// GeminiCLIUserAgent mimics Gemini CLI to maximize compatibility with internal endpoints.
|
// GeminiCLIUserAgent 静态回退值(不含 model)
|
||||||
// Note: The real Gemini CLI uses OS-appropriate platform strings.
|
// 优先使用 GetGeminiCLIUserAgent(model) 获取完整格式
|
||||||
// Use GetGeminiCLIUserAgent() for runtime-aware User-Agent.
|
GeminiCLIUserAgent = "GeminiCLI/0.33.1"
|
||||||
GeminiCLIUserAgent = "GeminiCLI/0.1.5"
|
|
||||||
|
// 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
|
// defaultGeminiCLIVersion 可通过环境变量 GEMINI_CLI_VERSION 覆盖
|
||||||
func GetGeminiCLIUserAgent() string {
|
var defaultGeminiCLIVersion = "0.33.1"
|
||||||
osName := strings.Title(runtime.GOOS) // Darwin, Linux, Windows
|
|
||||||
arch := strings.ToUpper(runtime.GOARCH)
|
func init() {
|
||||||
return fmt.Sprintf("GeminiCLI/0.1.5 (%s; %s)", osName, arch)
|
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().
|
resp, err := client.R().
|
||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
SetFormDataFromValues(formData).
|
SetFormDataFromValues(formData).
|
||||||
|
SetHeader("User-Agent", geminicli.GetGeminiCLITokenExchangeUA()).
|
||||||
|
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||||
SetSuccessResult(&tokenResp).
|
SetSuccessResult(&tokenResp).
|
||||||
Post(c.tokenURL)
|
Post(c.tokenURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,6 +108,8 @@ func (c *geminiOAuthClient) RefreshToken(ctx context.Context, oauthType, refresh
|
|||||||
resp, err := client.R().
|
resp, err := client.R().
|
||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
SetFormDataFromValues(formData).
|
SetFormDataFromValues(formData).
|
||||||
|
SetHeader("User-Agent", geminicli.GetGeminiCLITokenExchangeUA()).
|
||||||
|
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||||
SetSuccessResult(&tokenResp).
|
SetSuccessResult(&tokenResp).
|
||||||
Post(c.tokenURL)
|
Post(c.tokenURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -34,7 +34,8 @@ func (c *geminiCliCodeAssistClient) LoadCodeAssist(ctx context.Context, accessTo
|
|||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
SetHeader("Authorization", "Bearer "+accessToken).
|
SetHeader("Authorization", "Bearer "+accessToken).
|
||||||
SetHeader("Content-Type", "application/json").
|
SetHeader("Content-Type", "application/json").
|
||||||
SetHeader("User-Agent", geminicli.GeminiCLIUserAgent).
|
SetHeader("User-Agent", geminicli.GetGeminiCLIUserAgent()).
|
||||||
|
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||||
SetBody(reqBody).
|
SetBody(reqBody).
|
||||||
SetSuccessResult(&out).
|
SetSuccessResult(&out).
|
||||||
Post(c.baseURL + "/v1internal:loadCodeAssist")
|
Post(c.baseURL + "/v1internal:loadCodeAssist")
|
||||||
@ -78,7 +79,8 @@ func (c *geminiCliCodeAssistClient) OnboardUser(ctx context.Context, accessToken
|
|||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
SetHeader("Authorization", "Bearer "+accessToken).
|
SetHeader("Authorization", "Bearer "+accessToken).
|
||||||
SetHeader("Content-Type", "application/json").
|
SetHeader("Content-Type", "application/json").
|
||||||
SetHeader("User-Agent", geminicli.GeminiCLIUserAgent).
|
SetHeader("User-Agent", geminicli.GetGeminiCLIUserAgent()).
|
||||||
|
SetHeader("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient()).
|
||||||
SetBody(reqBody).
|
SetBody(reqBody).
|
||||||
SetSuccessResult(&out).
|
SetSuccessResult(&out).
|
||||||
Post(c.baseURL + "/v1internal:onboardUser")
|
Post(c.baseURL + "/v1internal:onboardUser")
|
||||||
@ -116,7 +118,7 @@ func createGeminiCliReqClient(proxyURL string) (*req.Client, error) {
|
|||||||
func defaultLoadCodeAssistRequest() *geminicli.LoadCodeAssistRequest {
|
func defaultLoadCodeAssistRequest() *geminicli.LoadCodeAssistRequest {
|
||||||
return &geminicli.LoadCodeAssistRequest{
|
return &geminicli.LoadCodeAssistRequest{
|
||||||
Metadata: geminicli.LoadCodeAssistMetadata{
|
Metadata: geminicli.LoadCodeAssistMetadata{
|
||||||
IDEType: "ANTIGRAVITY",
|
IDEType: "IDE_UNSPECIFIED",
|
||||||
Platform: "PLATFORM_UNSPECIFIED",
|
Platform: "PLATFORM_UNSPECIFIED",
|
||||||
PluginType: "GEMINI",
|
PluginType: "GEMINI",
|
||||||
},
|
},
|
||||||
@ -127,7 +129,7 @@ func defaultOnboardUserRequest() *geminicli.OnboardUserRequest {
|
|||||||
return &geminicli.OnboardUserRequest{
|
return &geminicli.OnboardUserRequest{
|
||||||
TierID: "LEGACY",
|
TierID: "LEGACY",
|
||||||
Metadata: geminicli.LoadCodeAssistMetadata{
|
Metadata: geminicli.LoadCodeAssistMetadata{
|
||||||
IDEType: "ANTIGRAVITY",
|
IDEType: "IDE_UNSPECIFIED",
|
||||||
Platform: "PLATFORM_UNSPECIFIED",
|
Platform: "PLATFORM_UNSPECIFIED",
|
||||||
PluginType: "GEMINI",
|
PluginType: "GEMINI",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1464,7 +1464,8 @@ func (s *AccountTestService) buildCodeAssistRequest(ctx context.Context, accessT
|
|||||||
|
|
||||||
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", geminicli.GeminiCLIUserAgent)
|
req.Header.Set("User-Agent", geminicli.GetGeminiCLIUserAgent())
|
||||||
|
req.Header.Set("X-Goog-Api-Client", geminicli.GetGeminiCLIGoogAPIClient())
|
||||||
|
|
||||||
return req, nil
|
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("Content-Type", "application/json")
|
||||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
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
|
return upstreamReq, "x-request-id", nil
|
||||||
} else {
|
} else {
|
||||||
// Mode 2: AI Studio API with OAuth (like API key mode, but using Bearer token)
|
// 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("Content-Type", "application/json")
|
||||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
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
|
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("Content-Type", "application/json")
|
||||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
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
|
return upstreamReq, "x-request-id", nil
|
||||||
} else {
|
} else {
|
||||||
// Mode 2: AI Studio API with OAuth (like API key mode, but using Bearer token)
|
// 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("Content-Type", "application/json")
|
||||||
upstreamReq.Header.Set("Authorization", "Bearer "+accessToken)
|
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
|
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("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{
|
client, err := httpclient.GetClient(httpclient.Options{
|
||||||
ProxyURL: strings.TrimSpace(proxyURL),
|
ProxyURL: strings.TrimSpace(proxyURL),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user