fix: retain codex thinking signature until item done

This commit is contained in:
Ravi Tharuma
2026-03-28 14:41:25 +01:00
parent 66eb12294a
commit 5fc2bd393e
2 changed files with 84 additions and 3 deletions

View File

@@ -179,8 +179,11 @@ func ConvertCodexResponseToClaude(_ context.Context, _ string, originalRequestRa
output = translatorcommon.AppendSSEEventBytes(output, "content_block_stop", template, 2)
} else if itemType == "reasoning" {
params.ThinkingSignature = itemResult.Get("encrypted_content").String()
if signature := itemResult.Get("encrypted_content").String(); signature != "" {
params.ThinkingSignature = signature
}
output = append(output, finalizeCodexThinkingBlock(params)...)
params.ThinkingSignature = ""
}
} else if typeStr == "response.function_call_arguments.delta" {
params.HasReceivedArgumentsDelta = true
@@ -389,7 +392,6 @@ func ClaudeTokenCount(_ context.Context, count int64) []byte {
func finalizeCodexThinkingBlock(params *ConvertCodexResponseToClaudeParams) []byte {
if !params.ThinkingBlockOpen {
params.ThinkingSignature = ""
return nil
}
@@ -408,7 +410,6 @@ func finalizeCodexThinkingBlock(params *ConvertCodexResponseToClaudeParams) []by
params.BlockIndex++
params.ThinkingBlockOpen = false
params.ThinkingStopPending = false
params.ThinkingSignature = ""
return output
}

View File

@@ -163,6 +163,86 @@ func TestConvertCodexResponseToClaude_StreamThinkingFinalizesPendingBlockBeforeN
}
}
func TestConvertCodexResponseToClaude_StreamThinkingRetainsSignatureAcrossMultipartReasoning(t *testing.T) {
ctx := context.Background()
originalRequest := []byte(`{"messages":[]}`)
var param any
chunks := [][]byte{
[]byte("data: {\"type\":\"response.output_item.added\",\"item\":{\"type\":\"reasoning\",\"encrypted_content\":\"enc_sig_multipart\"}}"),
[]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\"}"),
[]byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"Second part\"}"),
[]byte("data: {\"type\":\"response.reasoning_summary_part.done\"}"),
[]byte("data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"reasoning\"}}"),
}
var outputs [][]byte
for _, chunk := range chunks {
outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, &param)...)
}
signatureDeltaCount := 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_delta" && data.Get("delta.type").String() == "signature_delta" {
signatureDeltaCount++
if got := data.Get("delta.signature").String(); got != "enc_sig_multipart" {
t.Fatalf("unexpected signature delta: %q", got)
}
}
}
}
if signatureDeltaCount != 2 {
t.Fatalf("expected signature_delta for both multipart thinking blocks, got %d", signatureDeltaCount)
}
}
func TestConvertCodexResponseToClaude_StreamThinkingUsesEarlyCapturedSignatureWhenDoneOmitsIt(t *testing.T) {
ctx := context.Background()
originalRequest := []byte(`{"messages":[]}`)
var param any
chunks := [][]byte{
[]byte("data: {\"type\":\"response.output_item.added\",\"item\":{\"type\":\"reasoning\",\"encrypted_content\":\"enc_sig_early\"}}"),
[]byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"),
[]byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"Let me think\"}"),
[]byte("data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"reasoning\"}}"),
}
var outputs [][]byte
for _, chunk := range chunks {
outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, &param)...)
}
signatureDeltaCount := 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_delta" && data.Get("delta.type").String() == "signature_delta" {
signatureDeltaCount++
if got := data.Get("delta.signature").String(); got != "enc_sig_early" {
t.Fatalf("unexpected signature delta: %q", got)
}
}
}
}
if signatureDeltaCount != 1 {
t.Fatalf("expected signature_delta from early-captured signature, got %d", signatureDeltaCount)
}
}
func TestConvertCodexResponseToClaudeNonStream_ThinkingIncludesSignature(t *testing.T) {
ctx := context.Background()
originalRequest := []byte(`{"messages":[]}`)