mirror of
https://github.com/router-for-me/CLIProxyAPIPlus.git
synced 2026-04-20 22:51:45 +00:00
203 lines
6.4 KiB
Go
203 lines
6.4 KiB
Go
package claude
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestConvertCodexResponseToClaude_StreamThinkingIncludesSignature(t *testing.T) {
|
|
ctx := context.Background()
|
|
originalRequest := []byte(`{"messages":[]}`)
|
|
var param any
|
|
|
|
chunks := [][]byte{
|
|
[]byte("data: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_123\",\"model\":\"gpt-5\"}}"),
|
|
[]byte("data: {\"type\":\"response.reasoning_summary_part.added\"}"),
|
|
[]byte("data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"Let me think\"}"),
|
|
[]byte("data: {\"type\":\"response.reasoning_summary_part.done\"}"),
|
|
[]byte("data: {\"type\":\"response.output_item.done\",\"item\":{\"type\":\"reasoning\",\"encrypted_content\":\"enc_sig_123\"}}"),
|
|
}
|
|
|
|
var outputs [][]byte
|
|
for _, chunk := range chunks {
|
|
outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, ¶m)...)
|
|
}
|
|
|
|
startFound := false
|
|
signatureDeltaFound := false
|
|
stopFound := false
|
|
|
|
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: "))
|
|
switch data.Get("type").String() {
|
|
case "content_block_start":
|
|
if data.Get("content_block.type").String() == "thinking" {
|
|
startFound = true
|
|
if !data.Get("content_block.signature").Exists() {
|
|
t.Fatalf("thinking start block missing signature field: %s", line)
|
|
}
|
|
}
|
|
case "content_block_delta":
|
|
if data.Get("delta.type").String() == "signature_delta" {
|
|
signatureDeltaFound = true
|
|
if got := data.Get("delta.signature").String(); got != "enc_sig_123" {
|
|
t.Fatalf("unexpected signature delta: %q", got)
|
|
}
|
|
}
|
|
case "content_block_stop":
|
|
stopFound = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !startFound {
|
|
t.Fatal("expected thinking content_block_start event")
|
|
}
|
|
if !signatureDeltaFound {
|
|
t.Fatal("expected signature_delta event for thinking block")
|
|
}
|
|
if !stopFound {
|
|
t.Fatal("expected content_block_stop event for thinking block")
|
|
}
|
|
}
|
|
|
|
func TestConvertCodexResponseToClaude_StreamThinkingWithoutReasoningItemStillIncludesSignatureField(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\":\"Let me think\"}"),
|
|
[]byte("data: {\"type\":\"response.reasoning_summary_part.done\"}"),
|
|
[]byte("data: {\"type\":\"response.completed\",\"response\":{\"usage\":{\"input_tokens\":1,\"output_tokens\":1}}}"),
|
|
}
|
|
|
|
var outputs [][]byte
|
|
for _, chunk := range chunks {
|
|
outputs = append(outputs, ConvertCodexResponseToClaude(ctx, "", originalRequest, nil, chunk, ¶m)...)
|
|
}
|
|
|
|
thinkingStartFound := false
|
|
thinkingStopFound := false
|
|
signatureDeltaFound := false
|
|
|
|
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" {
|
|
thinkingStartFound = true
|
|
if !data.Get("content_block.signature").Exists() {
|
|
t.Fatalf("thinking start block missing signature field: %s", line)
|
|
}
|
|
}
|
|
if data.Get("type").String() == "content_block_stop" && data.Get("index").Int() == 0 {
|
|
thinkingStopFound = true
|
|
}
|
|
if data.Get("type").String() == "content_block_delta" && data.Get("delta.type").String() == "signature_delta" {
|
|
signatureDeltaFound = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !thinkingStartFound {
|
|
t.Fatal("expected thinking content_block_start event")
|
|
}
|
|
if !thinkingStopFound {
|
|
t.Fatal("expected thinking content_block_stop event")
|
|
}
|
|
if signatureDeltaFound {
|
|
t.Fatal("did not expect signature_delta without encrypted_content")
|
|
}
|
|
}
|
|
|
|
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":[]}`)
|
|
response := []byte(`{
|
|
"type":"response.completed",
|
|
"response":{
|
|
"id":"resp_123",
|
|
"model":"gpt-5",
|
|
"usage":{"input_tokens":10,"output_tokens":20},
|
|
"output":[
|
|
{
|
|
"type":"reasoning",
|
|
"encrypted_content":"enc_sig_nonstream",
|
|
"summary":[{"type":"summary_text","text":"internal reasoning"}]
|
|
},
|
|
{
|
|
"type":"message",
|
|
"content":[{"type":"output_text","text":"final answer"}]
|
|
}
|
|
]
|
|
}
|
|
}`)
|
|
|
|
out := ConvertCodexResponseToClaudeNonStream(ctx, "", originalRequest, nil, response, nil)
|
|
parsed := gjson.ParseBytes(out)
|
|
|
|
thinking := parsed.Get("content.0")
|
|
if thinking.Get("type").String() != "thinking" {
|
|
t.Fatalf("expected first content block to be thinking, got %s", thinking.Raw)
|
|
}
|
|
if got := thinking.Get("signature").String(); got != "enc_sig_nonstream" {
|
|
t.Fatalf("expected signature to be preserved, got %q", got)
|
|
}
|
|
if got := thinking.Get("thinking").String(); got != "internal reasoning" {
|
|
t.Fatalf("unexpected thinking text: %q", got)
|
|
}
|
|
}
|