diff --git a/packages/sdk/src/shared/model.ts b/packages/sdk/src/shared/model.ts index 2f496da..5c74dab 100644 --- a/packages/sdk/src/shared/model.ts +++ b/packages/sdk/src/shared/model.ts @@ -43,6 +43,10 @@ export const realtimeModels = z.union([ z.literal("lucy-restyle"), z.literal("lucy-restyle-2"), z.literal("live-avatar"), + // Latest aliases (server-side resolution) + z.literal("lucy-latest"), + z.literal("lucy-vton-latest"), + z.literal("lucy-restyle-latest"), // Deprecated names (use canonical names above instead) z.literal("mirage"), z.literal("mirage_v2"), @@ -57,6 +61,11 @@ export const videoModels = z.union([ z.literal("lucy-2.1"), z.literal("lucy-restyle-2"), z.literal("lucy-motion"), + // Latest aliases (server-side resolution) + z.literal("lucy-latest"), + z.literal("lucy-restyle-latest"), + z.literal("lucy-clip-latest"), + z.literal("lucy-motion-latest"), // Deprecated names (use canonical names above instead) z.literal("lucy-pro-v2v"), z.literal("lucy-restyle-v2v"), @@ -65,6 +74,8 @@ export const videoModels = z.union([ export const imageModels = z.union([ // Canonical name z.literal("lucy-image-2"), + // Latest alias (server-side resolution) + z.literal("lucy-image-latest"), // Deprecated name (use canonical name above instead) z.literal("lucy-pro-i2i"), ]); @@ -216,6 +227,29 @@ export const modelInputSchemas = { seed: z.number().optional().describe("The seed to use for the generation"), resolution: motionResolutionSchema, }), + // Latest aliases (server-side resolution) + "lucy-latest": videoEdit2Schema, + "lucy-restyle-latest": restyleSchema, + "lucy-clip-latest": videoEditSchema, + "lucy-motion-latest": z.object({ + data: fileInputSchema.describe( + "The image data to use for generation (File, Blob, ReadableStream, URL, or string URL). Output video is limited to 5 seconds.", + ), + trajectory: z + .array( + z.object({ + frame: z.number().min(0), + x: z.number().min(0), + y: z.number().min(0), + }), + ) + .min(2) + .max(1000) + .describe("The trajectory of the desired movement of the object in the image"), + seed: z.number().optional().describe("The seed to use for the generation"), + resolution: motionResolutionSchema, + }), + "lucy-image-latest": imageEditSchema, // Deprecated names (kept for backward compatibility) "lucy-pro-v2v": videoEditSchema, "lucy-pro-i2i": imageEditSchema, @@ -327,6 +361,31 @@ const _models = { height: 720, inputSchema: z.object({}), }, + // Latest aliases (server-side resolution) + "lucy-latest": { + urlPath: "/v1/stream", + name: "lucy-latest" as const, + fps: 20, + width: 1088, + height: 624, + inputSchema: z.object({}), + }, + "lucy-vton-latest": { + urlPath: "/v1/stream", + name: "lucy-vton-latest" as const, + fps: 20, + width: 1088, + height: 624, + inputSchema: z.object({}), + }, + "lucy-restyle-latest": { + urlPath: "/v1/stream", + name: "lucy-restyle-latest" as const, + fps: 22, + width: 1280, + height: 704, + inputSchema: z.object({}), + }, // Deprecated names (use canonical names above instead) mirage: { urlPath: "/v1/stream", @@ -380,6 +439,16 @@ const _models = { height: 704, inputSchema: modelInputSchemas["lucy-image-2"], }, + // Latest alias (server-side resolution) + "lucy-image-latest": { + urlPath: "/v1/generate/lucy-image-latest", + queueUrlPath: "/v1/jobs/lucy-image-latest", + name: "lucy-image-latest" as const, + fps: 25, + width: 1280, + height: 704, + inputSchema: modelInputSchemas["lucy-image-latest"], + }, // Deprecated name "lucy-pro-i2i": { urlPath: "/v1/generate/lucy-pro-i2i", @@ -438,6 +507,43 @@ const _models = { height: 704, inputSchema: modelInputSchemas["lucy-motion"], }, + // Latest aliases (server-side resolution) + "lucy-latest": { + urlPath: "/v1/generate/lucy-latest", + queueUrlPath: "/v1/jobs/lucy-latest", + name: "lucy-latest" as const, + fps: 20, + width: 1088, + height: 624, + inputSchema: modelInputSchemas["lucy-latest"], + }, + "lucy-restyle-latest": { + urlPath: "/v1/generate/lucy-restyle-latest", + queueUrlPath: "/v1/jobs/lucy-restyle-latest", + name: "lucy-restyle-latest" as const, + fps: 22, + width: 1280, + height: 704, + inputSchema: modelInputSchemas["lucy-restyle-latest"], + }, + "lucy-clip-latest": { + urlPath: "/v1/generate/lucy-clip-latest", + queueUrlPath: "/v1/jobs/lucy-clip-latest", + name: "lucy-clip-latest" as const, + fps: 25, + width: 1280, + height: 704, + inputSchema: modelInputSchemas["lucy-clip-latest"], + }, + "lucy-motion-latest": { + urlPath: "/v1/generate/lucy-motion-latest", + queueUrlPath: "/v1/jobs/lucy-motion-latest", + name: "lucy-motion-latest" as const, + fps: 25, + width: 1280, + height: 704, + inputSchema: modelInputSchemas["lucy-motion-latest"], + }, // Deprecated names (use canonical names above instead) "lucy-pro-v2v": { urlPath: "/v1/generate/lucy-pro-v2v", diff --git a/packages/sdk/tests/e2e.test.ts b/packages/sdk/tests/e2e.test.ts index 561d3b3..39bcb4e 100644 --- a/packages/sdk/tests/e2e.test.ts +++ b/packages/sdk/tests/e2e.test.ts @@ -91,6 +91,20 @@ describe.concurrent("E2E Tests", { timeout: TIMEOUT, retry: 2 }, () => { }); }); + describe("Process API - Image Models (latest aliases)", () => { + it("lucy-image-latest: image-to-image", async () => { + const result = await client.process({ + model: models.image("lucy-image-latest"), + prompt: "Oil painting in the style of Van Gogh", + data: imageBlob, + seed: 333, + enhance_prompt: false, + }); + + await expectResult(result, "lucy-image-latest", ".png"); + }); + }); + describe("Process API - Image Models (deprecated names)", () => { it("lucy-pro-i2i (deprecated): image-to-image", async () => { const result = await client.process({ @@ -233,6 +247,56 @@ describe.concurrent("E2E Tests", { timeout: TIMEOUT, retry: 2 }, () => { await expectResult(result, "lucy-2-v2v", ".mp4"); }); + // Latest aliases (server-side resolution) + it("lucy-latest: video editing", async () => { + const result = await client.queue.submitAndPoll({ + model: models.video("lucy-latest"), + prompt: "Watercolor painting style with soft brushstrokes", + data: videoBlob, + seed: 42, + }); + + await expectResult(result, "lucy-latest", ".mp4"); + }); + + it("lucy-restyle-latest: video restyling", async () => { + const result = await client.queue.submitAndPoll({ + model: models.video("lucy-restyle-latest"), + prompt: "Cyberpunk neon city style", + data: videoBlob, + seed: 777, + }); + + await expectResult(result, "lucy-restyle-latest", ".mp4"); + }); + + it("lucy-clip-latest: video-to-video", async () => { + const result = await client.queue.submitAndPoll({ + model: models.video("lucy-clip-latest"), + prompt: "Lego World animated style", + data: videoBlob, + seed: 999, + enhance_prompt: true, + }); + + await expectResult(result, "lucy-clip-latest", ".mp4"); + }); + + it("lucy-motion-latest: motion-guided image-to-video", async () => { + const result = await client.queue.submitAndPoll({ + model: models.video("lucy-motion-latest"), + data: imageBlob, + trajectory: [ + { frame: 0, x: 0, y: 0 }, + { frame: 1, x: 0.1, y: 0.2 }, + { frame: 2, x: 0.2, y: 0.4 }, + ], + seed: 555, + }); + + await expectResult(result, "lucy-motion-latest", ".mp4"); + }); + it("lucy-motion: motion-guided image-to-video", async () => { const result = await client.queue.submitAndPoll({ model: models.video("lucy-motion"), diff --git a/packages/sdk/tests/unit.test.ts b/packages/sdk/tests/unit.test.ts index 339d9f0..a492463 100644 --- a/packages/sdk/tests/unit.test.ts +++ b/packages/sdk/tests/unit.test.ts @@ -3504,6 +3504,103 @@ describe("Canonical Model Names", () => { }); }); + describe("Latest aliases", () => { + it("lucy-latest works as realtime model", () => { + const model = models.realtime("lucy-latest"); + expect(model.name).toBe("lucy-latest"); + expect(model.urlPath).toBe("/v1/stream"); + expect(model.fps).toBe(20); + expect(model.width).toBe(1088); + expect(model.height).toBe(624); + }); + + it("lucy-vton-latest works as realtime model", () => { + const model = models.realtime("lucy-vton-latest"); + expect(model.name).toBe("lucy-vton-latest"); + expect(model.urlPath).toBe("/v1/stream"); + expect(model.fps).toBe(20); + expect(model.width).toBe(1088); + expect(model.height).toBe(624); + }); + + it("lucy-restyle-latest works as realtime model", () => { + const model = models.realtime("lucy-restyle-latest"); + expect(model.name).toBe("lucy-restyle-latest"); + expect(model.urlPath).toBe("/v1/stream"); + expect(model.fps).toBe(22); + expect(model.width).toBe(1280); + expect(model.height).toBe(704); + }); + + it("lucy-latest works as video model", () => { + const model = models.video("lucy-latest"); + expect(model.name).toBe("lucy-latest"); + expect(model.urlPath).toBe("/v1/generate/lucy-latest"); + expect(model.queueUrlPath).toBe("/v1/jobs/lucy-latest"); + expect(model.fps).toBe(20); + expect(model.width).toBe(1088); + expect(model.height).toBe(624); + }); + + it("lucy-restyle-latest works as video model", () => { + const model = models.video("lucy-restyle-latest"); + expect(model.name).toBe("lucy-restyle-latest"); + expect(model.urlPath).toBe("/v1/generate/lucy-restyle-latest"); + expect(model.queueUrlPath).toBe("/v1/jobs/lucy-restyle-latest"); + expect(model.fps).toBe(22); + }); + + it("lucy-clip-latest works as video model", () => { + const model = models.video("lucy-clip-latest"); + expect(model.name).toBe("lucy-clip-latest"); + expect(model.urlPath).toBe("/v1/generate/lucy-clip-latest"); + expect(model.queueUrlPath).toBe("/v1/jobs/lucy-clip-latest"); + expect(model.fps).toBe(25); + }); + + it("lucy-motion-latest works as video model", () => { + const model = models.video("lucy-motion-latest"); + expect(model.name).toBe("lucy-motion-latest"); + expect(model.urlPath).toBe("/v1/generate/lucy-motion-latest"); + expect(model.queueUrlPath).toBe("/v1/jobs/lucy-motion-latest"); + expect(model.fps).toBe(25); + }); + + it("lucy-image-latest works as image model", () => { + const model = models.image("lucy-image-latest"); + expect(model.name).toBe("lucy-image-latest"); + expect(model.urlPath).toBe("/v1/generate/lucy-image-latest"); + expect(model.queueUrlPath).toBe("/v1/jobs/lucy-image-latest"); + }); + + it("lucy-latest is both a realtime and video model", () => { + expect(isRealtimeModel("lucy-latest")).toBe(true); + expect(isVideoModel("lucy-latest")).toBe(true); + }); + + it("lucy-restyle-latest is both a realtime and video model", () => { + expect(isRealtimeModel("lucy-restyle-latest")).toBe(true); + expect(isVideoModel("lucy-restyle-latest")).toBe(true); + }); + + it("does not log deprecation warnings for -latest aliases", () => { + _resetDeprecationWarnings(); + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + models.realtime("lucy-latest"); + models.realtime("lucy-vton-latest"); + models.realtime("lucy-restyle-latest"); + models.video("lucy-latest"); + models.video("lucy-restyle-latest"); + models.video("lucy-clip-latest"); + models.video("lucy-motion-latest"); + models.image("lucy-image-latest"); + + expect(warnSpy).not.toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + }); + describe("Dual-surface models", () => { it("lucy-2 is both a realtime and video model", () => { expect(isRealtimeModel("lucy-2")).toBe(true);