package openai import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" "time" "go.uber.org/zap" edgeservice "iop/apps/edge/internal/service" "iop/packages/go/config" iop "iop/proto/gen/iop" ) // chatSidebandServer returns a direct OpenAI-compatible provider route backed by // the given tunnel frames. It routes via the legacy openai_compat model route // (not provider-pool) so the raw provider tunnel passthrough path is exercised. func chatSidebandServer(frames chan *iop.ProviderTunnelFrame) (*Server, *providerFakeRunService) { fake := &providerFakeRunService{ tunnelFrames: frames, } srv := chatProviderRouteServer(fake, nil) return srv, fake } func chatProviderRouteServer(fake *providerFakeRunService, logger *zap.Logger) *Server { return NewServer(config.EdgeOpenAIConf{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "pool-model", Adapter: "openai_compat", Target: "served-model"}, }, }, fake, logger) } func TestChatCompletionsPassthroughDoesNotExposeSideband(t *testing.T) { providerBody := "data: {\"choices\":[{\"delta\":{\"content\":\"pure\"}}]}\n\ndata: [DONE]\n\n" frames := make(chan *iop.ProviderTunnelFrame, 4) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200, Headers: map[string]string{"Content-Type": "text/event-stream"}, } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(providerBody)} frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_USAGE, Usage: &iop.Usage{InputTokens: 3, OutputTokens: 5}, } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) srv, _ := chatSidebandServer(frames) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "stream":true, "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } // Byte-identity with the provider body proves no IOP extension markers or // response-mode labels are injected into the pure passthrough response. if got := w.Body.String(); got != providerBody { t.Fatalf("pure passthrough body not byte-identical:\n got: %q\nwant: %q", got, providerBody) } } func TestChatCompletionsPassthroughDoesNotLabelTransformed(t *testing.T) { providerBody := `{"ok":true}` frames := make(chan *iop.ProviderTunnelFrame, 3) frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(providerBody)} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) srv, _ := chatSidebandServer(frames) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } // Byte-identity with the provider body proves the pure passthrough response // carries no IOP response-mode label. if got := w.Body.String(); got != providerBody { t.Fatalf("pure passthrough body not byte-identical: %q", got) } } // chatPassthroughServer returns an openai.Server whose fake service relays the // tunnel to the given provider fixture, emulating the Node relay. func chatPassthroughServer(providerURL string) (*Server, *providerFakeRunService) { fake := &providerFakeRunService{ poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel), tunnelProviderURL: providerURL, tunnelServedTarget: "served-model", } srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "pool-model", Providers: map[string]string{"prov-1": "served-model"}}, }) return srv, fake } // TestChatCompletionsPassthroughNonStreamingByteIdentity verifies SDD S04 for // the non-streaming path: when the provider omits a top-level model echo, the // caller receives the provider JSON body byte-identically, along with provider // status and headers. func TestChatCompletionsPassthroughNonStreamingByteIdentity(t *testing.T) { // Provider-original body with fields the normalized path would rewrite or // drop (reasoning_content, provider-specific key order and spacing). providerBody := "{\"id\":\"cmpl-provider-1\",\"object\":\"chat.completion\", \"choices\":[{\"index\":0,\"message\":{\"role\":\"assistant\",\"content\":\"hi\",\"reasoning_content\":\"because\"},\"finish_reason\":\"stop\"}],\"provider_extra\":{\"a\":1}}" var gotProviderReq []byte provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotProviderReq, _ = io.ReadAll(r.Body) w.Header().Set("Content-Type", "application/json") w.Header().Set("X-Provider-Marker", "prov-42") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(providerBody)) })) defer provider.Close() srv, fake := chatPassthroughServer(provider.URL) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if got := w.Body.String(); got != providerBody { t.Fatalf("body not byte-identical to provider:\n got: %q\nwant: %q", got, providerBody) } if got := w.Header().Get("X-Provider-Marker"); got != "prov-42" { t.Errorf("provider header not relayed: %q", got) } if got := w.Header().Get("Content-Type"); got != "application/json" { t.Errorf("content type not relayed: %q", got) } // The provider request body carries the served model (model rewrite only). if !strings.Contains(string(gotProviderReq), `"model":"served-model"`) { t.Errorf("provider request body missing served model: %s", gotProviderReq) } if !strings.Contains(string(gotProviderReq), `"content":"hello"`) { t.Errorf("provider request body lost caller messages: %s", gotProviderReq) } if len(fake.reqsSnapshot()) != 0 { t.Error("normalized SubmitRun must not be called on passthrough") } } func TestChatCompletionsPassthroughNonStreamingRewritesModelEcho(t *testing.T) { providerBody := `{"id":"cmpl-provider-1","object":"chat.completion","created":123,"model":"served-model","choices":[{"index":0,"message":{"role":"assistant","content":"hi","reasoning_content":"because"},"finish_reason":"stop"}],"provider_extra":{"a":1}}` var gotProviderReq []byte provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotProviderReq, _ = io.ReadAll(r.Body) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(providerBody)) })) defer provider.Close() srv, _ := chatPassthroughServer(provider.URL) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } var resp struct { Model string `json:"model"` Choices []struct { Message struct { Content string `json:"content"` ReasoningContent string `json:"reasoning_content"` } `json:"message"` } `json:"choices"` ProviderExtra map[string]int `json:"provider_extra"` } if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode response: %v body=%s", err, w.Body.String()) } if resp.Model != "pool-model" { t.Fatalf("response model: got %q, want pool-model; body=%s", resp.Model, w.Body.String()) } if resp.Choices[0].Message.Content != "hi" || resp.Choices[0].Message.ReasoningContent != "because" { t.Fatalf("provider content/reasoning not preserved: %+v", resp.Choices[0].Message) } if resp.ProviderExtra["a"] != 1 { t.Fatalf("provider_extra not preserved: %+v", resp.ProviderExtra) } if !strings.Contains(string(gotProviderReq), `"model":"served-model"`) { t.Fatalf("provider request body missing served model: %s", gotProviderReq) } } // TestChatCompletionsPassthroughStreamingByteIdentity verifies SDD S04 for the // streaming path: provider SSE bytes without top-level model echoes, including // provider-specific fields like reasoning_content and native tool_calls chunks, // reach the caller byte-identically. func TestChatCompletionsPassthroughStreamingByteIdentity(t *testing.T) { providerBody := "data: {\"choices\":[{\"delta\":{\"role\":\"assistant\"}}]}\n\n" + "data: {\"choices\":[{\"delta\":{\"reasoning_content\":\"thinking...\"}}]}\n\n" + "data: {\"choices\":[{\"delta\":{\"content\":\"not rewritten\"}}]}\n\n" + "data: {\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"name\":\"f\",\"arguments\":\"{}\"}}]}}]}\n\n" + "data: {\"choices\":[{\"delta\":{},\"finish_reason\":\"tool_calls\"}],\"usage\":{\"prompt_tokens\":3}}\n\n" + "data: [DONE]\n\n" provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(providerBody)) })) defer provider.Close() srv, _ := chatPassthroughServer(provider.URL) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "stream":true, "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if got := w.Body.String(); got != providerBody { t.Fatalf("SSE stream not byte-identical to provider:\n got: %q\nwant: %q", got, providerBody) } if got := w.Header().Get("Content-Type"); got != "text/event-stream" { t.Errorf("content type not relayed: %q", got) } } func TestChatCompletionsPassthroughStreamingRewritesModelEcho(t *testing.T) { providerBody := "data: {\"id\":\"chunk-1\",\"object\":\"chat.completion.chunk\",\"created\":123,\"model\":\"served-model\",\"choices\":[{\"delta\":{\"role\":\"assistant\"}}]}\n\n" + "data: {\"id\":\"chunk-1\",\"object\":\"chat.completion.chunk\",\"created\":123,\"model\":\"served-model\",\"choices\":[{\"delta\":{\"reasoning\":\"thinking...\"}}]}\n\n" + "data: {\"id\":\"chunk-1\",\"object\":\"chat.completion.chunk\",\"created\":123,\"model\":\"served-model\",\"choices\":[{\"delta\":{\"content\":\"hi\"}}]}\n\n" + "data: [DONE]\n\n" provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/event-stream") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(providerBody)) })) defer provider.Close() srv, _ := chatPassthroughServer(provider.URL) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "stream":true, "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } body := w.Body.String() if strings.Contains(body, `"model":"served-model"`) { t.Fatalf("response leaked provider-served model: %s", body) } if strings.Count(body, `"model":"pool-model"`) != 3 { t.Fatalf("response model aliases not rewritten in every chunk: %s", body) } if !strings.Contains(body, `"reasoning":"thinking..."`) || !strings.Contains(body, `"content":"hi"`) { t.Fatalf("provider reasoning/content not preserved: %s", body) } if !strings.Contains(body, "data: [DONE]") { t.Fatalf("done marker not preserved: %s", body) } if got := w.Header().Get("Content-Type"); got != "text/event-stream" { t.Errorf("content type not relayed: %q", got) } } // TestChatCompletionsPassthroughProviderErrorStatusRelayed verifies a provider // error status/body is passed to the caller unmodified instead of being // converted into an IOP error envelope. func TestChatCompletionsPassthroughProviderErrorStatusRelayed(t *testing.T) { providerBody := `{"error":{"message":"rate limited by provider","type":"rate_limit"}}` provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte(providerBody)) })) defer provider.Close() srv, _ := chatPassthroughServer(provider.URL) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusTooManyRequests { t.Fatalf("provider status not relayed: got %d", w.Code) } if got := w.Body.String(); got != providerBody { t.Fatalf("provider error body not byte-identical: %q", got) } } // TestChatCompletionsPassthroughLegacyProviderRouteUsesTunnel verifies that a // legacy model_routes entry with an openai_compat adapter also defaults to the // tunnel passthrough path (direct route, no provider pool). func TestChatCompletionsPassthroughLegacyProviderRouteUsesTunnel(t *testing.T) { frames := make(chan *iop.ProviderTunnelFrame, 3) frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(`{"ok":true}`)} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) fake := &providerFakeRunService{ tunnelFrames: frames, } srv := NewServer(config.EdgeOpenAIConf{ ModelRoutes: []config.OpenAIRouteEntry{ {Model: "compat-model", Adapter: "openai_compat", Target: "backend-model"}, }, }, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"compat-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } tunnelReqs := fake.tunnelReqsSnapshot() if len(tunnelReqs) != 1 { t.Fatalf("expected tunnel dispatch for openai_compat route, got %d", len(tunnelReqs)) } if tunnelReqs[0].Adapter != "openai_compat" || tunnelReqs[0].Target != "backend-model" { t.Errorf("tunnel adapter/target: got %q/%q", tunnelReqs[0].Adapter, tunnelReqs[0].Target) } if tunnelReqs[0].ProviderPool { t.Error("legacy route must not set ProviderPool") } bodies := fake.tunnelBodiesSnapshot() if len(bodies) != 1 || !strings.Contains(string(bodies[0]), `"model":"backend-model"`) { t.Errorf("legacy route body must carry route target: %s", bodies) } } func TestChatCompletionsPassthroughNonProviderRouteKeepsNormalizedPath(t *testing.T) { fake := &providerFakeRunService{ fakeRunService: fakeRunService{events: bufferedRunEvents( &iop.RunEvent{Type: "delta", Delta: "ok"}, &iop.RunEvent{Type: "complete"}, )}, } srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama-fixed"}, fake, nil) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"any-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if len(fake.tunnelReqsSnapshot()) != 0 { t.Error("ollama route must not use the tunnel") } if len(fake.reqsSnapshot()) != 1 { t.Fatalf("expected normalized SubmitRun dispatch, got %d", len(fake.reqsSnapshot())) } } // TestChatCompletionsPassthroughTunnelErrorBeforeStartReturns502 verifies an // ERROR frame before response-start becomes a JSON error response. func TestChatCompletionsPassthroughTunnelErrorBeforeStartReturns502(t *testing.T) { frames := make(chan *iop.ProviderTunnelFrame, 1) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_ERROR, Error: "provider connect refused", } close(frames) fake := &providerFakeRunService{ tunnelFrames: frames, } srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "pool-model", Providers: map[string]string{"prov-1": "served-model"}}, }) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusBadGateway { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "provider connect refused") { t.Fatalf("error message not surfaced: %s", w.Body.String()) } } // TestChatCompletionsPassthroughContextCancelSendsCancelRun verifies caller // disconnect propagates to the Node cancel path for tunnel dispatches. func TestChatCompletionsPassthroughContextCancelSendsCancelRun(t *testing.T) { // Frames channel stays open and empty: the tunnel is in-flight when the // caller context is cancelled. fake := &providerFakeRunService{ tunnelFrames: make(chan *iop.ProviderTunnelFrame), } srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "pool-model", Providers: map[string]string{"prov-1": "served-model"}}, }) ctx, cancel := context.WithCancel(context.Background()) cancel() req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)).WithContext(ctx) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) calls := fake.cancelCallsSnapshot() if len(calls) != 1 { t.Fatalf("expected 1 CancelRun call, got %d: %+v", len(calls), calls) } if calls[0].RunID != "run-tunnel" || calls[0].NodeRef != "node-1" { t.Fatalf("unexpected cancel request: %+v", calls[0]) } } // flakyResponseWriter is a tunnel-path ResponseWriter double: it accepts // failAfterWrites successful Write calls, then fails every subsequent Write, // optionally sleeping writeDelay per call to emulate a slow client. type flakyResponseWriter struct { mu sync.Mutex header http.Header status int body bytes.Buffer failAfterWrites int // -1: never fail writeDelay time.Duration writes int failedWrites int } func newFlakyResponseWriter(failAfterWrites int) *flakyResponseWriter { return &flakyResponseWriter{header: make(http.Header), failAfterWrites: failAfterWrites} } func (w *flakyResponseWriter) Header() http.Header { return w.header } func (w *flakyResponseWriter) WriteHeader(code int) { w.mu.Lock() defer w.mu.Unlock() if w.status == 0 { w.status = code } } func (w *flakyResponseWriter) Write(p []byte) (int, error) { if w.writeDelay > 0 { time.Sleep(w.writeDelay) } w.mu.Lock() defer w.mu.Unlock() if w.status == 0 { w.status = http.StatusOK } if w.failAfterWrites >= 0 && w.writes >= w.failAfterWrites { w.failedWrites++ return 0, errors.New("client connection write failed") } w.writes++ w.body.Write(p) return len(p), nil } func (w *flakyResponseWriter) Flush() {} func (w *flakyResponseWriter) snapshot() (status int, body string, failedWrites int) { w.mu.Lock() defer w.mu.Unlock() return w.status, w.body.String(), w.failedWrites } // TestChatCompletionsPassthroughWriteFailureSendsCancelRunOnce verifies S09 // for a caller write failure after response start: exactly one CancelRun is // propagated to the Node, the ordered prefix written before the failure is // preserved, and no tunnel bytes are written after the failed write even // though more frames (including END) are available. func TestChatCompletionsPassthroughWriteFailureSendsCancelRunOnce(t *testing.T) { frames := make(chan *iop.ProviderTunnelFrame, 5) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200, Headers: map[string]string{"Content-Type": "text/event-stream"}, } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: chunk-1\n\n")} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: chunk-2\n\n")} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: chunk-3\n\n")} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) srv, fake := chatSidebandServer(frames) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "stream":true, "messages":[{"role":"user","content":"hello"}] }`)) w := newFlakyResponseWriter(1) // first body write succeeds, second fails srv.handleChatCompletions(w, req) status, body, failedWrites := w.snapshot() if status != http.StatusOK { t.Fatalf("status: got %d body=%s", status, body) } if body != "data: chunk-1\n\n" { t.Fatalf("body after write failure must stay the ordered prefix:\n got: %q\nwant: %q", body, "data: chunk-1\n\n") } if failedWrites == 0 { t.Fatal("fixture did not exercise the write failure path") } calls := fake.cancelCallsSnapshot() if len(calls) != 1 { t.Fatalf("expected exactly 1 CancelRun call on write failure, got %d: %+v", len(calls), calls) } if calls[0].RunID != "run-tunnel" || calls[0].NodeRef != "node-1" { t.Fatalf("unexpected cancel request: %+v", calls[0]) } } // TestChatCompletionsPassthroughWaitTimeoutSendsCancelRun verifies the tunnel // wait timeout before response start returns a 502 and propagates exactly one // CancelRun to the Node. func TestChatCompletionsPassthroughWaitTimeoutSendsCancelRun(t *testing.T) { fake := &providerFakeRunService{ tunnelFrames: make(chan *iop.ProviderTunnelFrame), tunnelWaitTimeout: 50 * time.Millisecond, } srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "pool-model", Providers: map[string]string{"prov-1": "served-model"}}, }) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusBadGateway { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if !strings.Contains(w.Body.String(), "run timed out") { t.Fatalf("timeout error not surfaced: %s", w.Body.String()) } calls := fake.cancelCallsSnapshot() if len(calls) != 1 { t.Fatalf("expected exactly 1 CancelRun call on wait timeout, got %d: %+v", len(calls), calls) } if calls[0].RunID != "run-tunnel" { t.Fatalf("unexpected cancel request: %+v", calls[0]) } } // TestChatCompletionsPassthroughProviderErrorAfterStartStopsBodyWrites // verifies an ERROR frame after response start truncates the stream: bytes // written before the error are preserved in order, later frames are never // written, and no spurious CancelRun is sent for a provider-terminal failure. func TestChatCompletionsPassthroughProviderErrorAfterStartStopsBodyWrites(t *testing.T) { frames := make(chan *iop.ProviderTunnelFrame, 4) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200, Headers: map[string]string{"Content-Type": "text/event-stream"}, } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: before-error\n\n")} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_ERROR, Error: "provider stream failed"} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: after-error-must-not-write\n\n")} close(frames) srv, fake := chatSidebandServer(frames) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "stream":true, "messages":[{"role":"user","content":"hello"}] }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusOK { t.Fatalf("status already committed before the error must stay 200, got %d", w.Code) } if got := w.Body.String(); got != "data: before-error\n\n" { t.Fatalf("provider error must truncate after the ordered prefix:\n got: %q\nwant: %q", got, "data: before-error\n\n") } if calls := fake.cancelCallsSnapshot(); len(calls) != 0 { t.Fatalf("provider-terminal error must not trigger CancelRun, got %+v", calls) } } // TestChatCompletionsPassthroughSlowClientByteIdentity verifies a slow caller // applies backpressure without corrupting opaque relay data: every body frame // reaches the caller exactly once, in order, and a slow-but-alive client never // triggers CancelRun. func TestChatCompletionsPassthroughSlowClientByteIdentity(t *testing.T) { const chunkCount = 40 var want strings.Builder frames := make(chan *iop.ProviderTunnelFrame, chunkCount+2) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200, Headers: map[string]string{"Content-Type": "text/event-stream"}, } for i := 0; i < chunkCount; i++ { chunk := fmt.Sprintf("data: chunk-%02d\n\n", i) want.WriteString(chunk) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Sequence: int64(i + 1), Body: []byte(chunk), } } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) srv, fake := chatSidebandServer(frames) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "stream":true, "messages":[{"role":"user","content":"hello"}] }`)) w := newFlakyResponseWriter(-1) w.writeDelay = 2 * time.Millisecond srv.handleChatCompletions(w, req) status, body, _ := w.snapshot() if status != http.StatusOK { t.Fatalf("status: got %d", status) } if body != want.String() { t.Fatalf("slow client stream not byte-identical/ordered:\n got: %q\nwant: %q", body, want.String()) } if calls := fake.cancelCallsSnapshot(); len(calls) != 0 { t.Fatalf("slow but alive client must not trigger CancelRun, got %+v", calls) } } // responsesProviderTunnelServer builds a server whose catalog routes // "pool-model" through the OpenAI-compatible provider tunnel, wired to the given // provider frames and served target. func responsesProviderTunnelServer(frames chan *iop.ProviderTunnelFrame, servedTarget string) (*Server, *providerFakeRunService) { fake := &providerFakeRunService{ poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel), tunnelFrames: frames, tunnelServedTarget: servedTarget, } srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "pool-model", Providers: map[string]string{"prov-1": "served-model"}}, }) return srv, fake } // TestResponsesProviderTunnelAllowsUnknownFields verifies that Codex/Responses // unknown fields (tools, parallel_tool_calls, store) are accepted on the // provider passthrough route and preserved in the forwarded tunnel body. func TestResponsesProviderTunnelAllowsUnknownFields(t *testing.T) { srv, fake := responsesProviderTunnelServer(staticProviderTunnelFrames(`{"ok":true}`), "served-model") req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"pool-model", "input":"hi", "tools":[{"type":"web_search"}], "parallel_tool_calls":true, "store":false }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("provider passthrough must accept unknown fields: got %d body=%s", w.Code, w.Body.String()) } bodies := fake.tunnelBodiesSnapshot() if len(bodies) != 1 { t.Fatalf("expected 1 tunnel body, got %d", len(bodies)) } var providerReq map[string]any if err := json.Unmarshal(bodies[0], &providerReq); err != nil { t.Fatalf("provider body JSON: %v body=%s", err, bodies[0]) } if _, ok := providerReq["tools"]; !ok { t.Fatalf("tools must be preserved on passthrough: %+v", providerReq) } if providerReq["parallel_tool_calls"] != true { t.Fatalf("parallel_tool_calls must be preserved: %+v", providerReq) } if providerReq["store"] != false { t.Fatalf("store must be preserved: %+v", providerReq) } } // TestResponsesProviderTunnelSubmitsResponsesPath verifies the passthrough // dispatches a provider tunnel POST to /v1/responses and never calls SubmitRun. func TestResponsesProviderTunnelSubmitsResponsesPath(t *testing.T) { srv, fake := responsesProviderTunnelServer(staticProviderTunnelFrames(`{"ok":true}`), "served-model") req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{"model":"pool-model","input":"hi"}`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if len(fake.reqsSnapshot()) != 0 { t.Fatalf("responses passthrough must not call SubmitRun, got %d", len(fake.reqsSnapshot())) } reqs := fake.tunnelReqsSnapshot() if len(reqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(reqs)) } if reqs[0].Path != "/v1/responses" { t.Fatalf("tunnel path: got %q want /v1/responses", reqs[0].Path) } if reqs[0].Method != http.MethodPost { t.Fatalf("tunnel method: got %q want POST", reqs[0].Method) } } // TestResponsesProviderTunnelRewritesOnlyModel verifies the caller model alias // is rewritten to the provider-served model while max_output_tokens, tools, and // arbitrary fields are preserved verbatim. func TestResponsesProviderTunnelRewritesOnlyModel(t *testing.T) { srv, fake := responsesProviderTunnelServer(staticProviderTunnelFrames(`{"ok":true}`), "served-model") req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{ "model":"pool-model", "input":"hi", "max_output_tokens":123, "tools":[{"type":"web_search"}], "custom_field":"keep-me" }`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } bodies := fake.tunnelBodiesSnapshot() if len(bodies) != 1 { t.Fatalf("expected 1 tunnel body, got %d", len(bodies)) } var providerReq map[string]any if err := json.Unmarshal(bodies[0], &providerReq); err != nil { t.Fatalf("provider body JSON: %v body=%s", err, bodies[0]) } if providerReq["model"] != "served-model" { t.Fatalf("model must be rewritten to served target: %+v", providerReq["model"]) } if providerReq["max_output_tokens"].(float64) != 123 { t.Fatalf("max_output_tokens must be preserved: %+v", providerReq) } if _, ok := providerReq["tools"]; !ok { t.Fatalf("tools must be preserved: %+v", providerReq) } if providerReq["custom_field"] != "keep-me" { t.Fatalf("arbitrary fields must be preserved: %+v", providerReq) } } // TestResponsesProviderTunnelStreaming verifies a stream:true Responses request // sets Stream=true on the tunnel dispatch and relays raw SSE bytes verbatim. func TestResponsesProviderTunnelStreaming(t *testing.T) { frames := make(chan *iop.ProviderTunnelFrame, 4) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: 200, Headers: map[string]string{"Content-Type": "text/event-stream"}, } frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: {\"type\":\"response.output_text.delta\",\"delta\":\"hi\"}\n\n"), } frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte("data: [DONE]\n\n"), } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) srv, fake := responsesProviderTunnelServer(frames, "served-model") req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{"model":"pool-model","input":"hi","stream":true}`)) w := httptest.NewRecorder() srv.handleResponses(w, req) if w.Code != http.StatusOK { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } reqs := fake.tunnelReqsSnapshot() if len(reqs) != 1 { t.Fatalf("expected 1 tunnel dispatch, got %d", len(reqs)) } if !reqs[0].Stream { t.Fatalf("tunnel request Stream must be true for a stream:true responses request") } body := w.Body.String() if !strings.Contains(body, "response.output_text.delta") || !strings.Contains(body, "[DONE]") { t.Fatalf("SSE body must be relayed verbatim, got %q", body) } } func TestChatCompletionsProviderPoolTunnelRelaysProviderErrorBody(t *testing.T) { providerError := `{"error":{"message":"unknown field custom_provider_options","type":"invalid_request_error","param":"custom_provider_options"}}` frames := make(chan *iop.ProviderTunnelFrame, 3) frames <- &iop.ProviderTunnelFrame{ Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_RESPONSE_START, StatusCode: http.StatusUnprocessableEntity, Headers: map[string]string{"Content-Type": "application/json"}, } frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_BODY, Body: []byte(providerError)} frames <- &iop.ProviderTunnelFrame{Kind: iop.ProviderTunnelFrameKind_PROVIDER_TUNNEL_FRAME_KIND_END, End: true} close(frames) fake := &providerFakeRunService{ tunnelFrames: frames, tunnelServedTarget: "served-model", } srv := NewServer(config.EdgeOpenAIConf{}, fake, nil) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "pool-model", Providers: map[string]string{"prov-vllm": "served-model"}}, }) req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{ "model":"pool-model", "messages":[{"role":"user","content":"hello"}], "custom_provider_options":{"reject":true} }`)) w := httptest.NewRecorder() srv.handleChatCompletions(w, req) if w.Code != http.StatusUnprocessableEntity { t.Fatalf("status: got %d body=%s", w.Code, w.Body.String()) } if w.Body.String() != providerError { t.Fatalf("provider error body not relayed byte-identically:\n got: %q\nwant: %q", w.Body.String(), providerError) } if got := w.Header().Get("Content-Type"); !strings.Contains(got, "application/json") { t.Fatalf("Content-Type header not relayed: %q", got) } if len(fake.reqsSnapshot()) != 0 { t.Fatalf("provider error must not fall back to normalized SubmitRun, got %d calls", len(fake.reqsSnapshot())) } }