mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 06:52:13 +08:00
fix 第一次 400,第二次触发切账号信号
This commit is contained in:
@@ -2241,6 +2241,20 @@ func (s *AntigravityGatewayService) ForwardGemini(ctx context.Context, c *gin.Co
|
|||||||
contentType = resp.Header.Get("Content-Type")
|
contentType = resp.Header.Get("Content-Type")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if switchErr, ok := IsAntigravityAccountSwitchError(retryErr); ok {
|
||||||
|
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
|
||||||
|
Platform: account.Platform,
|
||||||
|
AccountID: account.ID,
|
||||||
|
AccountName: account.Name,
|
||||||
|
UpstreamStatusCode: http.StatusServiceUnavailable,
|
||||||
|
Kind: "failover",
|
||||||
|
Message: sanitizeUpstreamErrorMessage(retryErr.Error()),
|
||||||
|
})
|
||||||
|
return nil, &UpstreamFailoverError{
|
||||||
|
StatusCode: http.StatusServiceUnavailable,
|
||||||
|
ForceCacheBilling: switchErr.IsStickySession,
|
||||||
|
}
|
||||||
|
}
|
||||||
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
|
appendOpsUpstreamError(c, OpsUpstreamErrorEvent{
|
||||||
Platform: account.Platform,
|
Platform: account.Platform,
|
||||||
AccountID: account.ID,
|
AccountID: account.ID,
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ type queuedHTTPUpstreamStub struct {
|
|||||||
errors []error
|
errors []error
|
||||||
requestBodies [][]byte
|
requestBodies [][]byte
|
||||||
callCount int
|
callCount int
|
||||||
|
onCall func(*http.Request, *queuedHTTPUpstreamStub)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *queuedHTTPUpstreamStub) Do(req *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
|
func (s *queuedHTTPUpstreamStub) Do(req *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
|
||||||
@@ -152,6 +153,9 @@ func (s *queuedHTTPUpstreamStub) Do(req *http.Request, _ string, _ int64, _ int)
|
|||||||
|
|
||||||
idx := s.callCount
|
idx := s.callCount
|
||||||
s.callCount++
|
s.callCount++
|
||||||
|
if s.onCall != nil {
|
||||||
|
s.onCall(req, s)
|
||||||
|
}
|
||||||
|
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
if idx < len(s.responses) {
|
if idx < len(s.responses) {
|
||||||
@@ -679,6 +683,91 @@ func TestAntigravityGatewayService_ForwardGemini_RetriesCorruptedThoughtSignatur
|
|||||||
require.Equal(t, "signature_error", events[0].Kind)
|
require.Equal(t, "signature_error", events[0].Kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAntigravityGatewayService_ForwardGemini_SignatureRetryPropagatesFailover(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
writer := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(writer)
|
||||||
|
|
||||||
|
body, err := json.Marshal(map[string]any{
|
||||||
|
"contents": []map[string]any{
|
||||||
|
{"role": "user", "parts": []map[string]any{{"text": "hello"}}},
|
||||||
|
{"role": "model", "parts": []map[string]any{{"text": "thinking", "thought": true, "thoughtSignature": "sig_bad_1"}}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/antigravity/v1beta/models/gemini-3.1-pro-preview:streamGenerateContent", bytes.NewReader(body))
|
||||||
|
c.Request = req
|
||||||
|
|
||||||
|
firstRespBody := []byte(`{"response":{"error":{"code":400,"message":"Corrupted thought signature.","status":"INVALID_ARGUMENT"}}}`)
|
||||||
|
|
||||||
|
const originalModel = "gemini-3.1-pro-preview"
|
||||||
|
const mappedModel = "gemini-3.1-pro-high"
|
||||||
|
account := &Account{
|
||||||
|
ID: 8,
|
||||||
|
Name: "acc-gemini-signature-failover",
|
||||||
|
Platform: PlatformAntigravity,
|
||||||
|
Type: AccountTypeOAuth,
|
||||||
|
Status: StatusActive,
|
||||||
|
Concurrency: 1,
|
||||||
|
Credentials: map[string]any{
|
||||||
|
"access_token": "token",
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
originalModel: mappedModel,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream := &queuedHTTPUpstreamStub{
|
||||||
|
responses: []*http.Response{
|
||||||
|
{
|
||||||
|
StatusCode: http.StatusBadRequest,
|
||||||
|
Header: http.Header{
|
||||||
|
"Content-Type": []string{"application/json"},
|
||||||
|
"X-Request-Id": []string{"req-sig-failover-1"},
|
||||||
|
},
|
||||||
|
Body: io.NopCloser(bytes.NewReader(firstRespBody)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onCall: func(_ *http.Request, stub *queuedHTTPUpstreamStub) {
|
||||||
|
if stub.callCount != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
futureResetAt := time.Now().Add(30 * time.Second).Format(time.RFC3339)
|
||||||
|
account.Extra = map[string]any{
|
||||||
|
modelRateLimitsKey: map[string]any{
|
||||||
|
mappedModel: map[string]any{
|
||||||
|
"rate_limit_reset_at": futureResetAt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := &AntigravityGatewayService{
|
||||||
|
settingService: NewSettingService(&antigravitySettingRepoStub{}, &config.Config{Gateway: config.GatewayConfig{MaxLineSize: defaultMaxLineSize}}),
|
||||||
|
tokenProvider: &AntigravityTokenProvider{},
|
||||||
|
httpUpstream: upstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := svc.ForwardGemini(context.Background(), c, account, originalModel, "streamGenerateContent", true, body, true)
|
||||||
|
require.Nil(t, result)
|
||||||
|
|
||||||
|
var failoverErr *UpstreamFailoverError
|
||||||
|
require.ErrorAs(t, err, &failoverErr, "signature retry should propagate failover instead of falling back to the original 400")
|
||||||
|
require.Equal(t, http.StatusServiceUnavailable, failoverErr.StatusCode)
|
||||||
|
require.True(t, failoverErr.ForceCacheBilling)
|
||||||
|
require.Len(t, upstream.requestBodies, 1, "retry should stop at preflight failover and not issue a second upstream request")
|
||||||
|
|
||||||
|
raw, ok := c.Get(OpsUpstreamErrorsKey)
|
||||||
|
require.True(t, ok)
|
||||||
|
events, ok := raw.([]*OpsUpstreamErrorEvent)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Len(t, events, 2)
|
||||||
|
require.Equal(t, "signature_error", events[0].Kind)
|
||||||
|
require.Equal(t, "failover", events[1].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
// TestStreamUpstreamResponse_UsageAndFirstToken
|
// TestStreamUpstreamResponse_UsageAndFirstToken
|
||||||
// 验证:usage 字段可被累积/覆盖更新,并且能记录首 token 时间
|
// 验证:usage 字段可被累积/覆盖更新,并且能记录首 token 时间
|
||||||
func TestStreamUpstreamResponse_UsageAndFirstToken(t *testing.T) {
|
func TestStreamUpstreamResponse_UsageAndFirstToken(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user