fix Kimi Claude tool-use reasoning replay#3719
Conversation
|
This pull request targeted The base branch has been automatically changed to |
There was a problem hiding this comment.
Code Review
This pull request introduces reasoning normalization for Claude-sourced requests in the Kimi executor, ensuring that assistant tool-use messages are properly patched with reasoning content. The feedback highlights a critical concurrency issue where mutating the shared auth attributes map directly could lead to data races and panics, and suggests cloning the auth object. Additionally, opportunities for optimization and simplification were identified, such as caching the parsed reasoning text to avoid redundant JSON parsing and removing duplicate fallback checks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| func prepareKimiClaudeSourceRequest(auth *cliproxyauth.Auth, req cliproxyexecutor.Request) (*cliproxyauth.Auth, cliproxyexecutor.Request, error) { | ||
| if auth == nil { | ||
| auth = &cliproxyauth.Auth{} | ||
| } | ||
| if auth.Attributes == nil { | ||
| auth.Attributes = make(map[string]string) | ||
| } | ||
| auth.Attributes["base_url"] = kimiauth.KimiAPIBaseURL | ||
|
|
||
| body, err := normalizeKimiClaudeToolUseReasoning(req.Payload) | ||
| if err != nil { | ||
| return auth, req, err | ||
| } | ||
| req.Payload = body | ||
| return auth, req, nil | ||
| } |
There was a problem hiding this comment.
Mutating auth.Attributes directly can lead to concurrent map write panics if the auth object is shared or reused across concurrent requests. To prevent data races and side effects, we should clone the auth object and its Attributes map before modifying them.
func prepareKimiClaudeSourceRequest(auth *cliproxyauth.Auth, req cliproxyexecutor.Request) (*cliproxyauth.Auth, cliproxyexecutor.Request, error) {
var clonedAuth *cliproxyauth.Auth
if auth != nil {
cloned := *auth
clonedAuth = &cloned
} else {
clonedAuth = &cliproxyauth.Auth{}
}
newAttrs := make(map[string]string, len(clonedAuth.Attributes)+1)
for k, v := range clonedAuth.Attributes {
newAttrs[k] = v
}
newAttrs["base_url"] = kimiauth.KimiAPIBaseURL
clonedAuth.Attributes = newAttrs
body, err := normalizeKimiClaudeToolUseReasoning(req.Payload)
if err != nil {
return clonedAuth, req, err
}
req.Payload = body
return clonedAuth, req, nil
}| if reasoningText := kimiClaudeAssistantReasoningText(msg); reasoningText != "" { | ||
| latestReasoning = reasoningText | ||
| hasLatestReasoning = true | ||
| } | ||
|
|
||
| if !hasKimiClaudeToolUse(msg) { | ||
| continue | ||
| } | ||
|
|
||
| reasoning := msg.Get("reasoning_content") | ||
| if reasoning.Exists() && strings.TrimSpace(reasoning.String()) != "" { | ||
| continue | ||
| } | ||
|
|
||
| reasoningText := fallbackKimiClaudeAssistantReasoning(msg, hasLatestReasoning, latestReasoning) |
There was a problem hiding this comment.
We can optimize the reasoning extraction by storing the result of kimiClaudeAssistantReasoningText(msg) in a local variable. This avoids parsing the content array twice for messages that need patching, and allows us to skip calling fallbackKimiClaudeAssistantReasoning entirely when the current message already has reasoning.
currentReasoning := kimiClaudeAssistantReasoningText(msg)
if currentReasoning != "" {
latestReasoning = currentReasoning
hasLatestReasoning = true
}
if !hasKimiClaudeToolUse(msg) {
continue
}
reasoning := msg.Get("reasoning_content")
if reasoning.Exists() && strings.TrimSpace(reasoning.String()) != "" {
continue
}
reasoningText := currentReasoning
if reasoningText == "" {
reasoningText = fallbackKimiClaudeAssistantReasoning(msg, hasLatestReasoning, latestReasoning)
}| func fallbackKimiClaudeAssistantReasoning(msg gjson.Result, hasLatest bool, latest string) string { | ||
| if reasoningText := kimiClaudeAssistantReasoningText(msg); reasoningText != "" { | ||
| return reasoningText | ||
| } | ||
| if hasLatest && strings.TrimSpace(latest) != "" { | ||
| return latest | ||
| } |
There was a problem hiding this comment.
Since normalizeKimiClaudeToolUseReasoning now handles the non-empty currentReasoning check directly, we can simplify fallbackKimiClaudeAssistantReasoning by removing the redundant call to kimiClaudeAssistantReasoningText.
| func fallbackKimiClaudeAssistantReasoning(msg gjson.Result, hasLatest bool, latest string) string { | |
| if reasoningText := kimiClaudeAssistantReasoningText(msg); reasoningText != "" { | |
| return reasoningText | |
| } | |
| if hasLatest && strings.TrimSpace(latest) != "" { | |
| return latest | |
| } | |
| func fallbackKimiClaudeAssistantReasoning(msg gjson.Result, hasLatest bool, latest string) string { | |
| if hasLatest && strings.TrimSpace(latest) != "" { | |
| return latest | |
| } |
1070325 to
1360d49
Compare
1360d49 to
bc47ef7
Compare
|
useful! thanks |
Summary
reasoning_contentto Claude-format assistant messages that containtool_useblocks, using existing reasoning, thinking, or text content as fallback.Root cause
Kimi Claude-source traffic was delegated directly to
ClaudeExecutor, which skipped Kimi's existing tool-message normalizer. When thinking is enabled, Kimi rejects assistant tool-call history that lacksreasoning_content, causing 400 responses such asthinking is enabled but reasoning_content is missing in assistant tool call message.Testing
go test ./internal/runtime/executor -count=1go build -o test-output ./cmd/server && rm test-outputgo test ./... -count=1