| package protocol_test |
|
|
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "strings" |
| "testing" |
|
|
| "ccLoad/internal/protocol" |
| "ccLoad/internal/protocol/builtin" |
| ) |
|
|
| |
| |
| func TestRegistry_Stream_OpenAIToCodex_ToolCalls(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| |
| `data: {"id":"chatcmpl-1","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_abc","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-1","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-1","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Beijing\"}"}}]}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-1","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}` + "\n\n", |
| |
| "data: [DONE]\n\n", |
| } |
|
|
| var state any |
| var allOutput bytes.Buffer |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.OpenAI, protocol.Codex, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| for _, b := range out { |
| allOutput.Write(b) |
| } |
| } |
|
|
| result := allOutput.String() |
| if !strings.Contains(result, `event: response.output_item.done`) { |
| t.Fatalf("expected response.output_item.done event, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"type":"function_call"`) { |
| t.Fatalf("expected type=function_call, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"name":"get_weather"`) { |
| t.Fatalf("expected name=get_weather, got:\n%s", result) |
| } |
| |
| if !strings.Contains(result, `city`) { |
| t.Fatalf("expected city in arguments, got:\n%s", result) |
| } |
| if !strings.Contains(result, `Beijing`) { |
| t.Fatalf("expected Beijing in arguments, got:\n%s", result) |
| } |
| |
| if !strings.Contains(result, `"call_id":"call_abc"`) { |
| t.Fatalf("expected call_id=call_abc, got:\n%s", result) |
| } |
| |
| if !strings.Contains(result, `event: response.completed`) { |
| t.Fatalf("expected response.completed event, got:\n%s", result) |
| } |
| } |
|
|
| |
| func TestRegistry_Stream_CodexToOpenAI_FunctionCall(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| |
| codexChunk := `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_xyz","name":"search","arguments":"{\"q\":\"hello\"}"}} |
| |
| ` |
|
|
| var state any |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.OpenAI, "gpt-4o", nil, nil, []byte(codexChunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| if len(out) == 0 { |
| t.Fatalf("expected output chunks, got none") |
| } |
|
|
| result := string(bytes.Join(out, nil)) |
| if !strings.Contains(result, `"tool_calls"`) { |
| t.Fatalf("expected tool_calls in output, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"id":"call_xyz"`) { |
| t.Fatalf("expected id=call_xyz, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"name":"search"`) { |
| t.Fatalf("expected name=search, got:\n%s", result) |
| } |
| if !strings.Contains(result, `hello`) { |
| t.Fatalf("expected 'hello' in arguments, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_CodexToOpenAI_FunctionCallStringArgumentsStayRawJSON(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| codexChunk := `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_skill_1","name":"Skill","arguments":"{\"args\":\"skill: \\\"superpowers:using-superpowers\\\"\",\"skill\":\"superpowers:using-superpowers\"}"}} |
| |
| ` |
|
|
| var state any |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.OpenAI, "gpt-4o", nil, nil, []byte(codexChunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| if len(out) != 1 { |
| t.Fatalf("expected one output chunk, got %#v", out) |
| } |
| result := string(out[0]) |
| if !strings.Contains(result, `"arguments":"{\"args\":\"skill: \\\"superpowers:using-superpowers\\\"\",\"skill\":\"superpowers:using-superpowers\"}"`) { |
| t.Fatalf("expected raw JSON arguments string, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_CodexToOpenAI_FunctionCallIndices(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_1","name":"lookup","arguments":"{\"q\":\"one\"}"}} |
| |
| `, |
| `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_2","name":"search","arguments":"{\"q\":\"two\"}"}} |
| |
| `, |
| } |
|
|
| var state any |
| var outputs [][]byte |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.OpenAI, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| outputs = append(outputs, out...) |
| } |
|
|
| if len(outputs) != 2 { |
| t.Fatalf("expected 2 output chunks, got %d", len(outputs)) |
| } |
| if !strings.Contains(string(outputs[0]), `"index":0`) { |
| t.Fatalf("expected first tool call index 0, got:\n%s", outputs[0]) |
| } |
| if !strings.Contains(string(outputs[1]), `"index":1`) { |
| t.Fatalf("expected second tool call index 1, got:\n%s", outputs[1]) |
| } |
| } |
|
|
| func TestRegistry_Stream_CodexToOpenAI_FunctionCallCompletion(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_1","name":"lookup","arguments":"{\"q\":\"one\"}"}} |
| |
| `, |
| `event: response.completed |
| data: {"type":"response.completed","response":{"id":"resp_1","model":"gpt-4o","usage":{"input_tokens":3,"output_tokens":5,"total_tokens":8}}} |
| |
| `, |
| } |
|
|
| var state any |
| var outputs [][]byte |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.OpenAI, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| outputs = append(outputs, out...) |
| } |
|
|
| if len(outputs) != 3 { |
| t.Fatalf("expected tool call chunk + finish chunk + [DONE], got %d", len(outputs)) |
| } |
| if !strings.Contains(string(outputs[1]), `"finish_reason":"tool_calls"`) { |
| t.Fatalf("expected completion finish_reason=tool_calls, got:\n%s", outputs[1]) |
| } |
| if string(outputs[2]) != "data: [DONE]\n\n" { |
| t.Fatalf("expected OpenAI DONE sentinel, got:\n%s", outputs[2]) |
| } |
| } |
|
|
| |
| |
| func TestRegistry_Stream_CodexToAnthropic_FunctionCall(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| codexChunk := `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","id":"toolu_01","call_id":"call_fc1","name":"calculator","arguments":"{\"expr\":\"1+2\"}"}} |
| |
| ` |
|
|
| var state any |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.Anthropic, "claude-3-5-sonnet", nil, nil, []byte(codexChunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| if len(out) == 0 { |
| t.Fatalf("expected output chunks, got none") |
| } |
|
|
| result := string(bytes.Join(out, nil)) |
| if !strings.Contains(result, `event: content_block_start`) { |
| t.Fatalf("expected content_block_start, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"type":"tool_use"`) { |
| t.Fatalf("expected type=tool_use, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"name":"calculator"`) { |
| t.Fatalf("expected name=calculator, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_delta`) { |
| t.Fatalf("expected content_block_delta, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"input_json_delta"`) { |
| t.Fatalf("expected input_json_delta type, got:\n%s", result) |
| } |
| if !strings.Contains(result, `expr`) { |
| t.Fatalf("expected expr in partial_json, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_stop`) { |
| t.Fatalf("expected content_block_stop, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_CodexToAnthropic_FunctionCallUsesCallID(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| codexChunk := `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_fc1","name":"calculator","arguments":"{\"expr\":\"1+2\"}"}} |
| |
| ` |
|
|
| var state any |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.Anthropic, "claude-3-5-sonnet", nil, nil, []byte(codexChunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| if len(out) == 0 { |
| t.Fatalf("expected output chunks, got none") |
| } |
|
|
| result := string(bytes.Join(out, nil)) |
| if !strings.Contains(result, `"id":"call_fc1"`) { |
| t.Fatalf("expected tool_use id to preserve call_id, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_CodexToAnthropic_FunctionCallCompletion(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_fc1","name":"calculator","arguments":"{\"expr\":\"1+2\"}"}} |
| |
| `, |
| `event: response.completed |
| data: {"type":"response.completed","response":{"id":"resp_1","model":"claude-3-5-sonnet","usage":{"input_tokens":3,"output_tokens":5,"total_tokens":8}}} |
| |
| `, |
| } |
|
|
| var state any |
| var outputs [][]byte |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.Anthropic, "claude-3-5-sonnet", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| outputs = append(outputs, out...) |
| } |
|
|
| result := string(bytes.Join(outputs, nil)) |
| if strings.Count(result, `event: content_block_stop`) != 1 { |
| t.Fatalf("expected exactly one content_block_stop for tool-only response, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"stop_reason":"tool_use"`) { |
| t.Fatalf("expected stop_reason=tool_use for tool-only response, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_CodexToAnthropic_TextThenFunctionCall(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| `event: response.output_text.delta |
| data: {"type":"response.output_text.delta","delta":"thinking..."} |
| |
| `, |
| `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"function_call","call_id":"call_fc1","name":"calculator","arguments":"{\"expr\":\"1+2\"}"}} |
| |
| `, |
| `event: response.completed |
| data: {"type":"response.completed","response":{"id":"resp_1","model":"claude-3-5-sonnet","usage":{"input_tokens":3,"output_tokens":5,"total_tokens":8}}} |
| |
| `, |
| } |
|
|
| var state any |
| var outputs [][]byte |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.Anthropic, "claude-3-5-sonnet", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| outputs = append(outputs, out...) |
| } |
|
|
| result := string(bytes.Join(outputs, nil)) |
| if strings.Count(result, `event: content_block_start`) != 2 { |
| t.Fatalf("expected separate text/tool content blocks, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_start`) || !strings.Contains(result, `"index":0`) || !strings.Contains(result, `"type":"text"`) { |
| t.Fatalf("expected text block at index 0, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"index":1`) || !strings.Contains(result, `"type":"tool_use"`) || !strings.Contains(result, `"id":"call_fc1"`) || !strings.Contains(result, `"name":"calculator"`) { |
| t.Fatalf("expected tool block at index 1, got:\n%s", result) |
| } |
| if strings.Count(result, `event: content_block_stop`) != 2 { |
| t.Fatalf("expected exactly two content_block_stop events, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"stop_reason":"tool_use"`) { |
| t.Fatalf("expected stop_reason=tool_use when final block is tool_use, got:\n%s", result) |
| } |
| } |
|
|
| |
| |
| func TestRegistry_Stream_CodexToAnthropic_Reasoning(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| |
| codexChunk := `event: response.output_item.done |
| data: {"type":"response.output_item.done","item":{"type":"reasoning","id":"rs_01","summary":[{"type":"summary_text","text":"step by step reasoning"}]}} |
| |
| ` |
|
|
| var state any |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.Codex, protocol.Anthropic, "claude-3-5-sonnet", nil, nil, []byte(codexChunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| if len(out) == 0 { |
| t.Fatalf("expected output chunks, got none") |
| } |
|
|
| result := string(bytes.Join(out, nil)) |
| if !strings.Contains(result, `event: content_block_start`) { |
| t.Fatalf("expected content_block_start, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"type":"thinking"`) { |
| t.Fatalf("expected type=thinking, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_delta`) { |
| t.Fatalf("expected content_block_delta, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"thinking_delta"`) { |
| t.Fatalf("expected thinking_delta type, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"step by step reasoning"`) { |
| t.Fatalf("expected reasoning text, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_stop`) { |
| t.Fatalf("expected content_block_stop, got:\n%s", result) |
| } |
| } |
|
|
| |
| |
| func TestRegistry_Stream_OpenAIToAnthropic_ToolCalls(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| |
| `data: {"id":"chatcmpl-2","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_t1","type":"function","function":{"name":"translate","arguments":""}}]}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-2","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"text\":\"hello\"}"}}]}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-2","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}` + "\n\n", |
| "data: [DONE]\n\n", |
| } |
|
|
| var state any |
| var allOutput bytes.Buffer |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.OpenAI, protocol.Anthropic, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| for _, b := range out { |
| allOutput.Write(b) |
| } |
| } |
|
|
| result := allOutput.String() |
| if !strings.Contains(result, `event: content_block_start`) { |
| t.Fatalf("expected content_block_start, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"type":"tool_use"`) { |
| t.Fatalf("expected type=tool_use, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"name":"translate"`) { |
| t.Fatalf("expected name=translate, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_delta`) { |
| t.Fatalf("expected content_block_delta, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"input_json_delta"`) { |
| t.Fatalf("expected input_json_delta type, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"partial_json":"{\"text\":\"hello\"}"`) { |
| t.Fatalf("expected tool arguments in partial_json, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_stop`) { |
| t.Fatalf("expected content_block_stop, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"stop_reason":"tool_use"`) { |
| t.Fatalf("expected stop_reason=tool_use, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_OpenAIToAnthropic_TextThenFragmentedToolCalls(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| `data: {"id":"chatcmpl-4","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"content":"让我检查一下"}}]}` + "\n\n", |
| `data: {"id":"chatcmpl-4","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_lookup","type":"function","function":{"name":"Bash","arguments":""}}]}}]}` + "\n\n", |
| `data: {"id":"chatcmpl-4","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"command\":\"ls\""}}]}}]}` + "\n\n", |
| `data: {"id":"chatcmpl-4","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"}"}}]},"finish_reason":"tool_calls"}]}` + "\n\n", |
| "data: [DONE]\n\n", |
| } |
|
|
| var state any |
| var allOutput bytes.Buffer |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.OpenAI, protocol.Anthropic, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| for _, b := range out { |
| allOutput.Write(b) |
| } |
| } |
|
|
| result := allOutput.String() |
| events := parseSSEEvents(t, result) |
| expectedEvents := []string{ |
| "message_start", |
| "content_block_start", |
| "content_block_delta", |
| "content_block_stop", |
| "content_block_start", |
| "content_block_delta", |
| "content_block_stop", |
| "message_delta", |
| "message_stop", |
| } |
| if len(events) != len(expectedEvents) { |
| t.Fatalf("expected %d SSE events, got %d:\n%s", len(expectedEvents), len(events), result) |
| } |
| for i, expected := range expectedEvents { |
| if events[i].Event != expected { |
| t.Fatalf("expected event[%d]=%s, got %s:\n%s", i, expected, events[i].Event, result) |
| } |
| } |
|
|
| textStart := mustMap(t, events[1].Data["content_block"]) |
| if mustInt(t, events[1].Data["index"]) != 0 || mustString(t, textStart["type"]) != "text" { |
| t.Fatalf("expected text block to start at index 0, got:\n%s", result) |
| } |
| textDelta := mustMap(t, events[2].Data["delta"]) |
| if mustInt(t, events[2].Data["index"]) != 0 || mustString(t, textDelta["type"]) != "text_delta" || mustString(t, textDelta["text"]) != "让我检查一下" { |
| t.Fatalf("expected text delta to stay on index 0, got:\n%s", result) |
| } |
| if mustInt(t, events[3].Data["index"]) != 0 { |
| t.Fatalf("expected text block to close on index 0 before tool call, got:\n%s", result) |
| } |
|
|
| toolStart := mustMap(t, events[4].Data["content_block"]) |
| if mustInt(t, events[4].Data["index"]) != 1 || |
| mustString(t, toolStart["type"]) != "tool_use" || |
| mustString(t, toolStart["id"]) != "call_lookup" || |
| mustString(t, toolStart["name"]) != "Bash" { |
| t.Fatalf("expected tool block to open on index 1, got:\n%s", result) |
| } |
| toolDelta := mustMap(t, events[5].Data["delta"]) |
| if mustInt(t, events[5].Data["index"]) != 1 || |
| mustString(t, toolDelta["type"]) != "input_json_delta" || |
| mustString(t, toolDelta["partial_json"]) != `{"command":"ls"}` { |
| t.Fatalf("expected fragmented tool arguments to be reassembled, got:\n%s", result) |
| } |
| if mustInt(t, events[6].Data["index"]) != 1 { |
| t.Fatalf("expected tool block to close on index 1, got:\n%s", result) |
| } |
|
|
| messageDelta := mustMap(t, events[7].Data["delta"]) |
| if mustString(t, messageDelta["stop_reason"]) != "tool_use" { |
| t.Fatalf("expected stop_reason=tool_use, got:\n%s", result) |
| } |
| } |
|
|
| func TestRegistry_Stream_OpenAIToAnthropic_ToolCalls_AllSplitPoints(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| arguments := `{"command":"ls"}` |
| for split := 0; split <= len(arguments); split++ { |
| t.Run(fmt.Sprintf("split-%d", split), func(t *testing.T) { |
| chunks := []string{ |
| `data: {"id":"chatcmpl-2","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_lookup","type":"function","function":{"name":"Bash","arguments":""}}]}}]}` + "\n\n", |
| fmt.Sprintf(`data: {"id":"chatcmpl-2","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":%q}}]}}]}`+"\n\n", arguments[:split]), |
| fmt.Sprintf(`data: {"id":"chatcmpl-2","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":%q}}]},"finish_reason":"tool_calls"}]}`+"\n\n", arguments[split:]), |
| "data: [DONE]\n\n", |
| } |
|
|
| var state any |
| var allOutput bytes.Buffer |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.OpenAI, protocol.Anthropic, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("split %d stream error: %v", split, err) |
| } |
| for _, b := range out { |
| allOutput.Write(b) |
| } |
| } |
|
|
| result := allOutput.String() |
| if !strings.Contains(result, `"id":"call_lookup"`) || !strings.Contains(result, `"name":"Bash"`) { |
| t.Fatalf("split %d expected tool_use metadata, got:\n%s", split, result) |
| } |
| if !strings.Contains(result, `"partial_json":"{\"command\":\"ls\"}"`) || !strings.Contains(result, `"stop_reason":"tool_use"`) { |
| t.Fatalf("split %d expected reassembled anthropic tool payload, got:\n%s", split, result) |
| } |
| }) |
| } |
| } |
|
|
| |
| |
| func TestRegistry_Stream_OpenAIToAnthropic_Reasoning(t *testing.T) { |
| t.Parallel() |
| reg := protocol.NewRegistry() |
| builtin.Register(reg) |
|
|
| chunks := []string{ |
| |
| `data: {"id":"chatcmpl-3","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"reasoning_content":"Let me think"}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-3","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{"reasoning_content":" carefully"}}]}` + "\n\n", |
| |
| `data: {"id":"chatcmpl-3","object":"chat.completion.chunk","model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}` + "\n\n", |
| "data: [DONE]\n\n", |
| } |
|
|
| var state any |
| var allOutput bytes.Buffer |
| for _, chunk := range chunks { |
| out, err := reg.TranslateResponseStream(context.Background(), protocol.OpenAI, protocol.Anthropic, "gpt-4o", nil, nil, []byte(chunk), &state) |
| if err != nil { |
| t.Fatalf("stream error: %v", err) |
| } |
| for _, b := range out { |
| allOutput.Write(b) |
| } |
| } |
|
|
| result := allOutput.String() |
| if !strings.Contains(result, `event: content_block_start`) { |
| t.Fatalf("expected content_block_start, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"type":"thinking"`) { |
| t.Fatalf("expected type=thinking, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"thinking_delta"`) { |
| t.Fatalf("expected thinking_delta type, got:\n%s", result) |
| } |
| if !strings.Contains(result, `"Let me think"`) { |
| t.Fatalf("expected first reasoning chunk, got:\n%s", result) |
| } |
| if !strings.Contains(result, `" carefully"`) { |
| t.Fatalf("expected second reasoning chunk, got:\n%s", result) |
| } |
| if !strings.Contains(result, `event: content_block_stop`) { |
| t.Fatalf("expected content_block_stop, got:\n%s", result) |
| } |
| |
| if !strings.Contains(result, `"stop_reason":"end_turn"`) { |
| t.Fatalf("expected stop_reason=end_turn, got:\n%s", result) |
| } |
| } |
|
|