diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go index 27c543dd..7601f35b 100644 --- a/backend/internal/domain/constants.go +++ b/backend/internal/domain/constants.go @@ -72,6 +72,7 @@ const ( // 与前端 useModelWhitelist.ts 中的 antigravityDefaultMappings 保持一致 var DefaultAntigravityModelMapping = map[string]string{ // Claude 白名单 + "claude-opus-4-8": "claude-opus-4-8", // 官方模型 "claude-opus-4-7": "claude-opus-4-7", // 官方模型 "claude-opus-4-6-thinking": "claude-opus-4-6-thinking", // 官方模型 "claude-opus-4-6": "claude-opus-4-6-thinking", // 简称映射 @@ -122,6 +123,7 @@ var DefaultAntigravityModelMapping = map[string]string{ // aws_region 自动调整为匹配的区域前缀(如 eu.、apac.、jp. 等) var DefaultBedrockModelMapping = map[string]string{ // Claude Opus + "claude-opus-4-8": "us.anthropic.claude-opus-4-8-v1", "claude-opus-4-7": "us.anthropic.claude-opus-4-7-v1", "claude-opus-4-6-thinking": "us.anthropic.claude-opus-4-6-v1", "claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1", diff --git a/backend/internal/domain/constants_test.go b/backend/internal/domain/constants_test.go index de66137f..fe6272c5 100644 --- a/backend/internal/domain/constants_test.go +++ b/backend/internal/domain/constants_test.go @@ -24,3 +24,27 @@ func TestDefaultAntigravityModelMapping_ImageCompatibilityAliases(t *testing.T) } } } + +func TestDefaultAntigravityModelMapping_ContainsOpus48(t *testing.T) { + t.Parallel() + + got, ok := DefaultAntigravityModelMapping["claude-opus-4-8"] + if !ok { + t.Fatal("expected mapping for claude-opus-4-8 to exist") + } + if got != "claude-opus-4-8" { + t.Fatalf("unexpected claude-opus-4-8 mapping: got %q", got) + } +} + +func TestDefaultBedrockModelMapping_ContainsOpus48(t *testing.T) { + t.Parallel() + + got, ok := DefaultBedrockModelMapping["claude-opus-4-8"] + if !ok { + t.Fatal("expected Bedrock mapping for claude-opus-4-8 to exist") + } + if got != "us.anthropic.claude-opus-4-8-v1" { + t.Fatalf("unexpected Bedrock claude-opus-4-8 mapping: got %q", got) + } +} diff --git a/backend/internal/pkg/antigravity/claude_types.go b/backend/internal/pkg/antigravity/claude_types.go index 0b8ae5f2..b651db94 100644 --- a/backend/internal/pkg/antigravity/claude_types.go +++ b/backend/internal/pkg/antigravity/claude_types.go @@ -155,6 +155,7 @@ var claudeModels = []modelDef{ {ID: "claude-opus-4-6", DisplayName: "Claude Opus 4.6", CreatedAt: "2026-02-05T00:00:00Z"}, {ID: "claude-opus-4-6-thinking", DisplayName: "Claude Opus 4.6 Thinking", CreatedAt: "2026-02-05T00:00:00Z"}, {ID: "claude-opus-4-7", DisplayName: "Claude Opus 4.7", CreatedAt: "2026-04-17T00:00:00Z"}, + {ID: "claude-opus-4-8", DisplayName: "Claude Opus 4.8", CreatedAt: "2026-05-29T00:00:00Z"}, {ID: "claude-sonnet-4-6", DisplayName: "Claude Sonnet 4.6", CreatedAt: "2026-02-17T00:00:00Z"}, } diff --git a/backend/internal/pkg/antigravity/claude_types_test.go b/backend/internal/pkg/antigravity/claude_types_test.go index 9fc09b1b..fdf5c66d 100644 --- a/backend/internal/pkg/antigravity/claude_types_test.go +++ b/backend/internal/pkg/antigravity/claude_types_test.go @@ -12,6 +12,7 @@ func TestDefaultModels_ContainsNewAndLegacyImageModels(t *testing.T) { } requiredIDs := []string{ + "claude-opus-4-8", "claude-opus-4-6-thinking", "gemini-2.5-flash-image", "gemini-2.5-flash-image-preview", diff --git a/backend/internal/pkg/antigravity/request_transformer.go b/backend/internal/pkg/antigravity/request_transformer.go index b5de8166..9068ad97 100644 --- a/backend/internal/pkg/antigravity/request_transformer.go +++ b/backend/internal/pkg/antigravity/request_transformer.go @@ -204,6 +204,8 @@ type modelInfo struct { // 只有在此映射表中的模型才会注入身份提示词 // 注意:模型映射逻辑在网关层完成;这里仅用于按模型前缀判断是否注入身份提示词。 var modelInfoMap = map[string]modelInfo{ + "claude-opus-4-8": {DisplayName: "Claude Opus 4.8", CanonicalID: "claude-opus-4-8"}, + "claude-opus-4-7": {DisplayName: "Claude Opus 4.7", CanonicalID: "claude-opus-4-7"}, "claude-opus-4-5": {DisplayName: "Claude Opus 4.5", CanonicalID: "claude-opus-4-5-20250929"}, "claude-opus-4-6": {DisplayName: "Claude Opus 4.6", CanonicalID: "claude-opus-4-6"}, "claude-sonnet-4-6": {DisplayName: "Claude Sonnet 4.6", CanonicalID: "claude-sonnet-4-6"}, @@ -587,7 +589,8 @@ func maxOutputTokensLimit(model string) int { func isAntigravityOpusHighTierModel(model string) bool { lower := strings.ToLower(model) return strings.HasPrefix(lower, "claude-opus-4-6") || - strings.HasPrefix(lower, "claude-opus-4-7") + strings.HasPrefix(lower, "claude-opus-4-7") || + strings.HasPrefix(lower, "claude-opus-4-8") } func buildGenerationConfig(req *ClaudeRequest) *GeminiGenerationConfig { diff --git a/backend/internal/pkg/claude/constants.go b/backend/internal/pkg/claude/constants.go index 351f2f8b..dde84724 100644 --- a/backend/internal/pkg/claude/constants.go +++ b/backend/internal/pkg/claude/constants.go @@ -134,6 +134,12 @@ var DefaultModels = []Model{ DisplayName: "Claude Opus 4.7", CreatedAt: "2026-04-17T00:00:00Z", }, + { + ID: "claude-opus-4-8", + Type: "model", + DisplayName: "Claude Opus 4.8", + CreatedAt: "2026-05-29T00:00:00Z", + }, { ID: "claude-sonnet-4-6", Type: "model", diff --git a/backend/internal/service/antigravity_model_mapping_test.go b/backend/internal/service/antigravity_model_mapping_test.go index a29000e7..652b6d66 100644 --- a/backend/internal/service/antigravity_model_mapping_test.go +++ b/backend/internal/service/antigravity_model_mapping_test.go @@ -88,6 +88,18 @@ func TestAntigravityGatewayService_GetMappedModel(t *testing.T) { accountMapping: nil, expected: "claude-sonnet-4-5", }, + { + name: "默认映射透传 - claude-opus-4-8", + requestedModel: "claude-opus-4-8", + accountMapping: nil, + expected: "claude-opus-4-8", + }, + { + name: "默认映射透传 - claude-opus-4-7", + requestedModel: "claude-opus-4-7", + accountMapping: nil, + expected: "claude-opus-4-7", + }, { name: "默认映射透传 - claude-opus-4-6-thinking", requestedModel: "claude-opus-4-6-thinking", @@ -210,6 +222,7 @@ func TestAntigravityGatewayService_IsModelSupported(t *testing.T) { {"直接支持 - gemini-3-flash", "gemini-3-flash", true}, // 可映射(有明确前缀映射) + {"可映射 - claude-opus-4-8", "claude-opus-4-8", true}, {"可映射 - claude-opus-4-6", "claude-opus-4-6", true}, // 前缀透传(claude 和 gemini 前缀) diff --git a/backend/internal/service/bedrock_request_test.go b/backend/internal/service/bedrock_request_test.go index 94f1a118..c5b71da7 100644 --- a/backend/internal/service/bedrock_request_test.go +++ b/backend/internal/service/bedrock_request_test.go @@ -174,6 +174,7 @@ func TestIsBedrockClaude45OrNewer(t *testing.T) { expect bool }{ {"us.anthropic.claude-opus-4-6-v1", true}, + {"us.anthropic.claude-opus-4-8-v1", true}, {"us.anthropic.claude-sonnet-4-6", true}, {"us.anthropic.claude-sonnet-4-5-20250929-v1:0", true}, {"us.anthropic.claude-opus-4-5-20251101-v1:0", true}, @@ -511,6 +512,20 @@ func TestResolveBedrockModelID(t *testing.T) { assert.Equal(t, "au.anthropic.claude-opus-4-6-v1", modelID) }) + t.Run("default opus 4.8 mapping uses regional Bedrock model id", func(t *testing.T) { + account := &Account{ + Platform: PlatformAnthropic, + Type: AccountTypeBedrock, + Credentials: map[string]any{ + "aws_region": "eu-west-1", + }, + } + + modelID, ok := ResolveBedrockModelID(account, "claude-opus-4-8") + require.True(t, ok) + assert.Equal(t, "eu.anthropic.claude-opus-4-8-v1", modelID) + }) + t.Run("force global rewrites anthropic regional model id", func(t *testing.T) { account := &Account{ Platform: PlatformAnthropic, @@ -714,6 +729,7 @@ func TestIsBedrockOpus47OrNewer(t *testing.T) { modelID string expect bool }{ + {"us.anthropic.claude-opus-4-8-v1", true}, {"us.anthropic.claude-opus-4-7-v1", true}, {"us.anthropic.claude-opus-4-6-v1", false}, {"us.anthropic.claude-opus-4-5-20251101-v1:0", false}, @@ -886,10 +902,12 @@ func TestIsBedrockOpus47OrNewer_EdgeCases(t *testing.T) { modelID string expect bool }{ + {"anthropic.claude-opus-4-8-v1", true}, {"anthropic.claude-opus-4-7-v1", true}, {"us.anthropic.claude-opus-4-7-20270101-v1:0", true}, {"", false}, // Forward() passes parsed.Model (standard names), not Bedrock IDs + {"claude-opus-4-8", true}, {"claude-opus-4-7", true}, {"claude-opus-4-6", false}, {"claude-sonnet-4-7", false}, diff --git a/backend/migrations/144_add_opus48_to_model_mapping.sql b/backend/migrations/144_add_opus48_to_model_mapping.sql new file mode 100644 index 00000000..18fbff93 --- /dev/null +++ b/backend/migrations/144_add_opus48_to_model_mapping.sql @@ -0,0 +1,16 @@ +-- 为已持久化的 Antigravity model_mapping 添加 claude-opus-4-8。 +-- +-- 未持久化 model_mapping 的账号会直接使用 DefaultAntigravityModelMapping, +-- 因此这里只需要回填已有映射对象。 + +UPDATE accounts +SET credentials = jsonb_set( + credentials, + '{model_mapping,claude-opus-4-8}', + '"claude-opus-4-8"'::jsonb, + true +) +WHERE platform = 'antigravity' + AND deleted_at IS NULL + AND jsonb_typeof(credentials->'model_mapping') = 'object' + AND credentials->'model_mapping'->>'claude-opus-4-8' IS NULL; diff --git a/frontend/src/components/account/AccountStatusIndicator.vue b/frontend/src/components/account/AccountStatusIndicator.vue index 8438c584..a37b5c8e 100644 --- a/frontend/src/components/account/AccountStatusIndicator.vue +++ b/frontend/src/components/account/AccountStatusIndicator.vue @@ -222,6 +222,8 @@ const formatScopeName = (scope: string): string => { // Claude 系列 'claude-opus-4-6': 'COpus46', 'claude-opus-4-6-thinking': 'COpus46T', + 'claude-opus-4-7': 'COpus47', + 'claude-opus-4-8': 'COpus48', 'claude-sonnet-4-6': 'CSon46', 'claude-sonnet-4-5': 'CSon45', 'claude-sonnet-4-5-thinking': 'CSon45T', diff --git a/frontend/src/components/account/AccountUsageCell.vue b/frontend/src/components/account/AccountUsageCell.vue index 64f1366b..887fbf79 100644 --- a/frontend/src/components/account/AccountUsageCell.vue +++ b/frontend/src/components/account/AccountUsageCell.vue @@ -664,6 +664,7 @@ const antigravityClaudeUsageFromAPI = computed(() => getAntigravityUsageFromAPI([ 'claude-sonnet-4-5', 'claude-opus-4-5-thinking', 'claude-sonnet-4-6', 'claude-opus-4-6', 'claude-opus-4-6-thinking', + 'claude-opus-4-7', 'claude-opus-4-8', ]) ) diff --git a/frontend/src/composables/__tests__/useModelWhitelist.spec.ts b/frontend/src/composables/__tests__/useModelWhitelist.spec.ts index f2e19dc1..e64250a4 100644 --- a/frontend/src/composables/__tests__/useModelWhitelist.spec.ts +++ b/frontend/src/composables/__tests__/useModelWhitelist.spec.ts @@ -35,6 +35,11 @@ describe('useModelWhitelist', () => { expect(models).toContain('gemini-3-pro-image') }) + it('Claude 模型列表包含 Opus 4.8', () => { + expect(getModelsByPlatform('claude')).toContain('claude-opus-4-8') + expect(getModelsByPlatform('antigravity')).toContain('claude-opus-4-8') + }) + it('gemini 模型列表包含原生生图模型', () => { const models = getModelsByPlatform('gemini') diff --git a/frontend/src/composables/useModelWhitelist.ts b/frontend/src/composables/useModelWhitelist.ts index 1f747595..dc192180 100644 --- a/frontend/src/composables/useModelWhitelist.ts +++ b/frontend/src/composables/useModelWhitelist.ts @@ -29,6 +29,7 @@ export const claudeModels = [ 'claude-opus-4-5-20251101', 'claude-opus-4-6', 'claude-opus-4-7', + 'claude-opus-4-8', 'claude-sonnet-4-6' ] @@ -53,6 +54,7 @@ const antigravityModels = [ 'claude-opus-4-6', 'claude-opus-4-6-thinking', 'claude-opus-4-7', + 'claude-opus-4-8', 'claude-opus-4-5-thinking', 'claude-sonnet-4-6', 'claude-sonnet-4-5', @@ -238,6 +240,7 @@ const anthropicPresetMappings = [ { label: 'Opus 4.5', from: 'claude-opus-4-5-20251101', to: 'claude-opus-4-5-20251101', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' }, { label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'claude-opus-4-6', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' }, { label: 'Opus 4.7', from: 'claude-opus-4-7', to: 'claude-opus-4-7', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' }, + { label: 'Opus 4.8', from: 'claude-opus-4-8', to: 'claude-opus-4-8', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' }, { label: 'Haiku 3.5', from: 'claude-3-5-haiku-20241022', to: 'claude-3-5-haiku-20241022', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' }, { label: 'Haiku 4.5', from: 'claude-haiku-4-5-20251001', to: 'claude-haiku-4-5-20251001', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' }, { label: 'Opus->Sonnet', from: 'claude-opus-4-6', to: 'claude-sonnet-4-5-20250929', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' } @@ -297,13 +300,15 @@ const antigravityPresetMappings = [ { label: 'Sonnet 4.5', from: 'claude-sonnet-4-5', to: 'claude-sonnet-4-5', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' }, { label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'claude-opus-4-6-thinking', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, { label: 'Opus 4.6-thinking', from: 'claude-opus-4-6-thinking', to: 'claude-opus-4-6-thinking', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, - { label: 'Opus 4.7', from: 'claude-opus-4-7', to: 'claude-opus-4-7', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' } + { label: 'Opus 4.7', from: 'claude-opus-4-7', to: 'claude-opus-4-7', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, + { label: 'Opus 4.8', from: 'claude-opus-4-8', to: 'claude-opus-4-8', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' } ] // Bedrock 预设映射(与后端 DefaultBedrockModelMapping 保持一致) const bedrockPresetMappings = [ { label: 'Opus 4.6', from: 'claude-opus-4-6', to: 'us.anthropic.claude-opus-4-6-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, { label: 'Opus 4.7', from: 'claude-opus-4-7', to: 'us.anthropic.claude-opus-4-7-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, + { label: 'Opus 4.8', from: 'claude-opus-4-8', to: 'us.anthropic.claude-opus-4-8-v1', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, { label: 'Sonnet 4.6', from: 'claude-sonnet-4-6', to: 'us.anthropic.claude-sonnet-4-6', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' }, { label: 'Opus 4.5', from: 'claude-opus-4-5-thinking', to: 'us.anthropic.claude-opus-4-5-20251101-v1:0', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }, { label: 'Sonnet 4.5', from: 'claude-sonnet-4-5', to: 'us.anthropic.claude-sonnet-4-5-20250929-v1:0', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' },