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
6060 rawJSON = bytes.TrimSpace(rawJSON[5:])
6161
6262 // 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}]}`)
6464
6565 rootResult := gjson.ParseBytes(rawJSON)
6666
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
4545 t.Fatalf("expected model %q, got %q", modelName, gotModel)
4646 }
4747 }
48+
49+func 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+
69+func 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+}