diff --git a/internal/runtime/executor/codex_executor.go b/internal/runtime/executor/codex_executor.go index ed38570d..7bbf0e68 100644 --- a/internal/runtime/executor/codex_executor.go +++ b/internal/runtime/executor/codex_executor.go @@ -220,7 +220,8 @@ func (e *CodexExecutor) executeCompact(ctx context.Context, auth *cliproxyauth.A body = applyPayloadConfigWithRoot(e.cfg, baseModel, to.String(), "", body, originalTranslated, requestedModel) body, _ = sjson.SetBytes(body, "model", baseModel) body, _ = sjson.DeleteBytes(body, "stream") - 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_compact_test.go b/internal/runtime/executor/codex_executor_compact_test.go index 4fcd7a8e..02c6db29 100644 --- a/internal/runtime/executor/codex_executor_compact_test.go +++ b/internal/runtime/executor/codex_executor_compact_test.go @@ -15,44 +15,65 @@ import ( ) func TestCodexExecutorCompactAddsDefaultInstructions(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", "application/json") - _, _ = w.Write([]byte(`{"id":"resp_1","object":"response.compaction","usage":{"input_tokens":1,"output_tokens":2,"total_tokens":3}}`)) - })) - defer server.Close() + cases := []struct { + name string + payload string + }{ + { + name: "missing instructions", + payload: `{"model":"gpt-5.4","input":"hello"}`, + }, + { + name: "null instructions", + payload: `{"model":"gpt-5.4","instructions":null,"input":"hello"}`, + }, + } - executor := NewCodexExecutor(&config.Config{}) - auth := &cliproxyauth.Auth{Attributes: map[string]string{ - "base_url": server.URL, - "api_key": "test", - }} + for _, tc := range cases { + t.Run(tc.name, func(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", "application/json") + _, _ = w.Write([]byte(`{"id":"resp_1","object":"response.compaction","usage":{"input_tokens":1,"output_tokens":2,"total_tokens":3}}`)) + })) + defer server.Close() - resp, err := executor.Execute(context.Background(), auth, cliproxyexecutor.Request{ - Model: "gpt-5.4", - Payload: []byte(`{"model":"gpt-5.4","input":"hello"}`), - }, cliproxyexecutor.Options{ - SourceFormat: sdktranslator.FromString("openai-response"), - Alt: "responses/compact", - Stream: false, - }) - if err != nil { - t.Fatalf("Execute error: %v", err) - } - if gotPath != "/responses/compact" { - t.Fatalf("path = %q, want %q", gotPath, "/responses/compact") - } - if !gjson.GetBytes(gotBody, "instructions").Exists() { - t.Fatalf("expected instructions in compact request body, got %s", string(gotBody)) - } - if gjson.GetBytes(gotBody, "instructions").String() != "" { - t.Fatalf("instructions = %q, want empty string", gjson.GetBytes(gotBody, "instructions").String()) - } - if string(resp.Payload) != `{"id":"resp_1","object":"response.compaction","usage":{"input_tokens":1,"output_tokens":2,"total_tokens":3}}` { - t.Fatalf("payload = %s", string(resp.Payload)) + executor := NewCodexExecutor(&config.Config{}) + auth := &cliproxyauth.Auth{Attributes: map[string]string{ + "base_url": server.URL, + "api_key": "test", + }} + + resp, err := executor.Execute(context.Background(), auth, cliproxyexecutor.Request{ + Model: "gpt-5.4", + Payload: []byte(tc.payload), + }, cliproxyexecutor.Options{ + SourceFormat: sdktranslator.FromString("openai-response"), + Alt: "responses/compact", + Stream: false, + }) + if err != nil { + t.Fatalf("Execute error: %v", err) + } + if gotPath != "/responses/compact" { + t.Fatalf("path = %q, want %q", gotPath, "/responses/compact") + } + if !gjson.GetBytes(gotBody, "instructions").Exists() { + t.Fatalf("expected instructions in compact request body, got %s", string(gotBody)) + } + 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()) + } + if string(resp.Payload) != `{"id":"resp_1","object":"response.compaction","usage":{"input_tokens":1,"output_tokens":2,"total_tokens":3}}` { + t.Fatalf("payload = %s", string(resp.Payload)) + } + }) } }