package clients import ( "context" "encoding/json" "net/http" "net/http/httptest" "reflect" "testing" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) func TestOpenAIChatNativeDeepPassthroughPreservesOfficialAndFutureFields(t *testing.T) { requestBody := completeChatPassthroughBody() var captured map[string]any var idempotencyKey string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { idempotencyKey = r.Header.Get("Idempotency-Key") if err := json.NewDecoder(r.Body).Decode(&captured); err != nil { t.Fatal(err) } _ = json.NewEncoder(w).Encode(map[string]any{ "id": "chatcmpl-native", "object": "chat.completion", "model": "provider-chat", "choices": []any{map[string]any{"message": map[string]any{"role": "assistant", "content": "ok"}}}, }) })) defer server.Close() _, err := (OpenAIClient{HTTPClient: server.Client()}).Run(context.Background(), Request{ Kind: "chat.completions", Model: "public-chat", Body: requestBody, OriginalBody: requestBody, UpstreamIdempotencyKey: "gateway-task-id", Candidate: store.RuntimeModelCandidate{ Provider: "openai", BaseURL: server.URL, ProviderModelName: "provider-chat", Credentials: map[string]any{"apiKey": "test-key"}, }, }) if err != nil { t.Fatal(err) } want := jsonRoundTripMap(t, requestBody) delete(want, "request_id") want["model"] = "provider-chat" if !reflect.DeepEqual(captured, want) { t.Fatalf("native Chat request changed\n got: %#v\nwant: %#v", captured, want) } if idempotencyKey != "gateway-task-id" { t.Fatalf("missing Gateway task idempotency key: %q", idempotencyKey) } } func TestOpenAIResponsesNativeDeepPassthroughPreservesOfficialAndFutureFields(t *testing.T) { requestBody := completeResponsesPassthroughBody() var captured map[string]any var idempotencyKey string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { idempotencyKey = r.Header.Get("Idempotency-Key") if err := json.NewDecoder(r.Body).Decode(&captured); err != nil { t.Fatal(err) } _ = json.NewEncoder(w).Encode(map[string]any{ "id": "resp-native", "object": "response", "status": "completed", "output": []any{}, }) })) defer server.Close() _, err := (OpenAIClient{HTTPClient: server.Client()}).Run(context.Background(), Request{ Kind: "responses", Model: "public-responses", Body: requestBody, OriginalBody: requestBody, UpstreamProtocol: ProtocolOpenAIResponses, UpstreamPreviousResponseID: "resp_upstream_parent", UpstreamIdempotencyKey: "gateway-task-id", Candidate: store.RuntimeModelCandidate{ Provider: "openai", BaseURL: server.URL, ProviderModelName: "provider-responses", Credentials: map[string]any{"apiKey": "test-key"}, }, }) if err != nil { t.Fatal(err) } want := jsonRoundTripMap(t, requestBody) delete(want, "request_id") want["model"] = "provider-responses" want["previous_response_id"] = "resp_upstream_parent" if !reflect.DeepEqual(captured, want) { t.Fatalf("native Responses request changed\n got: %#v\nwant: %#v", captured, want) } if idempotencyKey != "gateway-task-id" { t.Fatalf("missing Gateway task idempotency key: %q", idempotencyKey) } } func completeChatPassthroughBody() map[string]any { return map[string]any{ "model": "caller-chat", "messages": []any{ map[string]any{"role": "assistant", "content": nil, "function_call": map[string]any{"name": "legacy", "arguments": "{\"x\":1}"}}, map[string]any{"role": "assistant", "content": nil, "tool_calls": []any{ map[string]any{"id": "call_function", "type": "function", "function": map[string]any{"name": "lookup", "arguments": "{\"q\":1}"}}, map[string]any{"id": "call_custom", "type": "custom", "custom": map[string]any{"name": "shell", "input": "pwd"}}, }}, }, "audio": map[string]any{"format": "wav", "voice": "alloy"}, "frequency_penalty": 0.1, "function_call": map[string]any{"name": "legacy"}, "functions": []any{map[string]any{"name": "legacy", "parameters": map[string]any{"type": "object"}}}, "logit_bias": map[string]any{"1": 2}, "logprobs": true, "max_completion_tokens": 101, "max_tokens": 102, "metadata": map[string]any{"trace": "1"}, "modalities": []any{"text"}, "moderation": map[string]any{"type": "auto"}, "n": 1, "parallel_tool_calls": true, "prediction": map[string]any{"type": "content", "content": "answer"}, "presence_penalty": 0.2, "prompt_cache_key": "cache", "prompt_cache_options": map[string]any{"type": "ephemeral"}, "prompt_cache_retention": "in_memory", "reasoning_effort": "low", "response_format": map[string]any{"type": "json_object"}, "safety_identifier": "safe", "seed": 7, "service_tier": "default", "stop": []any{"END"}, "store": false, "stream": false, "stream_options": map[string]any{"include_usage": true}, "temperature": 0.7, "tool_choice": map[string]any{"type": "custom", "custom": map[string]any{"name": "shell"}}, "tools": []any{ map[string]any{"type": "function", "function": map[string]any{"name": "lookup", "parameters": map[string]any{"type": "object"}}}, map[string]any{"type": "custom", "custom": map[string]any{"name": "shell", "format": map[string]any{"type": "text"}}}, }, "top_logprobs": 2, "top_p": 0.9, "user": "user-1", "verbosity": "low", "web_search_options": map[string]any{"search_context_size": "low"}, "future_official_field": map[string]any{"nested": []any{map[string]any{"keep": true}}}, "request_id": "gateway-only", } } func completeResponsesPassthroughBody() map[string]any { return map[string]any{ "background": false, "context_management": map[string]any{"type": "compaction", "compact_threshold": 2000}, "conversation": "conv_1", "include": []any{"message.output_text.logprobs"}, "input": []any{map[string]any{"type": "message", "role": "user", "content": []any{ map[string]any{"type": "input_text", "text": "hello", "prompt_cache_breakpoint": map[string]any{"type": "ephemeral"}}, }}}, "instructions": "be concise", "max_output_tokens": 200, "max_tool_calls": 3, "metadata": map[string]any{"trace": "1"}, "model": "caller-responses", "moderation": map[string]any{"type": "auto"}, "parallel_tool_calls": true, "previous_response_id": "resp_caller_parent", "prompt": map[string]any{"id": "pmpt_1", "variables": map[string]any{"x": "y"}}, "prompt_cache_key": "cache", "prompt_cache_options": map[string]any{"type": "ephemeral"}, "prompt_cache_retention": "24h", "reasoning": map[string]any{"effort": "low", "summary": "auto"}, "safety_identifier": "safe", "service_tier": "default", "store": true, "stream": false, "stream_options": map[string]any{"include_obfuscation": true}, "temperature": 0.7, "text": map[string]any{"format": map[string]any{"type": "json_schema", "name": "answer", "schema": map[string]any{"type": "object"}}, "verbosity": "low"}, "tool_choice": map[string]any{"type": "custom", "name": "shell"}, "tools": []any{map[string]any{"type": "custom", "name": "shell", "description": "run", "format": map[string]any{"type": "text"}}}, "top_logprobs": 2, "top_p": 0.9, "truncation": "auto", "user": "user-1", "future_official_field": map[string]any{"nested": []any{map[string]any{"keep": true}}}, "request_id": "gateway-only", } } func jsonRoundTripMap(t *testing.T, value map[string]any) map[string]any { t.Helper() raw, err := json.Marshal(value) if err != nil { t.Fatal(err) } var out map[string]any if err := json.Unmarshal(raw, &out); err != nil { t.Fatal(err) } return out }