fix: align gemini-cli upstream communication headers
Removed legacy Client-Metadata and explicit API-Client headers. Dynamically generating accurate User-Agent strings matching the official cli.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -47,11 +48,12 @@ const (
|
|||||||
codexCallbackPort = 1455
|
codexCallbackPort = 1455
|
||||||
geminiCLIEndpoint = "https://cloudcode-pa.googleapis.com"
|
geminiCLIEndpoint = "https://cloudcode-pa.googleapis.com"
|
||||||
geminiCLIVersion = "v1internal"
|
geminiCLIVersion = "v1internal"
|
||||||
geminiCLIUserAgent = "google-api-nodejs-client/9.15.1"
|
|
||||||
geminiCLIApiClient = "gl-node/22.17.0"
|
|
||||||
geminiCLIClientMetadata = "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getGeminiCLIUserAgent() string {
|
||||||
|
return fmt.Sprintf("GeminiCLI/1.0.0/unknown (%s; %s)", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
type callbackForwarder struct {
|
type callbackForwarder struct {
|
||||||
provider string
|
provider string
|
||||||
server *http.Server
|
server *http.Server
|
||||||
@@ -2270,9 +2272,7 @@ func callGeminiCLI(ctx context.Context, httpClient *http.Client, endpoint string
|
|||||||
return fmt.Errorf("create request: %w", errRequest)
|
return fmt.Errorf("create request: %w", errRequest)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", geminiCLIUserAgent)
|
req.Header.Set("User-Agent", getGeminiCLIUserAgent())
|
||||||
req.Header.Set("X-Goog-Api-Client", geminiCLIApiClient)
|
|
||||||
req.Header.Set("Client-Metadata", geminiCLIClientMetadata)
|
|
||||||
|
|
||||||
resp, errDo := httpClient.Do(req)
|
resp, errDo := httpClient.Do(req)
|
||||||
if errDo != nil {
|
if errDo != nil {
|
||||||
@@ -2342,7 +2342,7 @@ func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projec
|
|||||||
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", geminiCLIUserAgent)
|
req.Header.Set("User-Agent", getGeminiCLIUserAgent())
|
||||||
resp, errDo := httpClient.Do(req)
|
resp, errDo := httpClient.Do(req)
|
||||||
if errDo != nil {
|
if errDo != nil {
|
||||||
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
||||||
@@ -2363,7 +2363,7 @@ func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projec
|
|||||||
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", geminiCLIUserAgent)
|
req.Header.Set("User-Agent", getGeminiCLIUserAgent())
|
||||||
resp, errDo = httpClient.Do(req)
|
resp, errDo = httpClient.Do(req)
|
||||||
if errDo != nil {
|
if errDo != nil {
|
||||||
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -29,11 +30,12 @@ import (
|
|||||||
const (
|
const (
|
||||||
geminiCLIEndpoint = "https://cloudcode-pa.googleapis.com"
|
geminiCLIEndpoint = "https://cloudcode-pa.googleapis.com"
|
||||||
geminiCLIVersion = "v1internal"
|
geminiCLIVersion = "v1internal"
|
||||||
geminiCLIUserAgent = "google-api-nodejs-client/9.15.1"
|
|
||||||
geminiCLIApiClient = "gl-node/22.17.0"
|
|
||||||
geminiCLIClientMetadata = "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getGeminiCLIUserAgent() string {
|
||||||
|
return fmt.Sprintf("GeminiCLI/1.0.0/unknown (%s; %s)", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
type projectSelectionRequiredError struct{}
|
type projectSelectionRequiredError struct{}
|
||||||
|
|
||||||
func (e *projectSelectionRequiredError) Error() string {
|
func (e *projectSelectionRequiredError) Error() string {
|
||||||
@@ -409,9 +411,7 @@ func callGeminiCLI(ctx context.Context, httpClient *http.Client, endpoint string
|
|||||||
return fmt.Errorf("create request: %w", errRequest)
|
return fmt.Errorf("create request: %w", errRequest)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", geminiCLIUserAgent)
|
req.Header.Set("User-Agent", getGeminiCLIUserAgent())
|
||||||
req.Header.Set("X-Goog-Api-Client", geminiCLIApiClient)
|
|
||||||
req.Header.Set("Client-Metadata", geminiCLIClientMetadata)
|
|
||||||
|
|
||||||
resp, errDo := httpClient.Do(req)
|
resp, errDo := httpClient.Do(req)
|
||||||
if errDo != nil {
|
if errDo != nil {
|
||||||
@@ -630,7 +630,7 @@ func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projec
|
|||||||
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", geminiCLIUserAgent)
|
req.Header.Set("User-Agent", getGeminiCLIUserAgent())
|
||||||
resp, errDo := httpClient.Do(req)
|
resp, errDo := httpClient.Do(req)
|
||||||
if errDo != nil {
|
if errDo != nil {
|
||||||
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
||||||
@@ -651,7 +651,7 @@ func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projec
|
|||||||
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
return false, fmt.Errorf("failed to create request: %w", errRequest)
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("User-Agent", geminiCLIUserAgent)
|
req.Header.Set("User-Agent", getGeminiCLIUserAgent())
|
||||||
resp, errDo = httpClient.Do(req)
|
resp, errDo = httpClient.Do(req)
|
||||||
if errDo != nil {
|
if errDo != nil {
|
||||||
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
return false, fmt.Errorf("failed to execute request: %w", errDo)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -81,7 +82,7 @@ func (e *GeminiCLIExecutor) PrepareRequest(req *http.Request, auth *cliproxyauth
|
|||||||
return statusErr{code: http.StatusUnauthorized, msg: "missing access token"}
|
return statusErr{code: http.StatusUnauthorized, msg: "missing access token"}
|
||||||
}
|
}
|
||||||
req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
||||||
applyGeminiCLIHeaders(req)
|
applyGeminiCLIHeaders(req, "unknown")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +190,7 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
|
|||||||
}
|
}
|
||||||
reqHTTP.Header.Set("Content-Type", "application/json")
|
reqHTTP.Header.Set("Content-Type", "application/json")
|
||||||
reqHTTP.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
reqHTTP.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
||||||
applyGeminiCLIHeaders(reqHTTP)
|
applyGeminiCLIHeaders(reqHTTP, attemptModel)
|
||||||
reqHTTP.Header.Set("Accept", "application/json")
|
reqHTTP.Header.Set("Accept", "application/json")
|
||||||
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
||||||
URL: url,
|
URL: url,
|
||||||
@@ -334,7 +335,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
|
|||||||
}
|
}
|
||||||
reqHTTP.Header.Set("Content-Type", "application/json")
|
reqHTTP.Header.Set("Content-Type", "application/json")
|
||||||
reqHTTP.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
reqHTTP.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
||||||
applyGeminiCLIHeaders(reqHTTP)
|
applyGeminiCLIHeaders(reqHTTP, attemptModel)
|
||||||
reqHTTP.Header.Set("Accept", "text/event-stream")
|
reqHTTP.Header.Set("Accept", "text/event-stream")
|
||||||
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
||||||
URL: url,
|
URL: url,
|
||||||
@@ -515,7 +516,7 @@ func (e *GeminiCLIExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.
|
|||||||
}
|
}
|
||||||
reqHTTP.Header.Set("Content-Type", "application/json")
|
reqHTTP.Header.Set("Content-Type", "application/json")
|
||||||
reqHTTP.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
reqHTTP.Header.Set("Authorization", "Bearer "+tok.AccessToken)
|
||||||
applyGeminiCLIHeaders(reqHTTP)
|
applyGeminiCLIHeaders(reqHTTP, baseModel)
|
||||||
reqHTTP.Header.Set("Accept", "application/json")
|
reqHTTP.Header.Set("Accept", "application/json")
|
||||||
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
recordAPIRequest(ctx, e.cfg, upstreamRequestLog{
|
||||||
URL: url,
|
URL: url,
|
||||||
@@ -738,21 +739,18 @@ func stringValue(m map[string]any, key string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// applyGeminiCLIHeaders sets required headers for the Gemini CLI upstream.
|
// applyGeminiCLIHeaders sets required headers for the Gemini CLI upstream.
|
||||||
func applyGeminiCLIHeaders(r *http.Request) {
|
func applyGeminiCLIHeaders(r *http.Request, model string) {
|
||||||
var ginHeaders http.Header
|
var ginHeaders http.Header
|
||||||
if ginCtx, ok := r.Context().Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
|
if ginCtx, ok := r.Context().Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
|
||||||
ginHeaders = ginCtx.Request.Header
|
ginHeaders = ginCtx.Request.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
misc.EnsureHeader(r.Header, ginHeaders, "User-Agent", "google-api-nodejs-client/9.15.1")
|
if model == "" {
|
||||||
misc.EnsureHeader(r.Header, ginHeaders, "X-Goog-Api-Client", "gl-node/22.17.0")
|
model = "unknown"
|
||||||
misc.EnsureHeader(r.Header, ginHeaders, "Client-Metadata", geminiCLIClientMetadata())
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// geminiCLIClientMetadata returns a compact metadata string required by upstream.
|
userAgent := fmt.Sprintf("GeminiCLI/1.0.0/%s (%s; %s)", model, runtime.GOOS, runtime.GOARCH)
|
||||||
func geminiCLIClientMetadata() string {
|
misc.EnsureHeader(r.Header, ginHeaders, "User-Agent", userAgent)
|
||||||
// Keep parity with CLI client defaults
|
|
||||||
return "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cliPreviewFallbackOrder returns preview model candidates for a base model.
|
// cliPreviewFallbackOrder returns preview model candidates for a base model.
|
||||||
|
|||||||
Reference in New Issue
Block a user