From 76b53d6b5b6c7cc48b174d2cfcf611b4f5ccefce Mon Sep 17 00:00:00 2001 From: Ravi Tharuma Date: Tue, 24 Mar 2026 19:34:11 +0100 Subject: [PATCH] fix: finalize pending thinking block before next summary part --- .../codex/claude/codex_claude_response.go | 3 ++ .../claude/codex_claude_response_test.go | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/internal/translator/codex/claude/codex_claude_response.go b/internal/translator/codex/claude/codex_claude_response.go index 0ddd0845..4f027543 100644 --- a/internal/translator/codex/claude/codex_claude_response.go +++ b/internal/translator/codex/claude/codex_claude_response.go @@ -81,6 +81,9 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa output = translatorcommon.AppendSSEEventBytes(output, "message_start", template, 2) } else if typeStr == "response.reasoning_summary_part.added" { + if params.ThinkingBlockOpen && params.ThinkingStopPending { + output = append(output, finalizeCodexThinkingBlock(params)...) + } template = []byte(`{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}}`) template, _ = sjson.SetBytes(template, "index", params.BlockIndex) params.ThinkingBlockOpen = true diff --git a/internal/translator/codex/claude/codex_claude_response_test.go b/internal/translator/codex/claude/codex_claude_response_test.go index d903dcf7..5a25057c 100644 --- a/internal/translator/codex/claude/codex_claude_response_test.go +++ b/internal/translator/codex/claude/codex_claude_response_test.go @@ -121,6 +121,48 @@ func TestConvertCodexResponseToClaude_StreamThinkingWithoutReasoningItemStillInc } } +func TestConvertCodexResponseToClaude_StreamThinkingFinalizesPendingBlockBeforeNextSummaryPart(t *testing.T) { + ctx := context.Background() + originalRequest := []byte(`{"messages":[]}`) + var param any + + chunks := [][]byte{ + []byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"), + []byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"First part\"}"), + []byte("data: {\"type\":\"response.reasoning_summary_part.done\"}"), + []byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"), + } + + var outputs [][]byte + for _, chunk := range chunks { + outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, ¶m)...) + } + + startCount := 0 + stopCount := 0 + for _, out := range outputs { + for _, line := range strings.Split(string(out), "\n") { + if !strings.HasPrefix(line, "data: ") { + continue + } + data := gjson.Parse(strings.TrimPrefix(line, "data: ")) + if data.Get("type").String() == "content_block_start" && data.Get("content_block.type").String() == "thinking" { + startCount++ + } + if data.Get("type").String() == "content_block_stop" { + stopCount++ + } + } + } + + if startCount != 2 { + t.Fatalf("expected 2 thinking block starts, got %d", startCount) + } + if stopCount != 1 { + t.Fatalf("expected pending thinking block to be finalized before second start, got %d stops", stopCount) + } +} + func TestConvertCodexResponseToClaudeNonStream_ThinkingIncludesSignature(t *testing.T) { ctx := context.Background() originalRequest := []byte(`{"messages":[]}`)