From 065dc811b663fb68c5a53f58f6fdee80bb68a289 Mon Sep 17 00:00:00 2001 From: Rolando Santamaria Maso Date: Sun, 26 Jul 2026 21:01:03 +0200 Subject: [PATCH] feat(llm): support Kimi Code models (kimi-for-coding, k3) Kimi Code's OpenAI-compatible endpoint (https://api.kimi.com/coding/v1) rejects any explicit temperature other than the default (1) with a 400 "invalid temperature: only 1 is allowed for this model". Add the kimi-for-coding* and k3* model ID prefixes to modelForbidsTemperature so the client omits the field, matching the existing handling for OpenAI reasoning models. Also wire the documented --temperature CLI flag into the agent config: it was parsed into flags.Temp but never consumed, so every run kept the hardcoded deterministic default (0). The continue command takes no CLI flags and keeps the fixed default. --- cmd/odek/main.go | 4 ++-- internal/llm/client.go | 6 ++++-- internal/llm/client_test.go | 15 +++++++++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/cmd/odek/main.go b/cmd/odek/main.go index 8df1bb6..f112bd7 100644 --- a/cmd/odek/main.go +++ b/cmd/odek/main.go @@ -1332,7 +1332,7 @@ func run(args []string) error { NoProjectFile: resolved.NoAgents, Thinking: resolved.Thinking, ThinkingBudget: f.ThinkingBudget, - Temperature: 0, // deterministic by default; override with --temperature + Temperature: f.Temp, // 0 = deterministic default; negative = omit from request Tools: tools, ToolFilter: odek.ToolFilterConfig{Enabled: resolved.Tools.Enabled, Disabled: resolved.Tools.Disabled}, SandboxCleanup: sandboxCleanup, @@ -2404,7 +2404,7 @@ func continueCmd(args []string) error { UntrustedWrapper: func(source, content string) string { return wrapUntrusted(context.Background(), source, content) }, NoProjectFile: resolved.NoAgents, Thinking: resolved.Thinking, - Temperature: 0, // deterministic by default; override with --temperature + Temperature: 0, // deterministic by default; continue takes no CLI flags Tools: tools, ToolFilter: odek.ToolFilterConfig{Enabled: resolved.Tools.Enabled, Disabled: resolved.Tools.Disabled}, SandboxCleanup: sandboxCleanup, diff --git a/internal/llm/client.go b/internal/llm/client.go index c277bf2..d461b26 100644 --- a/internal/llm/client.go +++ b/internal/llm/client.go @@ -81,12 +81,14 @@ func (c *Client) sendsThinkingObject() bool { // temperature parameter. OpenAI reasoning models (o1/o3/o4 families and the // gpt-5 series) only accept the default temperature (1); sending any other // value — including odek's deterministic default 0 — returns a 400 -// "unsupported_value" error. Matching is model-name-based and +// "unsupported_value" error. Kimi Code models (kimi-for-coding*, k3*) behave +// the same way: the endpoint answers 400 "invalid temperature: only 1 is +// allowed for this model". Matching is model-name-based and // provider-agnostic, since OpenAI-compatible proxies serving these model // IDs enforce the same constraint. func modelForbidsTemperature(model string) bool { m := strings.ToLower(model) - for _, prefix := range []string{"o1", "o3", "o4", "gpt-5"} { + for _, prefix := range []string{"o1", "o3", "o4", "gpt-5", "kimi-for-coding", "k3"} { if strings.HasPrefix(m, prefix) { return true } diff --git a/internal/llm/client_test.go b/internal/llm/client_test.go index de9a578..a59fdec 100644 --- a/internal/llm/client_test.go +++ b/internal/llm/client_test.go @@ -1038,10 +1038,11 @@ func TestClient_IsAnthropic(t *testing.T) { } } -// OpenAI reasoning models (o1/o3/o4, gpt-5 family) reject any explicit -// temperature other than the default (1) with a 400. The client must omit -// the field for those models while still sending odek's deterministic -// default (0) to models that accept it. +// OpenAI reasoning models (o1/o3/o4, gpt-5 family) and Kimi Code models +// (kimi-for-coding*, k3*) reject any explicit temperature other than the +// default (1) with a 400. The client must omit the field for those models +// while still sending odek's deterministic default (0) to models that +// accept it. func TestCall_OmitsTemperatureForReasoningModels(t *testing.T) { cases := []struct { model string @@ -1053,9 +1054,15 @@ func TestCall_OmitsTemperatureForReasoningModels(t *testing.T) { {"o1-preview", false}, {"o4-mini", false}, {"GPT-5-MINI", false}, // case-insensitive + {"kimi-for-coding", false}, + {"kimi-for-coding-highspeed", false}, + {"k3", false}, + {"k3-256k", false}, + {"Kimi-For-Coding", false}, // case-insensitive {"gpt-4o-mini", true}, {"deepseek-chat", true}, {"claude-sonnet-4-5", true}, + {"kimi-latest", true}, // Moonshot platform models accept temperature } for _, tc := range cases {