From 8b474bb9690958eaff8e17b1c57e6872de58eab5 Mon Sep 17 00:00:00 2001 From: Adir Amsalem Date: Mon, 10 Aug 2026 11:45:42 +0300 Subject: [PATCH] feat(models): add lucy-vton-3.5 (realtime + batch) at 720p Re-adds lucy-vton-3.5 on both the realtime and batch/video surfaces after the earlier revert, and repoints the lucy-vton-latest alias to it. Geometry is 1280x720, not the 1088x624 the reverted change shipped: the backend serves this model at 720p (bouncer supported_resolutions ["720p"], inference_server/server_configs/lucy-vton-3.5.yaml canonical supported_landscape_resolutions entry [720, 1280]). The lucy-vton-latest rows move to 720p with it. Claude-Session: https://claude.ai/code/session_01QoAEWRoPpnJYkEV57yUvob --- packages/sdk/AGENTS.md | 6 ++-- packages/sdk/index.html | 1 + packages/sdk/src/process/types.ts | 9 +++++- packages/sdk/src/shared/model.ts | 37 +++++++++++++++++++++---- packages/sdk/tests/e2e-realtime.test.ts | 1 + packages/sdk/tests/e2e.test.ts | 23 +++++++++++++++ packages/sdk/tests/unit.test.ts | 35 ++++++++++++++++++----- 7 files changed, 96 insertions(+), 16 deletions(-) diff --git a/packages/sdk/AGENTS.md b/packages/sdk/AGENTS.md index 9b65a252..9b4aed5d 100644 --- a/packages/sdk/AGENTS.md +++ b/packages/sdk/AGENTS.md @@ -72,8 +72,9 @@ - `lucy-2.5` - Real-time video editing (supports reference image) - `lucy-vton-2` - Real-time virtual try-on 2 - `lucy-vton-3` - Real-time virtual try-on 3 +- `lucy-vton-3.5` - Real-time virtual try-on 3.5 - `lucy-restyle-2` - Real-time video restyling -- `lucy-latest`, `lucy-vton-latest`, `lucy-restyle-latest` - Server-resolved aliases for the latest stable version (`lucy-vton-latest` → `lucy-vton-3`) +- `lucy-latest`, `lucy-vton-latest`, `lucy-restyle-latest` - Server-resolved aliases for the latest stable version (`lucy-vton-latest` → `lucy-vton-3.5`) - Deprecated: `lucy-2.1-vton-2` → `lucy-vton-2` ### Video Models (Queue API) @@ -82,8 +83,9 @@ - `lucy-2.5` - long-form video editing (720p) - `lucy-vton-2` - virtual try-on 2 video editing - `lucy-vton-3` - virtual try-on 3 video editing +- `lucy-vton-3.5` - virtual try-on 3.5 video editing - `lucy-restyle-2` - video restyling -- `lucy-latest`, `lucy-vton-latest`, `lucy-restyle-latest`, `lucy-clip-latest` - Server-resolved aliases (`lucy-vton-latest` → `lucy-vton-3`) +- `lucy-latest`, `lucy-vton-latest`, `lucy-restyle-latest`, `lucy-clip-latest` - Server-resolved aliases (`lucy-vton-latest` → `lucy-vton-3.5`) - Deprecated: `lucy-2.1-vton-2` → `lucy-vton-2` ### Image Models (Process API) diff --git a/packages/sdk/index.html b/packages/sdk/index.html index 67c5680a..1b70bb50 100644 --- a/packages/sdk/index.html +++ b/packages/sdk/index.html @@ -341,6 +341,7 @@

Configuration

