diff --git a/CHANGELOG.md b/CHANGELOG.md index f863c5ff..154f0d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +##### **2026年7月6日(v0.4.6-fix1)** + +English: + +🐛 Fixes +- Fix Claude CLI login being treated as a host-managed provider: when `access` is `cli_login`, the bridge now leaves `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST` unset, avoiding the host-managed credential path for users already authenticated through the Claude CLI (by @zkpaiminmin) +- Fix Claude model metadata after the lineup update: replace the retired `claude-opus-4-7` built-in option with `claude-opus-4-8`, and add `claude-sonnet-5` with 200K / 1M context handling, max reasoning support, usage pricing, model mapping, and localized labels across all supported languages (by @zkpaiminmin) + +中文: + +🐛 修复 +- 修复 Claude CLI 登录被误判为 host-managed provider:当认证状态为 `cli_login` 时,bridge 不再设置 `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST`,避免已通过 Claude CLI 登录的用户走错 host-managed 凭证路径(by @zkpaiminmin) +- 修复 Claude 模型清单与元数据滞后:内置模型移除已下线的 `claude-opus-4-7`,切换到 `claude-opus-4-8`,并新增 `claude-sonnet-5` 的 200K / 1M 上下文、max reasoning、用量计价、模型映射和全语言本地化标签(by @zkpaiminmin) + +--- + ##### **2026年6月28日(v0.4.6)** English: diff --git a/ai-bridge/config/api-config.js b/ai-bridge/config/api-config.js index c7e914db..45dad597 100644 --- a/ai-bridge/config/api-config.js +++ b/ai-bridge/config/api-config.js @@ -231,7 +231,7 @@ export function buildWebviewControlledSettingsOverride(modelId) { * Reads settings via loadClaudeSettings() (same source as setupApiKey) so the * auth-decision and the provider-management-decision always see the same env. * - * @returns {boolean} true unless a cloud provider switch is active in settings. + * @returns {boolean} true unless CLI login or a cloud provider switch is active. */ function shouldHostManageProvider() { if (getClaudeRuntimeState().access === 'cli_login') { diff --git a/ai-bridge/config/api-config.test.js b/ai-bridge/config/api-config.test.js index 2413b7b4..87a6683b 100644 --- a/ai-bridge/config/api-config.test.js +++ b/ai-bridge/config/api-config.test.js @@ -306,6 +306,7 @@ test('buildCliEnv leaves CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST unset for CLI logi // inherited copy from a parent Claude Code host. const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-gui-api-config-')); writeCodemossClaudeConfig(tempHome, '__cli_login__'); + writeClaudeSettingsEnv(tempHome, {}); const env = runBuildCliEnv(tempHome); diff --git a/ai-bridge/services/prompt-enhancer.test.js b/ai-bridge/services/prompt-enhancer.test.js index 1ffbece2..a79e8518 100644 --- a/ai-bridge/services/prompt-enhancer.test.js +++ b/ai-bridge/services/prompt-enhancer.test.js @@ -36,7 +36,7 @@ test('resolvePromptEnhancerRuntimeConfig throws a strict error when manual provi effectiveProvider: null, resolutionSource: 'unavailable', models: { - claude: 'claude-opus-4-7', + claude: 'claude-opus-4-8', codex: 'gpt-5.4', }, availability: { diff --git a/ai-bridge/utils/model-utils.test.mjs b/ai-bridge/utils/model-utils.test.mjs index 1d7bfadc..5784a7f7 100644 --- a/ai-bridge/utils/model-utils.test.mjs +++ b/ai-bridge/utils/model-utils.test.mjs @@ -11,7 +11,7 @@ import { // --- mapModelIdToSdkName ------------------------------------------------ test('mapModelIdToSdkName maps Claude families to short SDK names', () => { - assert.equal(mapModelIdToSdkName('claude-opus-4-7'), 'opus'); + assert.equal(mapModelIdToSdkName('claude-opus-4-8'), 'opus'); assert.equal(mapModelIdToSdkName('claude-haiku-4-5'), 'haiku'); assert.equal(mapModelIdToSdkName('claude-sonnet-4-6'), 'sonnet'); // Unknown / third-party IDs fall back to sonnet (because the SDK uses @@ -34,7 +34,7 @@ test('resolveModelFromSettings applies model-specific settings mapping', () => { ANTHROPIC_DEFAULT_SONNET_MODEL: 'glm-4.7', ANTHROPIC_DEFAULT_HAIKU_MODEL: 'glm-4.7-flash', }; - assert.equal(resolveModelFromSettings('claude-opus-4-7', env), 'glm-4.7-opus'); + assert.equal(resolveModelFromSettings('claude-opus-4-8', env), 'glm-4.7-opus'); assert.equal(resolveModelFromSettings('claude-sonnet-4-6', env), 'glm-4.7'); assert.equal(resolveModelFromSettings('claude-haiku-4-5', env), 'glm-4.7-flash'); }); @@ -45,7 +45,7 @@ test('resolveModelFromSettings honors global ANTHROPIC_MODEL override', () => { ANTHROPIC_DEFAULT_SONNET_MODEL: 'ignored', }; assert.equal(resolveModelFromSettings('claude-sonnet-4-6', env), 'override-everywhere'); - assert.equal(resolveModelFromSettings('claude-opus-4-7', env), 'override-everywhere'); + assert.equal(resolveModelFromSettings('claude-opus-4-8', env), 'override-everywhere'); }); test('resolveModelFromSettings ignores empty / whitespace mapping values', () => { @@ -54,7 +54,7 @@ test('resolveModelFromSettings ignores empty / whitespace mapping values', () => ANTHROPIC_DEFAULT_OPUS_MODEL: '', }; assert.equal(resolveModelFromSettings('claude-sonnet-4-6', env), 'claude-sonnet-4-6'); - assert.equal(resolveModelFromSettings('claude-opus-4-7', env), 'claude-opus-4-7'); + assert.equal(resolveModelFromSettings('claude-opus-4-8', env), 'claude-opus-4-8'); }); test('resolveModelFromSettings does NOT remap non-Anthropic model IDs', () => { @@ -111,7 +111,7 @@ test('resolveModelFromSettings preserves [1m] across ANTHROPIC_MODEL global over test('resolveModelFromSettings preserves [1m] for opus mapping', () => { const env = { ANTHROPIC_DEFAULT_OPUS_MODEL: 'mimo-v2.5-pro' }; - assert.equal(resolveModelFromSettings('claude-opus-4-7[1m]', env), 'mimo-v2.5-pro[1m]'); + assert.equal(resolveModelFromSettings('claude-opus-4-8[1m]', env), 'mimo-v2.5-pro[1m]'); }); // --- setModelEnvironmentVariables --------------------------------------- @@ -161,7 +161,7 @@ test('setModelEnvironmentVariables routes haiku base to haiku env', () => { test('modelSupportsVision only matches the canonical claude- prefix', () => { assert.equal(modelSupportsVision('claude-sonnet-4-6'), true); - assert.equal(modelSupportsVision('claude-opus-4-7'), true); + assert.equal(modelSupportsVision('claude-opus-4-8'), true); // Third-party proxies that merely contain "claude" must NOT be treated as // native vision-capable models. assert.equal(modelSupportsVision('claude-compatible-proxy'), true); // starts with 'claude-' diff --git a/build.gradle b/build.gradle index 69c23c2b..eafbe9ac 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,7 @@ tasks.named('checkstyleTest') { } group = 'com.github.idea-claude-code-gui' -version = '0.4.6' +version = '0.4.6-fix1' java { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/gradlew.bat b/gradlew.bat index a9254cb3..44a16fb0 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,93 +1,93 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@rem Fix Windows console encoding issues - use English locale for all Java processes -@rem This prevents garbled characters when running on Windows with non-English locale -@set JAVA_TOOL_OPTIONS=-Duser.language=en -Dfile.encoding=UTF-8 - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@rem Fix Windows console encoding issues - use English locale for all Java processes +@rem This prevents garbled characters when running on Windows with non-English locale +@set JAVA_TOOL_OPTIONS=-Duser.language=en -Dfile.encoding=UTF-8 + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/github/claudecodegui/handler/provider/ModelProviderHandler.java b/src/main/java/com/github/claudecodegui/handler/provider/ModelProviderHandler.java index 1da40235..cff11174 100644 --- a/src/main/java/com/github/claudecodegui/handler/provider/ModelProviderHandler.java +++ b/src/main/java/com/github/claudecodegui/handler/provider/ModelProviderHandler.java @@ -26,16 +26,16 @@ public class ModelProviderHandler { static final Map MODEL_CONTEXT_LIMITS = new HashMap<>(); static { // Claude models with 1M context (base IDs) + MODEL_CONTEXT_LIMITS.put("claude-sonnet-5", 200_000); MODEL_CONTEXT_LIMITS.put("claude-sonnet-4-6", 200_000); MODEL_CONTEXT_LIMITS.put("claude-fable-5", 200_000); MODEL_CONTEXT_LIMITS.put("claude-opus-4-8", 200_000); - MODEL_CONTEXT_LIMITS.put("claude-opus-4-7", 200_000); MODEL_CONTEXT_LIMITS.put("claude-opus-4-6", 200_000); // Claude models with [1m] suffix - 1M context + MODEL_CONTEXT_LIMITS.put("claude-sonnet-5[1m]", 1_000_000); MODEL_CONTEXT_LIMITS.put("claude-sonnet-4-6[1m]", 1_000_000); MODEL_CONTEXT_LIMITS.put("claude-fable-5[1m]", 1_000_000); MODEL_CONTEXT_LIMITS.put("claude-opus-4-8[1m]", 1_000_000); - MODEL_CONTEXT_LIMITS.put("claude-opus-4-7[1m]", 1_000_000); MODEL_CONTEXT_LIMITS.put("claude-opus-4-6[1m]", 1_000_000); // Haiku - no 1M context available MODEL_CONTEXT_LIMITS.put("claude-haiku-4-5", 200_000); diff --git a/src/main/java/com/github/claudecodegui/provider/claude/ClaudeSDKBridge.java b/src/main/java/com/github/claudecodegui/provider/claude/ClaudeSDKBridge.java index 1d61eee9..f00b9397 100644 --- a/src/main/java/com/github/claudecodegui/provider/claude/ClaudeSDKBridge.java +++ b/src/main/java/com/github/claudecodegui/provider/claude/ClaudeSDKBridge.java @@ -496,7 +496,7 @@ public class ClaudeSDKBridge extends BaseSDKBridge { * * @param sessionId The session ID to query context for * @param cwd Working directory - * @param model The model ID to use for the runtime (e.g., "claude-opus-4-7") + * @param model The model ID to use for the runtime (e.g., "claude-opus-4-8") * @return CompletableFuture with context usage data as JsonObject */ public CompletableFuture getContextUsage(String sessionId, String cwd, String model) { diff --git a/src/main/java/com/github/claudecodegui/provider/claude/ClaudeUsageAggregator.java b/src/main/java/com/github/claudecodegui/provider/claude/ClaudeUsageAggregator.java index 38c2ec4a..98f0fdc2 100644 --- a/src/main/java/com/github/claudecodegui/provider/claude/ClaudeUsageAggregator.java +++ b/src/main/java/com/github/claudecodegui/provider/claude/ClaudeUsageAggregator.java @@ -55,6 +55,7 @@ class ClaudeUsageAggregator { Map.entry("claude-sonnet-4-20250514", TIERED_SONNET_PRICING), Map.entry("claude-sonnet-4-5", TIERED_SONNET_PRICING), Map.entry("claude-sonnet-4-6", DEFAULT_PRICING), + Map.entry("claude-sonnet-5", DEFAULT_PRICING), Map.entry("claude-haiku-4", HAIKU_4_5_PRICING), Map.entry("claude-haiku-4-5", HAIKU_4_5_PRICING) ); @@ -68,6 +69,7 @@ class ClaudeUsageAggregator { "claude-opus-4-1", "claude-opus-4", "claude-sonnet-4-20250514", + "claude-sonnet-5", "claude-sonnet-4-6", "claude-sonnet-4-5", "claude-sonnet-4", diff --git a/src/test/java/com/github/claudecodegui/handler/ContextHandlerTest.java b/src/test/java/com/github/claudecodegui/handler/ContextHandlerTest.java index 17712097..94c47964 100644 --- a/src/test/java/com/github/claudecodegui/handler/ContextHandlerTest.java +++ b/src/test/java/com/github/claudecodegui/handler/ContextHandlerTest.java @@ -21,14 +21,14 @@ public class ContextHandlerTest { JsonObject body = new JsonObject(); body.addProperty("sessionId", "sess-123"); body.addProperty("cwd", "/home/user/project"); - body.addProperty("model", "claude-opus-4-7[1m]"); + body.addProperty("model", "claude-opus-4-8[1m]"); body.addProperty("requestId", "req-abc"); String[] result = ContextHandler.parseContextUsageRequest(GSON, body.toString()); assertEquals("sess-123", result[0]); assertEquals("/home/user/project", result[1]); - assertEquals("claude-opus-4-7[1m]", result[2]); + assertEquals("claude-opus-4-8[1m]", result[2]); assertEquals("req-abc", result[3]); } @@ -70,14 +70,14 @@ public class ContextHandlerTest { public void parseNullJsonFieldsReturnNull() { JsonObject body = new JsonObject(); body.add("sessionId", null); - body.addProperty("model", "claude-opus-4-7"); + body.addProperty("model", "claude-opus-4-8"); body.add("requestId", null); String[] result = ContextHandler.parseContextUsageRequest(GSON, body.toString()); assertNull(result[0]); // sessionId is null assertNull(result[1]); // cwd not present - assertEquals("claude-opus-4-7", result[2]); + assertEquals("claude-opus-4-8", result[2]); assertNull(result[3]); // requestId is null } @@ -94,11 +94,11 @@ public class ContextHandlerTest { @Test public void parseModelWith1MContextSuffix() { JsonObject body = new JsonObject(); - body.addProperty("model", "claude-opus-4-7[1m]"); + body.addProperty("model", "claude-opus-4-8[1m]"); String[] result = ContextHandler.parseContextUsageRequest(GSON, body.toString()); - assertEquals("claude-opus-4-7[1m]", result[2]); + assertEquals("claude-opus-4-8[1m]", result[2]); } @Test diff --git a/src/test/java/com/github/claudecodegui/handler/provider/ModelProviderHandlerTest.java b/src/test/java/com/github/claudecodegui/handler/provider/ModelProviderHandlerTest.java index acf25b02..6f806b6f 100644 --- a/src/test/java/com/github/claudecodegui/handler/provider/ModelProviderHandlerTest.java +++ b/src/test/java/com/github/claudecodegui/handler/provider/ModelProviderHandlerTest.java @@ -74,12 +74,16 @@ public class ModelProviderHandlerTest { @Test public void shouldReturnCorrectContextLimitsForClaudeModels() { // Base IDs without [1m] suffix - 200k context by default + assertTrue(ModelProviderHandler.MODEL_CONTEXT_LIMITS.containsKey("claude-sonnet-5")); + assertTrue(ModelProviderHandler.MODEL_CONTEXT_LIMITS.containsKey("claude-sonnet-5[1m]")); + assertEquals(200_000, ModelProviderHandler.getModelContextLimit("claude-sonnet-5")); assertEquals(200_000, ModelProviderHandler.getModelContextLimit("claude-sonnet-4-6")); - assertEquals(200_000, ModelProviderHandler.getModelContextLimit("claude-opus-4-7")); + assertEquals(200_000, ModelProviderHandler.getModelContextLimit("claude-opus-4-8")); assertEquals(200_000, ModelProviderHandler.getModelContextLimit("claude-opus-4-6")); // IDs with [1m] suffix - 1M context + assertEquals(1_000_000, ModelProviderHandler.getModelContextLimit("claude-sonnet-5[1m]")); assertEquals(1_000_000, ModelProviderHandler.getModelContextLimit("claude-sonnet-4-6[1m]")); - assertEquals(1_000_000, ModelProviderHandler.getModelContextLimit("claude-opus-4-7[1m]")); + assertEquals(1_000_000, ModelProviderHandler.getModelContextLimit("claude-opus-4-8[1m]")); assertEquals(1_000_000, ModelProviderHandler.getModelContextLimit("claude-opus-4-6[1m]")); // Haiku - no 1M context available assertEquals(200_000, ModelProviderHandler.getModelContextLimit("claude-haiku-4-5")); diff --git a/src/test/java/com/github/claudecodegui/service/GitCommitMessageServiceCommitAiConfigTest.java b/src/test/java/com/github/claudecodegui/service/GitCommitMessageServiceCommitAiConfigTest.java index 4a4b137d..09965643 100644 --- a/src/test/java/com/github/claudecodegui/service/GitCommitMessageServiceCommitAiConfigTest.java +++ b/src/test/java/com/github/claudecodegui/service/GitCommitMessageServiceCommitAiConfigTest.java @@ -29,12 +29,12 @@ public class GitCommitMessageServiceCommitAiConfigTest { @Test public void shouldRouteToResolvedClaudeModel() { - TestableGitCommitMessageService service = new TestableGitCommitMessageService(buildConfig("claude", "claude-opus-4-7", "gpt-5.5")); + TestableGitCommitMessageService service = new TestableGitCommitMessageService(buildConfig("claude", "claude-opus-4-8", "gpt-5.5")); ResultCapture callback = new ResultCapture(); service.generateCommitMessage(Collections.emptyList(), callback); - assertEquals("claude-opus-4-7", service.lastClaudeModel); + assertEquals("claude-opus-4-8", service.lastClaudeModel); assertNull(service.lastCodexModel); assertEquals("fix: use claude routing", callback.success); } diff --git a/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServiceCommitAiConfigTest.java b/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServiceCommitAiConfigTest.java index 4f841a43..36084510 100644 --- a/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServiceCommitAiConfigTest.java +++ b/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServiceCommitAiConfigTest.java @@ -75,13 +75,13 @@ public class CodemossSettingsServiceCommitAiConfigTest { CodemossSettingsService service = new CodemossSettingsService(); - invokeSetCommitAiConfig(service, "claude", "claude-opus-4-7", "gpt-5.4"); + invokeSetCommitAiConfig(service, "claude", "claude-opus-4-8", "gpt-5.4"); JsonObject config = invokeGetCommitAiConfig(service); assertEquals("claude", config.get("provider").getAsString()); assertEquals("claude", config.get("effectiveProvider").getAsString()); assertEquals("manual", config.get("resolutionSource").getAsString()); - assertEquals("claude-opus-4-7", config.getAsJsonObject("models").get("claude").getAsString()); + assertEquals("claude-opus-4-8", config.getAsJsonObject("models").get("claude").getAsString()); assertEquals("gpt-5.4", config.getAsJsonObject("models").get("codex").getAsString()); } @@ -93,7 +93,7 @@ public class CodemossSettingsServiceCommitAiConfigTest { CodemossSettingsService service = new CodemossSettingsService(); - invokeSetCommitAiConfig(service, "claude", "claude-opus-4-7", "gpt-5.4"); + invokeSetCommitAiConfig(service, "claude", "claude-opus-4-8", "gpt-5.4"); JsonObject config = invokeGetCommitAiConfig(service); assertEquals("claude", config.get("provider").getAsString()); @@ -114,7 +114,7 @@ public class CodemossSettingsServiceCommitAiConfigTest { CodemossSettingsService service = new CodemossSettingsService(); invokeSetPromptEnhancerConfig(service, "claude", "claude-opus-4-6", "gpt-5.4"); - invokeSetCommitAiConfig(service, "codex", "claude-opus-4-7", "gpt-5.5"); + invokeSetCommitAiConfig(service, "codex", "claude-opus-4-8", "gpt-5.5"); JsonObject promptEnhancerConfig = invokeGetPromptEnhancerConfig(service); JsonObject commitAiConfig = invokeGetCommitAiConfig(service); @@ -125,7 +125,7 @@ public class CodemossSettingsServiceCommitAiConfigTest { assertEquals("codex", commitAiConfig.get("provider").getAsString()); assertEquals("gpt-5.5", commitAiConfig.getAsJsonObject("models").get("codex").getAsString()); - assertEquals("claude-opus-4-7", commitAiConfig.getAsJsonObject("models").get("claude").getAsString()); + assertEquals("claude-opus-4-8", commitAiConfig.getAsJsonObject("models").get("claude").getAsString()); } private JsonObject invokeGetCommitAiConfig(CodemossSettingsService service) throws Exception { diff --git a/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServicePromptEnhancerConfigTest.java b/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServicePromptEnhancerConfigTest.java index 0b0d5485..abf95993 100644 --- a/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServicePromptEnhancerConfigTest.java +++ b/src/test/java/com/github/claudecodegui/settings/CodemossSettingsServicePromptEnhancerConfigTest.java @@ -75,13 +75,13 @@ public class CodemossSettingsServicePromptEnhancerConfigTest { CodemossSettingsService service = new CodemossSettingsService(); - invokeSetPromptEnhancerConfig(service, "claude", "claude-opus-4-7", "gpt-5.4"); + invokeSetPromptEnhancerConfig(service, "claude", "claude-opus-4-8", "gpt-5.4"); JsonObject config = invokeGetPromptEnhancerConfig(service); assertEquals("claude", config.get("provider").getAsString()); assertEquals("claude", config.get("effectiveProvider").getAsString()); assertEquals("manual", config.get("resolutionSource").getAsString()); - assertEquals("claude-opus-4-7", config.getAsJsonObject("models").get("claude").getAsString()); + assertEquals("claude-opus-4-8", config.getAsJsonObject("models").get("claude").getAsString()); assertEquals("gpt-5.4", config.getAsJsonObject("models").get("codex").getAsString()); } @@ -93,7 +93,7 @@ public class CodemossSettingsServicePromptEnhancerConfigTest { CodemossSettingsService service = new CodemossSettingsService(); - invokeSetPromptEnhancerConfig(service, "claude", "claude-opus-4-7", "gpt-5.4"); + invokeSetPromptEnhancerConfig(service, "claude", "claude-opus-4-8", "gpt-5.4"); JsonObject config = invokeGetPromptEnhancerConfig(service); assertEquals("claude", config.get("provider").getAsString()); diff --git a/webview/src/components/ChatInputBox/ButtonArea.tsx b/webview/src/components/ChatInputBox/ButtonArea.tsx index 00c9ca98..a89813b0 100644 --- a/webview/src/components/ChatInputBox/ButtonArea.tsx +++ b/webview/src/components/ChatInputBox/ButtonArea.tsx @@ -132,9 +132,9 @@ export const ButtonArea = ({ */ const applyModelMapping = useCallback((model: ModelInfo, mapping: { main?: string; haiku?: string; sonnet?: string; opus?: string }): ModelInfo => { const modelKeyMap: Record = { + 'claude-sonnet-5': 'sonnet', 'claude-sonnet-4-6': 'sonnet', 'claude-opus-4-8': 'opus', - 'claude-opus-4-7': 'opus', 'claude-haiku-4-5': 'haiku', }; diff --git a/webview/src/components/ChatInputBox/selectors/ModelSelect.test.tsx b/webview/src/components/ChatInputBox/selectors/ModelSelect.test.tsx index 2876bf11..e3f9571d 100644 --- a/webview/src/components/ChatInputBox/selectors/ModelSelect.test.tsx +++ b/webview/src/components/ChatInputBox/selectors/ModelSelect.test.tsx @@ -74,8 +74,12 @@ describe('ModelSelect', () => { expect(screen.getByRole('button').textContent).toContain('glm-4.7'); }); - it('Claude 内置模型列表应移除已下线的 Opus 4.6,且不使用 [1m] 后缀 ID', () => { + it('Claude 内置模型列表应新增 Sonnet 5、保留 Sonnet 4.6,并移除 Opus 4.7', () => { const ids = CLAUDE_MODELS.map((model) => model.id); + expect(ids).toContain('claude-sonnet-5'); + expect(ids).toContain('claude-sonnet-4-6'); + expect(ids).toContain('claude-opus-4-8'); + expect(ids).not.toContain('claude-opus-4-7'); expect(ids).not.toContain('claude-opus-4-6'); expect(ids.some((id) => id.endsWith('[1m]'))).toBe(false); }); diff --git a/webview/src/components/ChatInputBox/selectors/ModelSelect.tsx b/webview/src/components/ChatInputBox/selectors/ModelSelect.tsx index 6636d475..5d36751e 100644 --- a/webview/src/components/ChatInputBox/selectors/ModelSelect.tsx +++ b/webview/src/components/ChatInputBox/selectors/ModelSelect.tsx @@ -42,10 +42,10 @@ const DEFAULT_MODEL_MAP: Record = AVAILABLE_MODELS.reduce( ); const MODEL_LABEL_KEYS: Record = { + 'claude-sonnet-5': 'models.claude.sonnet5.label', 'claude-sonnet-4-6': 'models.claude.sonnet46.label', 'claude-fable-5': 'models.claude.fable5.label', 'claude-opus-4-8': 'models.claude.opus48.label', - 'claude-opus-4-7': 'models.claude.opus46.label', 'claude-opus-4-6': 'models.claude.opus46_1m.label', 'claude-opus-4-6[1m]': 'models.claude.opus46_1m.label', 'claude-haiku-4-5': 'models.claude.haiku45.label', @@ -61,10 +61,10 @@ const MODEL_LABEL_KEYS: Record = { }; const MODEL_DESCRIPTION_KEYS: Record = { + 'claude-sonnet-5': 'models.claude.sonnet5.description', 'claude-sonnet-4-6': 'models.claude.sonnet46.description', 'claude-fable-5': 'models.claude.fable5.description', 'claude-opus-4-8': 'models.claude.opus48.description', - 'claude-opus-4-7': 'models.claude.opus46.description', 'claude-opus-4-6': 'models.claude.opus46_1m.description', 'claude-opus-4-6[1m]': 'models.claude.opus46_1m.description', 'claude-haiku-4-5': 'models.claude.haiku45.description', @@ -85,9 +85,9 @@ const MODEL_DESCRIPTION_KEYS: Record = { * Legacy Opus 4.6 IDs share the same opus mapping bucket. */ const MODEL_ID_TO_MAPPING_KEY: Record = { + 'claude-sonnet-5': 'sonnet', 'claude-sonnet-4-6': 'sonnet', 'claude-opus-4-8': 'opus', - 'claude-opus-4-7': 'opus', 'claude-opus-4-6': 'opus', 'claude-opus-4-6[1m]': 'opus', 'claude-haiku-4-5': 'haiku', diff --git a/webview/src/components/ChatInputBox/selectors/ReasoningSelect.test.tsx b/webview/src/components/ChatInputBox/selectors/ReasoningSelect.test.tsx index 8d7f4b58..f7e427ae 100644 --- a/webview/src/components/ChatInputBox/selectors/ReasoningSelect.test.tsx +++ b/webview/src/components/ChatInputBox/selectors/ReasoningSelect.test.tsx @@ -9,13 +9,13 @@ vi.mock('react-i18next', () => ({ })); describe('ReasoningSelect', () => { - it('shows xhigh and max for Claude Opus 4.7', () => { + it('shows xhigh and max for Claude Opus 4.8', () => { render( , ); @@ -41,6 +41,22 @@ describe('ReasoningSelect', () => { expect(screen.getByText('Max')).toBeTruthy(); }); + it('shows max but not xhigh for Claude Sonnet 5', () => { + render( + , + ); + + fireEvent.click(screen.getByRole('button')); + + expect(screen.queryByText('XHigh')).toBeNull(); + expect(screen.getByText('Max')).toBeTruthy(); + }); + it('resets unavailable effort when selected Claude model changes', () => { const onChange = vi.fn(); diff --git a/webview/src/components/ChatInputBox/selectors/ReasoningSelect.tsx b/webview/src/components/ChatInputBox/selectors/ReasoningSelect.tsx index 9ae128ca..ed7b6c8e 100644 --- a/webview/src/components/ChatInputBox/selectors/ReasoningSelect.tsx +++ b/webview/src/components/ChatInputBox/selectors/ReasoningSelect.tsx @@ -34,8 +34,8 @@ interface ReasoningSelectProps { * Controls the depth of reasoning for AI models. * Visibility and available levels depend on the selected model: * - Codex: low/medium/high/xhigh - * - Claude Opus 4.7: low/medium/high/xhigh/max - * - Claude Opus 4.6 and Sonnet 4.6: low/medium/high/max + * - Claude Opus 4.8: low/medium/high/xhigh/max + * - Claude Sonnet 5, Opus 4.6, and Sonnet 4.6: low/medium/high/max * - Claude Haiku 4.5 and legacy models: hidden (no adaptive thinking support) */ export const ReasoningSelect = ({ value, onChange, disabled, selectedModel, currentProvider }: ReasoningSelectProps) => { diff --git a/webview/src/components/ChatInputBox/types.ts b/webview/src/components/ChatInputBox/types.ts index 10d23ec6..9ed2bd71 100644 --- a/webview/src/components/ChatInputBox/types.ts +++ b/webview/src/components/ChatInputBox/types.ts @@ -319,9 +319,9 @@ export const CLAUDE_MODELS: ModelInfo[] = [ description: 'Opus 4.8 · Latest and most capable', }, { - id: 'claude-opus-4-7', - label: 'Opus 4.7', - description: 'Opus 4.7 · Previous flagship model', + id: 'claude-sonnet-5', + label: 'Sonnet 5', + description: 'Sonnet 5 · Upgraded Sonnet model', }, { id: 'claude-fable-5', @@ -423,9 +423,9 @@ export const AVAILABLE_PROVIDERS: ProviderInfo[] = [ export const EFFORT_SUPPORTED_CLAUDE_MODELS = new Set([ 'claude-fable-5', 'claude-opus-4-8', - 'claude-opus-4-7', 'claude-opus-4-6', 'claude-opus-4-6[1m]', + 'claude-sonnet-5', 'claude-sonnet-4-6', ]); @@ -435,7 +435,6 @@ export const EFFORT_SUPPORTED_CLAUDE_MODELS = new Set([ export const XHIGH_EFFORT_CLAUDE_MODELS = new Set([ 'claude-fable-5', 'claude-opus-4-8', - 'claude-opus-4-7', ]); /** @@ -444,9 +443,9 @@ export const XHIGH_EFFORT_CLAUDE_MODELS = new Set([ export const MAX_EFFORT_CLAUDE_MODELS = new Set([ 'claude-fable-5', 'claude-opus-4-8', - 'claude-opus-4-7', 'claude-opus-4-6', 'claude-opus-4-6[1m]', + 'claude-sonnet-5', 'claude-sonnet-4-6', ]); diff --git a/webview/src/components/settings/PromptEnhancerSection/index.test.tsx b/webview/src/components/settings/PromptEnhancerSection/index.test.tsx index a76dbf4d..2469a348 100644 --- a/webview/src/components/settings/PromptEnhancerSection/index.test.tsx +++ b/webview/src/components/settings/PromptEnhancerSection/index.test.tsx @@ -51,7 +51,7 @@ describe('PromptEnhancerSection', () => { effectiveProvider: 'claude', resolutionSource: 'manual', models: { - claude: 'claude-opus-4-7', + claude: 'claude-opus-4-8', codex: 'gpt-5.4', }, availability: { diff --git a/webview/src/hooks/useMessageSender.context.test.ts b/webview/src/hooks/useMessageSender.context.test.ts index b27a6931..410868e3 100644 --- a/webview/src/hooks/useMessageSender.context.test.ts +++ b/webview/src/hooks/useMessageSender.context.test.ts @@ -9,7 +9,7 @@ describe('useMessageSender - /context command', () => { t, addToast: vi.fn(), currentProvider: 'claude', - selectedModel: 'claude-opus-4-7', + selectedModel: 'claude-opus-4-8', permissionMode: 'default', reasoningEffort: 'high', codexFastMode: 'normal', @@ -50,7 +50,7 @@ describe('useMessageSender - /context command', () => { it('sends get_context_usage with base model when longContext is disabled', () => { const opts = createOptions({ - selectedModel: 'claude-opus-4-7', + selectedModel: 'claude-opus-4-8', longContextEnabled: false, }); @@ -65,13 +65,13 @@ describe('useMessageSender - /context command', () => { expect(call).toMatch(/^get_context_usage:/); const payload = JSON.parse(call.substring('get_context_usage:'.length)); - expect(payload.model).toBe('claude-opus-4-7'); + expect(payload.model).toBe('claude-opus-4-8'); expect(payload.requestId).toBeTruthy(); }); it('sends get_context_usage with [1m] suffix when longContext is enabled', () => { const opts = createOptions({ - selectedModel: 'claude-opus-4-7', + selectedModel: 'claude-opus-4-8', longContextEnabled: true, }); @@ -84,7 +84,7 @@ describe('useMessageSender - /context command', () => { expect(window.sendToJava).toHaveBeenCalledTimes(1); const call = (window.sendToJava as any).mock.calls[0][0] as string; const payload = JSON.parse(call.substring('get_context_usage:'.length)); - expect(payload.model).toBe('claude-opus-4-7[1m]'); + expect(payload.model).toBe('claude-opus-4-8[1m]'); }); it('opens dialog with loading state before sending bridge event', () => { @@ -153,7 +153,7 @@ describe('useMessageSender - /context command', () => { it('includes explicit Claude high reasoning effort in plain message payload', () => { const opts = createOptions({ currentProvider: 'claude', - selectedModel: 'claude-opus-4-7', + selectedModel: 'claude-opus-4-8', reasoningEffort: 'high', }); @@ -170,7 +170,7 @@ describe('useMessageSender - /context command', () => { it('includes explicit Claude high reasoning effort in attachment message payload', () => { const opts = createOptions({ currentProvider: 'claude', - selectedModel: 'claude-opus-4-7', + selectedModel: 'claude-opus-4-8', reasoningEffort: 'high', }); diff --git a/webview/src/i18n/locales/en.json b/webview/src/i18n/locales/en.json index ff26b95d..7d5086ab 100644 --- a/webview/src/i18n/locales/en.json +++ b/webview/src/i18n/locales/en.json @@ -1646,9 +1646,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · Use the default model" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · Previous flagship model" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Upgraded Sonnet model" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/es.json b/webview/src/i18n/locales/es.json index 9c103eb7..4e6486e4 100644 --- a/webview/src/i18n/locales/es.json +++ b/webview/src/i18n/locales/es.json @@ -1402,9 +1402,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · Modelo recomendado por defecto" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · Modelo insignia anterior" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Modelo mejorado de Sonnet 4.6" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/fr.json b/webview/src/i18n/locales/fr.json index ecc7b518..c58e0959 100644 --- a/webview/src/i18n/locales/fr.json +++ b/webview/src/i18n/locales/fr.json @@ -1402,9 +1402,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · Modèle recommandé par défaut" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · Modèle phare précédent" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Modèle amélioré de Sonnet 4.6" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/hi.json b/webview/src/i18n/locales/hi.json index 307ecf80..7a6287e5 100644 --- a/webview/src/i18n/locales/hi.json +++ b/webview/src/i18n/locales/hi.json @@ -1401,9 +1401,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · डिफ़ॉल्ट अनुशंसित मॉडल" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · पिछला फ्लैगशिप मॉडल" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Sonnet 4.6 का अपग्रेड मॉडल" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/ja.json b/webview/src/i18n/locales/ja.json index 6cad15d8..4aa0ee94 100644 --- a/webview/src/i18n/locales/ja.json +++ b/webview/src/i18n/locales/ja.json @@ -1413,9 +1413,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · デフォルトモデルを使用" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · 前世代フラッグシップモデル" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Sonnet 4.6 のアップグレードモデル" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/ko.json b/webview/src/i18n/locales/ko.json index acd0fdb8..3c9c6bcf 100644 --- a/webview/src/i18n/locales/ko.json +++ b/webview/src/i18n/locales/ko.json @@ -1519,9 +1519,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · 기본 모델 사용" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · 이전 세대 플래그십 모델" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Sonnet 4.6 업그레이드 모델" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/pt-BR.json b/webview/src/i18n/locales/pt-BR.json index 7fd9f5de..3807b031 100644 --- a/webview/src/i18n/locales/pt-BR.json +++ b/webview/src/i18n/locales/pt-BR.json @@ -1574,9 +1574,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · Usar o modelo padrão" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · Modelo principal anterior" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Modelo aprimorado do Sonnet 4.6" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/ru.json b/webview/src/i18n/locales/ru.json index eac425f0..e26ae440 100644 --- a/webview/src/i18n/locales/ru.json +++ b/webview/src/i18n/locales/ru.json @@ -1428,9 +1428,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · Модель по умолчанию" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · Предыдущая флагманская модель" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Обновленная модель Sonnet 4.6" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/zh-TW.json b/webview/src/i18n/locales/zh-TW.json index 36d4aa37..b8171a75 100644 --- a/webview/src/i18n/locales/zh-TW.json +++ b/webview/src/i18n/locales/zh-TW.json @@ -1407,9 +1407,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · 預設推薦模型" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · 上一代旗艦模型" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Sonnet 4.6 升級模型" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/i18n/locales/zh.json b/webview/src/i18n/locales/zh.json index 8b20ab0c..dfcda2a7 100644 --- a/webview/src/i18n/locales/zh.json +++ b/webview/src/i18n/locales/zh.json @@ -1651,9 +1651,9 @@ "label": "Sonnet 4.6", "description": "Sonnet 4.6 · 默认推荐模型" }, - "opus46": { - "label": "Opus 4.7", - "description": "Opus 4.7 · 上一代旗舰模型" + "sonnet5": { + "label": "Sonnet 5", + "description": "Sonnet 5 · Sonnet 4.6 升级模型" }, "opus46_1m": { "label": "Opus 4.6", diff --git a/webview/src/version/changelog.ts b/webview/src/version/changelog.ts index cab83d18..983fc66f 100644 --- a/webview/src/version/changelog.ts +++ b/webview/src/version/changelog.ts @@ -12,6 +12,18 @@ export interface ChangelogEntry { } export const CHANGELOG_DATA: ChangelogEntry[] = [ + { + version: '0.4.6-fix1', + date: '2026-07-06', + content: { + en: `🐛 Fixes +- Fix Claude CLI login being treated as a host-managed provider: when \`access\` is \`cli_login\`, the bridge now leaves \`CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST\` unset, avoiding the host-managed credential path for users already authenticated through the Claude CLI (by @zkpaiminmin) +- Fix Claude model metadata after the lineup update: replace the retired \`claude-opus-4-7\` built-in option with \`claude-opus-4-8\`, and add \`claude-sonnet-5\` with 200K / 1M context handling, max reasoning support, usage pricing, model mapping, and localized labels across all supported languages (by @zkpaiminmin)`, + zh: `🐛 修复 +- 修复 Claude CLI 登录被误判为 host-managed provider:当认证状态为 \`cli_login\` 时,bridge 不再设置 \`CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST\`,避免已通过 Claude CLI 登录的用户走错 host-managed 凭证路径(by @zkpaiminmin) +- 修复 Claude 模型清单与元数据滞后:内置模型移除已下线的 \`claude-opus-4-7\`,切换到 \`claude-opus-4-8\`,并新增 \`claude-sonnet-5\` 的 200K / 1M 上下文、max reasoning、用量计价、模型映射和全语言本地化标签(by @zkpaiminmin)`, + }, + }, { version: '0.4.6', date: '2026-06-28',