mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
feat(kimi): declare the 256K context window for Kimi For Coding
Claude Code caps unknown model ids at 200K, so the preset's standalone CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144 was clamped by min(window, value) and never took effect. Pair it with CLAUDE_CODE_MAX_CONTEXT_TOKENS and route the endpoint's kimi-for-coding alias on every tier, since both context envs are ignored for claude-* prefixed model ids. Saved providers get the same context defaults injected into effective live settings at switch time, with a mirror-inverse strip on backfill so injected values never harden into provider config. Explicit user values always win over the injected defaults.
This commit is contained in:
@@ -29,6 +29,7 @@ use super::normalize_claude_models_in_value;
|
||||
/// usable context.
|
||||
const CODEX_OAUTH_CLAUDE_MAX_CONTEXT_TOKENS: &str = "372000";
|
||||
const CODEX_OAUTH_CLAUDE_AUTO_COMPACT_WINDOW: &str = "372000";
|
||||
const KIMI_FOR_CODING_CONTEXT_TOKENS: &str = "262144";
|
||||
|
||||
/// Model env keys Claude Code may route requests through. The defaults above
|
||||
/// are calibrated against gpt-5.6's Codex catalog, so every configured model
|
||||
@@ -67,6 +68,16 @@ fn provider_env_targets_gpt56(provider_env: Option<&serde_json::Map<String, Valu
|
||||
saw_model
|
||||
}
|
||||
|
||||
fn is_kimi_for_coding_provider(provider: &Provider) -> bool {
|
||||
provider
|
||||
.settings_config
|
||||
.pointer("/env/ANTHROPIC_BASE_URL")
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.map(|url| url.trim_end_matches('/'))
|
||||
== Some("https://api.kimi.com/coding")
|
||||
}
|
||||
|
||||
/// Claude Code assigns unknown non-Claude model ids a 200K context window.
|
||||
/// Codex OAuth deliberately exposes GPT ids through Claude Code, so enrich the
|
||||
/// effective live settings for both newly-created and already-saved providers.
|
||||
@@ -122,6 +133,36 @@ fn apply_codex_oauth_claude_context_defaults(settings: &mut Value, provider: &Pr
|
||||
}
|
||||
}
|
||||
|
||||
/// Kimi For Coding serves a 256K window, but Claude Code caps unknown models at
|
||||
/// 200K unless `CLAUDE_CODE_MAX_CONTEXT_TOKENS` is set — and that env is ignored
|
||||
/// for `claude-`-prefixed ids, so these defaults only bite when the provider also
|
||||
/// routes the endpoint's `kimi-for-coding` alias (the preset does). Keep the
|
||||
/// defaults provider-owned so an old shared snippet cannot override them.
|
||||
fn apply_kimi_for_coding_context_defaults(settings: &mut Value, provider: &Provider) {
|
||||
if !is_kimi_for_coding_provider(provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
let provider_env = provider
|
||||
.settings_config
|
||||
.get("env")
|
||||
.and_then(Value::as_object);
|
||||
let Some(env) = settings.get_mut("env").and_then(Value::as_object_mut) else {
|
||||
return;
|
||||
};
|
||||
|
||||
for key in [
|
||||
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||
] {
|
||||
let value = provider_env
|
||||
.and_then(|provider_env| provider_env.get(key))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Value::String(KIMI_FOR_CODING_CONTEXT_TOKENS.to_string()));
|
||||
env.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sanitize_claude_settings_for_live(settings: &Value) -> Value {
|
||||
let mut v = settings.clone();
|
||||
if let Some(obj) = v.as_object_mut() {
|
||||
@@ -640,6 +681,7 @@ pub(crate) fn build_effective_settings_with_common_config(
|
||||
|
||||
if matches!(app_type, AppType::Claude) {
|
||||
apply_codex_oauth_claude_context_defaults(&mut effective_settings, provider);
|
||||
apply_kimi_for_coding_context_defaults(&mut effective_settings, provider);
|
||||
}
|
||||
|
||||
Ok(effective_settings)
|
||||
@@ -748,6 +790,30 @@ fn strip_injected_codex_oauth_context_defaults(settings: &mut Value, provider: &
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_injected_kimi_for_coding_context_defaults(settings: &mut Value, provider: &Provider) {
|
||||
if !is_kimi_for_coding_provider(provider) {
|
||||
return;
|
||||
}
|
||||
let provider_env = provider
|
||||
.settings_config
|
||||
.get("env")
|
||||
.and_then(Value::as_object);
|
||||
let Some(env) = settings.get_mut("env").and_then(Value::as_object_mut) else {
|
||||
return;
|
||||
};
|
||||
for key in [
|
||||
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||
] {
|
||||
if provider_env.is_some_and(|provider_env| provider_env.contains_key(key)) {
|
||||
continue;
|
||||
}
|
||||
if env.get(key).and_then(Value::as_str) == Some(KIMI_FOR_CODING_CONTEXT_TOKENS) {
|
||||
env.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_live_settings_for_provider_backfill(
|
||||
app_type: &AppType,
|
||||
provider: &Provider,
|
||||
@@ -756,6 +822,7 @@ fn restore_live_settings_for_provider_backfill(
|
||||
if matches!(app_type, AppType::Claude) {
|
||||
let mut settings = live_settings;
|
||||
strip_injected_codex_oauth_context_defaults(&mut settings, provider);
|
||||
strip_injected_kimi_for_coding_context_defaults(&mut settings, provider);
|
||||
return settings;
|
||||
}
|
||||
if !matches!(app_type, AppType::Codex) {
|
||||
@@ -1860,6 +1927,92 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn kimi_for_coding_effective_settings_backfill_256k_context() {
|
||||
let db = Database::memory().expect("create memory db");
|
||||
let provider = Provider::with_id(
|
||||
"kimi-for-coding".to_string(),
|
||||
"Kimi For Coding".to_string(),
|
||||
json!({
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.kimi.com/coding/",
|
||||
"ANTHROPIC_MODEL": "kimi-for-coding",
|
||||
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "262144"
|
||||
}
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
let effective =
|
||||
build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||
.expect("build effective settings");
|
||||
assert_eq!(
|
||||
effective["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||
json!("262144")
|
||||
);
|
||||
assert_eq!(
|
||||
effective["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||
json!("262144")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kimi_for_coding_context_defaults_preserve_user_overrides() {
|
||||
let db = Database::memory().expect("create memory db");
|
||||
let provider = Provider::with_id(
|
||||
"kimi-for-coding".to_string(),
|
||||
"Kimi For Coding".to_string(),
|
||||
json!({
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.kimi.com/coding",
|
||||
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "300000",
|
||||
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "250000"
|
||||
}
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
let effective =
|
||||
build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||
.expect("build effective settings");
|
||||
assert_eq!(
|
||||
effective["env"]["CLAUDE_CODE_MAX_CONTEXT_TOKENS"],
|
||||
json!("300000")
|
||||
);
|
||||
assert_eq!(
|
||||
effective["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||
json!("250000")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kimi_for_coding_backfill_strips_only_injected_context_default() {
|
||||
let db = Database::memory().expect("create memory db");
|
||||
let provider = Provider::with_id(
|
||||
"kimi-for-coding".to_string(),
|
||||
"Kimi For Coding".to_string(),
|
||||
json!({
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.kimi.com/coding/",
|
||||
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "262144"
|
||||
}
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
let live = build_effective_settings_with_common_config(&db, &AppType::Claude, &provider)
|
||||
.expect("build effective settings");
|
||||
let backfilled =
|
||||
strip_common_config_from_live_settings(&db, &AppType::Claude, &provider, live);
|
||||
assert!(backfilled["env"]
|
||||
.get("CLAUDE_CODE_MAX_CONTEXT_TOKENS")
|
||||
.is_none());
|
||||
assert_eq!(
|
||||
backfilled["env"]["CLAUDE_CODE_AUTO_COMPACT_WINDOW"],
|
||||
json!("262144")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_oauth_effective_settings_backfill_gpt_context_defaults() {
|
||||
let db = Database::memory().expect("create memory db");
|
||||
|
||||
@@ -486,10 +486,23 @@ export const providerPresets: ProviderPreset[] = [
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: "https://api.kimi.com/coding/",
|
||||
ANTHROPIC_AUTH_TOKEN: "",
|
||||
// CLAUDE_CODE_MAX_CONTEXT_TOKENS 只对非 claude- 前缀模型 id 生效,
|
||||
// 必须显式路由端点别名 kimi-for-coding(与 codex/hermes/opencode 预设一致)
|
||||
ANTHROPIC_MODEL: "kimi-for-coding",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-for-coding",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-for-coding",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-for-coding",
|
||||
CLAUDE_CODE_MAX_CONTEXT_TOKENS: "${CLAUDE_CODE_MAX_CONTEXT_TOKENS}",
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: "${CLAUDE_CODE_AUTO_COMPACT_WINDOW}",
|
||||
},
|
||||
},
|
||||
templateValues: {
|
||||
CLAUDE_CODE_MAX_CONTEXT_TOKENS: {
|
||||
label: "Max Context Tokens",
|
||||
placeholder: "262144",
|
||||
defaultValue: "262144",
|
||||
editorValue: "262144",
|
||||
},
|
||||
CLAUDE_CODE_AUTO_COMPACT_WINDOW: {
|
||||
label: "Auto Compact Window",
|
||||
placeholder: "262144",
|
||||
|
||||
@@ -11,21 +11,50 @@ describe("Kimi For Coding Provider Preset", () => {
|
||||
expect(kimiForCoding).toBeDefined();
|
||||
});
|
||||
|
||||
it("should use template placeholder for Claude Code auto-compact window", () => {
|
||||
// CLAUDE_CODE_MAX_CONTEXT_TOKENS is ignored for claude-* model ids, so the
|
||||
// preset must route the endpoint's own alias for the context envs to bite
|
||||
it("should route the kimi-for-coding model id on every tier", () => {
|
||||
const env = (kimiForCoding!.settingsConfig as any).env;
|
||||
expect(env).toMatchObject({
|
||||
ANTHROPIC_MODEL: "kimi-for-coding",
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: "kimi-for-coding",
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: "kimi-for-coding",
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "kimi-for-coding",
|
||||
});
|
||||
});
|
||||
|
||||
it("should use matching context and auto-compact placeholders", () => {
|
||||
const env = (kimiForCoding!.settingsConfig as any).env;
|
||||
expect(env).toHaveProperty(
|
||||
"CLAUDE_CODE_MAX_CONTEXT_TOKENS",
|
||||
"${CLAUDE_CODE_MAX_CONTEXT_TOKENS}",
|
||||
);
|
||||
expect(env).toHaveProperty(
|
||||
"CLAUDE_CODE_AUTO_COMPACT_WINDOW",
|
||||
"${CLAUDE_CODE_AUTO_COMPACT_WINDOW}",
|
||||
);
|
||||
});
|
||||
|
||||
it("should expose auto-compact window as editable template value with Kimi default", () => {
|
||||
const values = (kimiForCoding!.templateValues as any)
|
||||
?.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
|
||||
expect(values).toBeDefined();
|
||||
expect(values.defaultValue).toBe("262144");
|
||||
expect(values.editorValue).toBe("262144");
|
||||
expect(values.label).toBe("Auto Compact Window");
|
||||
it("should expose matching 256K context and auto-compact values", () => {
|
||||
const values = kimiForCoding!.templateValues as any;
|
||||
expect(values?.CLAUDE_CODE_MAX_CONTEXT_TOKENS).toMatchObject({
|
||||
defaultValue: "262144",
|
||||
editorValue: "262144",
|
||||
});
|
||||
expect(values?.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toMatchObject({
|
||||
defaultValue: "262144",
|
||||
editorValue: "262144",
|
||||
label: "Auto Compact Window",
|
||||
});
|
||||
});
|
||||
|
||||
it("should resolve both Kimi context placeholders", () => {
|
||||
const config = applyTemplateValues(
|
||||
kimiForCoding!.settingsConfig,
|
||||
kimiForCoding!.templateValues,
|
||||
) as any;
|
||||
expect(config.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS).toBe("262144");
|
||||
expect(config.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW).toBe("262144");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user