From c020e2d03f60c8b07a63b3622e0dccf334bbdd7c Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Fri, 19 Jun 2026 03:43:26 +0800 Subject: [PATCH] feat(translator): drop `apply_patch` custom tool in OpenAI responses - Added logic in `ConvertOpenAIResponsesRequestToClaude` to exclude `apply_patch` custom tools. - Introduced `isOpenAIResponsesApplyPatchCustomTool` helper function to identify and filter the tool. - Added `TestConvertOpenAIResponsesRequestToClaude_DropsApplyPatchCustomTool` to validate the behavior. Closes: #3243 --- .../claude_openai-responses_request.go | 7 ++++ .../claude_openai-responses_request_test.go | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/internal/translator/claude/openai/responses/claude_openai-responses_request.go b/internal/translator/claude/openai/responses/claude_openai-responses_request.go index c1af9d11b..0599f99c5 100644 --- a/internal/translator/claude/openai/responses/claude_openai-responses_request.go +++ b/internal/translator/claude/openai/responses/claude_openai-responses_request.go @@ -522,6 +522,9 @@ func convertResponsesToolToClaudeTools(tool gjson.Result, toolNameMap map[string return [][]byte{tJSON} } default: + if isOpenAIResponsesApplyPatchCustomTool(toolType, tool) { + return nil + } if isUnsupportedOpenAIBuiltinToolType(toolType) { return nil } @@ -532,6 +535,10 @@ func convertResponsesToolToClaudeTools(tool gjson.Result, toolNameMap map[string return nil } +func isOpenAIResponsesApplyPatchCustomTool(toolType string, tool gjson.Result) bool { + return toolType == "custom" && strings.TrimSpace(tool.Get("name").String()) == "apply_patch" +} + func convertResponsesNamespaceToolToClaude(tool gjson.Result, toolNameMap map[string]string) [][]byte { namespaceName := strings.TrimSpace(tool.Get("name").String()) children := tool.Get("tools") diff --git a/internal/translator/claude/openai/responses/claude_openai-responses_request_test.go b/internal/translator/claude/openai/responses/claude_openai-responses_request_test.go index fd6386dcf..1d5c1ed25 100644 --- a/internal/translator/claude/openai/responses/claude_openai-responses_request_test.go +++ b/internal/translator/claude/openai/responses/claude_openai-responses_request_test.go @@ -202,6 +202,40 @@ func TestConvertOpenAIResponsesRequestToClaude_KeepsToolUseAdjacentToToolResult( } } +func TestConvertOpenAIResponsesRequestToClaude_DropsApplyPatchCustomTool(t *testing.T) { + raw := []byte(`{ + "model":"claude-test", + "input":[{"role":"user","content":[{"type":"input_text","text":"hi"}]}], + "tools":[ + { + "type":"custom", + "name":"apply_patch", + "description":"Use the apply_patch tool to edit files.", + "format":{"type":"grammar","syntax":"lark","definition":"start: patch"} + }, + { + "type":"function", + "name":"exec_command", + "description":"Runs a command.", + "parameters":{"type":"object","properties":{"cmd":{"type":"string"}},"required":["cmd"]} + } + ] + }`) + + out := ConvertOpenAIResponsesRequestToClaude("claude-test", raw, false) + root := gjson.ParseBytes(out) + + if got := root.Get("tools.#").Int(); got != 1 { + t.Fatalf("tools count = %d, want 1. Output: %s", got, string(out)) + } + if got := root.Get("tools.0.name").String(); got != "exec_command" { + t.Fatalf("tools.0.name = %q, want exec_command. Output: %s", got, string(out)) + } + if got := root.Get("tools.#(name==\"apply_patch\")").Raw; got != "" { + t.Fatalf("apply_patch custom tool should be dropped. Output: %s", string(out)) + } +} + func testClaudeResponsesThinkingSignature(t *testing.T) (string, string) { t.Helper() channelBlock := []byte{}