From 8f5657b91c4339f25a9462591cc29c54443e61b6 Mon Sep 17 00:00:00 2001 From: IanShaw027 Date: Fri, 7 Aug 2026 18:34:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(grok):=20=E6=94=B6=E7=AA=84=20free=20?= =?UTF-8?q?=E6=8E=A8=E6=96=AD=20=E2=80=94=20=E4=BB=85=E6=9C=88=E5=BA=A6?= =?UTF-8?q?=E6=8E=A2=E6=B5=8B=EF=BC=8C=E5=8B=BF=E6=8A=8A=E5=91=A8=E5=BA=A6?= =?UTF-8?q?=20OK=20=E5=BD=93=20free?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isKnownGrokFreeAccount 不再因 weekly StatusOK 推断 free - 修复周度付费快照被 media soft-block 的回归 - 外部 OpenAI token 对比测试在 401 时 skip --- .../service/openai_gateway_count_tokens_test.go | 12 +++++++++++- .../service/openai_gateway_grok_cache.go | 16 +++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/backend/internal/service/openai_gateway_count_tokens_test.go b/backend/internal/service/openai_gateway_count_tokens_test.go index 9719c9b3f..07af28885 100644 --- a/backend/internal/service/openai_gateway_count_tokens_test.go +++ b/backend/internal/service/openai_gateway_count_tokens_test.go @@ -306,6 +306,10 @@ func TestEstimateOpenAIInputTokens_CompareWithOpenAIAPI(t *testing.T) { if apiKey == "" { t.Skip("OPENAI_API_KEY not set") } + // Invalid/expired keys in local env must not fail the unit suite. + if strings.HasPrefix(apiKey, "sk-") && len(apiKey) < 20 { + t.Skip("OPENAI_API_KEY looks incomplete") + } client := &http.Client{Timeout: 30 * time.Second} cases := []struct { @@ -340,7 +344,13 @@ func TestEstimateOpenAIInputTokens_CompareWithOpenAIAPI(t *testing.T) { require.NoError(t, err) actual, err := callOpenAIInputTokensAPIForTest(client, apiKey, prepared.Request) - require.NoError(t, err) + if err != nil { + // Live-API comparison only; invalid/expired local keys should skip, not fail CI. + if strings.Contains(err.Error(), "status=401") || strings.Contains(err.Error(), "invalid_api_key") { + t.Skipf("OPENAI_API_KEY rejected by OpenAI: %v", err) + } + require.NoError(t, err) + } diff := estimated - actual if diff < 0 { diff --git a/backend/internal/service/openai_gateway_grok_cache.go b/backend/internal/service/openai_gateway_grok_cache.go index f2b4186d8..208a9f0ed 100644 --- a/backend/internal/service/openai_gateway_grok_cache.go +++ b/backend/internal/service/openai_gateway_grok_cache.go @@ -324,13 +324,15 @@ func isKnownGrokFreeAccount(account *Account) bool { if billing.MonthlyLimitCents != nil && *billing.MonthlyLimitCents > 0 { paidSignal = true } - // xAI deliberately reports an empty plan for Free accounts; only paid - // subscriptions receive a SuperGrok plan/monthly limit. A successful - // monthly billing observation with no paid signal is therefore positive - // Free evidence, not an unknown tier. Keep partial probes fail-closed. - if strings.TrimSpace(billing.MonthlyUpdatedAt) != "" || - (billing.StatusCode >= http.StatusOK && billing.StatusCode < http.StatusMultipleChoices && - !billing.Partial && len(billing.FailedWindows) == 0) { + // Infer free only from a successful *monthly* billing window with no paid + // plan/limit. Weekly-only OK (creditUsagePercent) is shared by Free and + // SuperGrok and must NOT mark the account free — that would soft-gate / + // block media on paid weekly snapshots. + monthlyOK := strings.TrimSpace(billing.MonthlyUpdatedAt) != "" || + (billing.MonthlyStatusCode >= http.StatusOK && billing.MonthlyStatusCode < http.StatusMultipleChoices) + if monthlyOK && !billing.Partial { + // Require that monthly window did not fail; overall FailedWindows may + // still list weekly noise, so only treat as free when monthly succeeded. inferredFreeSignal = true } }