补齐 Grok CC 重试与 compaction 恢复

This commit is contained in:
IanShaw
2026-08-20 06:08:09 -07:00
parent 8db8791a73
commit ad87ddee12
3 changed files with 26 additions and 10 deletions
@@ -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)
@@ -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
}
@@ -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)