Merge pull request #4387 from router-for-me/think

Enhance Kimi thinking configuration and tests
This commit is contained in:
Luis Pater
2026-07-17 15:59:42 +08:00
committed by GitHub
5 changed files with 364 additions and 229 deletions
+17 -20
View File
@@ -2092,13 +2092,9 @@
"context_length": 131072,
"max_completion_tokens": 32768,
"thinking": {
"min": 1024,
"max": 32000,
"zero_allowed": true,
"dynamic_allowed": true,
"levels": [
"low",
"medium",
"high"
]
}
@@ -2114,13 +2110,9 @@
"context_length": 262144,
"max_completion_tokens": 32768,
"thinking": {
"min": 1024,
"max": 32000,
"zero_allowed": true,
"dynamic_allowed": true,
"levels": [
"low",
"medium",
"high"
]
}
@@ -2136,13 +2128,9 @@
"context_length": 262144,
"max_completion_tokens": 65536,
"thinking": {
"min": 1024,
"max": 32000,
"zero_allowed": true,
"dynamic_allowed": true,
"levels": [
"low",
"medium",
"high"
]
}
@@ -2158,13 +2146,9 @@
"context_length": 262144,
"max_completion_tokens": 65536,
"thinking": {
"min": 1024,
"max": 32000,
"zero_allowed": false,
"dynamic_allowed": true,
"levels": [
"low",
"medium",
"high"
]
}
@@ -2180,16 +2164,29 @@
"context_length": 262144,
"max_completion_tokens": 65536,
"thinking": {
"min": 1024,
"max": 32000,
"zero_allowed": false,
"dynamic_allowed": true,
"levels": [
"low",
"medium",
"high"
]
}
},
{
"id": "kimi-k3",
"object": "model",
"created": 1784073600,
"owned_by": "moonshot",
"type": "kimi",
"display_name": "Kimi K3",
"description": "Kimi K3 - Moonshot AI's next-generation flagship model (~2.5T MoE) with multimodal input and 1M context",
"context_length": 1048576,
"max_completion_tokens": 65536,
"thinking": {
"zero_allowed": false,
"levels": [
"max"
]
}
}
],
"antigravity": [
+45 -2
View File
@@ -421,8 +421,7 @@ func extractThinkingConfig(body []byte, provider string) ThinkingConfig {
case "codex", "xai":
return extractCodexConfig(body)
case "kimi":
// Kimi uses OpenAI-compatible reasoning_effort format
return extractOpenAIConfig(body)
return extractKimiConfig(body)
default:
return ThinkingConfig{}
}
@@ -680,6 +679,50 @@ func extractOpenAIConfig(body []byte) ThinkingConfig {
return ThinkingConfig{}
}
// extractKimiConfig extracts Kimi's native thinking object while retaining
// reasoning_effort as a legacy input fallback.
//
// Native fields take precedence over reasoning_effort. In particular,
// thinking.type="enabled" without an explicit effort means "use the upstream
// default" and therefore returns an empty config so ApplyThinking preserves the
// request unchanged instead of interpreting it as CPA's ModeAuto.
func extractKimiConfig(body []byte) ThinkingConfig {
thinkingType := gjson.GetBytes(body, "thinking.type")
if thinkingType.Exists() {
switch strings.ToLower(strings.TrimSpace(thinkingType.String())) {
case "disabled":
return ThinkingConfig{Mode: ModeNone, Budget: 0}
case "enabled":
if !gjson.GetBytes(body, "thinking.effort").Exists() {
return ThinkingConfig{}
}
}
}
if effort := gjson.GetBytes(body, "thinking.effort"); effort.Exists() {
value := strings.ToLower(strings.TrimSpace(effort.String()))
switch value {
case "":
return ThinkingConfig{}
case "none":
return ThinkingConfig{Mode: ModeNone, Budget: 0}
case "auto":
return ThinkingConfig{Mode: ModeAuto, Budget: -1}
default:
return ThinkingConfig{Mode: ModeLevel, Level: ThinkingLevel(value)}
}
}
// An explicit native thinking object without an effort should be left for
// the Kimi upstream to interpret and must not be overridden by the legacy
// field.
if thinkingType.Exists() {
return ThinkingConfig{}
}
return extractOpenAIConfig(body)
}
// extractCodexConfig extracts thinking configuration from Codex format request body.
//
// Codex API format (OpenAI Responses API):
+21 -12
View File
@@ -1,7 +1,8 @@
// Package kimi implements thinking configuration for Kimi (Moonshot AI) models.
//
// Kimi models use the OpenAI-compatible reasoning_effort format for enabled thinking
// levels, but use thinking.type=disabled when thinking is explicitly turned off.
// Kimi models use a native thinking object for both enabled and disabled thinking.
// The top-level reasoning_effort field is accepted only as a legacy input by the
// unified extraction layer and is removed from the final Kimi payload.
package kimi
import (
@@ -16,9 +17,10 @@ import (
// Applier implements thinking.ProviderApplier for Kimi models.
//
// Kimi-specific behavior:
// - Enabled thinking: reasoning_effort (string levels)
// - Enabled thinking: thinking.type="enabled" + thinking.effort=<level>
// - Disabled thinking: thinking.type="disabled"
// - Supports budget-to-level conversion
// - Preserves existing thinking.keep when enabling or changing effort
type Applier struct{}
var _ thinking.ProviderApplier = (*Applier)(nil)
@@ -37,7 +39,10 @@ func init() {
// Expected output format (enabled):
//
// {
// "reasoning_effort": "high"
// "thinking": {
// "type": "enabled",
// "effort": "high"
// }
// }
//
// Expected output format (disabled):
@@ -91,7 +96,7 @@ func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *
if effort == "" {
return body, nil
}
return applyReasoningEffort(body, effort)
return applyEnabledThinking(body, effort)
}
// applyCompatibleKimi applies thinking config for user-defined Kimi models.
@@ -127,17 +132,21 @@ func applyCompatibleKimi(body []byte, config thinking.ThinkingConfig) ([]byte, e
return body, nil
}
return applyReasoningEffort(body, effort)
return applyEnabledThinking(body, effort)
}
func applyReasoningEffort(body []byte, effort string) ([]byte, error) {
result, errDeleteThinking := sjson.DeleteBytes(body, "thinking")
if errDeleteThinking != nil {
return body, fmt.Errorf("kimi thinking: failed to clear thinking object: %w", errDeleteThinking)
func applyEnabledThinking(body []byte, effort string) ([]byte, error) {
result, errDeleteLegacyEffort := sjson.DeleteBytes(body, "reasoning_effort")
if errDeleteLegacyEffort != nil {
return body, fmt.Errorf("kimi thinking: failed to clear reasoning_effort: %w", errDeleteLegacyEffort)
}
result, errSetEffort := sjson.SetBytes(result, "reasoning_effort", effort)
result, errSetType := sjson.SetBytes(result, "thinking.type", "enabled")
if errSetType != nil {
return body, fmt.Errorf("kimi thinking: failed to set thinking.type: %w", errSetType)
}
result, errSetEffort := sjson.SetBytes(result, "thinking.effort", effort)
if errSetEffort != nil {
return body, fmt.Errorf("kimi thinking: failed to set reasoning_effort: %w", errSetEffort)
return body, fmt.Errorf("kimi thinking: failed to set thinking.effort: %w", errSetEffort)
}
return result, nil
}
+13 -3
View File
@@ -157,6 +157,13 @@ func ValidateConfig(config ThinkingConfig, modelInfo *registry.ModelInfo, fromFo
// Convert ModeAuto to mid-range if dynamic not allowed
if config.Mode == ModeAuto && !support.DynamicAllowed {
config = convertAutoToMidRange(config, support, toFormat, model)
// The canonical mid-range level may not be present in a model's discrete
// level subset (for example, Levels=[low, high]). Clamp the generated
// fallback just like a budget-derived level so providers never receive an
// unsupported value.
if config.Mode == ModeLevel && len(support.Levels) > 0 && !isLevelSupported(string(config.Level), support.Levels) {
config.Level = clampLevel(config.Level, modelInfo, toFormat)
}
}
if config.Mode == ModeNone && toFormat == "claude" {
@@ -170,9 +177,12 @@ func ValidateConfig(config ThinkingConfig, modelInfo *registry.ModelInfo, fromFo
config.Budget = clampBudget(config.Budget, modelInfo, toFormat)
}
// ModeNone with clamped Budget > 0: set Level to lowest for Level-only/Hybrid models
// This ensures Apply layer doesn't need to access support.Levels
if config.Mode == ModeNone && config.Budget > 0 && len(support.Levels) > 0 {
// ModeNone for a model that cannot be disabled falls back to the lowest
// supported level. Budget-capable models reach this path with Budget > 0;
// level-only models need the capability flags checked explicitly because
// their Min/Max range is zero.
cannotDisableLevelModel := !support.ZeroAllowed && !isLevelSupported(string(LevelNone), support.Levels)
if config.Mode == ModeNone && len(support.Levels) > 0 && (config.Budget > 0 || cannotDisableLevelModel) {
config.Level = ThinkingLevel(support.Levels[0])
}
}
+268 -192
View File
@@ -35,6 +35,9 @@ type thinkingTestCase struct {
expectValue string
expectField2 string
expectValue2 string
expectField3 string
expectValue3 string
expectAbsent []string
includeThoughts string
expectErr bool
}
@@ -241,6 +244,17 @@ func TestThinkingE2EMatrix_Suffix(t *testing.T) {
includeThoughts: "true",
expectErr: false,
},
// Case 17A: auto → medium → clamped to low when low/high are equally close
{
name: "17A",
from: "openai",
to: "codex",
model: "level-subset-model(auto)",
inputJSON: `{"model":"level-subset-model(auto)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "reasoning.effort",
expectValue: "low",
expectErr: false,
},
// gemini-budget-model (Min=128, Max=20000, ZeroAllowed=false, DynamicAllowed=true)
@@ -1041,37 +1055,12 @@ func TestThinkingE2EMatrix_Suffix(t *testing.T) {
expectValue: "128000",
expectErr: false,
},
// Case 88: Antigravity to Antigravity, budget 8192 → passthrough thinkingBudget
{
name: "88",
from: "antigravity",
to: "antigravity",
model: "antigravity-budget-model(8192)",
inputJSON: `{"model":"antigravity-budget-model(8192)","request":{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}}`,
expectField: "request.generationConfig.thinkingConfig.thinkingBudget",
expectValue: "8192",
includeThoughts: "true",
expectErr: false,
},
// Case 89: Antigravity to Antigravity, budget 64000 → clamped to Max
{
name: "89",
from: "antigravity",
to: "antigravity",
model: "antigravity-budget-model(64000)",
inputJSON: `{"model":"antigravity-budget-model(64000)","request":{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}}`,
expectField: "request.generationConfig.thinkingConfig.thinkingBudget",
expectValue: "20000",
includeThoughts: "true",
expectErr: false,
},
// Gemini Family Cross-Channel Consistency (Cases 90-95)
// Gemini Family Cross-Channel Consistency (Cases 88-89)
// Tests that gemini/antigravity as same API family should have consistent validation behavior
// Case 90: Gemini to Antigravity, budget 64000 (suffix) → clamped to Max
// Case 88: Gemini to Antigravity, budget 64000 (suffix) → clamped to Max
{
name: "90",
name: "88",
from: "gemini",
to: "antigravity",
model: "gemini-budget-model(64000)",
@@ -1081,9 +1070,9 @@ func TestThinkingE2EMatrix_Suffix(t *testing.T) {
includeThoughts: "true",
expectErr: false,
},
// Case 94: Gemini to Antigravity, budget 8192 → passthrough (normal value)
// Case 89: Gemini to Antigravity, budget 8192 → passthrough (normal value)
{
name: "94",
name: "89",
from: "gemini",
to: "antigravity",
model: "gemini-budget-model(8192)",
@@ -1472,38 +1461,38 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
name: "31A",
from: "openai",
to: "gemini",
model: "gemini-zero-mixed-model",
inputJSON: `{"model":"gemini-zero-mixed-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"none"}`,
model: "gemini-toggle-mixed-model",
inputJSON: `{"model":"gemini-toggle-mixed-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"none"}`,
expectField: "",
expectErr: false,
},
// Case 31C: reasoning_effort=none with zero allowed to Antigravity → delete thinkingConfig
// Case 31B: reasoning_effort=none with zero allowed to Antigravity → delete thinkingConfig
{
name: "31C",
name: "31B",
from: "openai",
to: "antigravity",
model: "gemini-zero-mixed-model",
inputJSON: `{"model":"gemini-zero-mixed-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"none"}`,
model: "gemini-toggle-mixed-model",
inputJSON: `{"model":"gemini-toggle-mixed-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"none"}`,
expectField: "",
expectErr: false,
},
// Case 31D: reasoning.effort=none with zero allowed → delete thinkingConfig
// Case 31C: reasoning.effort=none with zero allowed → delete thinkingConfig
{
name: "31C",
from: "openai-response",
to: "gemini",
model: "gemini-toggle-mixed-model",
inputJSON: `{"model":"gemini-toggle-mixed-model","input":[{"role":"user","content":"hi"}],"reasoning":{"effort":"none"}}`,
expectField: "",
expectErr: false,
},
// Case 31D: reasoning.effort=none with zero allowed to Antigravity → delete thinkingConfig
{
name: "31D",
from: "openai-response",
to: "gemini",
model: "gemini-zero-mixed-model",
inputJSON: `{"model":"gemini-zero-mixed-model","input":[{"role":"user","content":"hi"}],"reasoning":{"effort":"none"}}`,
expectField: "",
expectErr: false,
},
// Case 31F: reasoning.effort=none with zero allowed to Antigravity → delete thinkingConfig
{
name: "31F",
from: "openai-response",
to: "antigravity",
model: "gemini-zero-mixed-model",
inputJSON: `{"model":"gemini-zero-mixed-model","input":[{"role":"user","content":"hi"}],"reasoning":{"effort":"none"}}`,
model: "gemini-toggle-mixed-model",
inputJSON: `{"model":"gemini-toggle-mixed-model","input":[{"role":"user","content":"hi"}],"reasoning":{"effort":"none"}}`,
expectField: "",
expectErr: false,
},
@@ -2136,35 +2125,12 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
expectField: "",
expectErr: true,
},
// Case 88: Antigravity to Antigravity, thinkingBudget=8192 → passthrough
{
name: "88",
from: "antigravity",
to: "antigravity",
model: "antigravity-budget-model",
inputJSON: `{"model":"antigravity-budget-model","request":{"contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}}`,
expectField: "request.generationConfig.thinkingConfig.thinkingBudget",
expectValue: "8192",
includeThoughts: "true",
expectErr: false,
},
// Case 89: Antigravity to Antigravity, thinkingBudget=64000 → exceeds Max error
{
name: "89",
from: "antigravity",
to: "antigravity",
model: "antigravity-budget-model",
inputJSON: `{"model":"antigravity-budget-model","request":{"contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinkingBudget":64000}}}}`,
expectField: "",
expectErr: true,
},
// Gemini Family Cross-Channel Consistency (Cases 90-95)
// Gemini Family Cross-Channel Consistency (Cases 88-89)
// Tests that gemini/antigravity as same API family should have consistent validation behavior
// Case 90: Gemini to Antigravity, thinkingBudget=64000 → exceeds Max error (same family strict validation)
// Case 88: Gemini to Antigravity, thinkingBudget=64000 → exceeds Max error (same family strict validation)
{
name: "90",
name: "88",
from: "gemini",
to: "antigravity",
model: "gemini-budget-model",
@@ -2172,9 +2138,9 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
expectField: "",
expectErr: true,
},
// Case 94: Gemini to Antigravity, thinkingBudget=8192 → passthrough (normal value)
// Case 89: Gemini to Antigravity, thinkingBudget=8192 → passthrough (normal value)
{
name: "94",
name: "89",
from: "gemini",
to: "antigravity",
model: "gemini-budget-model",
@@ -2189,88 +2155,205 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
runThinkingTests(t, cases)
}
// TestThinkingE2ENewProviderTargets covers provider-specific targets that do not
// have their own public translator format but do have ApplyThinking providers.
func TestThinkingE2ENewProviderTargets(t *testing.T) {
// TestThinkingE2EProviderTargets covers provider-specific targets that are not part of the main matrix.
func TestThinkingE2EProviderTargets(t *testing.T) {
reg := registry.GetGlobalRegistry()
uid := fmt.Sprintf("thinking-e2e-new-providers-%d", time.Now().UnixNano())
uid := fmt.Sprintf("thinking-e2e-provider-targets-%d", time.Now().UnixNano())
reg.RegisterClient(uid, "test", getTestModels())
defer reg.UnregisterClient(uid)
cases := []thinkingTestCase{
// Kimi target: enabled thinking uses reasoning_effort, explicit disable uses thinking.type=disabled.
// Kimi target: emit the native thinking object and accept reasoning_effort only as legacy input.
{
name: "K1",
from: "openai",
to: "kimi",
model: "kimi-level-model(high)",
inputJSON: `{"model":"kimi-level-model(high)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "reasoning_effort",
expectValue: "high",
name: "K1",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model(high)",
inputJSON: `{"model":"kimi-toggle-thinking-model(high)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K2",
from: "openai",
to: "kimi",
model: "kimi-level-model(none)",
inputJSON: `{"model":"kimi-level-model(none)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "thinking.type",
expectValue: "disabled",
name: "K2",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model(none)",
inputJSON: `{"model":"kimi-toggle-thinking-model(none)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "thinking.type",
expectValue: "disabled",
expectAbsent: []string{"thinking.effort", "reasoning_effort"},
},
{
name: "K3",
from: "gemini",
to: "kimi",
model: "kimi-level-model(32768)",
inputJSON: `{"model":"kimi-level-model(32768)","contents":[{"role":"user","parts":[{"text":"hi"}]}]}`,
expectField: "reasoning_effort",
expectValue: "high",
name: "K3",
from: "gemini",
to: "kimi",
model: "kimi-toggle-thinking-model(32768)",
inputJSON: `{"model":"kimi-toggle-thinking-model(32768)","contents":[{"role":"user","parts":[{"text":"hi"}]}]}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K4",
from: "claude",
to: "kimi",
model: "kimi-level-model(0)",
inputJSON: `{"model":"kimi-level-model(0)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "thinking.type",
expectValue: "disabled",
name: "K4",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model(auto)",
inputJSON: `{"model":"kimi-toggle-thinking-model(auto)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "medium",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K5",
from: "openai",
to: "kimi",
model: "kimi-level-model",
inputJSON: `{"model":"kimi-level-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"high"}`,
expectField: "reasoning_effort",
expectValue: "high",
name: "K5",
from: "openai",
to: "kimi",
model: "kimi-tiered-thinking-model(none)",
inputJSON: `{"model":"kimi-tiered-thinking-model(none)","messages":[{"role":"user","content":"hi"}]}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "low",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K6",
from: "openai-response",
to: "kimi",
model: "kimi-level-model",
inputJSON: `{"model":"kimi-level-model","input":[{"role":"user","content":"hi"}],"reasoning":{"effort":"none"}}`,
expectField: "thinking.type",
expectValue: "disabled",
name: "K6",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"high"}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K7",
from: "gemini",
to: "kimi",
model: "kimi-level-model",
inputJSON: `{"model":"kimi-level-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinkingBudget":32768}}}`,
expectField: "reasoning_effort",
expectValue: "high",
name: "K7",
from: "openai-response",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","input":[{"role":"user","content":"hi"}],"reasoning":{"effort":"none"}}`,
expectField: "thinking.type",
expectValue: "disabled",
expectAbsent: []string{"thinking.effort", "reasoning_effort"},
},
{
name: "K8",
from: "claude",
to: "kimi",
model: "kimi-level-model",
inputJSON: `{"model":"kimi-level-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"enabled","budget_tokens":0}}`,
expectField: "thinking.type",
expectValue: "disabled",
name: "K8",
from: "gemini",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinkingBudget":32768}}}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K9",
from: "gemini",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "medium",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K10",
from: "claude",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"enabled","budget_tokens":0}}`,
expectField: "thinking.type",
expectValue: "disabled",
expectAbsent: []string{"thinking.effort", "reasoning_effort"},
},
{
name: "K11",
from: "claude",
to: "kimi",
model: "kimi-tiered-thinking-model",
inputJSON: `{"model":"kimi-tiered-thinking-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"enabled","budget_tokens":0}}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "low",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K12",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"high","thinking":{"keep":"all"}}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectField3: "thinking.keep",
expectValue3: "all",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K13",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"enabled","effort":"high","keep":"all"}}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectField3: "thinking.keep",
expectValue3: "all",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K14",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"enabled","keep":"all"}}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.keep",
expectValue2: "all",
expectAbsent: []string{"thinking.effort", "reasoning_effort"},
},
{
name: "K15",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"thinking":{"effort":"high"},"reasoning_effort":"low"}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "high",
expectAbsent: []string{"reasoning_effort"},
},
{
name: "K16",
from: "openai",
to: "kimi",
model: "kimi-toggle-thinking-model",
inputJSON: `{"model":"kimi-toggle-thinking-model","messages":[{"role":"user","content":"hi"}],"reasoning_effort":"auto"}`,
expectField: "thinking.type",
expectValue: "enabled",
expectField2: "thinking.effort",
expectValue2: "medium",
expectAbsent: []string{"reasoning_effort"},
},
// xAI target: Grok uses Responses-compatible reasoning.effort with Grok-specific levels.
@@ -2370,8 +2453,8 @@ func TestThinkingE2ENewProviderTargets(t *testing.T) {
name: "I1",
from: "interactions",
to: "interactions",
model: "gemini-zero-mixed-model",
inputJSON: `{"model":"gemini-zero-mixed-model","generation_config":{"thinking_level":"high","thinking_summaries":"auto"},"input":"hi"}`,
model: "level-model",
inputJSON: `{"model":"level-model","generation_config":{"thinking_level":"high","thinking_summaries":"auto"},"input":"hi"}`,
expectField: "generation_config.thinking_level",
expectValue: "high",
expectField2: "generation_config.thinking_summaries",
@@ -2381,8 +2464,8 @@ func TestThinkingE2ENewProviderTargets(t *testing.T) {
name: "I2",
from: "interactions",
to: "interactions",
model: "gemini-zero-mixed-model(8192)",
inputJSON: `{"model":"gemini-zero-mixed-model(8192)","input":"hi"}`,
model: "level-model(8192)",
inputJSON: `{"model":"level-model(8192)","input":"hi"}`,
expectField: "generation_config.thinking_level",
expectValue: "medium",
expectField2: "generation_config.thinking_summaries",
@@ -2816,7 +2899,7 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
expectErr: false,
},
{
name: "C21",
name: "C19",
from: "claude",
to: "antigravity",
model: "antigravity-budget-model",
@@ -2828,7 +2911,7 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
},
{
name: "C22",
name: "C20",
from: "claude",
to: "claude",
model: "claude-sonnet-4-6-model",
@@ -2840,7 +2923,7 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
expectErr: false,
},
{
name: "C23",
name: "C21",
from: "claude",
to: "claude",
model: "claude-opus-4-6-model",
@@ -2852,7 +2935,7 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
expectErr: false,
},
{
name: "C24",
name: "C22",
from: "claude",
to: "claude",
model: "claude-opus-4-6-model",
@@ -2860,7 +2943,7 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
expectErr: true,
},
{
name: "C25",
name: "C23",
from: "claude",
to: "claude",
model: "claude-sonnet-4-6-model",
@@ -2872,7 +2955,7 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
expectErr: false,
},
{
name: "C26",
name: "C24",
from: "claude",
to: "claude",
model: "claude-sonnet-4-6-model",
@@ -2880,40 +2963,13 @@ func TestThinkingE2EClaudeAdaptive_Body(t *testing.T) {
expectErr: true,
},
{
name: "C27",
name: "C25",
from: "claude",
to: "claude",
model: "claude-sonnet-4-6-model",
inputJSON: `{"model":"claude-sonnet-4-6-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"adaptive"},"output_config":{"effort":"xhigh"}}`,
expectErr: true,
},
// Kimi models exposed via Claude-compatible /v1/messages keep wire format
// claude→claude, but the model type is kimi. Claude Code often sends
// effort=max; clamp to the highest Kimi-supported level (high).
{
name: "C28",
from: "claude",
to: "claude",
model: "kimi-level-model",
inputJSON: `{"model":"kimi-level-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"adaptive"},"output_config":{"effort":"max"}}`,
expectField: "thinking.type",
expectValue: "adaptive",
expectField2: "output_config.effort",
expectValue2: "high",
expectErr: false,
},
{
name: "C29",
from: "claude",
to: "claude",
model: "kimi-level-model",
inputJSON: `{"model":"kimi-level-model","messages":[{"role":"user","content":"hi"}],"thinking":{"type":"adaptive"},"output_config":{"effort":"xhigh"}}`,
expectField: "thinking.type",
expectValue: "adaptive",
expectField2: "output_config.effort",
expectValue2: "high",
expectErr: false,
},
}
runThinkingTests(t, cases)
@@ -2959,13 +3015,13 @@ func getTestModels() []*registry.ModelInfo {
Thinking: &registry.ThinkingSupport{Min: 128, Max: 32768, Levels: []string{"low", "high"}, ZeroAllowed: false, DynamicAllowed: true},
},
{
ID: "gemini-zero-mixed-model",
ID: "gemini-toggle-mixed-model",
Object: "model",
Created: 1700000000,
OwnedBy: "test",
Type: "gemini",
DisplayName: "Gemini Zero Mixed Model",
Thinking: &registry.ThinkingSupport{Min: 1, Max: 65535, Levels: []string{"minimal", "low", "medium", "high"}, ZeroAllowed: true, DynamicAllowed: true},
DisplayName: "Gemini Toggle Mixed Model",
Thinking: &registry.ThinkingSupport{Min: 128, Max: 32768, Levels: []string{"low", "high"}, ZeroAllowed: true, DynamicAllowed: true},
},
{
ID: "claude-budget-model",
@@ -2976,17 +3032,6 @@ func getTestModels() []*registry.ModelInfo {
DisplayName: "Claude Budget Model",
Thinking: &registry.ThinkingSupport{Min: 1024, Max: 128000, ZeroAllowed: true, DynamicAllowed: false},
},
{
ID: "claude-sonnet-4-6-model",
Object: "model",
Created: 1771372800, // 2026-02-17
OwnedBy: "anthropic",
Type: "claude",
DisplayName: "Claude 4.6 Sonnet",
ContextLength: 200000,
MaxCompletionTokens: 64000,
Thinking: &registry.ThinkingSupport{Min: 1024, Max: 128000, ZeroAllowed: true, DynamicAllowed: false, Levels: []string{"low", "medium", "high"}},
},
{
ID: "claude-opus-4-6-model",
Object: "model",
@@ -2999,6 +3044,17 @@ func getTestModels() []*registry.ModelInfo {
MaxCompletionTokens: 128000,
Thinking: &registry.ThinkingSupport{Min: 1024, Max: 128000, ZeroAllowed: true, DynamicAllowed: false, Levels: []string{"low", "medium", "high", "max"}},
},
{
ID: "claude-sonnet-4-6-model",
Object: "model",
Created: 1771372800, // 2026-02-17
OwnedBy: "anthropic",
Type: "claude",
DisplayName: "Claude 4.6 Sonnet",
ContextLength: 200000,
MaxCompletionTokens: 64000,
Thinking: &registry.ThinkingSupport{Min: 1024, Max: 128000, ZeroAllowed: true, DynamicAllowed: false, Levels: []string{"low", "medium", "high"}},
},
{
ID: "antigravity-budget-model",
Object: "model",
@@ -3009,14 +3065,23 @@ func getTestModels() []*registry.ModelInfo {
Thinking: &registry.ThinkingSupport{Min: 128, Max: 20000, ZeroAllowed: true, DynamicAllowed: true},
},
{
ID: "kimi-level-model",
ID: "kimi-toggle-thinking-model",
Object: "model",
Created: 1700000000,
OwnedBy: "moonshot",
Type: "kimi",
DisplayName: "Kimi Level Model",
DisplayName: "Kimi Toggle Thinking Model",
Thinking: &registry.ThinkingSupport{Levels: []string{"low", "medium", "high"}, ZeroAllowed: true, DynamicAllowed: false},
},
{
ID: "kimi-tiered-thinking-model",
Object: "model",
Created: 1700000000,
OwnedBy: "moonshot",
Type: "kimi",
DisplayName: "Kimi Tiered Thinking Model",
Thinking: &registry.ThinkingSupport{Levels: []string{"low", "medium", "high"}, ZeroAllowed: false, DynamicAllowed: false},
},
{
ID: "xai-level-model",
Object: "model",
@@ -3089,6 +3154,12 @@ func runThinkingTests(t *testing.T, cases []thinkingTestCase) {
t.Fatalf("unexpected error: %v, body=%s", err, string(body))
}
for _, fieldPath := range tc.expectAbsent {
if gjson.GetBytes(body, fieldPath).Exists() {
t.Fatalf("expected field %s to be absent, body=%s", fieldPath, string(body))
}
}
if tc.expectField == "" {
var hasThinking bool
switch tc.to {
@@ -3102,6 +3173,8 @@ func runThinkingTests(t *testing.T, cases []thinkingTestCase) {
hasThinking = gjson.GetBytes(body, "reasoning_effort").Exists()
case "codex":
hasThinking = gjson.GetBytes(body, "reasoning.effort").Exists() || gjson.GetBytes(body, "reasoning").Exists()
case "kimi":
hasThinking = gjson.GetBytes(body, "thinking").Exists() || gjson.GetBytes(body, "reasoning_effort").Exists()
}
if hasThinking {
t.Fatalf("expected no thinking field but found one, body=%s", string(body))
@@ -3127,6 +3200,9 @@ func runThinkingTests(t *testing.T, cases []thinkingTestCase) {
if tc.expectField2 != "" {
assertField(tc.expectField2, tc.expectValue2)
}
if tc.expectField3 != "" {
assertField(tc.expectField3, tc.expectValue3)
}
if tc.includeThoughts != "" && (tc.to == "gemini" || tc.to == "antigravity") {
path := "generationConfig.thinkingConfig.includeThoughts"