From f4007f53bac2f6aee24fa5c71dcb45dcf33bc469 Mon Sep 17 00:00:00 2001 From: Thong Van Date: Tue, 16 Dec 2025 13:01:09 +0700 Subject: [PATCH] fix(translator): emit message_start on first chunk regardless of role field Some OpenAI-compatible providers (like GitHub Copilot) may send tool_calls in the first streaming chunk without including the role field. The previous implementation only emitted message_start when the first chunk contained role="assistant", causing Anthropic protocol violations when tool calls arrived first. This fix ensures message_start is always emitted on the very first chunk, preventing 'content_block_start before message_start' errors in clients that strictly validate Anthropic SSE event ordering. --- internal/translator/openai/claude/openai_claude_response.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/translator/openai/claude/openai_claude_response.go b/internal/translator/openai/claude/openai_claude_response.go index dac4c970..af790dca 100644 --- a/internal/translator/openai/claude/openai_claude_response.go +++ b/internal/translator/openai/claude/openai_claude_response.go @@ -128,9 +128,10 @@ func convertOpenAIStreamingChunkToAnthropic(rawJSON []byte, param *ConvertOpenAI param.CreatedAt = root.Get("created").Int() } - // Check if this is the first chunk (has role) + // Emit message_start on the very first chunk, regardless of whether it has a role field. + // Some providers (like Copilot) may send tool_calls in the first chunk without a role field. if delta := root.Get("choices.0.delta"); delta.Exists() { - if role := delta.Get("role"); role.Exists() && role.String() == "assistant" && !param.MessageStarted { + if !param.MessageStarted { // Send message_start event messageStart := map[string]interface{}{ "type": "message_start",