mirror of
https://github.com/zhukunpenglinyutong/jetbrains-cc-gui.git
synced 2026-09-21 05:51:00 +08:00
fix(model): migrate retired claude-sonnet-4-7 default instead of pinning dead model
claude-sonnet-4-7 was retired from the Anthropic API but remains the default model hardcoded in both the webview (DEFAULT_CLAUDE_MODEL_ID, default props) and the Java side (SessionState, HandlerContext, ChatWindowDelegate, ClaudePricingTable). Every flow that falls back to the default - or restores a persisted tab that had Sonnet 4.7 selected - now fails on every send: There is an issue with the selected model (claude-sonnet-4-7[1m]). It may not exist or you may not have access to it. LEGACY_CLAUDE_MODEL_ID_ALIASES also mapped sonnet-4-6 -> sonnet-4-7, i.e. one retired id to another retired id, so normalizeClaudeModelId() kept returning a dead id (#1678). Webview: - DEFAULT_CLAUDE_MODEL_ID: sonnet-4-7 -> sonnet-5 (same-tier successor) - legacy aliases now map every retired id to a live model: sonnet-4-6/sonnet-4-7 -> sonnet-5, opus-4-6 -> opus-4-8 - remove claude-sonnet-4-7 from CLAUDE_MODELS and sonnet47 i18n entries; move the "Use the default model" description to Sonnet 5 (all locales) - default selectedModel props use DEFAULT_CLAUDE_MODEL_ID instead of duplicated literals Java: - sync all four defaults to claude-sonnet-5 - SessionState.setModel() migrates retired ids on write so restored .idea/claudeCodeTabState.xml tabs self-heal instead of spawning a CLI pinned to a dead model; [1m] suffix is preserved - lookup-table entries (pricing/context-limit/effort sets) keep the retired ids so historical usage stats keep resolving Fixes #1678
This commit is contained in:
@@ -17,7 +17,7 @@ import java.util.function.Supplier;
|
||||
*/
|
||||
public class HandlerContext {
|
||||
|
||||
public static final String DEFAULT_MODEL = "claude-sonnet-4-7";
|
||||
public static final String DEFAULT_MODEL = "claude-sonnet-5";
|
||||
public static final String DEFAULT_PROVIDER = "claude";
|
||||
|
||||
private final Project project;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.Map;
|
||||
*/
|
||||
public final class ClaudePricingTable {
|
||||
|
||||
public static final String DEFAULT_MODEL = "claude-sonnet-4-7";
|
||||
public static final String DEFAULT_MODEL = "claude-sonnet-5";
|
||||
|
||||
private static final ClaudePricing DEFAULT_PRICING = new ClaudePricing(3.0, 15.0, 3.75, 0.30);
|
||||
private static final ClaudePricing TIERED_SONNET_PRICING = new ClaudePricing(3.0, 15.0, 3.75, 0.30, 6.0, 22.5, 7.5, 0.60);
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SessionState {
|
||||
// explicit, informed opt-in — see security remediation A: shipping bypass as the
|
||||
// out-of-the-box default removed the only confirmation gate for AI-issued commands.
|
||||
private volatile String permissionMode = "default";
|
||||
private volatile String model = "claude-sonnet-4-7";
|
||||
private volatile String model = "claude-sonnet-5";
|
||||
private volatile String provider = "claude";
|
||||
// Reasoning effort (thinking depth). Null means "do not override SDK/settings".
|
||||
private volatile String reasoningEffort = null;
|
||||
@@ -213,7 +213,49 @@ public class SessionState {
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
this.model = normalizeRetiredModelId(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate retired Claude model ids to their live replacement on write.
|
||||
*
|
||||
* <p>Persisted tab state (.idea/claudeCodeTabState.xml) and history sessions keep
|
||||
* whatever model id was saved forever. When a model is retired from the API
|
||||
* (sonnet-4-6, sonnet-4-7, ...), restoring such a tab would otherwise spawn a CLI
|
||||
* pinned to a dead model that fails on every send ("It may not exist or you may
|
||||
* not have access to it") - see #1678. Migrating here self-heals restored tabs
|
||||
* without touching the persisted XML.</p>
|
||||
*
|
||||
* @param model raw model id (may be null, blank, carry a [1m] suffix, or be retired)
|
||||
* @return the model id to store - retired ids mapped to their live replacement,
|
||||
* anything else (including non-Claude ids) passed through unchanged
|
||||
*/
|
||||
static String normalizeRetiredModelId(String model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = model.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return model;
|
||||
}
|
||||
String base = trimmed;
|
||||
boolean oneM = false;
|
||||
if (base.endsWith("[1m]")) {
|
||||
base = base.substring(0, base.length() - "[1m]".length());
|
||||
oneM = true;
|
||||
}
|
||||
switch (base) {
|
||||
case "claude-sonnet-4-6":
|
||||
case "claude-sonnet-4-7":
|
||||
base = "claude-sonnet-5";
|
||||
break;
|
||||
case "claude-opus-4-6":
|
||||
base = "claude-opus-4-8";
|
||||
break;
|
||||
default:
|
||||
return trimmed;
|
||||
}
|
||||
return oneM ? base + "[1m]" : base;
|
||||
}
|
||||
|
||||
public void setProvider(String provider) {
|
||||
|
||||
@@ -440,7 +440,7 @@ public class ChatWindowDelegate {
|
||||
String mode = session != null ? session.getPermissionMode() : "default";
|
||||
com.github.claudecodegui.notifications.ClaudeNotifier.setMode(project, mode);
|
||||
|
||||
String model = session != null ? session.getModel() : "claude-sonnet-4-7";
|
||||
String model = session != null ? session.getModel() : "claude-sonnet-5";
|
||||
com.github.claudecodegui.notifications.ClaudeNotifier.setModel(project, model);
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.github.claudecodegui.session;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Regression tests for retired Claude model id migration on session state writes
|
||||
* (persisted tab / history restore self-heal) - see #1678.
|
||||
*/
|
||||
public class SessionStateTest {
|
||||
|
||||
@Test
|
||||
public void setModelMigratesRetiredSonnet47ToSonnet5() {
|
||||
SessionState state = new SessionState();
|
||||
// Saved by versions <= 0.5.2 where sonnet-4-7 was the default model.
|
||||
state.setModel("claude-sonnet-4-7");
|
||||
Assert.assertEquals("claude-sonnet-5", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setModelMigratesRetiredSonnet46ToSonnet5() {
|
||||
SessionState state = new SessionState();
|
||||
state.setModel("claude-sonnet-4-6");
|
||||
Assert.assertEquals("claude-sonnet-5", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setModelMigratesRetiredOpus46ToOpus48() {
|
||||
SessionState state = new SessionState();
|
||||
state.setModel("claude-opus-4-6");
|
||||
Assert.assertEquals("claude-opus-4-8", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setModelPreserves1MSuffixWhenMigrating() {
|
||||
SessionState state = new SessionState();
|
||||
state.setModel("claude-sonnet-4-7[1m]");
|
||||
Assert.assertEquals("claude-sonnet-5[1m]", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setModelLeavesLiveModelsUntouched() {
|
||||
SessionState state = new SessionState();
|
||||
state.setModel("claude-sonnet-5");
|
||||
Assert.assertEquals("claude-sonnet-5", state.getModel());
|
||||
state.setModel("claude-opus-4-8[1m]");
|
||||
Assert.assertEquals("claude-opus-4-8[1m]", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setModelLeavesNonClaudeAndUnknownIdsUntouched() {
|
||||
SessionState state = new SessionState();
|
||||
// Non-Claude provider models must pass through unchanged.
|
||||
state.setModel("gpt-5.6-sol");
|
||||
Assert.assertEquals("gpt-5.6-sol", state.getModel());
|
||||
state.setModel("qwen3.5-plus");
|
||||
Assert.assertEquals("qwen3.5-plus", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setModelHandlesNullAndBlank() {
|
||||
SessionState state = new SessionState();
|
||||
state.setModel(null);
|
||||
Assert.assertNull(state.getModel());
|
||||
state.setModel(" ");
|
||||
Assert.assertEquals(" ", state.getModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultModelIsTheLiveSonnet5() {
|
||||
SessionState state = new SessionState();
|
||||
// The initial value must never be a retired id (#1678).
|
||||
Assert.assertEquals("claude-sonnet-5", state.getModel());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { ButtonAreaProps, CodexFastMode, ModelInfo, PermissionMode, ReasoningEffort } from './types';
|
||||
import { DEFAULT_CLAUDE_MODEL_ID } from './types';
|
||||
import { CodexFastModeSelect, ConfigSelect, ModelSelect, ModeSelect, ProviderSelect, ReasoningSelect } from './selectors';
|
||||
import { STORAGE_KEYS, validateCodexCustomModels } from '../../types/provider';
|
||||
import type { CodexCustomModel } from '../../types/provider';
|
||||
@@ -73,7 +74,7 @@ export const ButtonArea = ({
|
||||
hasInputContent = false,
|
||||
isLoading = false,
|
||||
isEnhancing = false,
|
||||
selectedModel = 'claude-sonnet-4-7',
|
||||
selectedModel = DEFAULT_CLAUDE_MODEL_ID,
|
||||
permissionMode = 'default',
|
||||
currentProvider = 'claude',
|
||||
reasoningEffort = 'high',
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
ChatInputBoxProps,
|
||||
PermissionMode,
|
||||
} from './types.js';
|
||||
import { DEFAULT_CLAUDE_MODEL_ID } from './types.js';
|
||||
import { ChatInputBoxHeader } from './ChatInputBoxHeader.js';
|
||||
import { ChatInputBoxFooter } from './ChatInputBoxFooter.js';
|
||||
import { ResizeHandles } from './ResizeHandles.js';
|
||||
@@ -76,7 +77,7 @@ export const ChatInputBox = memo(forwardRef<ChatInputBoxHandle, ChatInputBoxProp
|
||||
(
|
||||
{
|
||||
isLoading = false,
|
||||
selectedModel = 'claude-sonnet-4-7',
|
||||
selectedModel = DEFAULT_CLAUDE_MODEL_ID,
|
||||
permissionMode = 'default',
|
||||
currentProvider = 'claude',
|
||||
usagePercentage = 0,
|
||||
|
||||
@@ -83,13 +83,13 @@ describe('ModelSelect', () => {
|
||||
'claude-opus-5',
|
||||
'claude-opus-4-8',
|
||||
'claude-sonnet-5',
|
||||
'claude-sonnet-4-7',
|
||||
'claude-haiku-4-5',
|
||||
]);
|
||||
const ids = CLAUDE_MODELS.map((model) => model.id);
|
||||
expect(ids).not.toContain('claude-opus-4-7');
|
||||
expect(ids).not.toContain('claude-opus-4-6');
|
||||
expect(ids).not.toContain('claude-sonnet-4-6');
|
||||
expect(ids).not.toContain('claude-sonnet-4-7');
|
||||
expect(ids.some((id) => id.endsWith('[1m]'))).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ const DEFAULT_MODEL_MAP: Record<string, ModelInfo> = AVAILABLE_MODELS.reduce(
|
||||
const MODEL_LABEL_KEYS: Record<string, string> = {
|
||||
'claude-opus-5': 'models.claude.opus5.label',
|
||||
'claude-sonnet-5': 'models.claude.sonnet5.label',
|
||||
'claude-sonnet-4-7': 'models.claude.sonnet47.label',
|
||||
'claude-sonnet-4-6': 'models.claude.sonnet46.label',
|
||||
'claude-fable-5': 'models.claude.fable5.label',
|
||||
'claude-opus-4-8': 'models.claude.opus48.label',
|
||||
@@ -89,7 +88,6 @@ const MODEL_LABEL_KEYS: Record<string, string> = {
|
||||
const MODEL_DESCRIPTION_KEYS: Record<string, string> = {
|
||||
'claude-opus-5': 'models.claude.opus5.description',
|
||||
'claude-sonnet-5': 'models.claude.sonnet5.description',
|
||||
'claude-sonnet-4-7': 'models.claude.sonnet47.description',
|
||||
'claude-sonnet-4-6': 'models.claude.sonnet46.description',
|
||||
'claude-fable-5': 'models.claude.fable5.description',
|
||||
'claude-opus-4-8': 'models.claude.opus48.description',
|
||||
|
||||
@@ -20,7 +20,12 @@ describe('normalizeClaudeModelId', () => {
|
||||
|
||||
it('migrates retired Sonnet 4.6 to the current default', () => {
|
||||
// Saved by versions <= 0.4.7 where sonnet-4-6 was the default model.
|
||||
expect(normalizeClaudeModelId('claude-sonnet-4-6')).toBe('claude-sonnet-4-7');
|
||||
expect(normalizeClaudeModelId('claude-sonnet-4-6')).toBe('claude-sonnet-5');
|
||||
});
|
||||
|
||||
it('migrates retired Sonnet 4.7 to the current default', () => {
|
||||
// Saved by versions <= 0.5.2 where sonnet-4-7 was the default model (#1678).
|
||||
expect(normalizeClaudeModelId('claude-sonnet-4-7')).toBe('claude-sonnet-5');
|
||||
});
|
||||
|
||||
it('migrates retired Opus 4.6 to Opus 4.8', () => {
|
||||
@@ -28,7 +33,8 @@ describe('normalizeClaudeModelId', () => {
|
||||
});
|
||||
|
||||
it('migrates retired IDs carrying a [1m] suffix', () => {
|
||||
expect(normalizeClaudeModelId('claude-sonnet-4-6[1m]')).toBe('claude-sonnet-4-7');
|
||||
expect(normalizeClaudeModelId('claude-sonnet-4-6[1m]')).toBe('claude-sonnet-5');
|
||||
expect(normalizeClaudeModelId('claude-sonnet-4-7[1m]')).toBe('claude-sonnet-5');
|
||||
expect(normalizeClaudeModelId('claude-opus-4-6[1m]')).toBe('claude-opus-4-8');
|
||||
});
|
||||
|
||||
|
||||
@@ -301,15 +301,18 @@ export function strip1MContextSuffix(modelId: string | undefined | null): string
|
||||
* CLAUDE_MODELS[0], which is the newest tier and the most likely to be missing
|
||||
* from a user's API relay.
|
||||
*/
|
||||
export const DEFAULT_CLAUDE_MODEL_ID = 'claude-sonnet-4-7';
|
||||
export const DEFAULT_CLAUDE_MODEL_ID = 'claude-sonnet-5';
|
||||
|
||||
/**
|
||||
* Retired model IDs → their current-generation replacement. Lookup happens after
|
||||
* Retired model IDs -> their current-generation replacement. Lookup happens after
|
||||
* the [1m] suffix is stripped, so keys must be base IDs. Without an entry here a
|
||||
* saved retired model fails validation and silently resets to the fallback.
|
||||
* Retired ids must always map to a LIVE model - mapping one retired id to another
|
||||
* (sonnet-4-6 -> sonnet-4-7) kept restoring tabs pinned to a dead model (#1678).
|
||||
*/
|
||||
const LEGACY_CLAUDE_MODEL_ID_ALIASES: Record<string, string> = {
|
||||
'claude-sonnet-4-6': 'claude-sonnet-4-7',
|
||||
'claude-sonnet-4-6': 'claude-sonnet-5',
|
||||
'claude-sonnet-4-7': 'claude-sonnet-5',
|
||||
'claude-opus-4-6': 'claude-opus-4-8',
|
||||
};
|
||||
|
||||
@@ -345,12 +348,7 @@ export const CLAUDE_MODELS: ModelInfo[] = [
|
||||
{
|
||||
id: 'claude-sonnet-5',
|
||||
label: 'Sonnet 5',
|
||||
description: 'Sonnet 5 · Upgraded Sonnet model',
|
||||
},
|
||||
{
|
||||
id: 'claude-sonnet-4-7',
|
||||
label: 'Sonnet 4.7',
|
||||
description: 'Sonnet 4.7 · Use the default model',
|
||||
description: 'Sonnet 5 · Use the default model',
|
||||
},
|
||||
{
|
||||
id: 'claude-haiku-4-5',
|
||||
|
||||
@@ -207,9 +207,9 @@ describe('useModelStatePersistence — retired model migration', () => {
|
||||
renderHook(() => useModelStatePersistence(makeOptions({ setSelectedClaudeModel })));
|
||||
vi.advanceTimersByTime(200);
|
||||
|
||||
expect(setSelectedClaudeModel).toHaveBeenCalledWith('claude-sonnet-4-7');
|
||||
expect(setSelectedClaudeModel).toHaveBeenCalledWith('claude-sonnet-5');
|
||||
expect(setSelectedClaudeModel).not.toHaveBeenCalledWith('claude-fable-5');
|
||||
expect(bridgeEventsFor('set_model')).toEqual([['set_model', 'claude-sonnet-4-7']]);
|
||||
expect(bridgeEventsFor('set_model')).toEqual([['set_model', 'claude-sonnet-5']]);
|
||||
});
|
||||
|
||||
it('migrates a backend-supplied retired model via __INITIAL_TAB_MODEL__', () => {
|
||||
@@ -225,8 +225,8 @@ describe('useModelStatePersistence — retired model migration', () => {
|
||||
renderHook(() => useModelStatePersistence(makeOptions({ setSelectedClaudeModel })));
|
||||
vi.advanceTimersByTime(200);
|
||||
|
||||
expect(setSelectedClaudeModel).toHaveBeenCalledWith('claude-sonnet-4-7');
|
||||
expect(bridgeEventsFor('set_model')).toEqual([['set_model', 'claude-sonnet-4-7']]);
|
||||
expect(setSelectedClaudeModel).toHaveBeenCalledWith('claude-sonnet-5');
|
||||
expect(bridgeEventsFor('set_model')).toEqual([['set_model', 'claude-sonnet-5']]);
|
||||
});
|
||||
|
||||
it('falls back to the default model (not the list head) for unrecognized saved models', () => {
|
||||
|
||||
@@ -1915,11 +1915,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Upgraded Sonnet model"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · Use the default model"
|
||||
"description": "Sonnet 5 · Use the default model"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1539,11 +1539,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Modelo mejorado de Sonnet 4.6"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · Modelo recomendado por defecto"
|
||||
"description": "Sonnet 5 · Modelo recomendado por defecto"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1539,11 +1539,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Modèle amélioré de Sonnet 4.6"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · Modèle recommandé par défaut"
|
||||
"description": "Sonnet 5 · Modèle recommandé par défaut"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1538,11 +1538,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Sonnet 4.6 का अपग्रेड मॉडल"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · डिफ़ॉल्ट अनुशंसित मॉडल"
|
||||
"description": "Sonnet 5 · डिफ़ॉल्ट अनुशंसित मॉडल"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1550,11 +1550,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Sonnet 4.6 のアップグレードモデル"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · デフォルトモデルを使用"
|
||||
"description": "Sonnet 5 · デフォルトモデルを使用"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1634,11 +1634,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Sonnet 4.6 업그레이드 모델"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · 기본 모델 사용"
|
||||
"description": "Sonnet 5 · 기본 모델 사용"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1689,11 +1689,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Modelo aprimorado do Sonnet 4.6"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · Usar o modelo padrão"
|
||||
"description": "Sonnet 5 · Usar o modelo padrão"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1565,11 +1565,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Обновленная модель Sonnet 4.6"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · Модель по умолчанию"
|
||||
"description": "Sonnet 5 · Модель по умолчанию"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1544,11 +1544,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Sonnet 4.6 升級模型"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · 預設推薦模型"
|
||||
"description": "Sonnet 5 · 預設推薦模型"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
@@ -1920,11 +1920,7 @@
|
||||
},
|
||||
"sonnet5": {
|
||||
"label": "Sonnet 5",
|
||||
"description": "Sonnet 5 · Sonnet 4.6 升级模型"
|
||||
},
|
||||
"sonnet47": {
|
||||
"label": "Sonnet 4.7",
|
||||
"description": "Sonnet 4.7 · 默认推荐模型"
|
||||
"description": "Sonnet 5 · 默认推荐模型"
|
||||
},
|
||||
"opus5": {
|
||||
"label": "Opus 5",
|
||||
|
||||
Reference in New Issue
Block a user