diff --git a/backend/internal/pkg/claude/constants.go b/backend/internal/pkg/claude/constants.go index 8b3441dc..eecee11e 100644 --- a/backend/internal/pkg/claude/constants.go +++ b/backend/internal/pkg/claude/constants.go @@ -71,6 +71,12 @@ var DefaultModels = []Model{ DisplayName: "Claude Opus 4.5", CreatedAt: "2025-11-01T00:00:00Z", }, + { + ID: "claude-opus-4-6", + Type: "model", + DisplayName: "Claude Opus 4.6", + CreatedAt: "2026-02-06T00:00:00Z", + }, { ID: "claude-sonnet-4-5-20250929", Type: "model", diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index f38f0cfa..562d2feb 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -1089,8 +1089,9 @@ func (r *accountRepository) UpdateExtra(ctx context.Context, id int64, updates m result, err := client.ExecContext( ctx, "UPDATE accounts SET extra = COALESCE(extra, '{}'::jsonb) || $1::jsonb, updated_at = NOW() WHERE id = $2 AND deleted_at IS NULL", - payload, id, + string(payload), id, ) + if err != nil { return err } diff --git a/backend/internal/service/gateway_cached_tokens_test.go b/backend/internal/service/gateway_cached_tokens_test.go index a51e928c..f886c855 100644 --- a/backend/internal/service/gateway_cached_tokens_test.go +++ b/backend/internal/service/gateway_cached_tokens_test.go @@ -102,8 +102,10 @@ func TestStreamingReconcile_MessageStart(t *testing.T) { } // 验证 cache_read_input_tokens 已被填充 - msg := event["message"].(map[string]any) - usage := msg["usage"].(map[string]any) + msg, ok := event["message"].(map[string]any) + require.True(t, ok) + usage, ok := msg["usage"].(map[string]any) + require.True(t, ok) assert.Equal(t, float64(23), usage["cache_read_input_tokens"]) // 验证重新序列化后 JSON 也包含正确值 @@ -134,8 +136,10 @@ func TestStreamingReconcile_MessageStart_NativeClaude(t *testing.T) { } } - msg := event["message"].(map[string]any) - usage := msg["usage"].(map[string]any) + msg, ok := event["message"].(map[string]any) + require.True(t, ok) + usage, ok := msg["usage"].(map[string]any) + require.True(t, ok) assert.Equal(t, float64(30), usage["cache_read_input_tokens"]) } @@ -159,11 +163,9 @@ func TestStreamingReconcile_MessageDelta(t *testing.T) { require.Equal(t, "message_delta", eventType) // 模拟 processSSEEvent 中的 reconcile 逻辑 - if u, ok := event["usage"].(map[string]any); ok { - reconcileCachedTokens(u) - } - - usage := event["usage"].(map[string]any) + usage, ok := event["usage"].(map[string]any) + require.True(t, ok) + reconcileCachedTokens(usage) assert.Equal(t, float64(15), usage["cache_read_input_tokens"]) } @@ -179,11 +181,9 @@ func TestStreamingReconcile_MessageDelta_NativeClaude(t *testing.T) { var event map[string]any require.NoError(t, json.Unmarshal([]byte(eventJSON), &event)) - if u, ok := event["usage"].(map[string]any); ok { - reconcileCachedTokens(u) - } - - usage := event["usage"].(map[string]any) + usage, ok := event["usage"].(map[string]any) + require.True(t, ok) + reconcileCachedTokens(usage) _, hasCacheRead := usage["cache_read_input_tokens"] assert.False(t, hasCacheRead, "不应为原生 Claude 响应注入 cache_read_input_tokens") } @@ -251,20 +251,8 @@ func TestNonStreamingReconcile_NativeClaude(t *testing.T) { } require.NoError(t, json.Unmarshal(body, &response)) - originalBody := make([]byte, len(body)) - copy(originalBody, body) - - if response.Usage.CacheReadInputTokens == 0 { - cachedTokens := gjson.GetBytes(body, "usage.cached_tokens").Int() - if cachedTokens > 0 { - response.Usage.CacheReadInputTokens = int(cachedTokens) - if newBody, err := sjson.SetBytes(body, "usage.cache_read_input_tokens", cachedTokens); err == nil { - body = newBody - } - } - } - - // 不应修改 + // CacheReadInputTokens == 30,条件不成立,整个 reconcile 分支不会执行 + assert.NotZero(t, response.Usage.CacheReadInputTokens) assert.Equal(t, 30, response.Usage.CacheReadInputTokens) } diff --git a/backend/internal/service/pricing_service.go b/backend/internal/service/pricing_service.go index bad08894..d8db0d67 100644 --- a/backend/internal/service/pricing_service.go +++ b/backend/internal/service/pricing_service.go @@ -579,6 +579,7 @@ func (s *PricingService) extractBaseName(model string) string { func (s *PricingService) matchByModelFamily(model string) *LiteLLMModelPricing { // Claude模型系列匹配规则 familyPatterns := map[string][]string{ + "opus-4.6": {"claude-opus-4.6", "claude-opus-4-6"}, "opus-4.5": {"claude-opus-4.5", "claude-opus-4-5"}, "opus-4": {"claude-opus-4", "claude-3-opus"}, "sonnet-4.5": {"claude-sonnet-4.5", "claude-sonnet-4-5"}, diff --git a/frontend/src/components/account/BulkEditAccountModal.vue b/frontend/src/components/account/BulkEditAccountModal.vue index 1f6b487b..3aa49481 100644 --- a/frontend/src/components/account/BulkEditAccountModal.vue +++ b/frontend/src/components/account/BulkEditAccountModal.vue @@ -707,6 +707,7 @@ const groupIds = ref([]) // All models list (combined Anthropic + OpenAI) const allModels = [ + { value: 'claude-opus-4-6', label: 'Claude Opus 4.6' }, { value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5' }, { value: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' }, { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5' }, @@ -746,6 +747,13 @@ const presetMappings = [ 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->Sonnet', from: 'claude-opus-4-5-20251101', diff --git a/frontend/src/composables/useModelWhitelist.ts b/frontend/src/composables/useModelWhitelist.ts index 2f5fa548..cb078267 100644 --- a/frontend/src/composables/useModelWhitelist.ts +++ b/frontend/src/composables/useModelWhitelist.ts @@ -38,6 +38,7 @@ export const claudeModels = [ 'claude-opus-4-1-20250805', 'claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001', 'claude-opus-4-5-20251101', + 'claude-opus-4-6', 'claude-2.1', 'claude-2.0', 'claude-instant-1.2' ] @@ -227,9 +228,10 @@ const anthropicPresetMappings = [ { label: 'Sonnet 4', from: 'claude-sonnet-4-20250514', to: 'claude-sonnet-4-20250514', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' }, { label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4-5-20250929', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' }, { 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: '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-5-20251101', 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' } + { 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' } ] const openaiPresetMappings = [