mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 11:43:23 +08:00
更新 Grok 默认模型与官方计费目录
This commit is contained in:
@@ -718,7 +718,7 @@ Sub2API supports both Grok subscription accounts through xAI OAuth and standard
|
||||
- Codex CLI style Responses WebSocket ingress is accepted on the Responses targets and bridged to xAI HTTP/SSE Responses upstream
|
||||
- Text models: `grok-4.5`, `grok-4.3`, `grok-build-0.1`, `grok-composer-2.5-fast`, `grok-4.20-0309-reasoning`, `grok-4.20-0309-non-reasoning`, and `grok-4.20-multi-agent-0309`
|
||||
- Media targets for Grok groups: `/v1/images/generations`, `/images/generations`, `/v1/images/edits`, `/images/edits`, `/v1/videos/generations`, `/videos/generations`, `/v1/videos/edits`, `/videos/edits`, `/v1/videos/extensions`, `/videos/extensions`, `/v1/videos/{request_id}`, and `/videos/{request_id}`. Generation, editing, and extension requests require the group image-generation permission.
|
||||
- Media models: `grok-imagine`, `grok-imagine-image-quality`, `grok-imagine-image`, `grok-imagine-edit`, `grok-imagine-video`, and `grok-imagine-video-1.5`
|
||||
- Media models: `grok-imagine`, `grok-imagine-image-quality`, `grok-imagine-image`, `grok-imagine-image-2.0`, `grok-imagine-edit`, `grok-imagine-video`, and `grok-imagine-video-1.5`
|
||||
- JSON image-edit and video-generation requests accept image references in `image`, `images`, `reference_images`, and `mask` objects. Use `url` for xAI-compatible payloads; the legacy `image_url` field remains accepted and is normalized to `url` before forwarding.
|
||||
- Out of scope for this provider: TTS, transcription, browser automation, cookies, and Grok web scraping
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ type Model struct {
|
||||
// DefaultTextModel is the built-in fallback for empty model fields and Grok
|
||||
// text aliases (e.g. "grok", "grok-latest"). Operators may override the runtime
|
||||
// default via settings key grok_default_text_model.
|
||||
const DefaultTextModel = "grok-4.5"
|
||||
const DefaultTextModel = "grok-4.6"
|
||||
|
||||
// Official Imagine model IDs (https://docs.x.ai/docs/models).
|
||||
const (
|
||||
@@ -66,10 +66,10 @@ const (
|
||||
// ModelMappingOptions controls optional expansions of the default mapping.
|
||||
// Cross-client wildcards (gpt-*/claude-*) default ON via settings
|
||||
// grok_cross_client_model_map_enabled so Codex/Claude clients keep working
|
||||
// against Grok groups (map to DefaultText / grok-4.5). Operators may disable.
|
||||
// against Grok groups (map to DefaultText / grok-4.6). Operators may disable.
|
||||
type ModelMappingOptions struct {
|
||||
// DefaultText is the target for empty models and optional cross-client maps.
|
||||
// Empty → DefaultTextModel (grok-4.5).
|
||||
// Empty → DefaultTextModel (grok-4.6).
|
||||
DefaultText string
|
||||
// EnableCrossClientMap merges gpt-*/codex-*/o*/claude-* → DefaultText.
|
||||
EnableCrossClientMap bool
|
||||
@@ -115,7 +115,7 @@ var grokTextResponsesModelAliases = map[string]string{
|
||||
"grok-3-mini": "grok-3-mini",
|
||||
"grok-3-mini-fast": "grok-3-mini-fast",
|
||||
"grok-build": "grok-build-0.1",
|
||||
"grok-build-latest": DefaultTextModel,
|
||||
"grok-build-latest": "grok-build-0.1",
|
||||
"grok-build-0.1": "grok-build-0.1",
|
||||
"grok-composer-2.5-fast": "grok-composer-2.5-fast",
|
||||
"grok-composer": "grok-composer-2.5-fast",
|
||||
@@ -161,7 +161,7 @@ func ModelMappingWithOptions(opts ModelMappingOptions) map[string]string {
|
||||
}
|
||||
for alias, canonical := range grokTextResponsesModelAliases {
|
||||
// Remap aliases that pointed at DefaultTextModel constant to runtime default.
|
||||
if (alias == "grok" || alias == "grok-latest" || alias == "grok-build-latest") && canonical == DefaultTextModel {
|
||||
if (alias == "grok" || alias == "grok-latest") && canonical == DefaultTextModel {
|
||||
mapping[alias] = defaultText
|
||||
} else {
|
||||
mapping[alias] = canonical
|
||||
@@ -272,7 +272,7 @@ func ResolveGrokTextResponsesModelID(model string, defaultText ...string) string
|
||||
}
|
||||
normalized := strings.ToLower(StripGrokProviderPrefix(trimmed))
|
||||
if canonical, ok := grokTextResponsesModelAliases[normalized]; ok {
|
||||
if (normalized == "grok" || normalized == "grok-latest" || normalized == "grok-build-latest") && canonical == DefaultTextModel {
|
||||
if (normalized == "grok" || normalized == "grok-latest") && canonical == DefaultTextModel {
|
||||
return fallback
|
||||
}
|
||||
return canonical
|
||||
|
||||
@@ -12,14 +12,14 @@ func TestDefaultModelMappingExcludesCrossClientWildcards(t *testing.T) {
|
||||
SetRuntimeModelMappingOptions(ModelMappingOptions{})
|
||||
mapping := DefaultModelMapping()
|
||||
|
||||
require.Equal(t, "grok-4.5", mapping["grok"])
|
||||
require.Equal(t, "grok-4.5", mapping["grok-latest"])
|
||||
require.Equal(t, "grok-4.6", mapping["grok"])
|
||||
require.Equal(t, "grok-4.6", mapping["grok-latest"])
|
||||
require.Equal(t, "grok-build-0.1", mapping["grok-build"])
|
||||
require.Equal(t, DefaultTextModel, mapping["grok-build-latest"])
|
||||
require.Equal(t, "grok-build-0.1", mapping["grok-build-latest"])
|
||||
require.Equal(t, DefaultImagineImageQualityModel, mapping["grok-imagine-edit"])
|
||||
require.Equal(t, DefaultImagineVideo15Model, mapping["grok-imagine-video-1.5"])
|
||||
require.Equal(t, DefaultImagineVideo15Model, mapping["grok-imagine-video-1.5-preview"])
|
||||
require.Equal(t, "grok-4.5", mapping["xai/grok"])
|
||||
require.Equal(t, "grok-4.6", mapping["xai/grok"])
|
||||
|
||||
// Cross-vendor wildcards must stay opt-in.
|
||||
_, hasGPT := mapping["gpt-*"]
|
||||
@@ -68,7 +68,7 @@ func TestDefaultModelsIncludesGrok46(t *testing.T) {
|
||||
|
||||
func TestResolveGrokTextResponsesModelID(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, "grok-4.5", ResolveGrokTextResponsesModelID(""))
|
||||
require.Equal(t, "grok-4.6", ResolveGrokTextResponsesModelID(""))
|
||||
require.Equal(t, "grok-4.3", ResolveGrokTextResponsesModelID("grok", "grok-4.3"))
|
||||
require.Equal(t, "grok-4.20-multi-agent-0309", ResolveGrokTextResponsesModelID("grok-4.20-multi-agent"))
|
||||
}
|
||||
@@ -78,3 +78,9 @@ func TestExplicitGrok45DoesNotFollowRuntimeDefault(t *testing.T) {
|
||||
require.Equal(t, "grok-4.5", ResolveGrokTextResponsesModelID("grok-4.5-latest", "grok-4.6"))
|
||||
require.Equal(t, "grok-4.6", ResolveGrokTextResponsesModelID("grok", "grok-4.6"))
|
||||
}
|
||||
|
||||
func TestBareGrokAliasesFollowGrok46Default(t *testing.T) {
|
||||
require.Equal(t, "grok-4.6", ResolveGrokTextResponsesModelID("grok"))
|
||||
require.Equal(t, "grok-4.6", ResolveGrokTextResponsesModelID("grok-latest"))
|
||||
require.Equal(t, "grok-build-0.1", ResolveGrokTextResponsesModelID("grok-build-latest"))
|
||||
}
|
||||
|
||||
@@ -350,14 +350,14 @@ func TestDefaultModelMappingIncludesGrokAliases(t *testing.T) {
|
||||
t.Cleanup(func() { SetRuntimeModelMappingOptions(original) })
|
||||
SetRuntimeModelMappingOptions(ModelMappingOptions{})
|
||||
mapping := DefaultModelMapping()
|
||||
require.Equal(t, "grok-4.5", mapping["grok"])
|
||||
require.Equal(t, "grok-4.5", mapping["grok-latest"])
|
||||
require.Equal(t, "grok-4.6", mapping["grok"])
|
||||
require.Equal(t, "grok-4.6", mapping["grok-latest"])
|
||||
require.Equal(t, "grok-4.6", mapping["grok-4.6"])
|
||||
require.Equal(t, "grok-4.6", mapping["grok-4.6-latest"])
|
||||
require.Equal(t, "grok-4.5", mapping["grok-4.5"])
|
||||
require.Equal(t, "grok-4.5", mapping["grok-4.5-latest"])
|
||||
require.Equal(t, "grok-build-0.1", mapping["grok-build"])
|
||||
require.Equal(t, "grok-4.5", mapping["grok-build-latest"])
|
||||
require.Equal(t, "grok-build-0.1", mapping["grok-build-latest"])
|
||||
require.Equal(t, "grok-composer-2.5-fast", mapping["grok-composer"])
|
||||
require.Equal(t, "grok-composer-2.5-fast", mapping["composer-2.5"])
|
||||
require.Equal(t, "grok-4.20-0309-reasoning", mapping["grok-4.20-reasoning"])
|
||||
@@ -368,7 +368,7 @@ func TestDefaultModelMappingIncludesGrokAliases(t *testing.T) {
|
||||
require.Equal(t, DefaultImagineImageQualityModel, mapping["grok-imagine-image-quality"])
|
||||
require.Equal(t, DefaultImagineImageQualityModel, mapping["grok-imagine-edit"])
|
||||
require.Equal(t, DefaultImagineVideoModel, mapping["grok-imagine-video"])
|
||||
require.Equal(t, DefaultImagineVideo15LegacyModel, mapping["grok-imagine-video-1.5"])
|
||||
require.Equal(t, DefaultImagineVideo15Model, mapping["grok-imagine-video-1.5"])
|
||||
require.Equal(t, DefaultImagineVideo15Model, mapping["grok-imagine-video-1.5-preview"])
|
||||
_, hasGPT := mapping["gpt-*"]
|
||||
require.False(t, hasGPT, "cross-client wildcards must be opt-in")
|
||||
|
||||
@@ -667,7 +667,8 @@ func (s *BillingService) initFallbackPricing() {
|
||||
SupportsCacheBreakdown: false,
|
||||
}
|
||||
|
||||
// xAI Grok 4.5: $2 input / $0.30 cached input / $6 output below 200k.
|
||||
// xAI Grok 4.5: $2 input / $0.30 cached input / $6 output below 200k;
|
||||
// long-context rates are $4 / $0.60 / $12 (>=200k prompt tokens).
|
||||
s.fallbackPrices["grok-4.5"] = &ModelPricing{
|
||||
InputPricePerToken: 2e-6,
|
||||
OutputPricePerToken: 6e-6,
|
||||
@@ -679,9 +680,8 @@ func (s *BillingService) initFallbackPricing() {
|
||||
LongContextOutputMultiplier: 2,
|
||||
}
|
||||
|
||||
// xAI Grok 4.6 (docs.x.ai/developers/models: $2 input / $0.50 cached input /
|
||||
// $6 output per MTok under 200k prompt tokens; ≥200k is 2× on input,
|
||||
// cached input, and output).
|
||||
// xAI Grok 4.6: $2 input / $0.50 cached input / $6 output below 200k;
|
||||
// long-context rates are $4 / $1 / $12 (>=200k prompt tokens).
|
||||
s.fallbackPrices["grok-4.6"] = &ModelPricing{
|
||||
InputPricePerToken: 2e-6,
|
||||
OutputPricePerToken: 6e-6,
|
||||
@@ -693,7 +693,8 @@ func (s *BillingService) initFallbackPricing() {
|
||||
LongContextOutputMultiplier: 2,
|
||||
}
|
||||
|
||||
// xAI Grok 4.3: $1.25 input / $0.20 cached / $2.50 output below 200k.
|
||||
// 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{
|
||||
InputPricePerToken: 1.25e-6,
|
||||
OutputPricePerToken: 2.5e-6,
|
||||
@@ -704,6 +705,18 @@ func (s *BillingService) initFallbackPricing() {
|
||||
LongContextInputMultiplier: 2,
|
||||
LongContextOutputMultiplier: 2,
|
||||
}
|
||||
// Grok 4.20 variants share the official $1.25 / $0.20 / $2.50 card
|
||||
// (and $2.50 / $0.40 / $5 long-context rates) with Grok 4.3.
|
||||
s.fallbackPrices["grok-4.20"] = &ModelPricing{
|
||||
InputPricePerToken: 1.25e-6,
|
||||
OutputPricePerToken: 2.5e-6,
|
||||
CacheReadPricePerToken: 0.2e-6,
|
||||
SupportsCacheBreakdown: false,
|
||||
LongContextInputThreshold: 200000,
|
||||
LongContextThresholdInclusive: true,
|
||||
LongContextInputMultiplier: 2,
|
||||
LongContextOutputMultiplier: 2,
|
||||
}
|
||||
|
||||
// Keep legacy Grok 3 Mini requests on their own historical xAI price card;
|
||||
// otherwise the generic Grok fallback bills them as Grok 4.5.
|
||||
@@ -924,21 +937,22 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing {
|
||||
}
|
||||
|
||||
switch modelLower {
|
||||
case "grok", "grok-latest", "grok-4.5", "grok-4.5-latest":
|
||||
return s.fallbackPrices["grok-4.5"]
|
||||
case "grok-4.6", "grok-4.6-latest":
|
||||
case "grok", "grok-latest", "grok-4.6", "grok-4.6-latest":
|
||||
return s.fallbackPrices["grok-4.6"]
|
||||
case "grok-4.5", "grok-4.5-latest":
|
||||
return s.fallbackPrices["grok-4.5"]
|
||||
case "grok-3-mini":
|
||||
return s.fallbackPrices["grok-3-mini"]
|
||||
case "grok-3-mini-fast":
|
||||
return s.fallbackPrices["grok-3-mini-fast"]
|
||||
case "grok-4.3",
|
||||
"grok-4.20-0309-reasoning",
|
||||
case "grok-4.3":
|
||||
return s.fallbackPrices["grok-4.3"]
|
||||
case "grok-4.20-0309-reasoning",
|
||||
"grok-4.20-0309-non-reasoning",
|
||||
"grok-4.20-multi-agent-0309",
|
||||
"grok-4.20-reasoning",
|
||||
"grok-4.20-non-reasoning":
|
||||
return s.fallbackPrices["grok-4.3"]
|
||||
return s.fallbackPrices["grok-4.20"]
|
||||
case "grok-build", "grok-build-latest", "grok-build-0.1", "grok-composer", "grok-composer-2.5-fast", "composer-2.5":
|
||||
return s.fallbackPrices["grok-build-0.1"]
|
||||
}
|
||||
@@ -956,7 +970,7 @@ func (s *BillingService) grokUnknownTextFamilyFallback(model string) *ModelPrici
|
||||
if s == nil || !isGrokUnknownTextFamilyModel(model) {
|
||||
return nil
|
||||
}
|
||||
return s.fallbackPrices["grok-4.5"]
|
||||
return s.fallbackPrices["grok-4.6"]
|
||||
}
|
||||
|
||||
func isGrokUnknownTextFamilyModel(model string) bool {
|
||||
|
||||
@@ -1179,7 +1179,7 @@ func TestCalculateCostWithLongContext_PropagatesError(t *testing.T) {
|
||||
func TestGetModelPricing_Grok45OfficialFallback(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
for _, model := range []string{"grok", "grok-latest", "grok-4.5", "grok-4.5-latest"} {
|
||||
for _, model := range []string{"grok-4.5", "grok-4.5-latest"} {
|
||||
model := model
|
||||
t.Run(model, func(t *testing.T) {
|
||||
pricing, err := svc.GetModelPricing(model)
|
||||
@@ -1192,6 +1192,17 @@ func TestGetModelPricing_Grok45OfficialFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelPricing_GrokBareAliasesUseGrok46(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
for _, model := range []string{"grok", "grok-latest"} {
|
||||
pricing, err := svc.GetModelPricing(model)
|
||||
require.NoError(t, err)
|
||||
require.InDelta(t, 2e-6, pricing.InputPricePerToken, 1e-12)
|
||||
require.InDelta(t, 0.5e-6, pricing.CacheReadPricePerToken, 1e-12)
|
||||
require.InDelta(t, 6e-6, pricing.OutputPricePerToken, 1e-12)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelPricing_Grok46OfficialFallback(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
|
||||
@@ -1211,6 +1222,25 @@ func TestGetModelPricing_Grok46OfficialFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModelPricing_GrokOfficialFamilyCards(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
for _, tc := range []struct {
|
||||
model string
|
||||
input, cached, output float64
|
||||
}{
|
||||
{"grok-4.3", 1.25e-6, 0.2e-6, 2.5e-6},
|
||||
{"grok-4.20-0309-reasoning", 1.25e-6, 0.2e-6, 2.5e-6},
|
||||
{"grok-build-0.1", 1e-6, 0.2e-6, 2e-6},
|
||||
} {
|
||||
p, err := svc.GetModelPricing(tc.model)
|
||||
require.NoError(t, err, tc.model)
|
||||
require.InDelta(t, tc.input, p.InputPricePerToken, 1e-12)
|
||||
require.InDelta(t, tc.cached, p.CacheReadPricePerToken, 1e-12)
|
||||
require.InDelta(t, tc.output, p.OutputPricePerToken, 1e-12)
|
||||
require.Equal(t, 200000, p.LongContextInputThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCostUnified_GroupLongContextToggleUsesPresetLadder(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
resolver := NewModelPricingResolver(nil, svc)
|
||||
@@ -1234,9 +1264,9 @@ func TestCalculateCostUnified_GroupLongContextToggleUsesPresetLadder(t *testing.
|
||||
require.InDelta(t, disabled.OutputCost*2, enabled.OutputCost, 1e-12)
|
||||
}
|
||||
|
||||
func TestGetModelPricing_UnknownGrokTextFallsBackToGrok45(t *testing.T) {
|
||||
func TestGetModelPricing_UnknownGrokTextFallsBackToGrok46(t *testing.T) {
|
||||
svc := newTestBillingService()
|
||||
baseline, err := svc.GetModelPricing("grok-4.5")
|
||||
baseline, err := svc.GetModelPricing("grok-4.6")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, model := range []string{"grok-5", "grok-5-latest", "x-ai/grok-7", "grok-4.7-beta"} {
|
||||
|
||||
@@ -193,7 +193,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
|
||||
SettingKeyChannelMonitorShowQuota: "false",
|
||||
|
||||
// Grok: safe defaults — no cross-vendor model rewrite unless operators enable it.
|
||||
SettingKeyGrokDefaultTextModel: "grok-4.5",
|
||||
SettingKeyGrokDefaultTextModel: "grok-4.6",
|
||||
SettingKeyGrokCrossClientModelMapEnabled: "true",
|
||||
SettingKeyGrokDefaultBaseURLMode: GrokDefaultBaseURLModeCLI,
|
||||
|
||||
@@ -806,7 +806,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
// Grok default mapping policy
|
||||
result.GrokDefaultTextModel = strings.TrimSpace(settings[SettingKeyGrokDefaultTextModel])
|
||||
if result.GrokDefaultTextModel == "" {
|
||||
result.GrokDefaultTextModel = "grok-4.5"
|
||||
result.GrokDefaultTextModel = "grok-4.6"
|
||||
}
|
||||
// Default true (missing/empty → enabled) so Claude/Codex→Grok mapping keeps working.
|
||||
// Operators can set false to disable silent cross-client rewrite.
|
||||
|
||||
@@ -423,7 +423,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
if v := strings.TrimSpace(settings.GrokDefaultTextModel); v != "" {
|
||||
updates[SettingKeyGrokDefaultTextModel] = v
|
||||
} else {
|
||||
updates[SettingKeyGrokDefaultTextModel] = "grok-4.5"
|
||||
updates[SettingKeyGrokDefaultTextModel] = "grok-4.6"
|
||||
}
|
||||
updates[SettingKeyGrokCrossClientModelMapEnabled] = strconv.FormatBool(settings.GrokCrossClientModelMapEnabled)
|
||||
updates[SettingKeyGrokDefaultBaseURLMode] = normalizeGrokDefaultBaseURLMode(settings.GrokDefaultBaseURLMode)
|
||||
|
||||
@@ -51,7 +51,8 @@ describe('useModelWhitelist', () => {
|
||||
expect(models).toContain('grok-4.5')
|
||||
expect(models).toContain('grok-4.5-latest')
|
||||
expect(models).toContain('grok-build-latest')
|
||||
expect(models).toContain('grok-imagine-video-1.5-preview')
|
||||
expect(models).toContain('grok-imagine-image-2.0')
|
||||
expect(models).toContain('grok-imagine-video-1.5')
|
||||
})
|
||||
|
||||
it('combined 模式支持 Grok 4.5 官方别名映射', () => {
|
||||
|
||||
@@ -157,8 +157,8 @@ const xaiModels = [
|
||||
'grok-imagine',
|
||||
'grok-imagine-image-quality',
|
||||
'grok-imagine-image',
|
||||
'grok-imagine-image-2.0',
|
||||
'grok-imagine-video',
|
||||
'grok-imagine-video-1.5-preview',
|
||||
'grok-imagine-video-1.5'
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user