diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go index 3be604daff..378b05501c 100644 --- a/backend/internal/domain/constants.go +++ b/backend/internal/domain/constants.go @@ -145,6 +145,12 @@ var DefaultAntigravityModelMapping = map[string]string{ "gemini-3.6-flash-low": "gemini-3.6-flash-low", "gemini-3.6-flash-medium": "gemini-3.6-flash-medium", "gemini-3.6-flash-tiered": "gemini-3.6-flash-tiered", + // Gemini 3.7 Flash tiered models + "gemini-3.7-flash": "gemini-3.7-flash", + "gemini-3.7-flash-high": "gemini-3.7-flash-high", + "gemini-3.7-flash-low": "gemini-3.7-flash-low", + "gemini-3.7-flash-medium": "gemini-3.7-flash-medium", + "gemini-3.7-flash-tiered": "gemini-3.7-flash-tiered", // Gemini 3.8 Flash tiered models "gemini-3.8-flash": "gemini-3.8-flash", "gemini-3.8-flash-high": "gemini-3.8-flash-high", diff --git a/backend/internal/domain/constants_test.go b/backend/internal/domain/constants_test.go index 7813cbbc7a..de7d882955 100644 --- a/backend/internal/domain/constants_test.go +++ b/backend/internal/domain/constants_test.go @@ -89,6 +89,14 @@ func TestDefaultAntigravityModelMapping_Gemini36FlashModels(t *testing.T) { } } +func TestDefaultAntigravityModelMapping_Gemini37FlashModels(t *testing.T) { + for _, model := range []string{"gemini-3.7-flash", "gemini-3.7-flash-high", "gemini-3.7-flash-low", "gemini-3.7-flash-medium", "gemini-3.7-flash-tiered"} { + if got := DefaultAntigravityModelMapping[model]; got != model { + t.Fatalf("expected %s to map to itself, got %q", model, got) + } + } +} + func TestDefaultAntigravityModelMapping_Gemini38FlashModels(t *testing.T) { for _, model := range []string{"gemini-3.8-flash", "gemini-3.8-flash-high", "gemini-3.8-flash-low", "gemini-3.8-flash-medium", "gemini-3.8-flash-tiered"} { if got := DefaultAntigravityModelMapping[model]; got != model { diff --git a/backend/internal/pkg/antigravity/claude_types.go b/backend/internal/pkg/antigravity/claude_types.go index 811c810546..23601c0457 100644 --- a/backend/internal/pkg/antigravity/claude_types.go +++ b/backend/internal/pkg/antigravity/claude_types.go @@ -184,6 +184,11 @@ var geminiModels = []modelDef{ {ID: "gemini-3.6-flash-low", DisplayName: "Gemini 3.6 Flash Low", CreatedAt: "2026-07-21T00:00:00Z", IsReasoning: true}, {ID: "gemini-3.6-flash-medium", DisplayName: "Gemini 3.6 Flash Medium", CreatedAt: "2026-07-21T00:00:00Z", IsReasoning: true}, {ID: "gemini-3.6-flash-tiered", DisplayName: "Gemini 3.6 Flash", CreatedAt: "2026-07-21T00:00:00Z", IsReasoning: true}, + {ID: "gemini-3.7-flash", DisplayName: "Gemini 3.7 Flash", CreatedAt: "2026-08-13T00:00:00Z"}, + {ID: "gemini-3.7-flash-high", DisplayName: "Gemini 3.7 Flash High", CreatedAt: "2026-08-13T00:00:00Z", IsReasoning: true}, + {ID: "gemini-3.7-flash-low", DisplayName: "Gemini 3.7 Flash Low", CreatedAt: "2026-08-13T00:00:00Z", IsReasoning: true}, + {ID: "gemini-3.7-flash-medium", DisplayName: "Gemini 3.7 Flash Medium", CreatedAt: "2026-08-13T00:00:00Z", IsReasoning: true}, + {ID: "gemini-3.7-flash-tiered", DisplayName: "Gemini 3.7 Flash", CreatedAt: "2026-08-13T00:00:00Z", IsReasoning: true}, {ID: "gemini-3.8-flash", DisplayName: "Gemini 3.8 Flash", CreatedAt: "2026-09-02T00:00:00Z"}, {ID: "gemini-3.8-flash-high", DisplayName: "Gemini 3.8 Flash High", CreatedAt: "2026-09-02T00:00:00Z", IsReasoning: true}, {ID: "gemini-3.8-flash-low", DisplayName: "Gemini 3.8 Flash Low", CreatedAt: "2026-09-02T00:00:00Z", IsReasoning: true}, diff --git a/backend/internal/pkg/antigravity/claude_types_test.go b/backend/internal/pkg/antigravity/claude_types_test.go index 860a9db28c..4b49fdb9b1 100644 --- a/backend/internal/pkg/antigravity/claude_types_test.go +++ b/backend/internal/pkg/antigravity/claude_types_test.go @@ -26,6 +26,11 @@ func TestDefaultModels_ContainsNewAndLegacyImageModels(t *testing.T) { "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-tiered", + "gemini-3.7-flash", + "gemini-3.7-flash-high", + "gemini-3.7-flash-low", + "gemini-3.7-flash-medium", + "gemini-3.7-flash-tiered", "gemini-3.8-flash", "gemini-3.8-flash-high", "gemini-3.8-flash-low", diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go index a962d94159..15fd4d16ce 100644 --- a/backend/internal/service/account.go +++ b/backend/internal/service/account.go @@ -662,6 +662,11 @@ func (a *Account) resolveModelMapping(rawMapping map[string]any) map[string]stri "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-tiered", + "gemini-3.7-flash", + "gemini-3.7-flash-high", + "gemini-3.7-flash-low", + "gemini-3.7-flash-medium", + "gemini-3.7-flash-tiered", "gemini-3.8-flash", "gemini-3.8-flash-high", "gemini-3.8-flash-low", diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index 2fe2a913f4..9d9fdff498 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -400,6 +400,18 @@ func (s *BillingService) initFallbackPricing() { SupportsCacheBreakdown: false, } + // Gemini 3.7 Flash (Google AI pricing: $0.75 input / $3.75 output / + // $0.075 cached input per MTok, promotional through 2026-12-31; official + // rates double to $1.50/$7.50/$0.15 from 2027-01-01). Antigravity's + // -high/-low/-medium/-tiered aliases are matched below so unavailable + // remote pricing never records token-bearing requests at $0. + s.fallbackPrices["gemini-3.7-flash"] = &ModelPricing{ + InputPricePerToken: 0.75e-6, + OutputPricePerToken: 3.75e-6, + CacheReadPricePerToken: 0.075e-6, + SupportsCacheBreakdown: false, + } + // Gemini 3.8 Flash (Google AI pricing: $0.75 input / $3.75 output / // $0.075 cached input per MTok, promotional through 2026-12-31; official // rates double to $1.50/$7.50/$0.15 from 2027-01-01). Antigravity's @@ -863,6 +875,9 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing { if strings.Contains(modelLower, "gemini-3.6-flash") || strings.Contains(modelLower, "gemini-3-6-flash") { return s.fallbackPrices["gemini-3.6-flash"] } + if strings.Contains(modelLower, "gemini-3.7-flash") || strings.Contains(modelLower, "gemini-3-7-flash") { + return s.fallbackPrices["gemini-3.7-flash"] + } if strings.Contains(modelLower, "gemini-3.8-flash") || strings.Contains(modelLower, "gemini-3-8-flash") { return s.fallbackPrices["gemini-3.8-flash"] } diff --git a/backend/internal/service/pricing_service.go b/backend/internal/service/pricing_service.go index 84b6d448e0..e0b344ac60 100644 --- a/backend/internal/service/pricing_service.go +++ b/backend/internal/service/pricing_service.go @@ -1116,7 +1116,7 @@ func normalizeModelNameForPricing(model string) string { // behavior, not the published token rate, so this keeps -high/-low/-medium and // -tiered requests on the same price card as their base model. func normalizeGeminiThinkingTierAlias(model string) string { - for _, baseModel := range []string{"gemini-3.6-flash", "gemini-3.8-flash"} { + for _, baseModel := range []string{"gemini-3.6-flash", "gemini-3.7-flash", "gemini-3.8-flash"} { for _, tier := range []string{"-high", "-low", "-medium", "-tiered"} { if model == baseModel+tier { return baseModel diff --git a/backend/internal/service/pricing_service_test.go b/backend/internal/service/pricing_service_test.go index dc7674ef5c..bfbac756ff 100644 --- a/backend/internal/service/pricing_service_test.go +++ b/backend/internal/service/pricing_service_test.go @@ -533,6 +533,51 @@ func TestBillingService_Gemini36FlashThinkingTierFallbacksAreBillable(t *testing } } +func TestPricingService_Gemini37FlashThinkingTiersUseBasePricing(t *testing.T) { + basePricing := &LiteLLMModelPricing{ + InputCostPerToken: 0.75e-6, + OutputCostPerToken: 3.75e-6, + CacheReadInputTokenCost: 0.075e-6, + } + svc := &PricingService{pricingData: map[string]*LiteLLMModelPricing{ + "gemini-3.7-flash": basePricing, + }} + + for _, model := range []string{ + "gemini-3.7-flash", + "gemini-3.7-flash-high", + "gemini-3.7-flash-low", + "gemini-3.7-flash-medium", + "gemini-3.7-flash-tiered", + } { + t.Run(model, func(t *testing.T) { + require.Same(t, basePricing, svc.GetModelPricing(model)) + }) + } +} + +func TestBillingService_Gemini37FlashThinkingTierFallbacksAreBillable(t *testing.T) { + svc := NewBillingService(&config.Config{}, nil) + tokens := UsageTokens{InputTokens: 1_000_000, OutputTokens: 1_000_000, CacheReadTokens: 1_000_000} + + for _, model := range []string{ + "gemini-3.7-flash", + "gemini-3.7-flash-high", + "gemini-3.7-flash-low", + "gemini-3.7-flash-medium", + "gemini-3.7-flash-tiered", + } { + t.Run(model, func(t *testing.T) { + cost, err := svc.CalculateCost(model, tokens, 1) + require.NoError(t, err) + require.InDelta(t, 0.75, cost.InputCost, 1e-12) + require.InDelta(t, 3.75, cost.OutputCost, 1e-12) + require.InDelta(t, 0.075, cost.CacheReadCost, 1e-12) + require.InDelta(t, 4.575, cost.TotalCost, 1e-12) + }) + } +} + func TestPricingService_Gemini38FlashThinkingTiersUseBasePricing(t *testing.T) { basePricing := &LiteLLMModelPricing{ InputCostPerToken: 0.75e-6,