diff --git a/config.example.yaml b/config.example.yaml index 75e0030c..1c48e02d 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -221,7 +221,7 @@ nonstream-keepalive-interval: 0 # Global OAuth model name aliases (per channel) # These aliases rename model IDs for both model listing and request routing. -# Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. +# Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kimi. # NOTE: Aliases do not apply to gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode. # You can repeat the same name with different aliases to expose multiple client model names. oauth-model-alias: @@ -262,6 +262,9 @@ oauth-model-alias: # iflow: # - name: "glm-4.7" # alias: "glm-god" +# kimi: +# - name: "kimi-k2.5" +# alias: "k2.5" # OAuth provider excluded models # oauth-excluded-models: @@ -284,6 +287,8 @@ oauth-model-alias: # - "vision-model" # iflow: # - "tstars2.0" +# kimi: +# - "kimi-k2-thinking" # Optional payload configuration # payload: diff --git a/sdk/cliproxy/auth/oauth_model_alias.go b/sdk/cliproxy/auth/oauth_model_alias.go index 4111663e..d5d2ff8a 100644 --- a/sdk/cliproxy/auth/oauth_model_alias.go +++ b/sdk/cliproxy/auth/oauth_model_alias.go @@ -221,7 +221,7 @@ func modelAliasChannel(auth *Auth) string { // and auth kind. Returns empty string if the provider/authKind combination doesn't support // OAuth model alias (e.g., API key authentication). // -// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. +// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow, kimi. func OAuthModelAliasChannel(provider, authKind string) string { provider = strings.ToLower(strings.TrimSpace(provider)) authKind = strings.ToLower(strings.TrimSpace(authKind)) @@ -245,7 +245,7 @@ func OAuthModelAliasChannel(provider, authKind string) string { return "" } return "codex" - case "gemini-cli", "aistudio", "antigravity", "qwen", "iflow": + case "gemini-cli", "aistudio", "antigravity", "qwen", "iflow", "kimi": return provider default: return "" diff --git a/sdk/cliproxy/auth/oauth_model_alias_test.go b/sdk/cliproxy/auth/oauth_model_alias_test.go index 6956411c..32390959 100644 --- a/sdk/cliproxy/auth/oauth_model_alias_test.go +++ b/sdk/cliproxy/auth/oauth_model_alias_test.go @@ -70,6 +70,15 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) { input: "gemini-2.5-pro(none)", want: "gemini-2.5-pro-exp-03-25(none)", }, + { + name: "kimi suffix preserved", + aliases: map[string][]internalconfig.OAuthModelAlias{ + "kimi": {{Name: "kimi-k2.5", Alias: "k2.5"}}, + }, + channel: "kimi", + input: "k2.5(high)", + want: "kimi-k2.5(high)", + }, { name: "case insensitive alias lookup with suffix", aliases: map[string][]internalconfig.OAuthModelAlias{ @@ -152,11 +161,21 @@ func createAuthForChannel(channel string) *Auth { return &Auth{Provider: "qwen"} case "iflow": return &Auth{Provider: "iflow"} + case "kimi": + return &Auth{Provider: "kimi"} default: return &Auth{Provider: channel} } } +func TestOAuthModelAliasChannel_Kimi(t *testing.T) { + t.Parallel() + + if got := OAuthModelAliasChannel("kimi", "oauth"); got != "kimi" { + t.Fatalf("OAuthModelAliasChannel() = %q, want %q", got, "kimi") + } +} + func TestApplyOAuthModelAlias_SuffixPreservation(t *testing.T) { t.Parallel() diff --git a/sdk/cliproxy/service_oauth_excluded_models_test.go b/sdk/cliproxy/service_oauth_excluded_models_test.go new file mode 100644 index 00000000..56315248 --- /dev/null +++ b/sdk/cliproxy/service_oauth_excluded_models_test.go @@ -0,0 +1,45 @@ +package cliproxy + +import ( + "testing" + + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" +) + +func TestOAuthExcludedModels_KimiOAuth(t *testing.T) { + t.Parallel() + + svc := &Service{ + cfg: &config.Config{ + OAuthExcludedModels: map[string][]string{ + "kimi": {"kimi-k2-thinking", "kimi-k2.5"}, + }, + }, + } + + got := svc.oauthExcludedModels("kimi", "oauth") + if len(got) != 2 { + t.Fatalf("expected 2 excluded models, got %d", len(got)) + } + if got[0] != "kimi-k2-thinking" || got[1] != "kimi-k2.5" { + t.Fatalf("unexpected excluded models: %#v", got) + } +} + +func TestOAuthExcludedModels_KimiAPIKeyReturnsNil(t *testing.T) { + t.Parallel() + + svc := &Service{ + cfg: &config.Config{ + OAuthExcludedModels: map[string][]string{ + "kimi": {"kimi-k2-thinking"}, + }, + }, + } + + got := svc.oauthExcludedModels("kimi", "apikey") + if got != nil { + t.Fatalf("expected nil for apikey auth kind, got %#v", got) + } +} + diff --git a/sdk/cliproxy/service_oauth_model_alias_test.go b/sdk/cliproxy/service_oauth_model_alias_test.go index 2caf7a17..e7c58058 100644 --- a/sdk/cliproxy/service_oauth_model_alias_test.go +++ b/sdk/cliproxy/service_oauth_model_alias_test.go @@ -90,3 +90,27 @@ func TestApplyOAuthModelAlias_ForkAddsMultipleAliases(t *testing.T) { t.Fatalf("expected forked model name %q, got %q", "models/g5-2", out[2].Name) } } + +func TestApplyOAuthModelAlias_KimiRename(t *testing.T) { + cfg := &config.Config{ + OAuthModelAlias: map[string][]config.OAuthModelAlias{ + "kimi": { + {Name: "kimi-k2.5", Alias: "k2.5"}, + }, + }, + } + models := []*ModelInfo{ + {ID: "kimi-k2.5", Name: "models/kimi-k2.5"}, + } + + out := applyOAuthModelAlias(cfg, "kimi", "oauth", models) + if len(out) != 1 { + t.Fatalf("expected 1 model, got %d", len(out)) + } + if out[0].ID != "k2.5" { + t.Fatalf("expected model id %q, got %q", "k2.5", out[0].ID) + } + if out[0].Name != "models/k2.5" { + t.Fatalf("expected model name %q, got %q", "models/k2.5", out[0].Name) + } +}