From 71aa6e357448736105f24f4032836bb6e570ed65 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Mon, 24 Aug 2026 16:07:27 +0800 Subject: [PATCH] fix(antigravity): preserve explicit Sonnet 4.5 routing --- backend/internal/domain/constants.go | 4 ++-- backend/internal/domain/constants_test.go | 17 +++++++++-------- .../antigravity_credits_overages_test.go | 6 +++--- .../service/antigravity_model_mapping_test.go | 13 +++++++++++-- .../service/antigravity_rate_limit_test.go | 6 +++--- .../antigravity_single_account_retry_test.go | 4 ++-- .../internal/service/model_rate_limit_test.go | 8 ++++---- 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go index 6500502014..1ed90ee940 100644 --- a/backend/internal/domain/constants.go +++ b/backend/internal/domain/constants.go @@ -105,8 +105,8 @@ var DefaultAntigravityModelMapping = map[string]string{ "claude-opus-4-6": "claude-opus-4-6-thinking", // 简称映射 "claude-opus-4-5-thinking": "claude-opus-4-6-thinking", // 迁移旧模型 "claude-sonnet-4-6": "claude-sonnet-4-6", - "claude-sonnet-4-5": "claude-sonnet-4-6", // 迁移旧模型 - "claude-sonnet-4-5-thinking": "claude-sonnet-4-6", // 迁移旧模型 + "claude-sonnet-4-5": "claude-sonnet-4-5", // 显式 canonical 选择透传 + "claude-sonnet-4-5-thinking": "claude-sonnet-4-6", // 迁移旧兼容别名 // Claude 详细版本 ID 映射 "claude-opus-4-5-20251101": "claude-opus-4-6-thinking", // 迁移旧模型 "claude-sonnet-4-5-20250929": "claude-sonnet-4-6", // 迁移旧模型 diff --git a/backend/internal/domain/constants_test.go b/backend/internal/domain/constants_test.go index 7cfcd7fa51..acac038ef2 100644 --- a/backend/internal/domain/constants_test.go +++ b/backend/internal/domain/constants_test.go @@ -43,16 +43,17 @@ func TestDefaultAntigravityModelMapping_ContainsNewClaudeModels(t *testing.T) { } } -func TestDefaultAntigravityModelMapping_MigratesLegacySonnet45(t *testing.T) { +func TestDefaultAntigravityModelMapping_PreservesExplicitSonnet45AndMigratesLegacyAliases(t *testing.T) { t.Parallel() - for _, model := range []string{ - "claude-sonnet-4-5", - "claude-sonnet-4-5-thinking", - "claude-sonnet-4-5-20250929", - } { - if got := DefaultAntigravityModelMapping[model]; got != "claude-sonnet-4-6" { - t.Fatalf("expected legacy model %q to map to claude-sonnet-4-6, got %q", model, got) + cases := map[string]string{ + "claude-sonnet-4-5": "claude-sonnet-4-5", + "claude-sonnet-4-5-thinking": "claude-sonnet-4-6", + "claude-sonnet-4-5-20250929": "claude-sonnet-4-6", + } + for model, want := range cases { + if got := DefaultAntigravityModelMapping[model]; got != want { + t.Fatalf("expected model %q to map to %q, got %q", model, want, got) } } } diff --git a/backend/internal/service/antigravity_credits_overages_test.go b/backend/internal/service/antigravity_credits_overages_test.go index bdba5cec7b..7a5224daf3 100644 --- a/backend/internal/service/antigravity_credits_overages_test.go +++ b/backend/internal/service/antigravity_credits_overages_test.go @@ -239,7 +239,7 @@ func TestAntigravityRetryLoop_ModelRateLimited_InjectsCredits(t *testing.T) { Extra: map[string]any{ "allow_overages": true, modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limited_at": time.Now().UTC().Format(time.RFC3339), "rate_limit_reset_at": time.Now().Add(30 * time.Minute).UTC().Format(time.RFC3339), }, @@ -290,7 +290,7 @@ func TestAntigravityRetryLoop_CreditsExhausted_DoesNotInject(t *testing.T) { Extra: map[string]any{ "allow_overages": true, modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limited_at": time.Now().UTC().Format(time.RFC3339), "rate_limit_reset_at": time.Now().Add(30 * time.Minute).UTC().Format(time.RFC3339), }, @@ -355,7 +355,7 @@ func TestAntigravityRetryLoop_CreditErrorMarksExhausted(t *testing.T) { Extra: map[string]any{ "allow_overages": true, modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limited_at": time.Now().UTC().Format(time.RFC3339), "rate_limit_reset_at": time.Now().Add(30 * time.Minute).UTC().Format(time.RFC3339), }, diff --git a/backend/internal/service/antigravity_model_mapping_test.go b/backend/internal/service/antigravity_model_mapping_test.go index 0f86aa4a9c..61cc7e31d6 100644 --- a/backend/internal/service/antigravity_model_mapping_test.go +++ b/backend/internal/service/antigravity_model_mapping_test.go @@ -89,10 +89,10 @@ func TestAntigravityGatewayService_GetMappedModel(t *testing.T) { expected: "claude-sonnet-4-6", }, { - name: "默认映射 - claude-sonnet-4-5 → claude-sonnet-4-6", + name: "显式 canonical 选择 - claude-sonnet-4-5 透传", requestedModel: "claude-sonnet-4-5", accountMapping: nil, - expected: "claude-sonnet-4-6", + expected: "claude-sonnet-4-5", }, { name: "默认映射透传 - claude-opus-4-8", @@ -118,6 +118,15 @@ func TestAntigravityGatewayService_GetMappedModel(t *testing.T) { accountMapping: nil, expected: "claude-sonnet-4-6", }, + { + name: "账户显式目标只映射一步 - custom-sonnet → claude-sonnet-4-5", + requestedModel: "custom-sonnet", + accountMapping: map[string]string{ + "custom-sonnet": "claude-sonnet-4-5", + "claude-sonnet-4-5": "claude-sonnet-4-6", + }, + expected: "claude-sonnet-4-5", + }, { name: "默认映射透传 - gemini-2.5-flash", requestedModel: "gemini-2.5-flash", diff --git a/backend/internal/service/antigravity_rate_limit_test.go b/backend/internal/service/antigravity_rate_limit_test.go index 7a06a4e49e..d632f14bda 100644 --- a/backend/internal/service/antigravity_rate_limit_test.go +++ b/backend/internal/service/antigravity_rate_limit_test.go @@ -206,7 +206,7 @@ func TestHandleUpstreamError_429_NonModelRateLimit(t *testing.T) { // 但 429 兜底逻辑会使用 requestedModel 设置模型级限流 require.Nil(t, result) require.Len(t, repo.modelRateLimitCalls, 1) - require.Equal(t, "claude-sonnet-4-6", repo.modelRateLimitCalls[0].modelKey) + require.Equal(t, "claude-sonnet-4-5", repo.modelRateLimitCalls[0].modelKey) } // TestHandleUpstreamError_429_NonModelRateLimit_UsesMappedModelKey 测试 429 非模型限流场景 @@ -915,7 +915,7 @@ func TestAntigravityRetryLoop_PreCheck_SwitchesWhenRateLimited(t *testing.T) { Concurrency: 1, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limit_reset_at": time.Now().Add(2 * time.Second).Format(time.RFC3339), }, }, @@ -958,7 +958,7 @@ func TestAntigravityRetryLoop_PreCheck_SwitchesWhenRemainingLong(t *testing.T) { Concurrency: 1, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limit_reset_at": time.Now().Add(11 * time.Second).Format(time.RFC3339), }, }, diff --git a/backend/internal/service/antigravity_single_account_retry_test.go b/backend/internal/service/antigravity_single_account_retry_test.go index 85677e59c4..6e58ab75d2 100644 --- a/backend/internal/service/antigravity_single_account_retry_test.go +++ b/backend/internal/service/antigravity_single_account_retry_test.go @@ -693,7 +693,7 @@ func TestAntigravityRetryLoop_PreCheck_SingleAccountRetry_SkipsRateLimit(t *test Concurrency: 1, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limit_reset_at": time.Now().Add(30 * time.Second).Format(time.RFC3339), }, }, @@ -737,7 +737,7 @@ func TestAntigravityRetryLoop_PreCheck_NoSingleAccountRetry_SwitchesOnRateLimit( Concurrency: 1, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limit_reset_at": time.Now().Add(30 * time.Second).Format(time.RFC3339), }, }, diff --git a/backend/internal/service/model_rate_limit_test.go b/backend/internal/service/model_rate_limit_test.go index 3293853d74..430d91baf6 100644 --- a/backend/internal/service/model_rate_limit_test.go +++ b/backend/internal/service/model_rate_limit_test.go @@ -255,7 +255,7 @@ func TestIsModelRateLimited_OpenAIImageGenerationIntentBlocksTextModelImageTool( require.True(t, account.isModelRateLimitedWithContext(WithOpenAIImageGenerationIntent(context.Background()), "gpt-5.4")) } -func TestIsModelRateLimited_Antigravity_LegacySonnetUsesCanonicalModelKey(t *testing.T) { +func TestIsModelRateLimited_Antigravity_ThinkingAffectsModelKey(t *testing.T) { now := time.Now() future := now.Add(10 * time.Minute).Format(time.RFC3339) @@ -263,7 +263,7 @@ func TestIsModelRateLimited_Antigravity_LegacySonnetUsesCanonicalModelKey(t *tes Platform: PlatformAntigravity, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5-thinking": map[string]any{ "rate_limit_reset_at": future, }, }, @@ -453,7 +453,7 @@ func TestGetRateLimitRemainingTime(t *testing.T) { Platform: PlatformAntigravity, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limit_reset_at": future15m, }, }, @@ -469,7 +469,7 @@ func TestGetRateLimitRemainingTime(t *testing.T) { Platform: PlatformAntigravity, Extra: map[string]any{ modelRateLimitsKey: map[string]any{ - "claude-sonnet-4-6": map[string]any{ + "claude-sonnet-4-5": map[string]any{ "rate_limit_reset_at": future5m, }, },