mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 10:21:16 +08:00
feat: add Claude subagent model config (#4830)
* feat: add Claude subagent takeover config * feat: add Claude subagent model field * i18n: add Claude subagent model labels * fix(proxy): preserve configured subagent model mapping * fix(providers): exclude subagent model from Claude common config * style: format rust code
This commit is contained in:
@@ -672,6 +672,7 @@ mod tests {
|
||||
"AWS_BEARER_TOKEN_BEDROCK": "bedrock-tok",
|
||||
"ANTHROPIC_BASE_URL": "https://example.com",
|
||||
"ANTHROPIC_MODEL": "claude-x",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "gpt-5.4-mini",
|
||||
// 可共享、非机密配置(复数 _TOKENS 不应被误剥)
|
||||
"ENABLE_TOOL_SEARCH": "true",
|
||||
"CLAUDE_CODE_MAX_OUTPUT_TOKENS": "8192"
|
||||
@@ -714,6 +715,9 @@ mod tests {
|
||||
// 端点/模型(provider-specific 非机密)也应剥掉
|
||||
assert!(env.and_then(|e| e.get("ANTHROPIC_BASE_URL")).is_none());
|
||||
assert!(env.and_then(|e| e.get("ANTHROPIC_MODEL")).is_none());
|
||||
assert!(env
|
||||
.and_then(|e| e.get("CLAUDE_CODE_SUBAGENT_MODEL"))
|
||||
.is_none());
|
||||
|
||||
// 可共享的非机密配置必须保留(含复数 _TOKENS 不被误剥)
|
||||
assert_eq!(
|
||||
@@ -2635,6 +2639,7 @@ impl ProviderService {
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL",
|
||||
"ANTHROPIC_BASE_URL",
|
||||
];
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ const PROXY_TOKEN_PLACEHOLDER: &str = "PROXY_MANAGED";
|
||||
/// 原因:接管模式下 `*_MODEL` 必须由 CC Switch 写成稳定的 Claude 角色别名,
|
||||
/// 再由本地代理映射到当前供应商真实模型;`*_MODEL_NAME` 也需要同步接管,
|
||||
/// 否则 Claude Code 模型菜单会残留上一个供应商的显示名称。
|
||||
const CLAUDE_MODEL_OVERRIDE_ENV_KEYS: [&str; 9] = [
|
||||
const CLAUDE_MODEL_OVERRIDE_ENV_KEYS: [&str; 10] = [
|
||||
"ANTHROPIC_MODEL",
|
||||
"ANTHROPIC_REASONING_MODEL", // legacy: 已废弃,但旧配置可能残留
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||
@@ -35,8 +35,8 @@ const CLAUDE_MODEL_OVERRIDE_ENV_KEYS: [&str; 9] = [
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
|
||||
// Legacy key (已废弃):历史版本使用该字段区分 small/fast 模型
|
||||
"ANTHROPIC_SMALL_FAST_MODEL",
|
||||
"ANTHROPIC_SMALL_FAST_MODEL", // Legacy key (已废弃):历史版本使用该字段区分 small/fast 模型
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL",
|
||||
];
|
||||
|
||||
const CLAUDE_TAKEOVER_HAIKU_MODEL: &str = "claude-haiku-4-5";
|
||||
@@ -222,7 +222,9 @@ impl ProxyService {
|
||||
.or(default_model)
|
||||
.or(small_fast_model);
|
||||
|
||||
let mut fields = Vec::with_capacity(6);
|
||||
let subagent_model = Self::claude_env_string(env, "CLAUDE_CODE_SUBAGENT_MODEL");
|
||||
|
||||
let mut fields = Vec::with_capacity(7);
|
||||
Self::push_claude_takeover_role_fields(
|
||||
&mut fields,
|
||||
env,
|
||||
@@ -250,6 +252,9 @@ impl ProxyService {
|
||||
true,
|
||||
opus_model,
|
||||
);
|
||||
if let Some(subagent_model) = subagent_model {
|
||||
fields.push(("CLAUDE_CODE_SUBAGENT_MODEL", subagent_model.to_string()));
|
||||
}
|
||||
fields
|
||||
}
|
||||
|
||||
@@ -2857,7 +2862,8 @@ mod tests {
|
||||
"ANTHROPIC_MODEL": "claude-sonnet-4.6",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4.5",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4.6",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-sonnet-4.6"
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-sonnet-4.6",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4.6[1M]"
|
||||
}
|
||||
}),
|
||||
None,
|
||||
@@ -2877,7 +2883,8 @@ mod tests {
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "stale-sonnet",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME": "Stale Sonnet",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "stale-opus",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME": "Stale Opus"
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME": "Stale Opus",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "stale-subagent"
|
||||
}
|
||||
});
|
||||
ProxyService::apply_claude_takeover_fields_for_provider(
|
||||
@@ -2917,10 +2924,53 @@ mod tests {
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
|
||||
Some("claude-sonnet-4.6"),
|
||||
);
|
||||
assert_env_str(
|
||||
env,
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL",
|
||||
Some("claude-sonnet-4.6[1M]"),
|
||||
);
|
||||
assert_env_str(env, "ANTHROPIC_API_KEY", Some(PROXY_TOKEN_PLACEHOLDER));
|
||||
assert_env_str(env, "ANTHROPIC_AUTH_TOKEN", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_account_claude_takeover_removes_stale_subagent_model_when_provider_omits_it() {
|
||||
let mut provider = Provider::with_id(
|
||||
"codex".to_string(),
|
||||
"Codex".to_string(),
|
||||
json!({
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://chatgpt.com/backend-api/codex",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "provider-sonnet"
|
||||
}
|
||||
}),
|
||||
None,
|
||||
);
|
||||
provider.meta = Some(ProviderMeta {
|
||||
provider_type: Some("codex_oauth".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut live_config = json!({
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://stale.example.com",
|
||||
"ANTHROPIC_API_KEY": "stale-key",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "stale-subagent"
|
||||
}
|
||||
});
|
||||
ProxyService::apply_claude_takeover_fields_for_provider(
|
||||
&mut live_config,
|
||||
"http://127.0.0.1:15721",
|
||||
&provider,
|
||||
);
|
||||
|
||||
let env = live_config
|
||||
.get("env")
|
||||
.and_then(|value| value.as_object())
|
||||
.expect("env should exist");
|
||||
assert_env_str(env, "CLAUDE_CODE_SUBAGENT_MODEL", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_account_claude_takeover_sources_codex_models_from_provider() {
|
||||
let mut provider = Provider::with_id(
|
||||
@@ -4564,7 +4614,8 @@ model = "gpt-5.1-codex"
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME": "DeepSeek V4 Flash",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL": "deepseek-v4-pro[1M]",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME": "DeepSeek V4 Pro",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-ultra [1m]"
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL": "deepseek-v4-ultra [1m]",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "deepseek-v4-pro[1M]"
|
||||
},
|
||||
"permissions": { "allow": ["Read"] }
|
||||
}),
|
||||
@@ -4591,7 +4642,8 @@ model = "gpt-5.1-codex"
|
||||
"ANTHROPIC_BASE_URL": "http://127.0.0.1:15721",
|
||||
"ANTHROPIC_API_KEY": PROXY_TOKEN_PLACEHOLDER,
|
||||
"ANTHROPIC_MODEL": "stale-model",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME": "Stale Sonnet"
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL_NAME": "Stale Sonnet",
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "stale-subagent"
|
||||
},
|
||||
"permissions": { "allow": ["Bash"] }
|
||||
}))
|
||||
@@ -4674,6 +4726,13 @@ model = "gpt-5.1-codex"
|
||||
Some("deepseek-v4-ultra"),
|
||||
"implicit display names should strip the local 1M marker"
|
||||
);
|
||||
assert_eq!(
|
||||
live_env
|
||||
.get("CLAUDE_CODE_SUBAGENT_MODEL")
|
||||
.and_then(|v| v.as_str()),
|
||||
Some("deepseek-v4-pro[1M]"),
|
||||
"subagent model should follow the target provider during hot switch"
|
||||
);
|
||||
|
||||
let backup = db
|
||||
.get_live_backup("claude")
|
||||
|
||||
Reference in New Issue
Block a user