Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
125
backend/test/codex_claude_parallel_function_calls_test.go
Normal file
125
backend/test/codex_claude_parallel_function_calls_test.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestCodexToClaudeParallelFunctionCallsHaveValidLifecycle(t *testing.T) {
|
||||
chunks := [][]byte{
|
||||
[]byte(`data: {"type":"response.created","response":{"id":"resp_parallel","model":"gpt-5"}}`),
|
||||
[]byte(`data: {"type":"response.output_item.added","item":{"type":"function_call","call_id":"call_a","name":"Read"},"output_index":1}`),
|
||||
[]byte(`data: {"type":"response.output_item.added","item":{"type":"function_call","call_id":"call_b","name":"Read"},"output_index":2}`),
|
||||
[]byte(`data: {"type":"response.function_call_arguments.delta","delta":"{\"file_path\":\"a\"}","output_index":1}`),
|
||||
[]byte(`data: {"type":"response.function_call_arguments.done","arguments":"{\"file_path\":\"a\"}","output_index":1}`),
|
||||
[]byte(`data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_a","name":"Read","arguments":"{\"file_path\":\"a\"}"},"output_index":1}`),
|
||||
[]byte(`data: {"type":"response.function_call_arguments.delta","delta":"{\"file_path\":\"b\"}","output_index":2}`),
|
||||
[]byte(`data: {"type":"response.function_call_arguments.done","arguments":"{\"file_path\":\"b\"}","output_index":2}`),
|
||||
[]byte(`data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_b","name":"Read","arguments":"{\"file_path\":\"b\"}"},"output_index":2}`),
|
||||
[]byte(`data: {"type":"response.completed","response":{"usage":{"input_tokens":1,"output_tokens":1},"output":[{"type":"function_call","call_id":"call_a","name":"Read","arguments":"{\"file_path\":\"a\"}"},{"type":"function_call","call_id":"call_b","name":"Read","arguments":"{\"file_path\":\"b\"}"}]}}`),
|
||||
}
|
||||
|
||||
originalRequest := []byte(`{"stream":true,"tools":[{"name":"Read"}]}`)
|
||||
var state any
|
||||
open := make(map[int64]struct{})
|
||||
started := make(map[int64]struct{})
|
||||
toolIDs := make(map[int64]string)
|
||||
arguments := make(map[int64]string)
|
||||
var startIndices []int64
|
||||
var stopIndices []int64
|
||||
messageState := 0
|
||||
|
||||
for _, chunk := range chunks {
|
||||
outputs := sdktranslator.TranslateStream(
|
||||
context.Background(),
|
||||
sdktranslator.FormatCodex,
|
||||
sdktranslator.FormatClaude,
|
||||
"gpt-5",
|
||||
originalRequest,
|
||||
nil,
|
||||
chunk,
|
||||
&state,
|
||||
)
|
||||
for _, output := range outputs {
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
if !strings.HasPrefix(line, "data: ") {
|
||||
continue
|
||||
}
|
||||
event := gjson.Parse(strings.TrimPrefix(line, "data: "))
|
||||
if messageState == 2 {
|
||||
t.Fatalf("event emitted after message_stop: %s", event.Raw)
|
||||
}
|
||||
index := event.Get("index").Int()
|
||||
switch event.Get("type").String() {
|
||||
case "content_block_start":
|
||||
if messageState != 0 {
|
||||
t.Fatalf("content block started after message terminal events: %s", event.Raw)
|
||||
}
|
||||
if len(open) != 0 {
|
||||
t.Fatalf("content block start emitted while another block remains open: %v", open)
|
||||
}
|
||||
if _, exists := started[index]; exists {
|
||||
t.Fatalf("content block index %d was reused", index)
|
||||
}
|
||||
open[index] = struct{}{}
|
||||
started[index] = struct{}{}
|
||||
startIndices = append(startIndices, index)
|
||||
toolIDs[index] = event.Get("content_block.id").String()
|
||||
case "content_block_delta":
|
||||
if _, exists := open[index]; !exists {
|
||||
t.Fatalf("content block delta targets unopened index %d", index)
|
||||
}
|
||||
if event.Get("delta.type").String() == "input_json_delta" {
|
||||
arguments[index] += event.Get("delta.partial_json").String()
|
||||
}
|
||||
case "content_block_stop":
|
||||
if _, exists := open[index]; !exists {
|
||||
t.Fatalf("content block stop targets unopened index %d", index)
|
||||
}
|
||||
delete(open, index)
|
||||
stopIndices = append(stopIndices, index)
|
||||
case "message_delta":
|
||||
if len(open) != 0 {
|
||||
t.Fatalf("message_delta emitted while content blocks remain open: %v", open)
|
||||
}
|
||||
if messageState != 0 {
|
||||
t.Fatalf("duplicate or out-of-order message_delta: %s", event.Raw)
|
||||
}
|
||||
messageState = 1
|
||||
case "message_stop":
|
||||
if len(open) != 0 {
|
||||
t.Fatalf("message_stop emitted while content blocks remain open: %v", open)
|
||||
}
|
||||
if messageState != 1 {
|
||||
t.Fatalf("message_stop emitted before message_delta: %s", event.Raw)
|
||||
}
|
||||
messageState = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(open) != 0 {
|
||||
t.Fatalf("content blocks remain open: %v", open)
|
||||
}
|
||||
if messageState != 2 {
|
||||
t.Fatalf("terminal message event state = %d, want message_delta followed by message_stop", messageState)
|
||||
}
|
||||
if len(startIndices) != 2 || startIndices[0] != 0 || startIndices[1] != 1 {
|
||||
t.Fatalf("start indices = %v, want [0 1]", startIndices)
|
||||
}
|
||||
if len(stopIndices) != 2 || stopIndices[0] != 0 || stopIndices[1] != 1 {
|
||||
t.Fatalf("stop indices = %v, want [0 1]", stopIndices)
|
||||
}
|
||||
if toolIDs[0] != "call_a" || toolIDs[1] != "call_b" {
|
||||
t.Fatalf("tool IDs = %v, want call_a and call_b", toolIDs)
|
||||
}
|
||||
if arguments[0] != `{"file_path":"a"}` || arguments[1] != `{"file_path":"b"}` {
|
||||
t.Fatalf("tool arguments = %v", arguments)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue