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:
@@ -1020,64 +1020,62 @@ func isClaudeOAuthToken(apiKey string) bool {
|
|||||||
// references in messages. Removed tools' corresponding tool_result blocks are preserved
|
// references in messages. Removed tools' corresponding tool_result blocks are preserved
|
||||||
// (they just become orphaned, which is safe for Claude).
|
// (they just become orphaned, which is safe for Claude).
|
||||||
func remapOAuthToolNames(body []byte) []byte {
|
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")
|
tools := gjson.GetBytes(body, "tools")
|
||||||
if !tools.Exists() || !tools.IsArray() {
|
if !tools.Exists() || !tools.IsArray() {
|
||||||
return body
|
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
|
|
||||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
|
||||||
// Skip built-in tools from removal check
|
|
||||||
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
|
var toolsJSON strings.Builder
|
||||||
toolsJSON.WriteByte('[')
|
toolsJSON.WriteByte('[')
|
||||||
for i, t := range newTools {
|
toolCount := 0
|
||||||
if i > 0 {
|
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||||
|
// Keep Anthropic built-in tools (web_search, code_execution, etc.) unchanged.
|
||||||
|
if tool.Get("type").Exists() && tool.Get("type").String() != "" {
|
||||||
|
if toolCount > 0 {
|
||||||
toolsJSON.WriteByte(',')
|
toolsJSON.WriteByte(',')
|
||||||
}
|
}
|
||||||
toolsJSON.WriteString(t.Raw)
|
toolsJSON.WriteString(tool.Raw)
|
||||||
|
toolCount++
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(']')
|
toolsJSON.WriteByte(']')
|
||||||
body, _ = sjson.SetRawBytes(body, "tools", []byte(toolsJSON.String()))
|
body, _ = sjson.SetRawBytes(body, "tools", []byte(toolsJSON.String()))
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Rename tool_choice if it references a known tool
|
// 2. Rename tool_choice if it references a known tool
|
||||||
toolChoiceType := gjson.GetBytes(body, "tool_choice.type").String()
|
toolChoiceType := gjson.GetBytes(body, "tool_choice.type").String()
|
||||||
if toolChoiceType == "tool" {
|
if toolChoiceType == "tool" {
|
||||||
tcName := gjson.GetBytes(body, "tool_choice.name").String()
|
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)
|
body, _ = sjson.SetBytes(body, "tool_choice.name", newName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user