fix(claude): preserve OAuth tool renames when filtering tools

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
wykk-12138
2026-04-09 22:20:15 +08:00
parent e8d1b79cb3
commit ac36119a02

View File

@@ -1020,64 +1020,62 @@ func isClaudeOAuthToken(apiKey string) bool {
// references in messages. Removed tools' corresponding tool_result blocks are preserved
// (they just become orphaned, which is safe for Claude).
func remapOAuthToolNames(body []byte) []byte {
// 1. Rename and filter tools array
// 1. Rewrite tools array in a single pass.
// IMPORTANT: do not mutate names first and then rebuild from an older gjson
// snapshot. gjson results are snapshots of the original bytes; rebuilding from a
// stale snapshot will preserve removals but overwrite renamed names back to their
// original lowercase values.
tools := gjson.GetBytes(body, "tools")
if !tools.Exists() || !tools.IsArray() {
return body
}
// First pass: rename tools that have Claude Code equivalents.
tools.ForEach(func(idx, tool gjson.Result) bool {
// Skip built-in tools (web_search, code_execution, etc.) which have a "type" field
if tool.Get("type").Exists() && tool.Get("type").String() != "" {
return true
}
name := tool.Get("name").String()
if newName, ok := oauthToolRenameMap[name]; ok {
path := fmt.Sprintf("tools.%d.name", idx.Int())
body, _ = sjson.SetBytes(body, path, newName)
}
return true
})
// Second pass: remove tools that are in oauthToolsToRemove by rebuilding the array.
// This avoids index-shifting issues with sjson.DeleteBytes.
var newTools []gjson.Result
toRemove := false
var toolsJSON strings.Builder
toolsJSON.WriteByte('[')
toolCount := 0
tools.ForEach(func(_, tool gjson.Result) bool {
// Skip built-in tools from removal check
// Keep Anthropic built-in tools (web_search, code_execution, etc.) unchanged.
if tool.Get("type").Exists() && tool.Get("type").String() != "" {
newTools = append(newTools, tool)
return true
}
name := tool.Get("name").String()
if oauthToolsToRemove[name] {
toRemove = true
return true
}
newTools = append(newTools, tool)
return true
})
if toRemove {
// Rebuild the tools array without removed tools
var toolsJSON strings.Builder
toolsJSON.WriteByte('[')
for i, t := range newTools {
if i > 0 {
if toolCount > 0 {
toolsJSON.WriteByte(',')
}
toolsJSON.WriteString(t.Raw)
toolsJSON.WriteString(tool.Raw)
toolCount++
return true
}
toolsJSON.WriteByte(']')
body, _ = sjson.SetRawBytes(body, "tools", []byte(toolsJSON.String()))
}
name := tool.Get("name").String()
if oauthToolsToRemove[name] {
return true
}
toolJSON := tool.Raw
if newName, ok := oauthToolRenameMap[name]; ok {
updatedTool, err := sjson.Set(toolJSON, "name", newName)
if err == nil {
toolJSON = updatedTool
}
}
if toolCount > 0 {
toolsJSON.WriteByte(',')
}
toolsJSON.WriteString(toolJSON)
toolCount++
return true
})
toolsJSON.WriteByte(']')
body, _ = sjson.SetRawBytes(body, "tools", []byte(toolsJSON.String()))
// 2. Rename tool_choice if it references a known tool
toolChoiceType := gjson.GetBytes(body, "tool_choice.type").String()
if toolChoiceType == "tool" {
tcName := gjson.GetBytes(body, "tool_choice.name").String()
if newName, ok := oauthToolRenameMap[tcName]; ok {
if oauthToolsToRemove[tcName] {
// The chosen tool was removed from the tools array, so drop tool_choice to
// keep the payload internally consistent and fall back to normal auto tool use.
body, _ = sjson.DeleteBytes(body, "tool_choice")
} else if newName, ok := oauthToolRenameMap[tcName]; ok {
body, _ = sjson.SetBytes(body, "tool_choice.name", newName)
}
}