15 files changed
+690
-172
No files matched your search
@@ -54,8 +54,8 @@
|
||||
{ "slug": "gemini-2.5-pro" },
|
||||
{ "slug": "gemini-2.5-flash" },
|
||||
{ "slug": "gemini-2.5-flash-lite" },
|
||||
{ "slug": "deepseek-v4-flash", "display_name": "DeepSeek V4 Flash", "context_window": 1000000, "claude_code_effort_level": "max" },
|
||||
{ "slug": "deepseek-v4-pro", "display_name": "DeepSeek V4 Pro", "context_window": 1000000, "claude_code_effort_level": "max" },
|
||||
{ "slug": "deepseek-v4-flash", "display_name": "DeepSeek V4 Flash", "context_window": 1000000},
|
||||
{ "slug": "deepseek-v4-pro", "display_name": "DeepSeek V4 Pro", "context_window": 1000000},
|
||||
{ "slug": "deepseek-chat", "display_name": "DeepSeek Chat" },
|
||||
{ "slug": "deepseek-reasoner", "display_name": "DeepSeek Reasoner" },
|
||||
{ "slug": "grok-4.5" },
|
||||
|
||||
@@ -677,6 +677,9 @@ pub(crate) fn resolve_claude_desktop_model_mappings(
|
||||
opus_1m: requested.opus_1m,
|
||||
sonnet_1m: requested.sonnet_1m,
|
||||
haiku_1m: requested.haiku_1m,
|
||||
max_context_tokens: requested.max_context_tokens,
|
||||
auto_compact_pct: requested.auto_compact_pct,
|
||||
disable_auto_compact: requested.disable_auto_compact,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -690,6 +693,17 @@ pub(crate) fn resolve_claude_code_model_mappings(
|
||||
return Ok(None);
|
||||
}
|
||||
let requested = requested.unwrap_or_else(|| ClaudeDesktopModelMappings::all(selected_model));
|
||||
if !(100_000..=1_000_000).contains(&requested.max_context_tokens) {
|
||||
return Err("Claude Code 最大窗口必须介于 100000 和 1000000 之间".to_string());
|
||||
}
|
||||
if !(1..=100).contains(&requested.auto_compact_pct) {
|
||||
return Err("Claude Code 触发压缩百分比必须介于 1 和 100 之间".to_string());
|
||||
}
|
||||
let max_context_tokens = if requested.opus_1m || requested.sonnet_1m || requested.haiku_1m {
|
||||
CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW
|
||||
} else {
|
||||
requested.max_context_tokens
|
||||
};
|
||||
let resolve =
|
||||
|model: &str| resolve_available_agent_model(models, &validate_agent_model(model)?);
|
||||
Ok(Some(ClaudeDesktopModelMappings {
|
||||
@@ -699,6 +713,9 @@ pub(crate) fn resolve_claude_code_model_mappings(
|
||||
opus_1m: requested.opus_1m,
|
||||
sonnet_1m: requested.sonnet_1m,
|
||||
haiku_1m: requested.haiku_1m,
|
||||
max_context_tokens,
|
||||
auto_compact_pct: requested.auto_compact_pct,
|
||||
disable_auto_compact: requested.disable_auto_compact,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -220,8 +220,7 @@ pub(crate) fn build_claude_agent_config(
|
||||
let mappings = mappings
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ClaudeDesktopModelMappings::all(model));
|
||||
let max_context_tokens = claude_code_max_context_tokens(&mappings, models)?;
|
||||
let use_max_context_override = !mappings.sonnet_1m;
|
||||
let max_context_tokens = mappings.max_context_tokens;
|
||||
let model_settings = claude_code_model_settings(&mappings);
|
||||
let subagent_model = model_settings.haiku.clone();
|
||||
let effort_level = claude_code_model_effort_level(models, &mappings.sonnet)?;
|
||||
@@ -229,6 +228,8 @@ pub(crate) fn build_claude_agent_config(
|
||||
env.remove("ANTHROPIC_API_KEY");
|
||||
env.remove("CLAUDE_CODE_EFFORT_LEVEL");
|
||||
env.remove(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV);
|
||||
env.remove(CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV);
|
||||
env.remove(DISABLE_AUTO_COMPACT_ENV);
|
||||
for (key, value) in [
|
||||
("ANTHROPIC_BASE_URL", base_url),
|
||||
("ANTHROPIC_AUTH_TOKEN", api_key),
|
||||
@@ -253,10 +254,18 @@ pub(crate) fn build_claude_agent_config(
|
||||
serde_json::Value::String(value.to_string()),
|
||||
);
|
||||
}
|
||||
if use_max_context_override {
|
||||
env.insert(
|
||||
CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV.to_string(),
|
||||
serde_json::Value::String(max_context_tokens.to_string()),
|
||||
);
|
||||
env.insert(
|
||||
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV.to_string(),
|
||||
serde_json::Value::String(mappings.auto_compact_pct.to_string()),
|
||||
);
|
||||
if mappings.disable_auto_compact {
|
||||
env.insert(
|
||||
CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV.to_string(),
|
||||
serde_json::Value::String(max_context_tokens.to_string()),
|
||||
DISABLE_AUTO_COMPACT_ENV.to_string(),
|
||||
serde_json::Value::String("1".to_string()),
|
||||
);
|
||||
}
|
||||
for (key, value) in claude_code_model_presentation_environment(&mappings, models)? {
|
||||
@@ -647,6 +656,8 @@ pub(crate) fn remove_claude_code_managed_configuration(
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
||||
CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV,
|
||||
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV,
|
||||
DISABLE_AUTO_COMPACT_ENV,
|
||||
] {
|
||||
env.remove(key);
|
||||
}
|
||||
@@ -1018,6 +1029,8 @@ pub(crate) fn build_restored_claude_code_config(
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
"ANTHROPIC_DEFAULT_FABLE_MODEL",
|
||||
CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV,
|
||||
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV,
|
||||
DISABLE_AUTO_COMPACT_ENV,
|
||||
] {
|
||||
restore_json_key(env, original_env, key);
|
||||
}
|
||||
|
||||
@@ -1749,35 +1749,52 @@ pub(crate) fn inspect_claude_code_model_mappings(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.or(fallback)?;
|
||||
Some((
|
||||
strip_claude_code_context_suffix(value).to_string(),
|
||||
has_claude_code_context_suffix(value),
|
||||
))
|
||||
let normalized = strip_claude_code_context_suffix(value);
|
||||
Some((normalized.to_string(), normalized.len() != value.len()))
|
||||
};
|
||||
let Some((opus, opus_1m)) = read_model("ANTHROPIC_DEFAULT_OPUS_MODEL") else {
|
||||
let Some((opus, opus_had_1m)) = read_model("ANTHROPIC_DEFAULT_OPUS_MODEL") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some((sonnet, sonnet_1m)) = read_model("ANTHROPIC_DEFAULT_SONNET_MODEL") else {
|
||||
let Some((sonnet, sonnet_had_1m)) = read_model("ANTHROPIC_DEFAULT_SONNET_MODEL") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some((haiku, haiku_1m)) = read_model("ANTHROPIC_DEFAULT_HAIKU_MODEL") else {
|
||||
let Some((haiku, haiku_had_1m)) = read_model("ANTHROPIC_DEFAULT_HAIKU_MODEL") else {
|
||||
return Ok(None);
|
||||
};
|
||||
let legacy_1m = opus_had_1m || sonnet_had_1m || haiku_had_1m;
|
||||
let max_context_tokens = env
|
||||
.get(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.and_then(|value| value.trim().parse::<u64>().ok())
|
||||
.unwrap_or_else(|| {
|
||||
if legacy_1m {
|
||||
CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW
|
||||
} else {
|
||||
default_claude_code_max_context_tokens()
|
||||
}
|
||||
});
|
||||
let auto_compact_pct = env
|
||||
.get(CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.and_then(|value| value.trim().parse::<u8>().ok())
|
||||
.unwrap_or_else(default_claude_auto_compact_pct);
|
||||
let disable_auto_compact = env
|
||||
.get(DISABLE_AUTO_COMPACT_ENV)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(|value| value == "1" || value.eq_ignore_ascii_case("true"));
|
||||
Ok(Some(ClaudeDesktopModelMappings {
|
||||
opus,
|
||||
sonnet,
|
||||
haiku,
|
||||
opus_1m,
|
||||
sonnet_1m,
|
||||
haiku_1m,
|
||||
opus_1m: opus_had_1m,
|
||||
sonnet_1m: sonnet_had_1m,
|
||||
haiku_1m: haiku_had_1m,
|
||||
max_context_tokens,
|
||||
auto_compact_pct,
|
||||
disable_auto_compact,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn has_claude_code_context_suffix(value: &str) -> bool {
|
||||
let value = value.trim();
|
||||
value.len() >= 4 && value[value.len() - 4..].eq_ignore_ascii_case("[1m]")
|
||||
}
|
||||
|
||||
pub(crate) fn strip_claude_code_context_suffix(value: &str) -> &str {
|
||||
let mut value = value.trim();
|
||||
loop {
|
||||
@@ -1850,15 +1867,10 @@ pub(crate) fn claude_effective_context_window(
|
||||
.max(CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW),
|
||||
);
|
||||
}
|
||||
context_window.map(|context_window| {
|
||||
if context_window >= CLAUDE_DESKTOP_EXTENDED_CONTEXT_WINDOW {
|
||||
DEFAULT_CLAUDE_CONTEXT_WINDOW
|
||||
} else {
|
||||
context_window
|
||||
}
|
||||
})
|
||||
context_window
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn claude_code_max_context_tokens(
|
||||
mappings: &ClaudeDesktopModelMappings,
|
||||
models: &[AgentModelOption],
|
||||
@@ -1913,6 +1925,9 @@ pub(crate) fn claude_code_model_settings(
|
||||
opus_1m: mappings.opus_1m,
|
||||
sonnet_1m: mappings.sonnet_1m,
|
||||
haiku_1m: mappings.haiku_1m,
|
||||
max_context_tokens: mappings.max_context_tokens,
|
||||
auto_compact_pct: mappings.auto_compact_pct,
|
||||
disable_auto_compact: mappings.disable_auto_compact,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1930,10 +1945,12 @@ pub(crate) fn claude_code_model_presentation(
|
||||
models: &[AgentModelOption],
|
||||
model_name: &str,
|
||||
enable_1m: bool,
|
||||
context_window_override: Option<u64>,
|
||||
role: Option<&str>,
|
||||
) -> (String, String) {
|
||||
let display_name = agent_model_display_name(models, model_name);
|
||||
let context_window = claude_effective_context_window(models, model_name, enable_1m)
|
||||
let context_window = context_window_override
|
||||
.or_else(|| claude_effective_context_window(models, model_name, enable_1m))
|
||||
.unwrap_or(DEFAULT_CLAUDE_CONTEXT_WINDOW);
|
||||
let context_label = format_context_window(context_window);
|
||||
let name = match role {
|
||||
@@ -1952,27 +1969,37 @@ pub(crate) fn claude_code_model_presentation_environment(
|
||||
models,
|
||||
&mappings.opus,
|
||||
mappings.opus_1m,
|
||||
Some(mappings.max_context_tokens),
|
||||
Some("Opus mapping"),
|
||||
);
|
||||
let sonnet = claude_code_model_presentation(
|
||||
models,
|
||||
&mappings.sonnet,
|
||||
mappings.sonnet_1m,
|
||||
Some(mappings.max_context_tokens),
|
||||
Some("Sonnet mapping"),
|
||||
);
|
||||
let haiku = claude_code_model_presentation(
|
||||
models,
|
||||
&mappings.haiku,
|
||||
mappings.haiku_1m,
|
||||
Some(mappings.max_context_tokens),
|
||||
Some("Haiku mapping"),
|
||||
);
|
||||
let fable = claude_code_model_presentation(
|
||||
models,
|
||||
&mappings.sonnet,
|
||||
mappings.sonnet_1m,
|
||||
Some(mappings.max_context_tokens),
|
||||
Some("Fable mapping"),
|
||||
);
|
||||
let custom = claude_code_model_presentation(models, &mappings.sonnet, mappings.sonnet_1m, None);
|
||||
let custom = claude_code_model_presentation(
|
||||
models,
|
||||
&mappings.sonnet,
|
||||
mappings.sonnet_1m,
|
||||
Some(mappings.max_context_tokens),
|
||||
None,
|
||||
);
|
||||
let custom_model = claude_code_model_setting(&mappings.sonnet, mappings.sonnet_1m);
|
||||
Ok([
|
||||
("ANTHROPIC_DEFAULT_OPUS_MODEL_NAME", opus.0),
|
||||
|
||||
@@ -150,11 +150,12 @@ mod tests {
|
||||
let catalog = parse_catalog(MODEL_CATALOG_JSON).unwrap();
|
||||
assert_eq!(catalog.context_windows["deepseek-v4-flash"], 1_000_000);
|
||||
assert_eq!(catalog.context_windows["deepseek-v4-pro"], 1_000_000);
|
||||
assert_eq!(
|
||||
catalog.claude_code_effort_levels["deepseek-v4-flash"],
|
||||
"max"
|
||||
);
|
||||
assert_eq!(catalog.claude_code_effort_levels["deepseek-v4-pro"], "max");
|
||||
assert!(!catalog
|
||||
.claude_code_effort_levels
|
||||
.contains_key("deepseek-v4-flash"));
|
||||
assert!(!catalog
|
||||
.claude_code_effort_levels
|
||||
.contains_key("deepseek-v4-pro"));
|
||||
assert_eq!(
|
||||
catalog.display_names["deepseek-v4-flash"],
|
||||
"DeepSeek V4 Flash"
|
||||
|
||||
@@ -70,7 +70,20 @@ pub(crate) async fn ensure_claude_desktop_model_aliases(
|
||||
models: &[AgentModelOption],
|
||||
) -> Result<(), String> {
|
||||
let content = fetch_management_config_yaml(config).await?;
|
||||
let updated = ensure_claude_desktop_model_aliases_in_yaml(&content, mappings, models)?;
|
||||
let updated = match ensure_claude_desktop_model_aliases_in_yaml(&content, mappings, models) {
|
||||
Ok(updated) => updated,
|
||||
Err(source_error) => {
|
||||
let definitions = fetch_codex_model_definitions(config)
|
||||
.await
|
||||
.map_err(|error| format!("{source_error};{error}"))?;
|
||||
ensure_claude_desktop_model_aliases_with_codex_oauth_in_yaml(
|
||||
&content,
|
||||
mappings,
|
||||
models,
|
||||
&definitions,
|
||||
)?
|
||||
}
|
||||
};
|
||||
if updated != content {
|
||||
put_management_config_yaml(config, &updated).await?;
|
||||
}
|
||||
@@ -92,6 +105,15 @@ pub(crate) fn ensure_claude_desktop_model_aliases_in_yaml(
|
||||
content: &str,
|
||||
mappings: &ClaudeDesktopModelMappings,
|
||||
models: &[AgentModelOption],
|
||||
) -> Result<String, String> {
|
||||
ensure_claude_desktop_model_aliases_with_codex_oauth_in_yaml(content, mappings, models, &[])
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_claude_desktop_model_aliases_with_codex_oauth_in_yaml(
|
||||
content: &str,
|
||||
mappings: &ClaudeDesktopModelMappings,
|
||||
models: &[AgentModelOption],
|
||||
codex_oauth_models: &[CodexModelDefinition],
|
||||
) -> Result<String, String> {
|
||||
let mut document = yaml_serde_edit::YamlValue::parse(content)
|
||||
.map_err(|error| format!("解析内核 YAML 配置失败: {error}"))?;
|
||||
@@ -113,7 +135,7 @@ pub(crate) fn ensure_claude_desktop_model_aliases_in_yaml(
|
||||
remove_managed_claude_model_alias(root, alias)?;
|
||||
}
|
||||
} else {
|
||||
ensure_claude_desktop_model_alias(root, source_model, alias)?;
|
||||
ensure_claude_desktop_model_alias(root, source_model, alias, codex_oauth_models)?;
|
||||
}
|
||||
}
|
||||
render_updated_core_yaml(&mut document, updated)
|
||||
@@ -167,6 +189,7 @@ pub(crate) fn remove_managed_claude_model_alias(
|
||||
changed |= models.len() != before;
|
||||
}
|
||||
}
|
||||
changed |= remove_oauth_claude_model_alias(root, alias, true)?;
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
@@ -200,6 +223,51 @@ pub(crate) fn remove_existing_claude_model_alias(
|
||||
changed |= models.len() != before;
|
||||
}
|
||||
}
|
||||
changed |= remove_oauth_claude_model_alias(root, alias, false)?;
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
pub(crate) fn remove_oauth_claude_model_alias(
|
||||
root: &mut serde_norway::Mapping,
|
||||
alias: &str,
|
||||
managed_only: bool,
|
||||
) -> Result<bool, String> {
|
||||
let Some(oauth_aliases) = yaml_mapping_value_mut(root, "oauth-model-alias") else {
|
||||
return Ok(false);
|
||||
};
|
||||
let oauth_aliases = oauth_aliases
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?;
|
||||
let mut changed = false;
|
||||
let mut empty_channels = Vec::new();
|
||||
for (channel, entries) in oauth_aliases.iter_mut() {
|
||||
let channel_name = channel.as_str().unwrap_or("unknown");
|
||||
let entries = entries
|
||||
.as_sequence_mut()
|
||||
.ok_or_else(|| format!("oauth-model-alias.{channel_name} 必须是数组"))?;
|
||||
let before = entries.len();
|
||||
entries.retain(|entry| {
|
||||
if managed_only {
|
||||
!is_managed_claude_model_alias(entry, alias)
|
||||
} else {
|
||||
configured_model_identity(entry)
|
||||
.is_none_or(|(_, client_model, _)| !client_model.eq_ignore_ascii_case(alias))
|
||||
}
|
||||
});
|
||||
if entries.len() != before {
|
||||
changed = true;
|
||||
if entries.is_empty() {
|
||||
empty_channels.push(channel.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for channel in empty_channels {
|
||||
oauth_aliases.remove(&channel);
|
||||
}
|
||||
let remove_oauth_section = changed && oauth_aliases.is_empty();
|
||||
if remove_oauth_section {
|
||||
root.remove(yaml_key("oauth-model-alias"));
|
||||
}
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
@@ -232,7 +300,7 @@ pub(crate) fn configured_managed_claude_alias_exists(
|
||||
root: &serde_norway::Mapping,
|
||||
alias: &str,
|
||||
) -> bool {
|
||||
MODEL_ALIAS_CONFIG_SECTIONS.iter().any(|section| {
|
||||
let configured_provider_alias = MODEL_ALIAS_CONFIG_SECTIONS.iter().any(|section| {
|
||||
yaml_mapping_value(root, section)
|
||||
.and_then(serde_norway::Value::as_sequence)
|
||||
.into_iter()
|
||||
@@ -242,13 +310,22 @@ pub(crate) fn configured_managed_claude_alias_exists(
|
||||
.filter_map(serde_norway::Value::as_sequence)
|
||||
.flatten()
|
||||
.any(|model| is_managed_claude_model_alias(model, alias))
|
||||
})
|
||||
});
|
||||
configured_provider_alias
|
||||
|| yaml_mapping_value(root, "oauth-model-alias")
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
.into_iter()
|
||||
.flat_map(|channels| channels.values())
|
||||
.filter_map(serde_norway::Value::as_sequence)
|
||||
.flatten()
|
||||
.any(|model| is_managed_claude_model_alias(model, alias))
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_claude_desktop_model_alias(
|
||||
root: &mut serde_norway::Mapping,
|
||||
source_model: &str,
|
||||
alias: &str,
|
||||
codex_oauth_models: &[CodexModelDefinition],
|
||||
) -> Result<(), String> {
|
||||
if let Some((existing_source, _)) = configured_model_client_identity(root, alias) {
|
||||
if existing_source.eq_ignore_ascii_case(source_model)
|
||||
@@ -261,6 +338,13 @@ pub(crate) fn ensure_claude_desktop_model_alias(
|
||||
if append_claude_desktop_model_alias(root, source_model, alias)? {
|
||||
return Ok(());
|
||||
}
|
||||
if codex_oauth_models
|
||||
.iter()
|
||||
.any(|model| model.id.eq_ignore_ascii_case(source_model))
|
||||
{
|
||||
append_managed_codex_oauth_model_alias(root, source_model, alias)?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(format!(
|
||||
"无法确定模型 {source_model} 的 CPA 配置来源,无法创建 Claude Desktop 别名 {alias}"
|
||||
))
|
||||
@@ -296,6 +380,24 @@ pub(crate) fn configured_model_client_identity(
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(oauth_aliases) =
|
||||
yaml_mapping_value(root, "oauth-model-alias").and_then(serde_norway::Value::as_mapping)
|
||||
{
|
||||
for entries in oauth_aliases
|
||||
.values()
|
||||
.filter_map(serde_norway::Value::as_sequence)
|
||||
{
|
||||
for model in entries {
|
||||
let Some((source, configured_client_model, _)) = configured_model_identity(model)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if configured_client_model.eq_ignore_ascii_case(client_model) {
|
||||
return Some((source, configured_client_model));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -359,6 +461,41 @@ pub(crate) fn append_claude_desktop_model_alias(
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub(crate) fn append_managed_codex_oauth_model_alias(
|
||||
root: &mut serde_norway::Mapping,
|
||||
source_model: &str,
|
||||
alias: &str,
|
||||
) -> Result<(), String> {
|
||||
let oauth_aliases = root
|
||||
.entry(yaml_key("oauth-model-alias"))
|
||||
.or_insert_with(|| serde_norway::Value::Mapping(serde_norway::Mapping::new()))
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| "oauth-model-alias 必须是 YAML 映射".to_string())?;
|
||||
let codex_aliases = oauth_aliases
|
||||
.entry(yaml_key("codex"))
|
||||
.or_insert_with(|| serde_norway::Value::Sequence(Vec::new()))
|
||||
.as_sequence_mut()
|
||||
.ok_or_else(|| "oauth-model-alias.codex 必须是数组".to_string())?;
|
||||
let display_name = managed_claude_alias_display_name(alias)
|
||||
.ok_or_else(|| format!("不支持的 Claude 托管别名: {alias}"))?;
|
||||
let mut alias_mapping = serde_norway::Mapping::new();
|
||||
alias_mapping.insert(
|
||||
yaml_key("name"),
|
||||
serde_norway::Value::String(source_model.to_string()),
|
||||
);
|
||||
alias_mapping.insert(
|
||||
yaml_key("alias"),
|
||||
serde_norway::Value::String(alias.to_string()),
|
||||
);
|
||||
alias_mapping.insert(yaml_key("fork"), serde_norway::Value::Bool(true));
|
||||
alias_mapping.insert(
|
||||
yaml_key("display-name"),
|
||||
serde_norway::Value::String(display_name.to_string()),
|
||||
);
|
||||
codex_aliases.push(serde_norway::Value::Mapping(alias_mapping));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_codex_model_definitions(
|
||||
config: &GuiConfigFile,
|
||||
) -> Result<Vec<CodexModelDefinition>, String> {
|
||||
|
||||
@@ -144,6 +144,9 @@ const MANAGED_CLAUDE_HAIKU_ALIAS_DISPLAY_NAME: &str =
|
||||
const DEFAULT_CLAUDE_CONTEXT_WINDOW: u64 = 200_000;
|
||||
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 MODEL_ALIAS_CONFIG_SECTIONS: &[&str] = &[
|
||||
"codex-api-key",
|
||||
"openai-compatibility",
|
||||
@@ -790,6 +793,20 @@ struct ClaudeDesktopModelMappings {
|
||||
sonnet_1m: bool,
|
||||
#[serde(default)]
|
||||
haiku_1m: bool,
|
||||
#[serde(default = "default_claude_code_max_context_tokens")]
|
||||
max_context_tokens: u64,
|
||||
#[serde(default = "default_claude_auto_compact_pct")]
|
||||
auto_compact_pct: u8,
|
||||
#[serde(default)]
|
||||
disable_auto_compact: bool,
|
||||
}
|
||||
|
||||
fn default_claude_code_max_context_tokens() -> u64 {
|
||||
DEFAULT_CLAUDE_CONTEXT_WINDOW
|
||||
}
|
||||
|
||||
fn default_claude_auto_compact_pct() -> u8 {
|
||||
DEFAULT_CLAUDE_AUTO_COMPACT_PCT
|
||||
}
|
||||
|
||||
impl ClaudeDesktopModelMappings {
|
||||
@@ -801,6 +818,9 @@ impl ClaudeDesktopModelMappings {
|
||||
opus_1m: false,
|
||||
sonnet_1m: false,
|
||||
haiku_1m: false,
|
||||
max_context_tokens: default_claude_code_max_context_tokens(),
|
||||
auto_compact_pct: default_claude_auto_compact_pct(),
|
||||
disable_auto_compact: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ fn claude_agent_config_preserves_existing_fields() {
|
||||
assert_eq!(value["env"]["ANTHROPIC_BASE_URL"], "http://127.0.0.1:8317");
|
||||
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!(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");
|
||||
assert_eq!(
|
||||
@@ -32,7 +35,7 @@ fn claude_agent_config_preserves_existing_fields() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_code_inspection_normalizes_1m_suffix() {
|
||||
fn claude_code_inspection_normalizes_legacy_1m_suffix() {
|
||||
let directory = agent_test_home("claude-code-1m-inspection");
|
||||
let path = directory.join("settings.json");
|
||||
fs::write(
|
||||
@@ -62,6 +65,9 @@ fn claude_code_inspection_normalizes_1m_suffix() {
|
||||
assert!(mappings.opus_1m);
|
||||
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!(!mappings.disable_auto_compact);
|
||||
fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
|
||||
@@ -73,6 +79,9 @@ fn claude_mapping_legacy_json_defaults_1m_preferences_off() {
|
||||
assert!(!mappings.opus_1m);
|
||||
assert!(!mappings.sonnet_1m);
|
||||
assert!(!mappings.haiku_1m);
|
||||
assert_eq!(mappings.max_context_tokens, 200_000);
|
||||
assert_eq!(mappings.auto_compact_pct, 100);
|
||||
assert!(!mappings.disable_auto_compact);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -84,6 +93,9 @@ fn claude_code_role_mappings_drive_settings() {
|
||||
opus_1m: false,
|
||||
sonnet_1m: false,
|
||||
haiku_1m: false,
|
||||
max_context_tokens: 272_000,
|
||||
auto_compact_pct: 80,
|
||||
disable_auto_compact: false,
|
||||
};
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
@@ -133,176 +145,98 @@ fn claude_code_role_mappings_drive_settings() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_code_deepseek_picker_shows_catalog_name_and_1m_context() {
|
||||
fn claude_code_runtime_settings_keep_per_role_1m_suffixes() {
|
||||
let mappings = ClaudeDesktopModelMappings {
|
||||
opus: "deepseek-v4-pro".to_string(),
|
||||
sonnet: "deepseek-v4-pro".to_string(),
|
||||
haiku: "deepseek-v4-flash".to_string(),
|
||||
opus: "custom-pro".to_string(),
|
||||
sonnet: "custom-pro".to_string(),
|
||||
haiku: "custom-flash".to_string(),
|
||||
opus_1m: true,
|
||||
sonnet_1m: true,
|
||||
haiku_1m: false,
|
||||
max_context_tokens: 1_000_000,
|
||||
auto_compact_pct: 75,
|
||||
disable_auto_compact: true,
|
||||
};
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
name: "deepseek-v4-pro".to_string(),
|
||||
alias: Some("DeepSeek V4 Pro".to_string()),
|
||||
name: "custom-pro".to_string(),
|
||||
alias: Some("Custom Pro".to_string()),
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
context_window: Some(200_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "deepseek-v4-flash".to_string(),
|
||||
alias: Some("DeepSeek V4 Flash".to_string()),
|
||||
name: "custom-flash".to_string(),
|
||||
alias: Some("Custom Flash".to_string()),
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
context_window: Some(200_000),
|
||||
},
|
||||
];
|
||||
let rendered = build_claude_agent_config(
|
||||
None,
|
||||
"http://127.0.0.1:8317",
|
||||
"test-key",
|
||||
"deepseek-v4-pro",
|
||||
"custom-pro",
|
||||
&models,
|
||||
Some(&mappings),
|
||||
)
|
||||
.unwrap();
|
||||
let value: serde_json::Value = serde_json::from_str(&rendered).unwrap();
|
||||
|
||||
assert!(value["env"]
|
||||
.get(CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV)
|
||||
.is_none());
|
||||
assert_eq!(value["env"]["ANTHROPIC_MODEL"], "deepseek-v4-pro[1m]");
|
||||
assert_eq!(value["env"][CLAUDE_CODE_MAX_CONTEXT_TOKENS_ENV], "1000000");
|
||||
assert_eq!(value["env"][CLAUDE_AUTOCOMPACT_PCT_OVERRIDE_ENV], "75");
|
||||
assert_eq!(value["env"][DISABLE_AUTO_COMPACT_ENV], "1");
|
||||
assert_eq!(value["env"]["ANTHROPIC_MODEL"], "custom-pro[1m]");
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_DEFAULT_OPUS_MODEL"],
|
||||
"deepseek-v4-pro[1m]"
|
||||
"custom-pro[1m]"
|
||||
);
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_DEFAULT_SONNET_MODEL"],
|
||||
"deepseek-v4-pro[1m]"
|
||||
"custom-pro[1m]"
|
||||
);
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_DEFAULT_HAIKU_MODEL"],
|
||||
"deepseek-v4-flash"
|
||||
"custom-flash"
|
||||
);
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_DEFAULT_FABLE_MODEL"],
|
||||
"deepseek-v4-pro[1m]"
|
||||
"custom-pro[1m]"
|
||||
);
|
||||
assert_eq!(
|
||||
value["env"]["CLAUDE_CODE_SUBAGENT_MODEL"],
|
||||
"deepseek-v4-flash"
|
||||
);
|
||||
assert_eq!(value["env"]["CLAUDE_CODE_EFFORT_LEVEL"], "max");
|
||||
assert_eq!(value["env"]["CLAUDE_CODE_SUBAGENT_MODEL"], "custom-flash");
|
||||
assert!(value["env"].get("CLAUDE_CODE_EFFORT_LEVEL").is_none());
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_CUSTOM_MODEL_OPTION"],
|
||||
"deepseek-v4-pro[1m]"
|
||||
"custom-pro[1m]"
|
||||
);
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_CUSTOM_MODEL_OPTION_NAME"],
|
||||
"DeepSeek V4 Pro (1M context)"
|
||||
"Custom Pro (1M context)"
|
||||
);
|
||||
assert_eq!(
|
||||
value["env"]["ANTHROPIC_DEFAULT_FABLE_MODEL_NAME"],
|
||||
"DeepSeek V4 Pro (Fable mapping, 1M context)"
|
||||
"Custom Pro (Fable mapping, 1M context)"
|
||||
);
|
||||
assert_eq!(value["model"], "deepseek-v4-pro[1m]");
|
||||
assert_eq!(value["model"], "custom-pro[1m]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_code_1m_suffix_follows_user_preference() {
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
name: "deepseek-v4-flash".to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "gpt-runtime-1m".to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
claude_code_model_setting("deepseek-v4-flash", true),
|
||||
"deepseek-v4-flash[1m]"
|
||||
);
|
||||
assert_eq!(
|
||||
claude_code_model_setting("deepseek-v4-flash", false),
|
||||
"deepseek-v4-flash"
|
||||
);
|
||||
assert_eq!(
|
||||
claude_code_model_setting("gpt-runtime-1m", true),
|
||||
"gpt-runtime-1m[1m]"
|
||||
);
|
||||
assert_eq!(
|
||||
claude_code_max_context_tokens(
|
||||
&ClaudeDesktopModelMappings::all("gpt-runtime-1m"),
|
||||
&models,
|
||||
)
|
||||
.unwrap(),
|
||||
DEFAULT_CLAUDE_CONTEXT_WINDOW
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_code_context_window_follows_primary_model_alias_source() {
|
||||
let mappings = ClaudeDesktopModelMappings {
|
||||
opus: "small-model".to_string(),
|
||||
sonnet: "primary-alias".to_string(),
|
||||
haiku: "large-model".to_string(),
|
||||
opus_1m: false,
|
||||
sonnet_1m: false,
|
||||
haiku_1m: false,
|
||||
};
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
name: mappings.opus.clone(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(128_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: "primary-model".to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(372_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: mappings.sonnet.clone(),
|
||||
alias: Some("primary-model".to_string()),
|
||||
is_alias: true,
|
||||
context_window: Some(200_000),
|
||||
},
|
||||
AgentModelOption {
|
||||
name: mappings.haiku.clone(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: Some(1_000_000),
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
claude_code_max_context_tokens(&mappings, &models).unwrap(),
|
||||
372_000
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_code_uses_200k_when_cpa_context_metadata_is_missing() {
|
||||
let mappings = ClaudeDesktopModelMappings::all("custom-model");
|
||||
fn claude_desktop_keeps_original_context_when_1m_is_off() {
|
||||
let models = vec![AgentModelOption {
|
||||
name: "custom-model".to_string(),
|
||||
name: "runtime-model".to_string(),
|
||||
alias: None,
|
||||
is_alias: false,
|
||||
context_window: None,
|
||||
context_window: Some(1_000_000),
|
||||
}];
|
||||
|
||||
assert_eq!(
|
||||
claude_code_max_context_tokens(&mappings, &models).unwrap(),
|
||||
DEFAULT_CLAUDE_CONTEXT_WINDOW
|
||||
let entry = claude_desktop_inference_model(
|
||||
CLAUDE_DESKTOP_SONNET_MODEL_ID,
|
||||
"runtime-model",
|
||||
false,
|
||||
&models,
|
||||
);
|
||||
|
||||
assert_eq!(entry["contextWindow"], 1_000_000);
|
||||
assert!(entry.get("supports1m").is_none());
|
||||
assert!(entry.get("prefer1m").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -532,6 +466,7 @@ fn claude_desktop_profile_keeps_non_claude_models_internal() {
|
||||
opus_1m: true,
|
||||
sonnet_1m: false,
|
||||
haiku_1m: true,
|
||||
..ClaudeDesktopModelMappings::all("")
|
||||
};
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
@@ -858,6 +793,7 @@ fn claude_desktop_aliases_expose_role_routes_only() {
|
||||
opus_1m: false,
|
||||
sonnet_1m: false,
|
||||
haiku_1m: false,
|
||||
..ClaudeDesktopModelMappings::all("")
|
||||
};
|
||||
let available_models = test_agent_models(&["gpt-5.6-sol"]);
|
||||
let rendered =
|
||||
@@ -909,6 +845,66 @@ fn claude_desktop_aliases_expose_role_routes_only() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_desktop_aliases_support_codex_oauth_models() {
|
||||
let input = "port: 8317\n";
|
||||
let mappings = ClaudeDesktopModelMappings::all("gpt-5.6-sol");
|
||||
let available_models = test_agent_models(&["gpt-5.6-sol"]);
|
||||
let definitions = vec![CodexModelDefinition {
|
||||
id: "gpt-5.6-sol".to_string(),
|
||||
display_name: None,
|
||||
description: None,
|
||||
context_window: Some(372_000),
|
||||
reasoning_levels: vec!["high".to_string()],
|
||||
supports_tools: Some(true),
|
||||
}];
|
||||
|
||||
let rendered = ensure_claude_desktop_model_aliases_with_codex_oauth_in_yaml(
|
||||
input,
|
||||
&mappings,
|
||||
&available_models,
|
||||
&definitions,
|
||||
)
|
||||
.unwrap();
|
||||
let value: serde_norway::Value = serde_norway::from_str(&rendered).unwrap();
|
||||
let root = value.as_mapping().unwrap();
|
||||
let aliases = yaml_mapping_value(root, "oauth-model-alias")
|
||||
.and_then(serde_norway::Value::as_mapping)
|
||||
.and_then(|channels| yaml_mapping_value(channels, "codex"))
|
||||
.and_then(serde_norway::Value::as_sequence)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(aliases.len(), 3);
|
||||
assert!(aliases.iter().all(|entry| {
|
||||
configured_model_identity(entry).is_some_and(|(source, _, _)| source == "gpt-5.6-sol")
|
||||
}));
|
||||
assert!(aliases.iter().all(|entry| {
|
||||
entry
|
||||
.as_mapping()
|
||||
.and_then(|entry| yaml_mapping_value(entry, "fork"))
|
||||
== Some(&serde_norway::Value::Bool(true))
|
||||
}));
|
||||
assert_eq!(
|
||||
ensure_claude_desktop_model_aliases_with_codex_oauth_in_yaml(
|
||||
&rendered,
|
||||
&mappings,
|
||||
&available_models,
|
||||
&definitions,
|
||||
)
|
||||
.unwrap(),
|
||||
rendered
|
||||
);
|
||||
|
||||
let cleaned = remove_managed_claude_model_aliases_in_yaml(&rendered).unwrap();
|
||||
let cleaned: serde_norway::Value = serde_norway::from_str(&cleaned).unwrap();
|
||||
let cleaned = cleaned.as_mapping().unwrap();
|
||||
assert!(yaml_mapping_value(cleaned, "oauth-model-alias").is_none());
|
||||
assert_eq!(
|
||||
yaml_mapping_value(cleaned, "port").and_then(serde_norway::Value::as_i64),
|
||||
Some(8317)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_managed_aliases_expand_flow_yaml_and_are_removed_selectively() {
|
||||
let compact = "# keep this comment\ncodex-api-key: [{api-key: test, models: [{name: gpt-5.6-sol, alias: ''}]}]\ncredential-concurrency: {max-limit: 1000000, cleanup-interval: 5s}\n";
|
||||
@@ -960,6 +956,27 @@ fn claude_alias_cleanup_does_not_delete_user_owned_routes() {
|
||||
assert!(configured_model_client_identity(root, CLAUDE_DESKTOP_SONNET_MODEL_ID).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_alias_cleanup_preserves_user_owned_oauth_routes() {
|
||||
let input = format!(
|
||||
"oauth-model-alias:\n codex:\n - name: user-opus\n alias: {opus}\n fork: true\n - name: app-sonnet\n alias: {sonnet}\n fork: true\n display-name: {managed_sonnet}\n",
|
||||
opus = CLAUDE_DESKTOP_OPUS_MODEL_ID,
|
||||
sonnet = CLAUDE_DESKTOP_SONNET_MODEL_ID,
|
||||
managed_sonnet = MANAGED_CLAUDE_SONNET_ALIAS_DISPLAY_NAME,
|
||||
);
|
||||
|
||||
let cleaned = remove_managed_claude_model_aliases_in_yaml(&input).unwrap();
|
||||
let value: serde_norway::Value = serde_norway::from_str(&cleaned).unwrap();
|
||||
let root = value.as_mapping().unwrap();
|
||||
assert_eq!(
|
||||
configured_model_client_identity(root, CLAUDE_DESKTOP_OPUS_MODEL_ID)
|
||||
.unwrap()
|
||||
.0,
|
||||
"user-opus"
|
||||
);
|
||||
assert!(configured_model_client_identity(root, CLAUDE_DESKTOP_SONNET_MODEL_ID).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_legacy_aliases_are_adopted_and_moved_to_the_selected_model() {
|
||||
let input = format!(
|
||||
@@ -991,6 +1008,7 @@ fn claude_desktop_uses_selected_alias_directly_with_original_context() {
|
||||
opus_1m: false,
|
||||
sonnet_1m: true,
|
||||
haiku_1m: false,
|
||||
..ClaudeDesktopModelMappings::all("")
|
||||
};
|
||||
let models = vec![
|
||||
AgentModelOption {
|
||||
@@ -1058,6 +1076,7 @@ fn claude_desktop_role_mappings_can_use_different_available_models() {
|
||||
opus_1m: true,
|
||||
sonnet_1m: false,
|
||||
haiku_1m: true,
|
||||
..ClaudeDesktopModelMappings::all("")
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
|
||||
+9
-1
@@ -476,6 +476,7 @@ export const jaOverrides = {
|
||||
'agents.error.noModels': '選択可能なモデルがないため、設定変更を適用できません',
|
||||
'agents.error.selectionGone': '選択中のモデルは利用可能一覧にありません。更新後に選び直してください',
|
||||
'agents.error.mappingSelectionGone': 'Claude Desktop のモデルマッピングに利用できないモデルがあります。モデルを更新して選び直してください',
|
||||
'agents.error.claudeCodeRuntimeSettingsInvalid': '最大コンテキストは 100000~1000000、自動圧縮率は 1~100 にしてください',
|
||||
'agents.error.applyFirst': '選択したモデルはまだ適用されていません。先に「設定変更を適用」をクリックしてください',
|
||||
'agents.status.detecting': '検出中',
|
||||
'agents.status.unsupported': 'プラットフォーム非対応',
|
||||
@@ -570,7 +571,14 @@ export const jaOverrides = {
|
||||
'agents.claudeDesktopMapping.title': 'Claude Desktop モデル → 上流モデル',
|
||||
'agents.claudeDesktopMapping.description': '役割ごとに上流モデルとコンテキスト機能を選択します。対応モデルでのみ 1M を有効にしてください。',
|
||||
'agents.claudeCodeMapping.title': 'Claude Code モデル → 上流モデル',
|
||||
'agents.claudeCodeMapping.description': 'タスク役割ごとに上流モデルとコンテキスト機能を選択します。対応モデルでのみ 1M を有効にしてください。',
|
||||
'agents.claudeCodeMapping.description': 'タスク役割ごとに上流モデルを選び、共通のコンテキストと自動圧縮ポリシーを設定します。',
|
||||
'agents.claudeCodeRuntime.maxContextTokens': '最大コンテキスト',
|
||||
'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code の既定値は 200000 です。上流モデルの上限を超えないでください。',
|
||||
'agents.claudeCodeRuntime.context1mHint': 'このマッピングで [1m] モデルを使用し、最大コンテキストを 1000000 にします。3 つすべてを無効にすると 200000 に戻ります。',
|
||||
'agents.claudeCodeRuntime.autoCompactPct': '自動圧縮の開始率',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': '既定値は 100% です。小さい値ほど早く圧縮します。',
|
||||
'agents.claudeCodeRuntime.disableAutoCompact': '自動圧縮を無効化',
|
||||
'agents.claudeCodeRuntime.disableAutoCompactHint': '有効時は /compact による手動圧縮のみ利用できます。',
|
||||
'agents.claudeDesktopMapping.customMapping': 'モデルエイリアスを使用',
|
||||
'agents.claudeDesktopMapping.customMappingHint': '有効にするとエイリアスモデルのみ、無効にするとエイリアス未設定の元モデルのみ表示します。',
|
||||
'agents.claudeDesktopMapping.opus': 'Claude Opus 5',
|
||||
|
||||
@@ -476,6 +476,7 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.error.noModels': 'No models are available, so configuration changes cannot be applied',
|
||||
'agents.error.selectionGone': 'The selected model is no longer available. Refresh and select it again.',
|
||||
'agents.error.mappingSelectionGone': 'A Claude Desktop mapping is no longer available. Refresh the models and select it again.',
|
||||
'agents.error.claudeCodeRuntimeSettingsInvalid': 'Maximum context must be 100000–1000000 and the auto-compact percentage must be 1–100.',
|
||||
'agents.error.applyFirst': 'The selected model has not been applied. Click “Apply Configuration Changes” first.',
|
||||
'agents.status.detecting': 'Detecting',
|
||||
'agents.status.unsupported': 'Unsupported Platform',
|
||||
@@ -565,7 +566,14 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.claudeDesktopMapping.title': 'Claude Desktop Model → Upstream Model',
|
||||
'agents.claudeDesktopMapping.description': 'Choose the upstream model and context capability for each role. Turn on 1M only for supported models.',
|
||||
'agents.claudeCodeMapping.title': 'Claude Code Model → Upstream Model',
|
||||
'agents.claudeCodeMapping.description': 'Choose the upstream model and context capability for each task role. Turn on 1M only for supported models.',
|
||||
'agents.claudeCodeMapping.description': 'Choose the upstream model for each task role and configure one shared context and auto-compact policy.',
|
||||
'agents.claudeCodeRuntime.maxContextTokens': 'Maximum context',
|
||||
'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.disableAutoCompact': 'Disable auto-compaction',
|
||||
'agents.claudeCodeRuntime.disableAutoCompactHint': 'Only manual /compact remains available when enabled.',
|
||||
'agents.claudeDesktopMapping.customMapping': 'Use Model Aliases',
|
||||
'agents.claudeDesktopMapping.customMappingHint': 'When enabled, only aliased models are shown; when disabled, only original models without aliases are shown.',
|
||||
'agents.claudeDesktopMapping.opus': 'Claude Opus 5',
|
||||
|
||||
@@ -474,6 +474,7 @@ export const zhCN = {
|
||||
'agents.error.noModels': '当前没有可选模型,无法应用配置修改',
|
||||
'agents.error.selectionGone': '当前选择已不在可用模型列表中,请刷新模型后重新选择',
|
||||
'agents.error.mappingSelectionGone': 'Claude Desktop 模型映射包含不可用模型,请刷新模型后重新选择',
|
||||
'agents.error.claudeCodeRuntimeSettingsInvalid': '最大窗口必须为 100000–1000000,触发压缩百分比必须为 1–100',
|
||||
'agents.error.applyFirst': '模型选择尚未应用,请先点击“应用配置修改”',
|
||||
'agents.status.detecting': '检测中',
|
||||
'agents.status.unsupported': '平台不支持',
|
||||
@@ -563,7 +564,14 @@ export const zhCN = {
|
||||
'agents.claudeDesktopMapping.title': 'Claude Desktop 模型 → 上游模型',
|
||||
'agents.claudeDesktopMapping.description': '分别选择上游模型和上下文能力;仅为确认支持的模型打开 1M。',
|
||||
'agents.claudeCodeMapping.title': 'Claude Code 模型 → 上游模型',
|
||||
'agents.claudeCodeMapping.description': '分别为不同任务角色选择上游模型和上下文能力;仅为确认支持的模型打开 1M。',
|
||||
'agents.claudeCodeMapping.description': '分别为不同任务角色选择上游模型,并统一配置上下文窗口与自动压缩。',
|
||||
'agents.claudeCodeRuntime.maxContextTokens': '最大窗口',
|
||||
'agents.claudeCodeRuntime.maxContextTokensHint': 'Claude Code 默认 200000;请勿超过上游模型实际支持的窗口。',
|
||||
'agents.claudeCodeRuntime.context1mHint': '为此映射使用 [1m] 模型并将最大窗口设为 1000000;三个映射均关闭后恢复为 200000。',
|
||||
'agents.claudeCodeRuntime.autoCompactPct': '触发压缩百分比',
|
||||
'agents.claudeCodeRuntime.autoCompactPctHint': '默认 100;调低后会更早自动压缩。',
|
||||
'agents.claudeCodeRuntime.disableAutoCompact': '禁止自动压缩',
|
||||
'agents.claudeCodeRuntime.disableAutoCompactHint': '开启后仅允许用户通过 /compact 手动压缩。',
|
||||
'agents.claudeDesktopMapping.customMapping': '使用模型别名',
|
||||
'agents.claudeDesktopMapping.customMappingHint': '开启后只显示别名模型;关闭后只显示未设置别名的原始模型。',
|
||||
'agents.claudeDesktopMapping.opus': 'Claude Opus 5',
|
||||
|
||||
+125
-3
@@ -104,9 +104,14 @@ type ClaudeModelMappings = {
|
||||
opus1m: boolean;
|
||||
sonnet1m: boolean;
|
||||
haiku1m: boolean;
|
||||
maxContextTokens: number;
|
||||
autoCompactPct: number;
|
||||
disableAutoCompact: boolean;
|
||||
};
|
||||
|
||||
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 createClaudeModelMappings = (model: string): ClaudeModelMappings => ({
|
||||
opus: model,
|
||||
@@ -115,6 +120,9 @@ const createClaudeModelMappings = (model: string): ClaudeModelMappings => ({
|
||||
opus1m: false,
|
||||
sonnet1m: false,
|
||||
haiku1m: false,
|
||||
maxContextTokens: DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS,
|
||||
autoCompactPct: DEFAULT_CLAUDE_AUTO_COMPACT_PCT,
|
||||
disableAutoCompact: false,
|
||||
});
|
||||
|
||||
const claudeMappingRoles = [
|
||||
@@ -731,6 +739,9 @@ export function AgentsPage() {
|
||||
opus1m: Boolean(source.opus1m),
|
||||
sonnet1m: Boolean(source.sonnet1m),
|
||||
haiku1m: Boolean(source.haiku1m),
|
||||
maxContextTokens: source.maxContextTokens ?? DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS,
|
||||
autoCompactPct: source.autoCompactPct ?? DEFAULT_CLAUDE_AUTO_COMPACT_PCT,
|
||||
disableAutoCompact: Boolean(source.disableAutoCompact),
|
||||
};
|
||||
return sameAgentModelMappings(current, next) ? current : next;
|
||||
});
|
||||
@@ -757,6 +768,12 @@ export function AgentsPage() {
|
||||
|| claudeMappingRoles.every((role) =>
|
||||
Boolean(findAgentModel(models, claudeModelMappingsDraft[role.key])),
|
||||
);
|
||||
const claudeCodeRuntimeSettingsReady = selected !== 'claude-code' || (
|
||||
claudeModelMappingsDraft.maxContextTokens >= 100_000
|
||||
&& claudeModelMappingsDraft.maxContextTokens <= 1_000_000
|
||||
&& claudeModelMappingsDraft.autoCompactPct >= 1
|
||||
&& claudeModelMappingsDraft.autoCompactPct <= 100
|
||||
);
|
||||
const claudeMappingDraftChanged = isClaudeModelMappingClient
|
||||
&& activeStatus?.modificationState === 'applied'
|
||||
&& !sameAgentModelMappings(
|
||||
@@ -781,7 +798,7 @@ export function AgentsPage() {
|
||||
&& activeStatus.installed
|
||||
&& !modelLoading
|
||||
&& (isClaudeModelMappingClient
|
||||
? claudeMappingsReady
|
||||
? claudeMappingsReady && claudeCodeRuntimeSettingsReady
|
||||
: selectedModelOption),
|
||||
);
|
||||
const modelHint = modelSelectionError
|
||||
@@ -836,7 +853,32 @@ export function AgentsPage() {
|
||||
enabled: boolean,
|
||||
) => {
|
||||
claudeModelMappingsDirtyRef.current = true;
|
||||
setClaudeModelMappingsDraft((current) => ({ ...current, [preference]: enabled }));
|
||||
setClaudeModelMappingsDraft((current) => {
|
||||
const next = { ...current, [preference]: enabled };
|
||||
if (selected === 'claude-code') {
|
||||
const any1mEnabled = next.opus1m || next.sonnet1m || next.haiku1m;
|
||||
next.maxContextTokens = any1mEnabled
|
||||
? 1_000_000
|
||||
: DEFAULT_CLAUDE_CODE_MAX_CONTEXT_TOKENS;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const changeClaudeCodeRuntimeSetting = (
|
||||
key: 'maxContextTokens' | 'autoCompactPct',
|
||||
value: number,
|
||||
) => {
|
||||
claudeModelMappingsDirtyRef.current = true;
|
||||
setClaudeModelMappingsDraft((current) => ({ ...current, [key]: value }));
|
||||
};
|
||||
|
||||
const changeClaudeCodeAutoCompactDisabled = (disabled: boolean) => {
|
||||
claudeModelMappingsDirtyRef.current = true;
|
||||
setClaudeModelMappingsDraft((current) => ({
|
||||
...current,
|
||||
disableAutoCompact: disabled,
|
||||
}));
|
||||
};
|
||||
|
||||
const changeClaudeCustomMapping = (enabled: boolean) => {
|
||||
@@ -850,6 +892,9 @@ export function AgentsPage() {
|
||||
opus1m: current.opus1m,
|
||||
sonnet1m: current.sonnet1m,
|
||||
haiku1m: current.haiku1m,
|
||||
maxContextTokens: current.maxContextTokens,
|
||||
autoCompactPct: current.autoCompactPct,
|
||||
disableAutoCompact: current.disableAutoCompact,
|
||||
};
|
||||
if (!sameAgentModelMappings(current, next)) {
|
||||
claudeModelMappingsDirtyRef.current = true;
|
||||
@@ -888,6 +933,15 @@ export function AgentsPage() {
|
||||
resolved[role.key] = model.name;
|
||||
resolved[role.contextKey] = claudeModelMappingsDraft[role.contextKey];
|
||||
}
|
||||
if (selected === 'claude-code') {
|
||||
if (!claudeCodeRuntimeSettingsReady) {
|
||||
setModelSelectionError(t('agents.error.claudeCodeRuntimeSettingsInvalid'));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
resolved.maxContextTokens = claudeModelMappingsDraft.maxContextTokens;
|
||||
resolved.autoCompactPct = claudeModelMappingsDraft.autoCompactPct;
|
||||
resolved.disableAutoCompact = claudeModelMappingsDraft.disableAutoCompact;
|
||||
return resolved;
|
||||
};
|
||||
|
||||
@@ -1291,6 +1345,72 @@ export function AgentsPage() {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{selected === 'claude-code' ? (
|
||||
<div className="agent-claude-code-runtime-settings">
|
||||
<div className="agent-claude-code-number-field">
|
||||
<span>{t('agents.claudeCodeRuntime.maxContextTokens')}</span>
|
||||
<input
|
||||
type="number"
|
||||
min={100_000}
|
||||
max={1_000_000}
|
||||
step={1_000}
|
||||
value={claudeModelMappingsDraft.maxContextTokens}
|
||||
onChange={(event) => changeClaudeCodeRuntimeSetting(
|
||||
'maxContextTokens',
|
||||
Math.trunc(event.currentTarget.valueAsNumber || 0),
|
||||
)}
|
||||
disabled={busy || modelLoading}
|
||||
aria-label={t('agents.claudeCodeRuntime.maxContextTokens')}
|
||||
aria-describedby="claude-code-max-context-hint"
|
||||
/>
|
||||
<small id="claude-code-max-context-hint">
|
||||
{t('agents.claudeCodeRuntime.maxContextTokensHint')}
|
||||
</small>
|
||||
</div>
|
||||
<label className="agent-claude-code-number-field">
|
||||
<span>{t('agents.claudeCodeRuntime.autoCompactPct')}</span>
|
||||
<div className="agent-claude-code-percent-input">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={claudeModelMappingsDraft.autoCompactPct}
|
||||
onChange={(event) => changeClaudeCodeRuntimeSetting(
|
||||
'autoCompactPct',
|
||||
Math.trunc(event.currentTarget.valueAsNumber || 0),
|
||||
)}
|
||||
disabled={busy || modelLoading || claudeModelMappingsDraft.disableAutoCompact}
|
||||
aria-describedby="claude-code-auto-compact-hint"
|
||||
/>
|
||||
<span>%</span>
|
||||
</div>
|
||||
<small id="claude-code-auto-compact-hint">
|
||||
{t('agents.claudeCodeRuntime.autoCompactPctHint')}
|
||||
</small>
|
||||
</label>
|
||||
<label
|
||||
className="agent-claude-code-disable-compact"
|
||||
title={t('agents.claudeCodeRuntime.disableAutoCompactHint')}
|
||||
>
|
||||
<span>
|
||||
<strong>{t('agents.claudeCodeRuntime.disableAutoCompact')}</strong>
|
||||
<small>{t('agents.claudeCodeRuntime.disableAutoCompactHint')}</small>
|
||||
</span>
|
||||
<span className="switch-control">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={claudeModelMappingsDraft.disableAutoCompact}
|
||||
onChange={(event) => changeClaudeCodeAutoCompactDisabled(
|
||||
event.currentTarget.checked,
|
||||
)}
|
||||
disabled={busy || modelLoading}
|
||||
/>
|
||||
<span className="switch-track" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="agent-claude-desktop-mapping-grid">
|
||||
{claudeMappingRoles.map((role) => (
|
||||
<div className="agent-claude-desktop-mapping-row" key={role.key}>
|
||||
@@ -1298,7 +1418,9 @@ export function AgentsPage() {
|
||||
<strong>{t(role.labelKey)}</strong>
|
||||
<label
|
||||
className="agent-claude-context-toggle"
|
||||
title={t('agents.claudeMapping.context1mHint')}
|
||||
title={t(selected === 'claude-code'
|
||||
? 'agents.claudeCodeRuntime.context1mHint'
|
||||
: 'agents.claudeMapping.context1mHint')}
|
||||
>
|
||||
<span>{t('agents.claudeMapping.context1m')}</span>
|
||||
<span className="switch-control">
|
||||
|
||||
@@ -18,6 +18,9 @@ export type AgentModelMappings = {
|
||||
opus1m?: boolean;
|
||||
sonnet1m?: boolean;
|
||||
haiku1m?: boolean;
|
||||
maxContextTokens?: number;
|
||||
autoCompactPct?: number;
|
||||
disableAutoCompact?: boolean;
|
||||
};
|
||||
|
||||
type ResolveAgentConfigurationActionOptions = {
|
||||
@@ -45,7 +48,10 @@ export const sameAgentModelMappings = (
|
||||
&& sameAgentModel(left.haiku, right.haiku)
|
||||
&& Boolean(left.opus1m) === Boolean(right.opus1m)
|
||||
&& Boolean(left.sonnet1m) === Boolean(right.sonnet1m)
|
||||
&& Boolean(left.haiku1m) === Boolean(right.haiku1m);
|
||||
&& Boolean(left.haiku1m) === Boolean(right.haiku1m)
|
||||
&& (left.maxContextTokens ?? 200_000) === (right.maxContextTokens ?? 200_000)
|
||||
&& (left.autoCompactPct ?? 100) === (right.autoCompactPct ?? 100)
|
||||
&& Boolean(left.disableAutoCompact) === Boolean(right.disableAutoCompact);
|
||||
|
||||
export function resolveAgentConfigurationAction({
|
||||
client,
|
||||
|
||||
+108
@@ -5519,6 +5519,98 @@ textarea:focus-visible {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.agent-claude-code-runtime-settings {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1fr) minmax(180px, 1fr) minmax(240px, 1.2fr);
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--theme-e3e1db);
|
||||
border-radius: 9px;
|
||||
background: var(--theme-faf9f5);
|
||||
}
|
||||
|
||||
.agent-claude-code-number-field {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 6px;
|
||||
color: var(--theme-5f5a53);
|
||||
font-size: var(--font-size-label);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agent-claude-code-number-field > input,
|
||||
.agent-claude-code-percent-input input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--theme-d3d0c9);
|
||||
border-radius: 7px;
|
||||
background: var(--theme-fff);
|
||||
color: var(--theme-2f2c28);
|
||||
font: inherit;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.agent-claude-code-number-field > input:focus,
|
||||
.agent-claude-code-percent-input input:focus {
|
||||
border-color: var(--theme-8069ad);
|
||||
outline: 2px solid rgba(128, 105, 173, 0.14);
|
||||
}
|
||||
|
||||
.agent-claude-code-number-field > input:disabled,
|
||||
.agent-claude-code-percent-input input:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.agent-claude-code-number-field small,
|
||||
.agent-claude-code-disable-compact small {
|
||||
color: var(--theme-817b72);
|
||||
font-size: var(--font-size-caption);
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agent-claude-code-percent-input {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.agent-claude-code-percent-input > span {
|
||||
color: var(--theme-817b72);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.agent-claude-code-disable-compact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--theme-e3e1db);
|
||||
border-radius: 8px;
|
||||
background: var(--theme-fff);
|
||||
color: var(--theme-5f5a53);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.agent-claude-code-disable-compact > span:first-child {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.agent-claude-code-disable-compact:has(input:checked) {
|
||||
border-color: rgba(174, 92, 82, 0.48);
|
||||
background: rgba(174, 92, 82, 0.06);
|
||||
}
|
||||
|
||||
.agent-claude-code-disable-compact:has(input:disabled) {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping-row {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
@@ -7264,6 +7356,14 @@ textarea:focus-visible {
|
||||
}
|
||||
|
||||
@container agent-panel (max-width: 760px) {
|
||||
.agent-claude-code-runtime-settings {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.agent-claude-code-disable-compact {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.agent-claude-desktop-mapping-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
@@ -7283,6 +7383,14 @@ textarea:focus-visible {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent-claude-code-runtime-settings {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent-claude-code-disable-compact {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.agent-codex-options {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -123,11 +123,9 @@ describe('agent configuration update action', () => {
|
||||
})).toBe('update');
|
||||
});
|
||||
|
||||
test.each<AgentConfigurationClientId>(['claude-code', 'claude-desktop'])(
|
||||
'%s updates when only a 1M preference changes',
|
||||
(client) => {
|
||||
test('Claude Desktop updates when only a 1M preference changes', () => {
|
||||
expect(resolveAgentConfigurationAction({
|
||||
client,
|
||||
client: 'claude-desktop',
|
||||
modificationState: 'applied',
|
||||
selectedModel: 'model-a',
|
||||
appliedModel: 'model-a',
|
||||
@@ -146,8 +144,34 @@ describe('agent configuration update action', () => {
|
||||
opus1m: false,
|
||||
},
|
||||
})).toBe('update');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('Claude Code updates when its shared context policy changes', () => {
|
||||
expect(resolveAgentConfigurationAction({
|
||||
client: 'claude-code',
|
||||
modificationState: 'applied',
|
||||
selectedModel: 'model-a',
|
||||
appliedModel: 'model-a',
|
||||
oauthConfiguration: false,
|
||||
appliedOauthConfiguration: false,
|
||||
modelMappings: {
|
||||
opus: 'model-a',
|
||||
sonnet: 'model-a',
|
||||
haiku: 'model-a',
|
||||
maxContextTokens: 372000,
|
||||
autoCompactPct: 80,
|
||||
disableAutoCompact: true,
|
||||
},
|
||||
appliedModelMappings: {
|
||||
opus: 'model-a',
|
||||
sonnet: 'model-a',
|
||||
haiku: 'model-a',
|
||||
maxContextTokens: 200000,
|
||||
autoCompactPct: 100,
|
||||
disableAutoCompact: false,
|
||||
},
|
||||
})).toBe('update');
|
||||
});
|
||||
|
||||
test('Pi never exposes the model update action', () => {
|
||||
expect(appliedConfiguration('pi', 'model-b')).toBe('close');
|
||||
|
||||
Reference in new issue
Block a user