Bring emergency main fixes into v0.4.7 work

Main carries the v0.4.6-fix1 hotfix merge. This merge preserves the current feature/v0.4.7 branch while applying those fixes, with the CLI-login API config test resolved by combining the stricter inherited-env guard from the branch with the explicit empty Claude settings fixture from main.

Constraint: Current branch needed emergency fixes already landed on origin/main

Rejected: Cherry-pick the hotfix commit | merging origin/main preserves upstream history and future conflict context

Confidence: high

Scope-risk: moderate

Directive: Keep CLI-login host-managed regression coverage aligned when changing provider env handling

Tested: node --test ai-bridge/config/api-config.test.js

Not-tested: Full Gradle and webview suites
This commit is contained in:
zhukunpenglinyutong
2026-07-06 16:08:49 +08:00
34 changed files with 230 additions and 176 deletions
+16
View File
@@ -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:
+1 -1
View File
@@ -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') {
+1
View File
@@ -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);
+1 -1
View File
@@ -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: {
+6 -6
View File
@@ -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-'
+1 -1
View File
@@ -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
Vendored
+93 -93
View File
@@ -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
@@ -26,16 +26,16 @@ public class ModelProviderHandler {
static final Map<String, Integer> 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);
@@ -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<JsonObject> getContextUsage(String sessionId, String cwd, String model) {
@@ -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",
@@ -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
@@ -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"));
@@ -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.<Change>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);
}
@@ -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 {
@@ -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());
@@ -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<string, keyof typeof mapping> = {
'claude-sonnet-5': 'sonnet',
'claude-sonnet-4-6': 'sonnet',
'claude-opus-4-8': 'opus',
'claude-opus-4-7': 'opus',
'claude-haiku-4-5': 'haiku',
};
@@ -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);
});
@@ -42,10 +42,10 @@ const DEFAULT_MODEL_MAP: Record<string, ModelInfo> = AVAILABLE_MODELS.reduce(
);
const MODEL_LABEL_KEYS: Record<string, string> = {
'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<string, string> = {
};
const MODEL_DESCRIPTION_KEYS: Record<string, string> = {
'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<string, string> = {
* Legacy Opus 4.6 IDs share the same opus mapping bucket.
*/
const MODEL_ID_TO_MAPPING_KEY: Record<string, string> = {
'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',
@@ -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(
<ReasoningSelect
value="high"
onChange={vi.fn()}
currentProvider="claude"
selectedModel="claude-opus-4-7"
selectedModel="claude-opus-4-8"
/>,
);
@@ -41,6 +41,22 @@ describe('ReasoningSelect', () => {
expect(screen.getByText('Max')).toBeTruthy();
});
it('shows max but not xhigh for Claude Sonnet 5', () => {
render(
<ReasoningSelect
value="high"
onChange={vi.fn()}
currentProvider="claude"
selectedModel="claude-sonnet-5"
/>,
);
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();
@@ -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) => {
+5 -6
View File
@@ -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',
]);
@@ -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: {
@@ -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',
});
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+3 -3
View File
@@ -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",
+12
View File
@@ -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',