diff --git a/internal/runtime/executor/kimi_executor.go b/internal/runtime/executor/kimi_executor.go index 6593a94b..e434d193 100644 --- a/internal/runtime/executor/kimi_executor.go +++ b/internal/runtime/executor/kimi_executor.go @@ -807,14 +807,24 @@ func stripKimiPrefix(model string) string { // It strips the CLIProxyAPI "kimi-" prefix and any Claude Code "[1m]" context // suffix while preserving a trailing thinking suffix (e.g. "(1024)"), so that // the upstream API receives IDs such as "k3(1024)" instead of "kimi-k3[1m](1024)". +// K2.7 Code aliases are remapped to the official Kimi Code model IDs before +// generic prefix stripping, so already-canonical IDs stay idempotent. func normalizeKimiUpstreamModel(model string) string { model = strings.TrimSpace(model) parsed := thinking.ParseSuffix(model) - base := parsed.ModelName - if strings.HasSuffix(strings.ToLower(base), "[1m]") { + base := strings.ToLower(strings.TrimSpace(parsed.ModelName)) + if strings.HasSuffix(base, "[1m]") { base = base[:len(base)-len("[1m]")] } - normalized := strings.ToLower(stripKimiPrefix(strings.TrimSpace(base))) + var normalized string + switch base { + case "kimi-k2.7-code", "k2.7-code", "kimi-for-coding", "for-coding": + normalized = "kimi-for-coding" + case "kimi-k2.7-code-highspeed", "k2.7-code-highspeed", "kimi-for-coding-highspeed", "for-coding-highspeed": + normalized = "kimi-for-coding-highspeed" + default: + normalized = stripKimiPrefix(base) + } if parsed.HasSuffix { return normalized + "(" + parsed.RawSuffix + ")" } diff --git a/internal/runtime/executor/kimi_executor_test.go b/internal/runtime/executor/kimi_executor_test.go index c9e79d92..dea518d4 100644 --- a/internal/runtime/executor/kimi_executor_test.go +++ b/internal/runtime/executor/kimi_executor_test.go @@ -528,6 +528,20 @@ func TestNormalizeKimiUpstreamModel(t *testing.T) { {"kimi-k3[1m](1024)", "k3(1024)"}, {"kimi-k2.6(high)", "k2.6(high)"}, {"kimi-k2.6[1m](high)", "k2.6(high)"}, + {"kimi-k2.7-code", "kimi-for-coding"}, + {"kimi-k2.7-code-highspeed", "kimi-for-coding-highspeed"}, + {"Kimi-K2.7-Code", "kimi-for-coding"}, + {"kimi-k2.7-code-highspeed(high)", "kimi-for-coding-highspeed(high)"}, + {"kimi-k2.7-code[1m](high)", "kimi-for-coding(high)"}, + {"k2.7-code", "kimi-for-coding"}, + {"k2.7-code-highspeed", "kimi-for-coding-highspeed"}, + {"kimi-for-coding", "kimi-for-coding"}, + {"kimi-for-coding-highspeed", "kimi-for-coding-highspeed"}, + {"Kimi-For-Coding", "kimi-for-coding"}, + {"kimi-for-coding-highspeed(high)", "kimi-for-coding-highspeed(high)"}, + {"kimi-for-coding[1m]", "kimi-for-coding"}, + {"for-coding", "kimi-for-coding"}, + {"for-coding-highspeed", "kimi-for-coding-highspeed"}, } for _, c := range cases { diff --git a/internal/runtime/executor/kimi_thinking_replay_test.go b/internal/runtime/executor/kimi_thinking_replay_test.go index 380589c1..2301427c 100644 --- a/internal/runtime/executor/kimi_thinking_replay_test.go +++ b/internal/runtime/executor/kimi_thinking_replay_test.go @@ -30,8 +30,10 @@ func TestKimiThinkingReplayModelFamily(t *testing.T) { {model: "kimi-k3", want: "k3"}, {model: "k3-256k", want: "k3"}, {model: "kimi-k3-256k(high)", want: "k3"}, - {model: "kimi-k2.7-code", want: "k2.7-code"}, - {model: "kimi-k2.7-code-highspeed", want: "k2.7-code-highspeed"}, + {model: "kimi-k2.7-code", want: "kimi-for-coding"}, + {model: "kimi-k2.7-code-highspeed", want: "kimi-for-coding-highspeed"}, + {model: "kimi-for-coding", want: "kimi-for-coding"}, + {model: "kimi-for-coding-highspeed(high)", want: "kimi-for-coding-highspeed"}, } for _, tc := range cases { t.Run(tc.model, func(t *testing.T) { @@ -89,7 +91,7 @@ func TestPrepareKimiThinkingReplayRequestSharesOnlyK3Variants(t *testing.T) { if !internalcache.CacheKimiThinkingReplayBestEffort(context.Background(), "k3", "execution:"+sessionID, []byte(cached)) { t.Fatal("failed to seed K3 thinking replay cache") } - if !internalcache.CacheKimiThinkingReplayBestEffort(context.Background(), "k2.7-code", "execution:"+sessionID, []byte(cached)) { + if !internalcache.CacheKimiThinkingReplayBestEffort(context.Background(), "kimi-for-coding", "execution:"+sessionID, []byte(cached)) { t.Fatal("failed to seed K2.7 Code thinking replay cache") } @@ -110,8 +112,8 @@ func TestPrepareKimiThinkingReplayRequestSharesOnlyK3Variants(t *testing.T) { k27Payload := []byte(`{"model":"kimi-k2.7-code-highspeed","messages":[{"role":"assistant","content":[{"type":"tool_use","id":"toolu_1","name":"Read","input":{"path":"README.md"}}]}]}`) preparedK27, scopeK27 := prepareKimiThinkingReplayRequest(context.Background(), cliproxyexecutor.Request{Model: "kimi-k2.7-code-highspeed", Payload: k27Payload}, opts) - if scopeK27.modelFamily != "k2.7-code-highspeed" { - t.Fatalf("K2.7 replay family = %q, want k2.7-code-highspeed", scopeK27.modelFamily) + if scopeK27.modelFamily != "kimi-for-coding-highspeed" { + t.Fatalf("K2.7 replay family = %q, want kimi-for-coding-highspeed", scopeK27.modelFamily) } if gjson.GetBytes(preparedK27.Payload, "messages.0.content.0.signature").Exists() { t.Fatalf("K2.7 variants must remain isolated: %s", preparedK27.Payload)