+ diff --git a/packages/sdk/src/process/types.ts b/packages/sdk/src/process/types.ts index eac794ca..a1b1194c 100644 --- a/packages/sdk/src/process/types.ts +++ b/packages/sdk/src/process/types.ts @@ -163,7 +163,14 @@ export type ModelSpecificInputs = T["name"] extends " ? ImageEditingInputs : T["name"] extends "lucy-restyle-v2v" | "lucy-restyle-2" ? VideoRestyleInputs - : T["name"] extends "lucy-2.1" | "lucy-2.5" | "lucy-vton-2" | "lucy-vton-3" | "lucy-2.1-vton-2" | "lucy-vton-latest" + : T["name"] extends + | "lucy-2.1" + | "lucy-2.5" + | "lucy-vton-2" + | "lucy-vton-3" + | "lucy-vton-3.5" + | "lucy-2.1-vton-2" + | "lucy-vton-latest" ? VideoEdit2Inputs : T["name"] extends "lucy-pro-v2v" | "lucy-clip" ? VideoEditInputs diff --git a/packages/sdk/src/shared/model.ts b/packages/sdk/src/shared/model.ts index a984eb6f..de4de439 100644 --- a/packages/sdk/src/shared/model.ts +++ b/packages/sdk/src/shared/model.ts @@ -7,6 +7,7 @@ const CANONICAL_MODEL_NAMES = [ "lucy-2.5", "lucy-vton-2", "lucy-vton-3", + "lucy-vton-3.5", "lucy-restyle-2", "lucy-clip", "lucy-image-2", @@ -17,6 +18,7 @@ const CANONICAL_REALTIME_MODEL_NAMES = [ "lucy-2.5", "lucy-vton-2", "lucy-vton-3", + "lucy-vton-3.5", "lucy-restyle-2", ] as const; const CANONICAL_VIDEO_MODEL_NAMES = [ @@ -25,6 +27,7 @@ const CANONICAL_VIDEO_MODEL_NAMES = [ "lucy-2.5", "lucy-vton-2", "lucy-vton-3", + "lucy-vton-3.5", "lucy-restyle-2", ] as const; const CANONICAL_IMAGE_MODEL_NAMES = ["lucy-image-2"] as const; @@ -69,6 +72,7 @@ export const realtimeModels = z.union([ z.literal("lucy-2.5"), z.literal("lucy-vton-2"), z.literal("lucy-vton-3"), + z.literal("lucy-vton-3.5"), z.literal("lucy-restyle-2"), // Latest aliases (server-side resolution) z.literal("lucy-latest"), @@ -84,6 +88,7 @@ export const videoModels = z.union([ z.literal("lucy-2.5"), z.literal("lucy-vton-2"), z.literal("lucy-vton-3"), + z.literal("lucy-vton-3.5"), z.literal("lucy-restyle-2"), // Latest aliases (server-side resolution) z.literal("lucy-latest"), @@ -261,6 +266,7 @@ export const modelInputSchemas = { "lucy-2.5": videoEdit2Schema, "lucy-vton-2": videoEdit2Schema, "lucy-vton-3": videoEdit2Schema, + "lucy-vton-3.5": videoEdit2Schema, // Latest aliases (server-side resolution) "lucy-latest": videoEdit2Schema, "lucy-vton-latest": videoEdit2Schema, @@ -369,6 +375,14 @@ const _models = { height: 624, inputSchema: z.object({}), }, + "lucy-vton-3.5": { + urlPath: "/v1/stream", + name: "lucy-vton-3.5" as const, + fps: { ideal: 30, max: 30 }, + width: 1280, + height: 720, + inputSchema: z.object({}), + }, "lucy-restyle-2": { urlPath: "/v1/stream", name: "lucy-restyle-2" as const, @@ -386,13 +400,13 @@ const _models = { height: 624, inputSchema: z.object({}), }, - // Server-side alias currently resolves to lucy-vton-3. + // Server-side alias currently resolves to lucy-vton-3.5. "lucy-vton-latest": { urlPath: "/v1/stream", name: "lucy-vton-latest" as const, fps: { ideal: 30, max: 30 }, - width: 1088, - height: 624, + width: 1280, + height: 720, inputSchema: z.object({}), }, "lucy-restyle-latest": { @@ -492,6 +506,15 @@ const _models = { height: 624, inputSchema: modelInputSchemas["lucy-vton-3"], }, + "lucy-vton-3.5": { + urlPath: "/v1/generate/lucy-vton-3.5", + queueUrlPath: "/v1/jobs/lucy-vton-3.5", + name: "lucy-vton-3.5" as const, + fps: 20, + width: 1280, + height: 720, + inputSchema: modelInputSchemas["lucy-vton-3.5"], + }, "lucy-restyle-2": { urlPath: "/v1/generate/lucy-restyle-2", queueUrlPath: "/v1/jobs/lucy-restyle-2", @@ -511,14 +534,14 @@ const _models = { height: 624, inputSchema: modelInputSchemas["lucy-latest"], }, - // Server-side alias currently resolves to lucy-vton-3. + // Server-side alias currently resolves to lucy-vton-3.5. "lucy-vton-latest": { urlPath: "/v1/generate/lucy-vton-latest", queueUrlPath: "/v1/jobs/lucy-vton-latest", name: "lucy-vton-latest" as const, fps: 20, - width: 1088, - height: 624, + width: 1280, + height: 720, inputSchema: modelInputSchemas["lucy-vton-latest"], }, "lucy-restyle-latest": { @@ -607,6 +630,7 @@ export const models = { * - `"lucy-2.5"` - Lucy 2.5 realtime video editing * - `"lucy-vton-2"` - Lucy virtual try-on 2 * - `"lucy-vton-3"` - Lucy virtual try-on 3 + * - `"lucy-vton-3.5"` - Lucy virtual try-on 3.5 (latest) * - `"lucy-restyle-2"` - Realtime video restyling */ realtime: (model: T): ModelDefinition => { @@ -626,6 +650,7 @@ export const models = { * - `"lucy-2.5"` - Long-form video editing (Lucy 2.5) * - `"lucy-vton-2"` - Virtual try-on 2 video editing * - `"lucy-vton-3"` - Virtual try-on 3 video editing + * - `"lucy-vton-3.5"` - Virtual try-on 3.5 video editing (latest) * - `"lucy-restyle-2"` - Video restyling */ video: (model: T): ModelDefinition & { fps: number; queueUrlPath: string } => { diff --git a/packages/sdk/tests/e2e-realtime.test.ts b/packages/sdk/tests/e2e-realtime.test.ts index 1aafb0ed..46357974 100644 --- a/packages/sdk/tests/e2e-realtime.test.ts +++ b/packages/sdk/tests/e2e-realtime.test.ts @@ -62,6 +62,7 @@ const REALTIME_MODELS: RealTimeModels[] = [ "lucy-2.5", "lucy-vton-2", "lucy-vton-3", + "lucy-vton-3.5", ]; const TIMEOUT = 1 * 60 * 1000; // 1 minute diff --git a/packages/sdk/tests/e2e.test.ts b/packages/sdk/tests/e2e.test.ts index 5aba0d1e..16b2911c 100644 --- a/packages/sdk/tests/e2e.test.ts +++ b/packages/sdk/tests/e2e.test.ts @@ -211,6 +211,29 @@ describe.concurrent("E2E Tests", { timeout: TIMEOUT, retry: 2 }, () => { await expectResult(result, "lucy-vton-3-reference_image", ".mp4"); }); + it("lucy-vton-3.5: virtual try-on (prompt)", async () => { + const result = await client.queue.submitAndPoll({ + model: models.video("lucy-vton-3.5"), + prompt: "Wearing a red leather jacket", + data: videoBlob, + seed: 42, + }); + + await expectResult(result, "lucy-vton-3.5-prompt", ".mp4"); + }); + + it("lucy-vton-3.5: virtual try-on (reference_image)", async () => { + const result = await client.queue.submitAndPoll({ + model: models.video("lucy-vton-3.5"), + prompt: "", + reference_image: imageBlob, + data: videoBlob, + seed: 42, + }); + + await expectResult(result, "lucy-vton-3.5-reference_image", ".mp4"); + }); + // Deprecated video model names (aliases) it("lucy-restyle-v2v (deprecated): video restyling", async () => { const result = await client.queue.submitAndPoll({ diff --git a/packages/sdk/tests/unit.test.ts b/packages/sdk/tests/unit.test.ts index 98983fff..9d55d03b 100644 --- a/packages/sdk/tests/unit.test.ts +++ b/packages/sdk/tests/unit.test.ts @@ -2435,6 +2435,7 @@ describe("Canonical Model Names", () => { "lucy-2.5", "lucy-vton-2", "lucy-vton-3", + "lucy-vton-3.5", "lucy-restyle-2", ]); expect(canonicalVideoModels.options).toEqual([ @@ -2443,6 +2444,7 @@ describe("Canonical Model Names", () => { "lucy-2.5", "lucy-vton-2", "lucy-vton-3", + "lucy-vton-3.5", "lucy-restyle-2", ]); expect(canonicalImageModels.options).toEqual(["lucy-image-2"]); @@ -2502,7 +2504,7 @@ describe("Canonical Model Names", () => { it("lists all models when called without options", () => { const listedModels = listModels(); - expect(listedModels).toHaveLength(25); + expect(listedModels).toHaveLength(27); expect(listedModels.some((model) => model.kind === "realtime" && model.name === "lucy-2.1")).toBe(true); expect(listedModels.some((model) => model.kind === "video" && model.name === "lucy-clip")).toBe(true); expect(listedModels.some((model) => model.kind === "image" && model.name === "lucy-image-2")).toBe(true); @@ -2595,6 +2597,15 @@ describe("Canonical Model Names", () => { expect(model.height).toBe(624); }); + it("lucy-vton-3.5 canonical name works", () => { + const model = models.realtime("lucy-vton-3.5"); + expect(model.name).toBe("lucy-vton-3.5"); + expect(model.urlPath).toBe("/v1/stream"); + expect(model.fps).toEqual({ ideal: 30, max: 30 }); + expect(model.width).toBe(1280); + expect(model.height).toBe(720); + }); + it("lucy-restyle-2 canonical name works", () => { const model = models.realtime("lucy-restyle-2"); expect(model.name).toBe("lucy-restyle-2"); @@ -2648,6 +2659,16 @@ describe("Canonical Model Names", () => { expect(model.height).toBe(624); }); + it("lucy-vton-3.5 as video model works", () => { + const model = models.video("lucy-vton-3.5"); + expect(model.name).toBe("lucy-vton-3.5"); + expect(model.urlPath).toBe("/v1/generate/lucy-vton-3.5"); + expect(model.queueUrlPath).toBe("/v1/jobs/lucy-vton-3.5"); + expect(model.fps).toBe(20); + expect(model.width).toBe(1280); + expect(model.height).toBe(720); + }); + it("lucy-restyle-2 as video model works", () => { const model = models.video("lucy-restyle-2"); expect(model.name).toBe("lucy-restyle-2"); @@ -2675,13 +2696,13 @@ describe("Canonical Model Names", () => { expect(model.height).toBe(624); }); - it("lucy-vton-latest works as realtime model and resolves server-side to lucy-vton-3", () => { + it("lucy-vton-latest works as realtime model and resolves server-side to lucy-vton-3.5", () => { const model = models.realtime("lucy-vton-latest"); expect(model.name).toBe("lucy-vton-latest"); expect(model.urlPath).toBe("/v1/stream"); expect(model.fps).toEqual({ ideal: 30, max: 30 }); - expect(model.width).toBe(1088); - expect(model.height).toBe(624); + expect(model.width).toBe(1280); + expect(model.height).toBe(720); }); it("lucy-restyle-latest works as realtime model", () => { @@ -2703,14 +2724,14 @@ describe("Canonical Model Names", () => { expect(model.height).toBe(624); }); - it("lucy-vton-latest works as video model and resolves server-side to lucy-vton-3", () => { + it("lucy-vton-latest works as video model and resolves server-side to lucy-vton-3.5", () => { const model = models.video("lucy-vton-latest"); expect(model.name).toBe("lucy-vton-latest"); expect(model.urlPath).toBe("/v1/generate/lucy-vton-latest"); expect(model.queueUrlPath).toBe("/v1/jobs/lucy-vton-latest"); expect(model.fps).toBe(20); - expect(model.width).toBe(1088); - expect(model.height).toBe(624); + expect(model.width).toBe(1280); + expect(model.height).toBe(720); }); it("lucy-restyle-latest works as video model", () => {