feat: add Kimi Coding compatibility and model preset

This commit is contained in:
Reginaldo 2026-02-23 23:07:46 -03:00
parent 7cbfa89a96
commit 2a2797f99d
3 changed files with 105 additions and 0 deletions

View file

@ -177,6 +177,13 @@ func DefaultConfig() *Config {
APIBase: "https://api.moonshot.cn/v1", APIBase: "https://api.moonshot.cn/v1",
APIKey: "", APIKey: "",
}, },
// Kimi Plan/Coding - https://www.kimi.com/code/docs
{
ModelName: "kimi-plan-coding",
Model: "moonshot/kimi-k2-thinking",
APIBase: "https://api.kimi.com/coding/v1",
APIKey: "",
},
// Groq - https://console.groq.com/keys // Groq - https://console.groq.com/keys
{ {

View file

@ -125,6 +125,9 @@ func (p *Provider) Chat(
if p.apiKey != "" { if p.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+p.apiKey) req.Header.Set("Authorization", "Bearer "+p.apiKey)
} }
if ua := selectUserAgent(p.apiBase); ua != "" {
req.Header.Set("User-Agent", ua)
}
resp, err := p.httpClient.Do(req) resp, err := p.httpClient.Do(req)
if err != nil { if err != nil {
@ -247,6 +250,15 @@ func normalizeModel(model, apiBase string) string {
} }
} }
func selectUserAgent(apiBase string) string {
base := strings.ToLower(apiBase)
if strings.Contains(base, "api.kimi.com/coding") {
// Kimi For Coding currently restricts access to recognized coding-agent clients.
return "KimiCLI/1.3"
}
return ""
}
func asInt(v any) (int, bool) { func asInt(v any) (int, bool) {
switch val := v.(type) { switch val := v.(type) {
case int: case int:

View file

@ -232,6 +232,92 @@ func TestProvider_ProxyConfigured(t *testing.T) {
} }
} }
func TestProviderChat_UsesKimiUserAgentOnCodingEndpoint(t *testing.T) {
var gotUA string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotUA = r.Header.Get("User-Agent")
resp := map[string]any{
"choices": []map[string]any{
{
"message": map[string]any{"content": "ok"},
"finish_reason": "stop",
},
},
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
kimiBase := server.URL + "/api.kimi.com/coding/v1"
p := NewProvider("key", kimiBase, "")
_, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "kimi-for-coding", nil)
if err != nil {
t.Fatalf("Chat() error = %v", err)
}
if gotUA != "KimiCLI/1.3" {
t.Fatalf("User-Agent = %q, want %q", gotUA, "KimiCLI/1.3")
}
}
func TestProviderChat_KimiCodingModelAliases(t *testing.T) {
tests := []struct {
name string
model string
}{
{name: "k2.5", model: "kimi-k2.5"},
{name: "k2-thinking", model: "kimi-k2-thinking"},
{name: "for-coding", model: "kimi-for-coding"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var gotUA string
var gotModel string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotUA = r.Header.Get("User-Agent")
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if m, ok := body["model"].(string); ok {
gotModel = m
}
resp := map[string]any{
"choices": []map[string]any{
{
"message": map[string]any{"content": "ok"},
"finish_reason": "stop",
},
},
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
kimiBase := server.URL + "/api.kimi.com/coding/v1"
p := NewProvider("key", kimiBase, "")
_, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, tt.model, nil)
if err != nil {
t.Fatalf("Chat() error = %v", err)
}
if gotUA != "KimiCLI/1.3" {
t.Fatalf("User-Agent = %q, want %q", gotUA, "KimiCLI/1.3")
}
if gotModel != tt.model {
t.Fatalf("model = %q, want %q", gotModel, tt.model)
}
})
}
}
func TestProviderChat_AcceptsNumericOptionTypes(t *testing.T) { func TestProviderChat_AcceptsNumericOptionTypes(t *testing.T) {
var requestBody map[string]any var requestBody map[string]any