Skip to content

Commit 5fc2bd3

Browse files
committed
fix: retain codex thinking signature until item done
1 parent 66eb122 commit 5fc2bd3

2 files changed

Lines changed: 84 additions & 3 deletions

File tree

internal/translator/codex/claude/codex_claude_response.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,11 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa
179179

180180
output = translatorcommon.AppendSSEEventBytes(output, "content_block_stop", template, 2)
181181
} else if itemType == "reasoning" {
182-
params.ThinkingSignature = itemResult.Get("encrypted_content").String()
182+
if signature := itemResult.Get("encrypted_content").String(); signature != "" {
183+
params.ThinkingSignature = signature
184+
}
183185
output = append(output, finalizeCodexThinkingBlock(params)...)
186+
params.ThinkingSignature = ""
184187
}
185188
} else if typeStr == "response.function_call_arguments.delta" {
186189
params.HasReceivedArgumentsDelta = true
@@ -389,7 +392,6 @@ func ClaudeTokenCount(_ context.Context, count int64) []byte {
389392

390393
func finalizeCodexThinkingBlock(params *ConvertCodexResponseToClaudeParams) []byte {
391394
if !params.ThinkingBlockOpen {
392-
params.ThinkingSignature = ""
393395
return nil
394396
}
395397

@@ -408,7 +410,6 @@ func finalizeCodexThinkingBlock(params *ConvertCodexResponseToClaudeParams) []by
408410
params.BlockIndex++
409411
params.ThinkingBlockOpen = false
410412
params.ThinkingStopPending = false
411-
params.ThinkingSignature = ""
412413

413414
return output
414415
}

internal/translator/codex/claude/codex_claude_response_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,86 @@ func TestConvertCodexResponseToClaude_StreamThinkingFinalizesPendingBlockBeforeN
163163
}
164164
}
165165

166+
func TestConvertCodexResponseToClaude_StreamThinkingRetainsSignatureAcrossMultipartReasoning(t *testing.T) {
167+
ctx := context.Background()
168+
originalRequest := []byte(`{"messages":[]}`)
169+
var param any
170+
171+
chunks := [][]byte{
172+
[]byte("data: {\"type\":\"response.output_item.added\",\"item\":{\"type\":\"reasoning\",\"encrypted_content\":\"enc_sig_multipart\"}}"),
173+
[]byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"),
174+
[]byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"First part\"}"),
175+
[]byte("data: {\"type\":\"response.reasoning_summary_part.done\"}"),
176+
[]byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"),
177+
[]byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"Second part\"}"),
178+
[]byte("data: {\"type\":\"response.reasoning_summary_part.done\"}"),
179+
[]byte("data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"reasoning\"}}"),
180+
}
181+
182+
var outputs [][]byte
183+
for _, chunk := range chunks {
184+
outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, &param)...)
185+
}
186+
187+
signatureDeltaCount := 0
188+
for _, out := range outputs {
189+
for _, line := range strings.Split(string(out), "\n") {
190+
if !strings.HasPrefix(line, "data: ") {
191+
continue
192+
}
193+
data := gjson.Parse(strings.TrimPrefix(line, "data: "))
194+
if data.Get("type").String() == "content_block_delta" && data.Get("delta.type").String() == "signature_delta" {
195+
signatureDeltaCount++
196+
if got := data.Get("delta.signature").String(); got != "enc_sig_multipart" {
197+
t.Fatalf("unexpected signature delta: %q", got)
198+
}
199+
}
200+
}
201+
}
202+
203+
if signatureDeltaCount != 2 {
204+
t.Fatalf("expected signature_delta for both multipart thinking blocks, got %d", signatureDeltaCount)
205+
}
206+
}
207+
208+
func TestConvertCodexResponseToClaude_StreamThinkingUsesEarlyCapturedSignatureWhenDoneOmitsIt(t *testing.T) {
209+
ctx := context.Background()
210+
originalRequest := []byte(`{"messages":[]}`)
211+
var param any
212+
213+
chunks := [][]byte{
214+
[]byte("data: {\"type\":\"response.output_item.added\",\"item\":{\"type\":\"reasoning\",\"encrypted_content\":\"enc_sig_early\"}}"),
215+
[]byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"),
216+
[]byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"Let me think\"}"),
217+
[]byte("data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"reasoning\"}}"),
218+
}
219+
220+
var outputs [][]byte
221+
for _, chunk := range chunks {
222+
outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, &param)...)
223+
}
224+
225+
signatureDeltaCount := 0
226+
for _, out := range outputs {
227+
for _, line := range strings.Split(string(out), "\n") {
228+
if !strings.HasPrefix(line, "data: ") {
229+
continue
230+
}
231+
data := gjson.Parse(strings.TrimPrefix(line, "data: "))
232+
if data.Get("type").String() == "content_block_delta" && data.Get("delta.type").String() == "signature_delta" {
233+
signatureDeltaCount++
234+
if got := data.Get("delta.signature").String(); got != "enc_sig_early" {
235+
t.Fatalf("unexpected signature delta: %q", got)
236+
}
237+
}
238+
}
239+
}
240+
241+
if signatureDeltaCount != 1 {
242+
t.Fatalf("expected signature_delta from early-captured signature, got %d", signatureDeltaCount)
243+
}
244+
}
245+
166246
func TestConvertCodexResponseToClaudeNonStream_ThinkingIncludesSignature(t *testing.T) {
167247
ctx := context.Background()
168248
originalRequest := []byte(`{"messages":[]}`)

0 commit comments

Comments
 (0)