fix: guard against empty tool_use/function.name in protocol conversion - #409
fix: guard against empty tool_use/function.name in protocol conversion#409spock-wen wants to merge 1 commit into
Conversation
Skip tool calls with empty function.name at all conversion points: - Streaming response: check name.String() != "" (not just Exists) - Non-streaming response: skip in convertOpenAINonStreamingToAnthropic - Non-streaming alternative: skip in ConvertOpenAIResponseToClaudeNonStream (both content-array and message-level tool_calls paths) - Request back-conversion: skip tool_use with empty name Upstream providers occasionally return tool_calls with empty function.name which causes downstream JSON with empty names that Claude Code rejects.
kittors
left a comment
There was a problem hiding this comment.
Request changes. The non-streaming guards are useful, but the streaming fix is incomplete.
In convertOpenAIStreamingChunkToAnthropic, an empty function.name no longer emits content_block_start, but the accumulator still stores arguments. When finish_reason=tool_calls or [DONE] is processed, the existing finalization path still iterates every accumulator and can emit input_json_delta/content_block_stop for a tool block that was never started. That leaves orphan Anthropic stream events.
It also does not adjust stop_reason when all tool calls were skipped: tool_calls/function_call can still become tool_use even though no valid tool_use block was emitted. Request-side handling also needs to avoid empty/whitespace tool_use, empty tools/tool_choice names, and orphan tool_result messages for skipped tool_use ids.
A complete fix should track whether a tool content block has actually started, finalize only started blocks, map tool_calls to end_turn when no valid tool was emitted, trim whitespace-only names, and add regression tests for empty-name streaming, usage chunks, arguments-before-name, mixed valid/empty tools, non-streaming, and request conversion. See #430/#431 for the fuller implementation direction.
Summary
Skip tool calls with empty
function.nameat all conversion points. Upstream providers occasionally returntool_callswith emptyfunction.name, which produces downstream JSON with empty names that Claude Code rejects.Changes
name.String() != ""(not justExists)convertOpenAINonStreamingToAnthropicConvertOpenAIResponseToClaudeNonStream(both content-array and message-leveltool_callspaths)tool_usewith empty nameFiles
internal/translator/openai/claude/openai_claude_request.go(+5, -1)internal/translator/openai/claude/openai_claude_response.go(+16, -4)Test Plan
go test ./test/...passes locally