From 940ddd332b8afac23aabc34c65c2b15137785ded Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 12 Jul 2026 23:46:23 +0800 Subject: [PATCH] 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. --- src-tauri/src/services/provider/live.rs | 153 +++++++++++++++++++++ src/config/claudeProviderPresets.ts | 13 ++ tests/config/claudeProviderPresets.test.ts | 45 ++++-- 3 files changed, 203 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/services/provider/live.rs b/src-tauri/src/services/provider/live.rs index a37e17c5a..1aa35a426 100644 --- a/src-tauri/src/services/provider/live.rs +++ b/src-tauri/src/services/provider/live.rs @@ -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 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"); diff --git a/src/config/claudeProviderPresets.ts b/src/config/claudeProviderPresets.ts index 799fa6947..7d63e7feb 100644 --- a/src/config/claudeProviderPresets.ts +++ b/src/config/claudeProviderPresets.ts @@ -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", diff --git a/tests/config/claudeProviderPresets.test.ts b/tests/config/claudeProviderPresets.test.ts index d0b31e683..2eb738313 100644 --- a/tests/config/claudeProviderPresets.test.ts +++ b/tests/config/claudeProviderPresets.test.ts @@ -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"); }); });