Route friendly Seedance aliases to configured upstream models
Popiai can emit human-facing model names such as seedance2.0, while the upstream channel table is keyed by the canonical doubao-seedance model ids. Normalize Seedance aliases at the server gateway boundary so CLI, Popiai, and any direct gateway-compatible client can keep using friendly names without sending an unconfigured model id upstream. Constraint: NewAPI channel selection requires canonical doubao-seedance-* model ids. Rejected: Fix only the PopiArt CLI default path | Popiai and other clients can still send explicit friendly aliases. Confidence: high Scope-risk: narrow Directive: Keep alias normalization at the server boundary before expanding client-specific mappings. Tested: go test ./internal/server -run 'Seedance|VideoGenerations' Tested: go test ./...
This commit is contained in:
@@ -2339,7 +2339,7 @@ func (c *newAPIClient) submitSeedanceVideoTask(ctx context.Context, token, model
|
||||
}
|
||||
|
||||
func buildSeedanceVideoGenerationPayload(modelID string, input map[string]any) (map[string]any, error) {
|
||||
modelID = strings.TrimSpace(modelID)
|
||||
modelID = normalizeSeedanceVideoModelID(modelID)
|
||||
if modelID == "" {
|
||||
return nil, errors.New("Seedance model id is required")
|
||||
}
|
||||
@@ -2401,6 +2401,36 @@ func buildSeedanceVideoGenerationPayload(modelID string, input map[string]any) (
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func normalizeSeedanceVideoModelID(modelID string) string {
|
||||
modelID = strings.TrimSpace(modelID)
|
||||
if modelID == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.NewReplacer("_", "-", ".", "-").Replace(modelID))
|
||||
normalized = strings.Join(strings.Fields(normalized), "")
|
||||
|
||||
switch normalized {
|
||||
case "seedance2-0", "seedance-2-0":
|
||||
return "doubao-seedance-2-0-260128"
|
||||
case "seedance2-0-fast", "seedance-2-0-fast":
|
||||
return "doubao-seedance-2-0-fast-260128"
|
||||
case "seedance1-5", "seedance-1-5", "seedance1-5-pro", "seedance-1-5-pro":
|
||||
return "doubao-seedance-1-5-pro-251215"
|
||||
case "seedance1-0-pro", "seedance-1-0-pro":
|
||||
return "doubao-seedance-1-0-pro-250528"
|
||||
case "seedance1-0-lite-t2v", "seedance-1-0-lite-t2v":
|
||||
return "doubao-seedance-1-0-lite-t2v"
|
||||
case "seedance1-0-lite-i2v", "seedance-1-0-lite-i2v":
|
||||
return "doubao-seedance-1-0-lite-i2v"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(normalized, "seedance-") {
|
||||
return "doubao-" + normalized
|
||||
}
|
||||
return modelID
|
||||
}
|
||||
|
||||
func (c *newAPIClient) submitJimengVideoTask(ctx context.Context, token, modelID string, input map[string]any) (string, error) {
|
||||
if !c.enabled() {
|
||||
return "", errors.New("PopiNewAPI base URL is not configured")
|
||||
|
||||
@@ -405,6 +405,33 @@ func TestSubmitSeedanceVideoTaskUsesVideoGenerationsEndpoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubmitSeedanceVideoTaskNormalizesFriendlyModelAlias(t *testing.T) {
|
||||
var gotBody map[string]any
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
|
||||
t.Fatalf("decode request body: %v", err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"id":"task_seedance_alias_123","status":"submitted"}`))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
client := newNewAPIClient(Config{NewAPIBaseURL: srv.URL})
|
||||
taskID, err := client.submitSeedanceVideoTask(context.Background(), "sk-test", "seedance2.0", map[string]any{
|
||||
"prompt": "keep the motion natural",
|
||||
"duration": 5,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("submitSeedanceVideoTask: %v", err)
|
||||
}
|
||||
if taskID != "task_seedance_alias_123" {
|
||||
t.Fatalf("unexpected task id: %q", taskID)
|
||||
}
|
||||
if gotBody["model"] != "doubao-seedance-2-0-260128" {
|
||||
t.Fatalf("expected canonical Seedance model, got %#v", gotBody["model"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVideoGenerationsRelaysSeedanceRequest(t *testing.T) {
|
||||
var gotBody map[string]any
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -466,6 +493,48 @@ func TestHandleVideoGenerationsRelaysSeedanceRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVideoGenerationsNormalizesSeedanceAlias(t *testing.T) {
|
||||
var gotBody map[string]any
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
|
||||
t.Fatalf("decode upstream body: %v", err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"code":"success","data":{"task_id":"task_seedance_alias_direct_123","status":"PENDING"}}`))
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
server, err := NewWithConfig(Config{
|
||||
NewAPIBaseURL: upstream.URL,
|
||||
SQLitePath: filepath.Join(t.TempDir(), "popiart.db"),
|
||||
SkillhubDir: makeEmptySkillhub(t),
|
||||
SessionSecret: "test-secret",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewWithConfig: %v", err)
|
||||
}
|
||||
token, _, ok, err := server.store.createSession("sk-test-upstream")
|
||||
if err != nil {
|
||||
t.Fatalf("createSession: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected session creation to succeed")
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/video/generations", strings.NewReader(`{"model":"seedance2.0","prompt":"ping"}`))
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
server.Handler().ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusAccepted {
|
||||
t.Fatalf("expected 202, got %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if gotBody["model"] != "doubao-seedance-2-0-260128" {
|
||||
t.Fatalf("expected canonical Seedance model, got %#v", gotBody["model"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVideoGenerationFetchSurfacesLastFrameURL(t *testing.T) {
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet || r.URL.Path != "/v1/video/generations/task_seedance_direct_123" {
|
||||
|
||||
Reference in New Issue
Block a user