diff --git a/internal/translator/codex/claude/codex_claude_response.go b/internal/translator/codex/claude/codex_claude_response.go index 6f18e24d..cdcf2e4f 100644 --- a/internal/translator/codex/claude/codex_claude_response.go +++ b/internal/translator/codex/claude/codex_claude_response.go @@ -22,8 +22,9 @@ var ( // ConvertCodexResponseToClaudeParams holds parameters for response conversion. type ConvertCodexResponseToClaudeParams struct { - HasToolCall bool - BlockIndex int + HasToolCall bool + BlockIndex int + HasReceivedArgumentsDelta bool } // ConvertCodexResponseToClaude performs sophisticated streaming response format conversion. @@ -137,6 +138,7 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa itemType := itemResult.Get("type").String() if itemType == "function_call" { (*param).(*ConvertCodexResponseToClaudeParams).HasToolCall = true + (*param).(*ConvertCodexResponseToClaudeParams).HasReceivedArgumentsDelta = false template = `{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"","name":"","input":{}}}` template, _ = sjson.Set(template, "index", (*param).(*ConvertCodexResponseToClaudeParams).BlockIndex) template, _ = sjson.Set(template, "content_block.id", itemResult.Get("call_id").String()) @@ -171,6 +173,7 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa output += fmt.Sprintf("data: %s\n\n", template) } } else if typeStr == "response.function_call_arguments.delta" { + (*param).(*ConvertCodexResponseToClaudeParams).HasReceivedArgumentsDelta = true template = `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}` template, _ = sjson.Set(template, "index", (*param).(*ConvertCodexResponseToClaudeParams).BlockIndex) template, _ = sjson.Set(template, "delta.partial_json", rootResult.Get("delta").String()) @@ -182,13 +185,16 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa // in a single "done" event without preceding "delta" events. // Emit the full arguments as a single input_json_delta so the // downstream Claude client receives the complete tool input. - if args := rootResult.Get("arguments").String(); args != "" { - template = `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}` - template, _ = sjson.Set(template, "index", (*param).(*ConvertCodexResponseToClaudeParams).BlockIndex) - template, _ = sjson.Set(template, "delta.partial_json", args) + // When delta events were already received, skip to avoid duplicating arguments. + if !(*param).(*ConvertCodexResponseToClaudeParams).HasReceivedArgumentsDelta { + if args := rootResult.Get("arguments").String(); args != "" { + template = `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}` + template, _ = sjson.Set(template, "index", (*param).(*ConvertCodexResponseToClaudeParams).BlockIndex) + template, _ = sjson.Set(template, "delta.partial_json", args) - output += "event: content_block_delta\n" - output += fmt.Sprintf("data: %s\n\n", template) + output += "event: content_block_delta\n" + output += fmt.Sprintf("data: %s\n\n", template) + } } }