From cecb89880e6a8200641de299aa79ab694fb778cd Mon Sep 17 00:00:00 2001 From: Steve Dudenhoeffer Date: Sun, 12 Jul 2026 09:25:55 -0400 Subject: [PATCH] feat(server): dispatch /v1/videos and /v1/videos/sync by model Registers the OpenAI/vLLM-Omni video generation endpoints as model-dispatched routes so llama-swap can swap in video upstreams (e.g. vllm serve --omni, or a ComfyUI shim exposing the same shape). Extraction is content-type driven, so both multipart form and JSON bodies resolve the model field. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj --- internal/server/server.go | 5 ++++ internal/server/server_test.go | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/internal/server/server.go b/internal/server/server.go index 0d27d89d..6bb5dded 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -80,6 +80,11 @@ var modelPostJSONRoutes = []string{ var modelPostFormRoutes = []string{ "/v1/audio/transcriptions", "/v1/images/edits", + + // video generation (vLLM-Omni / OpenAI videos shape); extraction is + // content-type driven, so JSON bodies dispatch on these paths too + "/v1/videos", + "/v1/videos/sync", } // modelGetRoutes are model-dispatched GET endpoints (the model arrives as a diff --git a/internal/server/server_test.go b/internal/server/server_test.go index f16a915f..c879c553 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -1,9 +1,11 @@ package server import ( + "bytes" "context" "encoding/json" "io" + "mime/multipart" "net/http" "net/http/httptest" "strings" @@ -339,3 +341,44 @@ func TestServer_LogStream_UnknownID_Returns400(t *testing.T) { t.Errorf("status=%d want 400", w.Code) } } + +func TestServer_VideoRoutesDispatch(t *testing.T) { + s := newTestServer( + newStubRouter([]string{"videogen-model"}, "video response"), + newStubRouter(nil, ""), + ) + + // JSON body on /v1/videos. + body := strings.NewReader(`{"model":"videogen-model","prompt":"a cat"}`) + req := httptest.NewRequest(http.MethodPost, "/v1/videos", body) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + s.ServeHTTP(w, req) + if w.Code != http.StatusOK || w.Body.String() != "video response" { + t.Fatalf("/v1/videos: status=%d body=%q", w.Code, w.Body.String()) + } + + // Multipart form on /v1/videos/sync. + var buf bytes.Buffer + mw := multipart.NewWriter(&buf) + mw.WriteField("model", "videogen-model") + mw.WriteField("prompt", "a cat") + mw.Close() + req = httptest.NewRequest(http.MethodPost, "/v1/videos/sync", &buf) + req.Header.Set("Content-Type", mw.FormDataContentType()) + w = httptest.NewRecorder() + s.ServeHTTP(w, req) + if w.Code != http.StatusOK || w.Body.String() != "video response" { + t.Fatalf("/v1/videos/sync: status=%d body=%q", w.Code, w.Body.String()) + } + + // Unknown model on a video route still 404s. + body = strings.NewReader(`{"model":"nope","prompt":"a cat"}`) + req = httptest.NewRequest(http.MethodPost, "/v1/videos", body) + req.Header.Set("Content-Type", "application/json") + w = httptest.NewRecorder() + s.ServeHTTP(w, req) + if w.Code != http.StatusNotFound { + t.Fatalf("/v1/videos unknown model: status=%d want 404", w.Code) + } +}