From 935db68517f8beef407db3da016eaef1799fce25 Mon Sep 17 00:00:00 2001 From: nagi330 <502369068@qq.com> Date: Tue, 22 Sep 2026 10:30:01 +0800 Subject: [PATCH] feat(grok):add grok 4.7 support --- backend/internal/handler/gateway_handler.go | 2 +- backend/internal/pkg/xai/models.go | 3 +++ backend/internal/pkg/xai/models_test.go | 7 ++++++- backend/internal/service/billing_service.go | 15 +++++++++++++++ .../internal/service/billing_service_test.go | 19 +++++++++++++++++++ .../service/openai_codex_models_service.go | 1 + .../internal/service/openai_gateway_grok.go | 6 ++++-- .../openai_gateway_grok_chat_bridge.go | 2 +- backend/internal/service/opencode_go.go | 1 + .../service/upstream_response_model.go | 2 ++ .../model_prices_and_context_window.json | 11 +++++++++++ frontend/src/composables/useModelWhitelist.ts | 4 +++- 12 files changed, 67 insertions(+), 6 deletions(-) diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go index c141431515..3bd69cead7 100644 --- a/backend/internal/handler/gateway_handler.go +++ b/backend/internal/handler/gateway_handler.go @@ -1375,7 +1375,7 @@ func writeGrokModelsList(c *gin.Context, modelIDs []string) { func grokModelSupportsConfigurableReasoning(modelID string) bool { switch strings.ToLower(strings.TrimSpace(modelID)) { - case "grok-4.6", "grok-4.6-latest", "grok-4.5", "grok-4.5-latest", "grok", "grok-latest", "grok-build", "grok-build-latest", "grok-build-0.1": + case "grok-4.7", "grok-4.7-latest", "grok-4.6", "grok-4.6-latest", "grok-4.5", "grok-4.5-latest", "grok", "grok-latest", "grok-build", "grok-build-latest", "grok-build-0.1": return true default: return false diff --git a/backend/internal/pkg/xai/models.go b/backend/internal/pkg/xai/models.go index 65634598f1..427b9bbf6d 100644 --- a/backend/internal/pkg/xai/models.go +++ b/backend/internal/pkg/xai/models.go @@ -84,6 +84,7 @@ func (o ModelMappingOptions) defaultText() string { var defaultModels = []Model{ // Text + {ID: "grok-4.7", Object: "model", Type: "model", OwnedBy: "xai", DisplayName: "Grok 4.7"}, {ID: "grok-4.6", Object: "model", Type: "model", OwnedBy: "xai", DisplayName: "Grok 4.6"}, {ID: "grok-4.5", Object: "model", Type: "model", OwnedBy: "xai", DisplayName: "Grok 4.5"}, {ID: "grok-4.3", Object: "model", Type: "model", OwnedBy: "xai", DisplayName: "Grok 4.3"}, @@ -106,6 +107,8 @@ var defaultModels = []Model{ var grokTextResponsesModelAliases = map[string]string{ "grok": DefaultTextModel, "grok-latest": DefaultTextModel, + "grok-4.7": "grok-4.7", + "grok-4.7-latest": "grok-4.7", "grok-4.6": "grok-4.6", "grok-4.6-latest": "grok-4.6", "grok-4.5": "grok-4.5", diff --git a/backend/internal/pkg/xai/models_test.go b/backend/internal/pkg/xai/models_test.go index f9d58e5a0c..eee030c4fe 100644 --- a/backend/internal/pkg/xai/models_test.go +++ b/backend/internal/pkg/xai/models_test.go @@ -68,9 +68,14 @@ func TestIsGrokImagineModel(t *testing.T) { require.False(t, IsGrokImagineModel("grok-build-0.1")) } -func TestDefaultModelsIncludesGrok46(t *testing.T) { +func TestDefaultModelsIncludesGrok46And47(t *testing.T) { t.Parallel() ids := DefaultModelIDs() + require.Contains(t, ids, "grok-4.7") + require.Equal(t, "grok-4.7", ResolveGrokTextResponsesModelID("grok-4.7")) + require.Equal(t, "grok-4.7", ResolveGrokTextResponsesModelID("grok-4.7-latest")) + + // Keep the existing assertions for the previous current model as well. require.Contains(t, ids, "grok-4.6") require.Equal(t, "grok-4.6", ResolveGrokTextResponsesModelID("grok-4.6")) require.Equal(t, "grok-4.6", ResolveGrokTextResponsesModelID("grok-4.6-latest")) diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index 29161ce338..1be36ba528 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -855,6 +855,19 @@ func (s *BillingService) initFallbackPricing() { LongContextOutputMultiplier: 2, } + // xAI Grok 4.7: $2 input / $0.50 cached input / $6 output below 200k; + // long-context rates are $4 / $1 / $12 (>=200k prompt tokens). + s.fallbackPrices["grok-4.7"] = &ModelPricing{ + InputPricePerToken: 2e-6, + OutputPricePerToken: 6e-6, + CacheReadPricePerToken: 0.5e-6, + SupportsCacheBreakdown: false, + LongContextInputThreshold: 200000, + LongContextThresholdInclusive: true, + LongContextInputMultiplier: 2, + LongContextOutputMultiplier: 2, + } + // xAI Grok 4.3: $1.25 input / $0.20 cached / $2.50 output below 200k; // long-context rates are $2.50 / $0.40 / $5. s.fallbackPrices["grok-4.3"] = &ModelPricing{ @@ -1128,6 +1141,8 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing { switch modelLower { case "grok", "grok-latest", "grok-4.6", "grok-4.6-latest": return s.fallbackPrices["grok-4.6"] + case "grok-4.7", "grok-4.7-latest": + return s.fallbackPrices["grok-4.7"] case "grok-4.5", "grok-4.5-latest": return s.fallbackPrices["grok-4.5"] case "grok-3-mini": diff --git a/backend/internal/service/billing_service_test.go b/backend/internal/service/billing_service_test.go index 59f20e0762..ed83a97308 100644 --- a/backend/internal/service/billing_service_test.go +++ b/backend/internal/service/billing_service_test.go @@ -1169,6 +1169,25 @@ func TestGetModelPricing_Grok46OfficialFallback(t *testing.T) { } } +func TestGetModelPricing_Grok47OfficialFallback(t *testing.T) { + svc := newTestBillingService() + + for _, model := range []string{"grok-4.7", "grok-4.7-latest"} { + model := model + t.Run(model, func(t *testing.T) { + pricing, err := svc.GetModelPricing(model) + require.NoError(t, err) + require.InDelta(t, 2e-6, pricing.InputPricePerToken, 1e-12) + require.InDelta(t, 6e-6, pricing.OutputPricePerToken, 1e-12) + require.InDelta(t, 0.5e-6, pricing.CacheReadPricePerToken, 1e-12) + require.Equal(t, 200000, pricing.LongContextInputThreshold) + require.InDelta(t, 2.0, pricing.LongContextInputMultiplier, 1e-12) + require.InDelta(t, 2.0, pricing.LongContextOutputMultiplier, 1e-12) + require.False(t, pricing.SupportsCacheBreakdown) + }) + } +} + func TestGetModelPricing_GrokOfficialFamilyCards(t *testing.T) { svc := newTestBillingService() for _, tc := range []struct { diff --git a/backend/internal/service/openai_codex_models_service.go b/backend/internal/service/openai_codex_models_service.go index e2658581e7..2c334524da 100644 --- a/backend/internal/service/openai_codex_models_service.go +++ b/backend/internal/service/openai_codex_models_service.go @@ -1267,6 +1267,7 @@ func isGrokCodexImageInputModel(model string) bool { case "grok-4.3", "grok-4.5", "grok-4.6", + "grok-4.7", "grok-build-0.1", "grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go index 5b9990b52c..06a17a961e 100644 --- a/backend/internal/service/openai_gateway_grok.go +++ b/backend/internal/service/openai_gateway_grok.go @@ -744,16 +744,18 @@ func normalizeGrokReasoningEffortValue(raw, model string) (string, bool) { } // GrokSupportsXHighReasoningEffort reports whether the model advertises and -// forwards the xhigh reasoning effort (Grok 4.6 and its undated alias). +// forwards the xhigh reasoning effort (Grok 4.6/4.7 and their aliases). func GrokSupportsXHighReasoningEffort(model string) bool { model = strings.ToLower(xai.StripGrokProviderPrefix(strings.TrimSpace(model))) - return model == "grok-4.6" || model == "grok-4.6-latest" + return model == "grok-4.7" || model == "grok-4.7-latest" || + model == "grok-4.6" || model == "grok-4.6-latest" } func grokSupportsReasoningEffort(model string) bool { model = strings.ToLower(xai.StripGrokProviderPrefix(strings.TrimSpace(model))) switch model { case "grok-4.5", "grok-4.5-latest", "grok-4.6", "grok-4.6-latest", + "grok-4.7", "grok-4.7-latest", "grok-4.3", "grok-4.3-latest", "grok-3-mini", "grok-3-mini-fast", "grok-4.20-0309-reasoning", "grok-4.20-reasoning", "grok-4.20-multi-agent-0309": diff --git a/backend/internal/service/openai_gateway_grok_chat_bridge.go b/backend/internal/service/openai_gateway_grok_chat_bridge.go index 27e49773e9..284b0dcf9f 100644 --- a/backend/internal/service/openai_gateway_grok_chat_bridge.go +++ b/backend/internal/service/openai_gateway_grok_chat_bridge.go @@ -498,7 +498,7 @@ func grokChatResponsesCacheIntentBody(body []byte) ([]byte, error) { func grokChatResponsesBridgeModel(model string) bool { switch strings.ToLower(xai.StripGrokProviderPrefix(strings.TrimSpace(model))) { - case "grok-4.5", "grok-4.6", "grok-4.6-latest": + case "grok-4.5", "grok-4.6", "grok-4.6-latest", "grok-4.7", "grok-4.7-latest": return true default: return false diff --git a/backend/internal/service/opencode_go.go b/backend/internal/service/opencode_go.go index a4bf6b1500..5e2dcc78a9 100644 --- a/backend/internal/service/opencode_go.go +++ b/backend/internal/service/opencode_go.go @@ -27,6 +27,7 @@ const ( // 供 /v1/models 在尚未同步上游列表时回退,以及账号白名单预填。 func DefaultOpenCodeGoModelIDs() []string { return []string{ + "grok-4.7", "grok-4.6", "gpt-5.6-luna", "glm-5.3-flash", diff --git a/backend/internal/service/upstream_response_model.go b/backend/internal/service/upstream_response_model.go index 766657f843..b22ec1c4a8 100644 --- a/backend/internal/service/upstream_response_model.go +++ b/backend/internal/service/upstream_response_model.go @@ -299,6 +299,8 @@ func canonicalGrokBuildRuntimeModel(model string) string { return "grok-4.5-build" case "grok-4.6", "grok-4.6-latest", "grok-4.6-build": return "grok-4.6-build" + case "grok-4.7", "grok-4.7-latest", "grok-4.7-build": + return "grok-4.7-build" default: return "" } diff --git a/backend/resources/model-pricing/model_prices_and_context_window.json b/backend/resources/model-pricing/model_prices_and_context_window.json index 579d18db86..bab05e4188 100644 --- a/backend/resources/model-pricing/model_prices_and_context_window.json +++ b/backend/resources/model-pricing/model_prices_and_context_window.json @@ -1,4 +1,15 @@ { + "grok-4.7": { + "cache_read_input_token_cost": 5e-07, + "input_cost_per_token": 2e-06, + "input_cost_per_token_above_200k_tokens": 4e-06, + "litellm_provider": "xai", + "max_input_tokens": 500000, + "mode": "chat", + "output_cost_per_token": 6e-06, + "output_cost_per_token_above_200k_tokens": 1.2e-05, + "supports_prompt_caching": true + }, "claude-3-7-sonnet-20250219": { "cache_creation_input_token_cost": 3.75e-06, "cache_creation_input_token_cost_above_1hr": 6e-06, diff --git a/frontend/src/composables/useModelWhitelist.ts b/frontend/src/composables/useModelWhitelist.ts index 91a847bf19..80696b28f8 100644 --- a/frontend/src/composables/useModelWhitelist.ts +++ b/frontend/src/composables/useModelWhitelist.ts @@ -144,6 +144,7 @@ const metaModels = [ // xAI Grok const xaiModels = [ + 'grok-4.7', 'grok-4.6', 'grok-4.5', 'grok-4.3', @@ -326,6 +327,7 @@ const geminiPresetMappings = [ ] const grokPresetMappings = [ + { label: 'Grok 4.7', from: 'grok-4.7', to: 'grok-4.7', color: 'bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-800/50 dark:text-slate-300' }, { label: 'Grok 4.6', from: 'grok-4.6', to: 'grok-4.6', color: 'bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-800/50 dark:text-slate-300' }, { label: 'Grok 4.5', from: 'grok-4.5', to: 'grok-4.5', color: 'bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-800/50 dark:text-slate-300' }, { label: 'Grok 4.3', from: 'grok-4.3', to: 'grok-4.3', color: 'bg-slate-100 text-slate-700 hover:bg-slate-200 dark:bg-slate-800/50 dark:text-slate-300' }, @@ -454,7 +456,7 @@ export function getModelsByPlatform(platform: string): string[] { case 'moonshot': case 'kimi': return moonshotModels case 'opencode_go': return [ - 'grok-4.6', 'gpt-5.6-luna', + 'grok-4.7', 'grok-4.6', 'gpt-5.6-luna', 'glm-5.3-flash', 'glm-5.3', 'glm-5.2', 'glm-5.1', 'kimi-k3', 'kimi-k2.7-code', 'kimi-k2.6', 'longcat-2.0',