From bd855abec9eb7dfee7c47b96e1e7eed578655826 Mon Sep 17 00:00:00 2001 From: MonsterQiu <72pgstan@gmail.com> Date: Tue, 31 Mar 2026 10:06:04 +0800 Subject: [PATCH] fix(codex): normalize null instructions for responses requests --- internal/runtime/executor/codex_executor.go | 3 +- .../codex_executor_instructions_test.go | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 internal/runtime/executor/codex_executor_instructions_test.go diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index fddf343d..bd5ef00b 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -114,7 +114,8 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re body, _ = sjson.DeleteBytes(body, "prompt_cache_retention") body, _ = sjson.DeleteBytes(body, "safety_identifier") body, _ = sjson.DeleteBytes(body, "stream_options") - if !gjson.GetBytes(body, "instructions").Exists() { + instructions := gjson.GetBytes(body, "instructions") + if !instructions.Exists() || instructions.Type == gjson.Null { body, _ = sjson.SetBytes(body, "instructions", "") } diff --git a/internal/runtime/executor/codex_executor_instructions_test.go b/internal/runtime/executor/codex_executor_instructions_test.go new file mode 100644 index 00000000..0ed791c0 --- /dev/null +++ b/internal/runtime/executor/codex_executor_instructions_test.go @@ -0,0 +1,54 @@ +package executor + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" + "github.com/tidwall/gjson" +) + +func TestCodexExecutorExecuteNormalizesNullInstructions(t *testing.T) { + var gotPath string + var gotBody []byte + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + body, _ := io.ReadAll(r.Body) + gotBody = body + w.Header().Set("Content-Type", "text/event-stream") + _, _ = w.Write([]byte("data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"created_at\":0,\"status\":\"completed\",\"background\":false,\"error\":null}}\n\n")) + })) + defer server.Close() + + executor := NewCodexExecutor(&config.Config{}) + auth := &cliproxyauth.Auth{Attributes: map[string]string{ + "base_url": server.URL, + "api_key": "test", + }} + + _, err := executor.Execute(context.Background(), auth, cliproxyexecutor.Request{ + Model: "gpt-5.4", + Payload: []byte(`{"model":"gpt-5.4","instructions":null,"input":"hello"}`), + }, cliproxyexecutor.Options{ + SourceFormat: sdktranslator.FromString("openai-response"), + Stream: false, + }) + if err != nil { + t.Fatalf("Execute error: %v", err) + } + if gotPath != "/responses" { + t.Fatalf("path = %q, want %q", gotPath, "/responses") + } + if gjson.GetBytes(gotBody, "instructions").Type != gjson.String { + t.Fatalf("instructions type = %v, want string", gjson.GetBytes(gotBody, "instructions").Type) + } + if gjson.GetBytes(gotBody, "instructions").String() != "" { + t.Fatalf("instructions = %q, want empty string", gjson.GetBytes(gotBody, "instructions").String()) + } +}