package executor import ( "context" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/router-for-me/CLIProxyAPI/v7/internal/config" cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator" "github.com/tidwall/gjson" ) func TestOpenAICompatExecutorCompactPassthrough(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() executor := NewOpenAICompatExecutor("openai-compatibility", &config.Config{}) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL + "/v1", "api_key": "test", }} payload := []byte(`{"model":"gpt-5.1-codex-max","input":[{"role":"user","content":"hi"}]}`) resp, err := executor.Execute(context.Background(), auth, cliproxyexecutor.Request{ Model: "gpt-5.1-codex-max", Payload: payload, }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai-response"), Alt: "responses/compact", Stream: false, }) if err != nil { t.Fatalf("Execute error: %v", err) } if gotPath != "/v1/responses/compact" { t.Fatalf("path = %q, want %q", gotPath, "/v1/responses/compact") } if !gjson.GetBytes(gotBody, "input").Exists() { t.Fatalf("expected input in body") } if gjson.GetBytes(gotBody, "messages").Exists() { t.Fatalf("unexpected messages in body") } 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)) } } func TestOpenAICompatExecutorPayloadOverrideWinsOverThinkingSuffix(t *testing.T) { var gotBody []byte server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) gotBody = body w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"id":"chatcmpl_1","object":"chat.completion","choices":[{"index":0,"message":{"role":"assistant","content":"ok"},"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}`)) })) defer server.Close() executor := NewOpenAICompatExecutor("openai-compatibility", &config.Config{ Payload: config.PayloadConfig{ Override: []config.PayloadRule{ { Models: []config.PayloadModelRule{ {Name: "custom-openai", Protocol: "openai"}, }, Params: map[string]any{ "reasoning_effort": "low", }, }, }, }, }) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL + "/v1", "api_key": "test", }} payload := []byte(`{"model":"custom-openai(high)","messages":[{"role":"user","content":"hi"}]}`) _, err := executor.Execute(context.Background(), auth, cliproxyexecutor.Request{ Model: "custom-openai(high)", Payload: payload, }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai"), Stream: false, }) if err != nil { t.Fatalf("Execute error: %v", err) } if got := gjson.GetBytes(gotBody, "reasoning_effort").String(); got != "low" { t.Fatalf("reasoning_effort = %q, want %q; body=%s", got, "low", string(gotBody)) } } func TestOpenAICompatExecutorStreamRejectsPlainJSONAfterBlankLines(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("\n\n: openrouter processing\n\nevent: error\n")) _, _ = w.Write([]byte(`{"error":{"message":"upstream failed","type":"server_error"}}` + "\n")) })) defer server.Close() executor := NewOpenAICompatExecutor("openai-compatibility", &config.Config{}) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL + "/v1", "api_key": "test", }} result, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{ Model: "openrouter-model", Payload: []byte(`{"model":"openrouter-model","messages":[{"role":"user","content":"hi"}],"stream":true}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai"), Stream: true, }) if err != nil { t.Fatalf("ExecuteStream error: %v", err) } var gotErr error for chunk := range result.Chunks { if chunk.Err != nil { gotErr = chunk.Err break } } if gotErr == nil { t.Fatalf("expected plain JSON stream error") } if status, ok := gotErr.(interface{ StatusCode() int }); !ok || status.StatusCode() != http.StatusBadGateway { t.Fatalf("stream error status = %v, want %d", gotErr, http.StatusBadGateway) } if !strings.Contains(gotErr.Error(), "upstream failed") { t.Fatalf("stream error = %v", gotErr) } } func TestOpenAICompatExecutorStreamSkipsKeepAliveUntilDataLine(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("\n\n: openrouter processing\n\nevent: ping\nid: 1\nretry: 1000\n")) _, _ = w.Write([]byte(`data: {"id":"chatcmpl_1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"hello"},"finish_reason":null}]}` + "\n")) })) defer server.Close() executor := NewOpenAICompatExecutor("openai-compatibility", &config.Config{}) auth := &cliproxyauth.Auth{Attributes: map[string]string{ "base_url": server.URL + "/v1", "api_key": "test", }} result, err := executor.ExecuteStream(context.Background(), auth, cliproxyexecutor.Request{ Model: "openrouter-model", Payload: []byte(`{"model":"openrouter-model","messages":[{"role":"user","content":"hi"}],"stream":true}`), }, cliproxyexecutor.Options{ SourceFormat: sdktranslator.FromString("openai"), Stream: true, }) if err != nil { t.Fatalf("ExecuteStream error: %v", err) } var got strings.Builder for chunk := range result.Chunks { if chunk.Err != nil { t.Fatalf("unexpected stream error: %v", chunk.Err) } got.Write(chunk.Payload) } if gjson.Get(got.String(), "choices.0.delta.content").String() != "hello" { t.Fatalf("stream payload = %s", got.String()) } }