fix antigravity user agent handling

This commit is contained in:
sususu98
2026-04-24 19:46:52 +08:00
parent b34f3be13e
commit e78d45acc9
4 changed files with 158 additions and 21 deletions

View File

@@ -478,7 +478,7 @@ func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Au
return resp, statusErr{code: http.StatusNotImplemented, msg: "/responses/compact not supported"}
}
baseModel := thinking.ParseSuffix(req.Model).ModelName
if inCooldown, remaining := antigravityIsInShortCooldown(auth, baseModel, time.Now()); inCooldown {
if inCooldown, remaining := antigravityIsInShortCooldown(auth, baseModel, time.Now()); inCooldown && !antigravityShouldBypassShortCooldown(ctx, e.cfg) {
log.Debugf("antigravity executor: auth %s in short cooldown for model %s (%s remaining), returning 429 to switch auth", auth.ID, baseModel, remaining)
d := remaining
return resp, statusErr{code: http.StatusTooManyRequests, msg: fmt.Sprintf("auth in short cooldown, %s remaining", remaining), retryAfter: &d}
@@ -680,7 +680,7 @@ attemptLoop:
// executeClaudeNonStream performs a claude non-streaming request to the Antigravity API.
func (e *AntigravityExecutor) executeClaudeNonStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) {
baseModel := thinking.ParseSuffix(req.Model).ModelName
if inCooldown, remaining := antigravityIsInShortCooldown(auth, baseModel, time.Now()); inCooldown {
if inCooldown, remaining := antigravityIsInShortCooldown(auth, baseModel, time.Now()); inCooldown && !antigravityShouldBypassShortCooldown(ctx, e.cfg) {
log.Debugf("antigravity executor: auth %s in short cooldown for model %s (%s remaining), returning 429 to switch auth", auth.ID, baseModel, remaining)
d := remaining
return resp, statusErr{code: http.StatusTooManyRequests, msg: fmt.Sprintf("auth in short cooldown, %s remaining", remaining), retryAfter: &d}
@@ -1139,7 +1139,7 @@ func (e *AntigravityExecutor) ExecuteStream(ctx context.Context, auth *cliproxya
baseModel := thinking.ParseSuffix(req.Model).ModelName
ctx = context.WithValue(ctx, "alt", "")
if inCooldown, remaining := antigravityIsInShortCooldown(auth, baseModel, time.Now()); inCooldown {
if inCooldown, remaining := antigravityIsInShortCooldown(auth, baseModel, time.Now()); inCooldown && !antigravityShouldBypassShortCooldown(ctx, e.cfg) {
log.Debugf("antigravity executor: auth %s in short cooldown for model %s (%s remaining), returning 429 to switch auth", auth.ID, baseModel, remaining)
d := remaining
return nil, statusErr{code: http.StatusTooManyRequests, msg: fmt.Sprintf("auth in short cooldown, %s remaining", remaining), retryAfter: &d}
@@ -1763,16 +1763,29 @@ func (e *AntigravityExecutor) updateAntigravityCreditsBalance(ctx context.Contex
return
}
loadReqBody := `{"metadata":{"ideType":"ANTIGRAVITY","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}}`
endpointURL := "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodPost, endpointURL, strings.NewReader(loadReqBody))
userAgent := resolveLoadCodeAssistUserAgent(auth)
loadReqBody, errMarshal := json.Marshal(map[string]any{
"metadata": map[string]string{
"ide_type": "ANTIGRAVITY",
"ide_version": misc.AntigravityVersionFromUserAgent(userAgent),
"ide_name": "antigravity",
},
})
if errMarshal != nil {
log.Debugf("antigravity executor: marshal loadCodeAssist request error: %v", errMarshal)
return
}
baseURL := buildBaseURL(auth)
endpointURL := strings.TrimSuffix(baseURL, "/") + "/v1internal:loadCodeAssist"
httpReq, errReq := http.NewRequestWithContext(ctx, http.MethodPost, endpointURL, bytes.NewReader(loadReqBody))
if errReq != nil {
log.Debugf("antigravity executor: create loadCodeAssist request error: %v", errReq)
return
}
httpReq.Header.Set("Authorization", "Bearer "+token)
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("User-Agent", resolveUserAgent(auth))
httpReq.Header.Set("User-Agent", userAgent)
httpReq.Header.Set("X-Goog-Api-Client", "gl-node/22.21.1")
httpClient := newAntigravityHTTPClient(ctx, e.cfg, auth, 0)
httpResp, errDo := httpClient.Do(httpReq)
@@ -2070,19 +2083,28 @@ func resolveHost(base string) string {
}
func resolveUserAgent(auth *cliproxyauth.Auth) string {
return misc.AntigravityRequestUserAgent(antigravityConfiguredUserAgent(auth))
}
func resolveLoadCodeAssistUserAgent(auth *cliproxyauth.Auth) string {
return misc.AntigravityLoadCodeAssistUserAgent(antigravityConfiguredUserAgent(auth))
}
func antigravityConfiguredUserAgent(auth *cliproxyauth.Auth) string {
raw := ""
if auth != nil {
if auth.Attributes != nil {
if ua := strings.TrimSpace(auth.Attributes["user_agent"]); ua != "" {
return ua
raw = ua
}
}
if auth.Metadata != nil {
if raw == "" && auth.Metadata != nil {
if ua, ok := auth.Metadata["user_agent"].(string); ok && strings.TrimSpace(ua) != "" {
return strings.TrimSpace(ua)
raw = strings.TrimSpace(ua)
}
}
}
return misc.AntigravityUserAgent()
return raw
}
func antigravityRetryAttempts(auth *cliproxyauth.Auth, cfg *config.Config) int {
@@ -2141,6 +2163,10 @@ func antigravityShouldRetrySoftRateLimit(statusCode int, body []byte) bool {
return decideAntigravity429(body).kind == antigravity429DecisionSoftRetry
}
func antigravityShouldBypassShortCooldown(ctx context.Context, cfg *config.Config) bool {
return cliproxyauth.AntigravityCreditsRequested(ctx) && antigravityCreditsRetryEnabled(cfg)
}
func antigravitySoftRateLimitDelay(attempt int) time.Duration {
if attempt < 0 {
attempt = 0

View File

@@ -216,6 +216,11 @@ func TestAntigravityExecute_CreditsInjectedWhenConductorRequests(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
_ = r.Body.Close()
if r.URL.Path == "/v1internal:loadCodeAssist" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"paidTier":{"id":"tier-1","availableCredits":[{"creditType":"GOOGLE_ONE_AI","creditAmount":"25000","minimumCreditAmountForUsage":"50"}]}}`))
return
}
requestBodies = append(requestBodies, string(body))
if !strings.Contains(string(body), `"enabledCreditTypes":["GOOGLE_ONE_AI"]`) {
@@ -269,6 +274,11 @@ func TestAntigravityExecute_NoCreditsWithoutConductorFlag(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
_ = r.Body.Close()
if r.URL.Path == "/v1internal:loadCodeAssist" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"paidTier":{"id":"tier-1","availableCredits":[{"creditType":"GOOGLE_ONE_AI","creditAmount":"25000","minimumCreditAmountForUsage":"50"}]}}`))
return
}
requestBodies = append(requestBodies, string(body))
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte(`{"error":{"status":"RESOURCE_EXHAUSTED","message":"QUOTA_EXHAUSTED"}}`))
@@ -429,6 +439,41 @@ func TestEnsureAccessToken_WarmTokenLoadsCreditsHint(t *testing.T) {
}
}
func TestUpdateAntigravityCreditsBalance_LoadCodeAssistUserAgent(t *testing.T) {
resetAntigravityCreditsRetryState()
t.Cleanup(resetAntigravityCreditsRetryState)
exec := NewAntigravityExecutor(&config.Config{})
const userAgent = "antigravity/1.23.2 windows/amd64 google-api-nodejs-client/10.3.0"
auth := &cliproxyauth.Auth{
ID: "auth-load-code-assist-ua",
Attributes: map[string]string{"user_agent": userAgent},
}
ctx := context.WithValue(context.Background(), "cliproxy.roundtripper", roundTripperFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.String() != "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist" {
t.Fatalf("unexpected request url %s", req.URL.String())
}
if got := req.Header.Get("User-Agent"); got != userAgent {
t.Fatalf("User-Agent = %q, want %q", got, userAgent)
}
if got := req.Header.Get("X-Goog-Api-Client"); got != "gl-node/22.21.1" {
t.Fatalf("X-Goog-Api-Client = %q, want %q", got, "gl-node/22.21.1")
}
body, _ := io.ReadAll(req.Body)
_ = req.Body.Close()
if string(body) != `{"metadata":{"ide_name":"antigravity","ide_type":"ANTIGRAVITY","ide_version":"1.23.2"}}` {
t.Fatalf("loadCodeAssist body = %s", string(body))
}
return &http.Response{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(`{"paidTier":{"id":"tier-1","availableCredits":[{"creditType":"GOOGLE_ONE_AI","creditAmount":"25000","minimumCreditAmountForUsage":"50"}]}}`)),
}, nil
}))
exec.updateAntigravityCreditsBalance(ctx, auth, "token")
}
func TestParseMetaFloat(t *testing.T) {
tests := []struct {
name string