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 <model> --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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
This commit is contained in:
@@ -80,6 +80,11 @@ var modelPostJSONRoutes = []string{
|
|||||||
var modelPostFormRoutes = []string{
|
var modelPostFormRoutes = []string{
|
||||||
"/v1/audio/transcriptions",
|
"/v1/audio/transcriptions",
|
||||||
"/v1/images/edits",
|
"/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
|
// modelGetRoutes are model-dispatched GET endpoints (the model arrives as a
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -339,3 +341,44 @@ func TestServer_LogStream_UnknownID_Returns400(t *testing.T) {
|
|||||||
t.Errorf("status=%d want 400", w.Code)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user