fix(agents): isolate Claude client model mapping drafts
This commit is contained in:
1 parent
927f4de98a
commit
4484e3a190
8 files changed
+62
-19
No files matched your search
@@ -146,7 +146,7 @@ const CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW: u64 = 1_000_000;
|
||||
const CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV: &str = "CLAUDE_CODE_MAX_CONTEXT_TOKENS";
|
||||
const CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV: &str = "CLAUDE_AUTOCOMPACT_PCT_OVERRIDE";
|
||||
const DISABLE_AUTO_COMPACT_ENV: &str = "DISABLE_AUTO_COMPACT";
|
||||
const DEFAULT_CLAUDE_AUTO_COMPACT_PCT: u8 = 100;
|
||||
const DEFAULT_CLAUDE_AUTO_COMPACT_PCT: u8 = 90;
|
||||
const MODEL_ALIAS_CONFIG_SECTIONS: &[&str] = &[
|
||||
"codex-api-key",
|
||||
"openai-compatibility",
|
||||
|
||||
@@ -23,7 +23,7 @@ fn claude_agent_config_preserves_existing_fields() {
|
||||
assert_eq!(value["env"]["ANTHROPIC_AUTH_TOKEN"], DEFAULT_API_KEY);
|
||||
assert_eq!(value["env"]["ANTHROPIC_MODEL"], "claude-test");
|
||||
assert_eq!(value["env"][CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV], "200000");
|
||||
assert_eq!(value["env"][CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV], "100");
|
||||
assert_eq!(value["env"][CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV], "90");
|
||||
assert!(value["env"].get(DISABLE_AUTO_COMPACT_ENV).is_none());
|
||||
assert!(value["env"].get("CLAUDE_CODE_EFFORT_LEVEL").is_none());
|
||||
assert_eq!(value["env"]["CLAUDE_CODE_SUBAGENT_MODEL"], "claude-test");
|
||||
@@ -66,7 +66,7 @@ fn claude_code_inspection_normalizes_legacy_1m_suffix() {
|
||||
assert!(mappings.sonnet_1m);
|
||||
assert!(!mappings.haiku_1m);
|
||||
assert_eq!(mappings.max_context_tokens, 1_000_000);
|
||||
assert_eq!(mappings.auto_compact_pct, 100);
|
||||
assert_eq!(mappings.auto_compact_pct, 90);
|
||||
assert!(!mappings.disable_auto_compact);
|
||||
fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
@@ -80,7 +80,7 @@ fn claude_mapping_legacy_json_defaults_1m_preferences_off() {
|
||||
assert!(!mappings.sonnet_1m);
|
||||
assert!(!mappings.haiku_1m);
|
||||
assert_eq!(mappings.max_context_tokens, 200_000);
|
||||
assert_eq!(mappings.auto_compact_pct, 100);
|
||||
assert_eq!(mappings.auto_compact_pct, 90);
|
||||
assert!(!mappings.disable_auto_compact);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -576,7 +576,7 @@ export const jaOverrides = {
|
||||
'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code の既定値は 200000 です。上流モデルの上限を超えないでください。',
|
||||
'agents.claudeCodeRuntime.context1mHint': 'このマッピングで [1m] モデルを使用し、最大コンテキストを 1000000 にします。3 つすべてを無効にすると 200000 に戻ります。',
|
||||
'agents.claudeCodeRuntime.autoCompactPct': '自動圧縮の開始率',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': '既定値は 100% です。小さい値ほど早く圧縮します。',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': '既定値は 90% です。小さい値ほど早く圧縮します。',
|
||||
'agents.claudeCodeRuntime.disableAutoCompact': '自動圧縮を無効化',
|
||||
'agents.claudeCodeRuntime.disableAutoCompactHint': '有効時は /compact による手動圧縮のみ利用できます。',
|
||||
'agents.claudeDesktopMapping.customMapping': 'モデルエイリアスを使用',
|
||||
|
||||
@@ -571,7 +571,7 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code defaults to 200000. Do not exceed the upstream model limit.',
|
||||
'agents.claudeCodeRuntime.context1mHint': 'Uses the [1m] model for this mapping and sets the maximum context to 1000000. It returns to 200000 after all three mappings are off.',
|
||||
'agents.claudeCodeRuntime.autoCompactPct': 'Auto-compact trigger',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': 'Defaults to 100%. Lower values compact earlier.',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': 'Defaults to 90%. Lower values compact earlier.',
|
||||
'agents.claudeCodeRuntime.disableAutoCompact': 'Disable auto-compaction',
|
||||
'agents.claudeCodeRuntime.disableAutoCompactHint': 'Only manual /compact remains available when enabled.',
|
||||
'agents.claudeDesktopMapping.customMapping': 'Use Model Aliases',
|
||||
|
||||
@@ -569,7 +569,7 @@ export const zhCN = {
|
||||
'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code 默认 200000;请勿超过上游模型实际支持的窗口。',
|
||||
'agents.claudeCodeRuntime.context1mHint': '为此映射使用 [1m] 模型并将最大窗口设为 1000000;三个映射均关闭后恢复为 200000。',
|
||||
'agents.claudeCodeRuntime.autoCompactPct': '触发压缩百分比',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': '默认 100;调低后会更早自动压缩。',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': '默认 90%;调低后会更早自动压缩。',
|
||||
'agents.claudeCodeRuntime.disableAutoCompact': '禁止自动压缩',
|
||||
'agents.claudeCodeRuntime.disableAutoCompactHint': '开启后仅允许用户通过 /compact 手动压缩。',
|
||||
'agents.claudeDesktopMapping.customMapping': '使用模型别名',
|
||||
|
||||
+18
-11
@@ -39,6 +39,7 @@ import {
|
||||
} from '../services/agentModelPicker';
|
||||
import {
|
||||
resolveAgentConfigurationAction,
|
||||
resolveAgentModelMappingsDraftSource,
|
||||
sameAgentModel,
|
||||
sameAgentModelMappings,
|
||||
} from '../services/agentConfigurationDraft';
|
||||
@@ -111,7 +112,7 @@ type ClaudeModelMappings = {
|
||||
|
||||
const CODEX_OAUTH_LOGIN_REQUIRED_ERROR = 'CODEX_OAUTH_LOGIN_REQUIRED';
|
||||
const DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS = 200_000;
|
||||
const DEFAULT_CLAUDE_AUTO_COMPACT_PCT = 100;
|
||||
const DEFAULT_CLAUDE_AUTO_COMPACT_PCT = 90;
|
||||
|
||||
const createClaudeModelMappings = (model: string): ClaudeModelMappings => ({
|
||||
opus: model,
|
||||
@@ -720,18 +721,24 @@ export function AgentsPage() {
|
||||
const appliedMappings = selected === 'claude-code'
|
||||
? activeStatus?.claudeCodeModelMappings
|
||||
: activeStatus?.claudeDesktopModelMappings;
|
||||
if (!claudeModelMappingsDirtyRef.current && appliedMappings) {
|
||||
const appliedModels = claudeMappingRoles
|
||||
.map((role) => findAgentModel(models, appliedMappings[role.key]))
|
||||
.filter((model): model is ModelOption => model !== null);
|
||||
if (appliedModels.length === claudeMappingRoles.length) {
|
||||
setClaudeCustomMapping(appliedModels.every((model) => Boolean(model.isAlias)));
|
||||
}
|
||||
if (!claudeModelMappingsDirtyRef.current) {
|
||||
const appliedModels = appliedMappings
|
||||
? claudeMappingRoles
|
||||
.map((role) => findAgentModel(models, appliedMappings[role.key]))
|
||||
.filter((model): model is ModelOption => model !== null)
|
||||
: [];
|
||||
setClaudeCustomMapping(
|
||||
appliedModels.length === claudeMappingRoles.length
|
||||
&& appliedModels.every((model) => Boolean(model.isAlias)),
|
||||
);
|
||||
}
|
||||
setClaudeModelMappingsDraft((current) => {
|
||||
const source = claudeModelMappingsDirtyRef.current
|
||||
? current
|
||||
: appliedMappings ?? current;
|
||||
const source = resolveAgentModelMappingsDraftSource(
|
||||
current,
|
||||
appliedMappings,
|
||||
createClaudeModelMappings(selectedModel),
|
||||
claudeModelMappingsDirtyRef.current,
|
||||
);
|
||||
const next: ClaudeModelMappings = {
|
||||
opus: findAgentModel(models, source.opus)?.name ?? selectedModel,
|
||||
sonnet: findAgentModel(models, source.sonnet)?.name ?? selectedModel,
|
||||
|
||||
@@ -50,9 +50,16 @@ export const sameAgentModelMappings = (
|
||||
&& Boolean(left.sonnet1m) === Boolean(right.sonnet1m)
|
||||
&& Boolean(left.haiku1m) === Boolean(right.haiku1m)
|
||||
&& (left.maxContextTokens ?? 200_000) === (right.maxContextTokens ?? 200_000)
|
||||
&& (left.autoCompactPct ?? 100) === (right.autoCompactPct ?? 100)
|
||||
&& (left.autoCompactPct ?? 90) === (right.autoCompactPct ?? 90)
|
||||
&& Boolean(left.disableAutoCompact) === Boolean(right.disableAutoCompact);
|
||||
|
||||
export const resolveAgentModelMappingsDraftSource = <T>(
|
||||
current: T,
|
||||
applied: T | null | undefined,
|
||||
fallback: T,
|
||||
dirty: boolean,
|
||||
): T => (dirty ? current : applied ?? fallback);
|
||||
|
||||
export function resolveAgentConfigurationAction({
|
||||
client,
|
||||
modificationState,
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from '../src/services/agentModelPicker';
|
||||
import {
|
||||
resolveAgentConfigurationAction,
|
||||
resolveAgentModelMappingsDraftSource,
|
||||
sameAgentModelMappings,
|
||||
type AgentConfigurationClientId,
|
||||
} from '../src/services/agentConfigurationDraft';
|
||||
|
||||
@@ -98,6 +100,33 @@ const appliedConfiguration = (
|
||||
});
|
||||
|
||||
describe('agent configuration update action', () => {
|
||||
test('Claude mapping clients do not inherit another client draft', () => {
|
||||
const codeDraft = { opus: 'code-opus', sonnet: 'code-sonnet', haiku: 'code-haiku' };
|
||||
const desktopFallback = {
|
||||
opus: 'desktop-model',
|
||||
sonnet: 'desktop-model',
|
||||
haiku: 'desktop-model',
|
||||
};
|
||||
expect(resolveAgentModelMappingsDraftSource(
|
||||
codeDraft,
|
||||
null,
|
||||
desktopFallback,
|
||||
false,
|
||||
)).toEqual(desktopFallback);
|
||||
expect(resolveAgentModelMappingsDraftSource(
|
||||
codeDraft,
|
||||
null,
|
||||
desktopFallback,
|
||||
true,
|
||||
)).toEqual(codeDraft);
|
||||
});
|
||||
|
||||
test('Claude Code missing auto-compact preference defaults to 90%', () => {
|
||||
const mappings = { opus: 'model-a', sonnet: 'model-a', haiku: 'model-a' };
|
||||
expect(sameAgentModelMappings(mappings, { ...mappings, autoCompactPct: 90 })).toBeTrue();
|
||||
expect(sameAgentModelMappings(mappings, { ...mappings, autoCompactPct: 100 })).toBeFalse();
|
||||
});
|
||||
|
||||
test.each<AgentConfigurationClientId>([
|
||||
'claude-code',
|
||||
'claude-desktop',
|
||||
|
||||
Reference in new issue
Block a user