import { expect, test } from "@playwright/test"; import { bindVip98kImages, resolveVip98kTaskInvocation, selectVip98kTaskInvocation, type Vip98kTaskInvocation, } from "@/lib/ai/vip98k-task-invocation"; import { buildVip98kModelPresets, isVip98kImageModelVisible, } from "@/lib/ai/vip98k-models"; import { addVip98kReferenceRetryToken, getStorageObjectKeyFromPublicUrl, getVip98kStagedMediaRetryDelay, getVip98kSubmissionRetryDelay, shouldStageVip98kReferenceUrl, } from "@/lib/ai/vip98k-reference-staging"; import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery"; const textToImageInvocation: Vip98kTaskInvocation = { kind: "tool", method: "POST", path: "/api/external/tools/generate?tool_id=google%2Fnano-banana-2%2Ftext-to-image", contentType: "application/json", requestExample: { prompt: "Generate an image" }, resultMethod: "GET", resultPath: "/api/external/tools/result?tool_id=google%2Fnano-banana-2%2Ftext-to-image&taskId={task_id}", }; test.describe("VIP98K reference-image routing", () => { test("identifies default-storage references that need provider staging", () => { expect( shouldStageVip98kReferenceUrl({ mediaUrl: "https://default.example/uploads/reference.jpg", sourcePublicUrl: "https://default.example", targetPublicUrl: "https://backup.example/project", }), ).toBe(true); expect( shouldStageVip98kReferenceUrl({ mediaUrl: "https://backup.example/project/uploads/reference.jpg", sourcePublicUrl: "https://default.example", targetPublicUrl: "https://backup.example/project", }), ).toBe(false); expect( shouldStageVip98kReferenceUrl({ mediaUrl: "https://external.example/reference.jpg", sourcePublicUrl: "https://default.example", targetPublicUrl: "https://backup.example/project", }), ).toBe(false); expect( getStorageObjectKeyFromPublicUrl( "https://default.example/assets/folder%20name/reference.jpg?token=secret", "https://default.example/assets", ), ).toBe("folder name/reference.jpg"); }); test("retries transient media rejection while a staged URL propagates", () => { expect( getVip98kStagedMediaRetryDelay({ status: 400, message: "The media request is invalid.", attempt: 1, maxAttempts: 3, stagedReferenceCount: 2, }), ).toBe(20_000); expect( getVip98kStagedMediaRetryDelay({ status: 400, message: "The media request is invalid.", attempt: 3, maxAttempts: 3, stagedReferenceCount: 2, }), ).toBeNull(); expect( addVip98kReferenceRetryToken( "https://cdn.example/reference.jpg?existing=1", "retry-2", ), ).toBe( "https://cdn.example/reference.jpg?existing=1&sg_media_retry=retry-2", ); }); test("retries provider reconciliation without treating it as a media failure", () => { expect( getVip98kSubmissionRetryDelay({ status: 503, message: "The media request is pending safe reconciliation.", attempt: 1, maxAttempts: 3, }), ).toBe(15_000); expect( getVip98kSubmissionRetryDelay({ status: 503, message: "pending safe reconciliation", attempt: 2, maxAttempts: 3, retryAfterMs: 8_000, }), ).toBe(8_000); expect( getVip98kSubmissionRetryDelay({ status: 503, message: "pending safe reconciliation", attempt: 3, maxAttempts: 3, }), ).toBeNull(); }); test("shows the newly published Nano Banana Pro image editor", () => { expect( isVip98kImageModelVisible({ modelId: "nano-banana-pro", type: "image", }), ).toBe(true); }); test("replaces a text-to-image alias invocation with the resolved reference tool", () => { const invocation = resolveVip98kTaskInvocation( "google/nano-banana-2/reference-to-image", textToImageInvocation ); expect(invocation?.path).toContain( "tool_id=google%2Fnano-banana-2%2Freference-to-image" ); expect(invocation?.resultPath).toContain( "tool_id=google%2Fnano-banana-2%2Freference-to-image" ); expect(invocation?.requestExample).toMatchObject({ prompt: "", video_clips: [{ fps: 1, url: "", ends: 0, start: 0 }], }); }); test("keeps the catalog invocation when it already targets the resolved tool", () => { const catalogInvocation: Vip98kTaskInvocation = { ...textToImageInvocation, path: "/api/external/tools/generate?tool_id=google%2Fnano-banana-2%2Freference-to-image", resultPath: "/api/external/tools/result?tool_id=google%2Fnano-banana-2%2Freference-to-image&taskId={task_id}", requestExample: { prompt: "Catalog prompt", video_clips: [{ fps: 2, url: "catalog", ends: 1, start: 0 }], }, }; expect( resolveVip98kTaskInvocation( "google/nano-banana-2/reference-to-image", catalogInvocation ) ).toBe(catalogInvocation); }); test("does not rewrite unrelated tools", () => { expect( resolveVip98kTaskInvocation("other/image-model", textToImageInvocation) ).toBe(textToImageInvocation); }); test("routes remove-background through the dedicated external tool", () => { const invocation = resolveVip98kTaskInvocation("remove-background", null); expect(invocation?.path).toBe( "/api/external/tools/generate?tool_id=remove-background" ); expect(invocation?.resultPath).toBe( "/api/external/tools/result?tool_id=remove-background&taskId={task_id}" ); expect(invocation?.requestExample).toEqual({ image_url: "" }); }); test("routes enhance through the dedicated external tool", () => { const invocation = resolveVip98kTaskInvocation("enhance", null); expect(invocation?.path).toBe( "/api/external/tools/generate?tool_id=enhance" ); expect(invocation?.requestExample).toEqual({ image_url: "", scale_factor: 4, }); }); test("selects the V2 media-task contract when a model receives reference images", () => { const generationInvocation: Vip98kTaskInvocation = { kind: "tool", method: "POST", path: "/v1/images/generations", contentType: "application/json", requestExample: { model: "nano-banana-2", n: 1, prompt: "Generate" }, resultMethod: "GET", resultPath: "", }; const editInvocation: Vip98kTaskInvocation = { kind: "task", method: "POST", path: "/extensions/v1/tasks", contentType: "application/json", requestExample: { file_urls: ["https://api.vip98k.com/logo.png"], model: "nano-banana-2", prompt: "Edit", }, resultMethod: "GET", resultPath: "/extensions/v1/tasks/{task_id}/result", }; expect( selectVip98kTaskInvocation({ modelId: "nano-banana-2", configuredInvocation: generationInvocation, configuredInvocations: [generationInvocation, editInvocation], imageCount: 2, }), ).toBe(editInvocation); }); test("binds every reference image to the V2 file_urls field", () => { const body: Record = { file_urls: ["https://api.vip98k.com/logo.png"], model: "nano-banana-2", prompt: "Edit", }; expect( bindVip98kImages(body, [ "https://example.com/a.png", "https://example.com/b.png", ]), ).toEqual(["file_urls"]); expect(body.file_urls).toEqual([ "https://example.com/a.png", "https://example.com/b.png", ]); }); test("keeps V2 processing-task contracts instead of rewriting them to retired routes", () => { const invocation: Vip98kTaskInvocation = { kind: "task", method: "POST", path: "/extensions/v1/tasks", contentType: "application/json", requestExample: { file_urls: ["https://api.vip98k.com/logo.png"], model: "enhance", }, resultMethod: "GET", resultPath: "/extensions/v1/tasks/{task_id}/result", }; expect(resolveVip98kTaskInvocation("enhance", invocation)).toBe(invocation); }); test("derives multi-image support and parameter controls from all V2 invocations", () => { const [preset] = buildVip98kModelPresets([ { id: "nano-banana-2", type: "image", capabilities: ["openai.images.generations", "text_to_image"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { model: "nano-banana-2", n: 1, prompt: "Generate" }, }, invocations: [ { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { model: "nano-banana-2", n: 1, prompt: "Generate" }, }, { kind: "task", method: "POST", path: "/extensions/v1/tasks", content_type: "application/json", request_example: { file_urls: ["https://api.vip98k.com/logo.png"], model: "nano-banana-2", prompt: "Edit", }, result_method: "GET", result_path: "/extensions/v1/tasks/{task_id}/result", }, ], }, ]); expect(preset).toMatchObject({ supportsVision: true, supportsImageGen: true, capabilities: { supportsMultiImageReference: true, maxReferenceImages: 8, supportedImageSizes: [], supportedAspectRatios: ["auto"], }, imageGenConfig: { supportsSize: false, supportedImageSizes: [], supportedAspectRatios: ["auto"], }, }); expect( (preset?.imageGenConfig?.vip98k as { invocations?: unknown[] })?.invocations, ).toHaveLength(2); }); test("detects V2 asset-only results that need delivery URL resolution", () => { expect( shouldResolveVip98kResultDelivery({ payload: { ready: true, result: { result_available: true, outputs: [{ asset_id: "opaque", type: "image" }], }, status: "succeeded", }, status: "succeeded", outputUrlCount: 0, }), ).toBe(true); expect( shouldResolveVip98kResultDelivery({ payload: { status: "succeeded" }, status: "succeeded", outputUrlCount: 1, }), ).toBe(false); }); test("excludes text-only VIP98K image models from editing workflows", () => { const [preset] = buildVip98kModelPresets([ { id: "nano-banana-pro", type: "image", capabilities: ["openai.images.generations", "text_to_image"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { model: "nano-banana-pro", n: 1, prompt: "Generate" }, }, }, ]); expect(preset).toBeUndefined(); }); test("does not inherit multi-image support from broad authorized metadata", () => { const presets = buildVip98kModelPresets([ { id: "nano-banana-2-lite", type: "image", capabilities: ["text_to_image", "image_to_image"], inputModes: ["text", "image"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { model: "nano-banana-2-lite", n: 1, prompt: "Generate", }, }, }, ]); expect(presets).toEqual([]); }); });