From ad87ddee125cf7d5aebac417acd5119a21e6c644 Mon Sep 17 00:00:00 2001 From: IanShaw <131567472+IanShaw027@users.noreply.github.com> Date: Thu, 20 Aug 2026 06:08:09 -0700 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=20Grok=20CC=20=E9=87=8D?= =?UTF-8?q?=E8=AF=95=E4=B8=8E=20compaction=20=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/openai_gateway_chat_completions_raw.go | 12 ++++++++---- backend/internal/service/openai_gateway_grok.go | 14 ++++++++------ .../internal/service/openai_gateway_grok_test.go | 10 ++++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/backend/internal/service/openai_gateway_chat_completions_raw.go b/backend/internal/service/openai_gateway_chat_completions_raw.go index 3c46194188..00a7ece52c 100644 --- a/backend/internal/service/openai_gateway_chat_completions_raw.go +++ b/backend/internal/service/openai_gateway_chat_completions_raw.go @@ -200,11 +200,15 @@ func (s *OpenAIGatewayService) forwardAsRawChatCompletions( }) s.handleGrokAccountUpstreamError(withGrokTeamRateLimitModel(ctx, upstreamModel), account, resp.StatusCode, resp.Header, respBody) if s.shouldFailoverGrokUpstreamError(resp.StatusCode, respBody) { + retryable, retryDelay, retryDeadline := grokSameAccountRetryMetadata(account, resp.StatusCode, respBody) return nil, &UpstreamFailoverError{ - StatusCode: resp.StatusCode, - ResponseBody: respBody, - ResponseHeaders: resp.Header.Clone(), - RetryableOnSameAccount: account.IsPoolMode() && account.IsPoolModeRetryableStatus(resp.StatusCode), + StatusCode: resp.StatusCode, + ResponseBody: respBody, + ResponseHeaders: resp.Header.Clone(), + RetryableOnSameAccount: retryable, + RequestScopedTransient: retryable && resp.StatusCode == http.StatusTooManyRequests, + SameAccountRetryDelay: retryDelay, + SameAccountRetryDeadline: retryDeadline, } } return s.handleChatCompletionsErrorResponse(resp, c, account, billingModel) diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index 1933cd2550..132f7b3159 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -258,7 +258,7 @@ func (s *OpenAIGatewayService) forwardGrokResponses( } func isGrokInvalidEncryptedContentResponse(statusCode int, body []byte) bool { - if statusCode != http.StatusBadRequest { + if statusCode != http.StatusBadRequest && statusCode != http.StatusUnprocessableEntity { return false } @@ -284,7 +284,7 @@ func isGrokInvalidEncryptedContentResponse(statusCode int, body []byte) bool { return false } - if strings.EqualFold(code, "invalid_encrypted_content") { + if strings.EqualFold(code, "invalid_encrypted_content") || strings.EqualFold(code, "invalid_compaction") || strings.EqualFold(code, "compaction_decode_error") { return true } // Keep the official xAI flat-code gate so unrelated 400s are not retried. @@ -292,12 +292,13 @@ func isGrokInvalidEncryptedContentResponse(statusCode int, body []byte) bool { return false } // Nested OpenAI-style envelopes may omit top-level code; require decrypt text. - if code == "" && !strings.Contains(normalizedMessage, "decrypt") { + if code == "" && !strings.Contains(normalizedMessage, "decrypt") && !strings.Contains(normalizedMessage, "decode the compaction blob") { return false } - return strings.Contains(normalizedMessage, "encrypted_content") && + return (strings.Contains(normalizedMessage, "encrypted_content") && (strings.Contains(normalizedMessage, "decrypt") || - strings.Contains(normalizedMessage, "unmodified")) + strings.Contains(normalizedMessage, "unmodified"))) || + strings.Contains(normalizedMessage, "decode the compaction blob") } // requestHasGrokEncryptedReasoning reports whether the outbound Responses body @@ -392,7 +393,8 @@ func trimGrokInvalidEncryptedContentRetryBody(body []byte) ([]byte, bool, error) hasEncryptedReasoning := false for _, item := range items { - if strings.TrimSpace(item.Get("type").String()) == "reasoning" && item.Get("encrypted_content").Exists() { + if (strings.TrimSpace(item.Get("type").String()) == "reasoning" && item.Get("encrypted_content").Exists()) || + (isOpenAICompactionType(strings.TrimSpace(item.Get("type").String())) && item.Get("encrypted_content").Exists()) { hasEncryptedReasoning = true break } diff --git a/backend/internal/service/openai_gateway_grok_test.go b/backend/internal/service/openai_gateway_grok_test.go index 541e3a3216..b89c9eef83 100644 --- a/backend/internal/service/openai_gateway_grok_test.go +++ b/backend/internal/service/openai_gateway_grok_test.go @@ -2053,6 +2053,16 @@ func TestForwardGrokResponsesInvalidEncryptedContentRecoveryDoesNotOvermatch(t * } } +func TestGrokCompactionBlobRecoveryStripsCompactionItem(t *testing.T) { + body := []byte(`{"model":"grok","input":[{"type":"compaction","id":"cmp_1","encrypted_content":"blob"},{"type":"message","role":"user","content":"hi"}]}`) + require.True(t, isGrokInvalidEncryptedContentResponse(http.StatusUnprocessableEntity, []byte(`{"code":"invalid_compaction","error":"could not decode the compaction blob"}`))) + retry, changed, err := trimGrokInvalidEncryptedContentRetryBody(body) + require.NoError(t, err) + require.True(t, changed) + require.Equal(t, "message", gjson.GetBytes(retry, "input.0.type").String()) + require.False(t, gjson.GetBytes(retry, "input.#(type==\"compaction\")").Exists()) +} + func TestForwardGrokResponsesInvalidEncryptedContentRecoveryNestedErrorShape(t *testing.T) { gin.SetMode(gin.TestMode)