History

Fixed: #2274

fix(translator): omit null content fields in Codex OpenAI tool call responses

Luis Pater committed 7 months ago

Commit d475aab1 parent 96f55572 files · +46 −1Browse files

Unified · Split

2 files
  1. internal/translator/codex/openai/chat-completions/codex_openai_response.go +1 −1
  2. internal/translator/codex/openai/chat-completions/codex_openai_response_test.go +45 −0
internal/translator/codex/openai/chat-completions/codex_openai_response.go+1−1
@@ -60,7 +60,7 @@ func ConvertCodexResponseToOpenAI(_ context.Context, modelName string, originalR · 59 unmodified lines
60 rawJSON = bytes.TrimSpace(rawJSON[5:])60 rawJSON = bytes.TrimSpace(rawJSON[5:])
61 61
62 // Initialize the OpenAI SSE template.62 // Initialize the OpenAI SSE template.
63 template := []byte(`{"id":"","object":"chat.completion.chunk","created":12345,"model":"model","choices":[{"index":0,"delta":{"role":null,"content":null,"reasoning_content":null,"tool_calls":null},"finish_reason":null,"native_finish_reason":null}]}`)63 template := []byte(`{"id":"","object":"chat.completion.chunk","created":12345,"model":"model","choices":[{"index":0,"delta":{},"finish_reason":null,"native_finish_reason":null}]}`)
64 64
65 rootResult := gjson.ParseBytes(rawJSON)65 rootResult := gjson.ParseBytes(rawJSON)
66 66
internal/translator/codex/openai/chat-completions/codex_openai_response_test.go+45−0
@@ -45,3 +45,48 @@ func TestConvertCodexResponseToOpenAI_FirstChunkUsesRequestModelName(t *testing. · 44 unmodified lines
45 t.Fatalf("expected model %q, got %q", modelName, gotModel)45 t.Fatalf("expected model %q, got %q", modelName, gotModel)
46 }46 }
47}47}
48
49func TestConvertCodexResponseToOpenAI_ToolCallChunkOmitsNullContentFields(t *testing.T) {
50 ctx := context.Background()
51 var param any
52
53 out := ConvertCodexResponseToOpenAI(ctx, "gpt-5.4", nil, nil, []byte(`data: {"type":"response.output_item.added","item":{"type":"function_call","call_id":"call_123","name":"websearch"}}`), &param)
54 if len(out) != 1 {
55 t.Fatalf("expected 1 chunk, got %d", len(out))
56 }
57
58 if gjson.GetBytes(out[0], "choices.0.delta.content").Exists() {
59 t.Fatalf("expected content to be omitted, got %s", string(out[0]))
60 }
61 if gjson.GetBytes(out[0], "choices.0.delta.reasoning_content").Exists() {
62 t.Fatalf("expected reasoning_content to be omitted, got %s", string(out[0]))
63 }
64 if !gjson.GetBytes(out[0], "choices.0.delta.tool_calls").Exists() {
65 t.Fatalf("expected tool_calls to exist, got %s", string(out[0]))
66 }
67}
68
69func TestConvertCodexResponseToOpenAI_ToolCallArgumentsDeltaOmitsNullContentFields(t *testing.T) {
70 ctx := context.Background()
71 var param any
72
73 out := ConvertCodexResponseToOpenAI(ctx, "gpt-5.4", nil, nil, []byte(`data: {"type":"response.output_item.added","item":{"type":"function_call","call_id":"call_123","name":"websearch"}}`), &param)
74 if len(out) != 1 {
75 t.Fatalf("expected tool call announcement chunk, got %d", len(out))
76 }
77
78 out = ConvertCodexResponseToOpenAI(ctx, "gpt-5.4", nil, nil, []byte(`data: {"type":"response.function_call_arguments.delta","delta":"{\"query\":\"OpenAI\"}"}`), &param)
79 if len(out) != 1 {
80 t.Fatalf("expected 1 chunk, got %d", len(out))
81 }
82
83 if gjson.GetBytes(out[0], "choices.0.delta.content").Exists() {
84 t.Fatalf("expected content to be omitted, got %s", string(out[0]))
85 }
86 if gjson.GetBytes(out[0], "choices.0.delta.reasoning_content").Exists() {
87 t.Fatalf("expected reasoning_content to be omitted, got %s", string(out[0]))
88 }
89 if !gjson.GetBytes(out[0], "choices.0.delta.tool_calls.0.function.arguments").Exists() {
90 t.Fatalf("expected tool call arguments delta to exist, got %s", string(out[0]))
91 }
92}