diff --git a/internal/runtime/executor/claude_executor.go b/internal/runtime/executor/claude_executor.go index 7a9f1005..9c291328 100644 --- a/internal/runtime/executor/claude_executor.go +++ b/internal/runtime/executor/claude_executor.go @@ -733,6 +733,11 @@ func applyClaudeToolPrefix(body []byte, prefix string) []byte { if tools := gjson.GetBytes(body, "tools"); tools.Exists() && tools.IsArray() { tools.ForEach(func(index, tool gjson.Result) bool { + // Skip built-in tools (web_search, code_execution, etc.) which have + // a "type" field and require their name to remain unchanged. + if tool.Get("type").Exists() && tool.Get("type").String() != "" { + return true + } name := tool.Get("name").String() if name == "" || strings.HasPrefix(name, prefix) { return true diff --git a/internal/runtime/executor/claude_executor_test.go b/internal/runtime/executor/claude_executor_test.go index 05f5b60c..36fb7ad4 100644 --- a/internal/runtime/executor/claude_executor_test.go +++ b/internal/runtime/executor/claude_executor_test.go @@ -25,6 +25,18 @@ func TestApplyClaudeToolPrefix(t *testing.T) { } } +func TestApplyClaudeToolPrefix_SkipsBuiltinTools(t *testing.T) { + input := []byte(`{"tools":[{"type":"web_search_20250305","name":"web_search"},{"name":"my_custom_tool","input_schema":{"type":"object"}}]}`) + out := applyClaudeToolPrefix(input, "proxy_") + + if got := gjson.GetBytes(out, "tools.0.name").String(); got != "web_search" { + t.Fatalf("built-in tool name should not be prefixed: tools.0.name = %q, want %q", got, "web_search") + } + if got := gjson.GetBytes(out, "tools.1.name").String(); got != "proxy_my_custom_tool" { + t.Fatalf("custom tool should be prefixed: tools.1.name = %q, want %q", got, "proxy_my_custom_tool") + } +} + func TestStripClaudeToolPrefixFromResponse(t *testing.T) { input := []byte(`{"content":[{"type":"tool_use","name":"proxy_alpha","id":"t1","input":{}},{"type":"tool_use","name":"bravo","id":"t2","input":{}}]}`) out := stripClaudeToolPrefixFromResponse(input, "proxy_")