import { mergeGenerationHistory, recoverReplacementRun } from "@/lib/agents/replacement-run-recovery"; import { randomBytes } from "node:crypto"; import sharp from "sharp"; import { isVip98kReferenceSizeAllowed, normalizeVip98kReferenceImage, VIP98K_REFERENCE_MAX_BYTES, } from "@/lib/ai/vip98k-reference-image"; import { expect, test } from "@playwright/test"; import { bindVip98kImages, normalizeVip98kInvocationHeaders, resolveVip98kTaskInvocation, resolveVip98kImageTaskSize, selectVip98kTaskInvocation, type Vip98kTaskInvocation, } from "@/lib/ai/vip98k-task-invocation"; import { buildVip98kModelPresets, isVip98kImageModelVisible, } from "@/lib/ai/vip98k-models"; import { addVip98kReferenceRetryToken, getStorageObjectKeyFromPublicUrl, getVip98kReferenceStagingObjectKey, getVip98kStagedMediaRetryDelay, getVip98kSubmissionRetryDelay, isVip98kStagedReferenceUrl, shouldStageVip98kReferenceUrl, } from "@/lib/ai/vip98k-reference-staging"; import { shouldResolveVip98kResultDelivery } from "@/lib/ai/vip98k-result-delivery"; import { normalizeVip98kRuntimeConfig, resolveVip98kSubmissionTimeoutMs, } from "@/lib/ai/vip98k-runtime-config"; import { BANANAIMAGE_MODEL_PRESETS } from "@/lib/ai/bananaimage-models"; 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("keeps the provider-selected relay route header", () => { const [preset] = buildVip98kModelPresets([ { id: "gpt-image-2.5-flare", type: "image", capabilities: ["image-to-image"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", headers: { "X-Relay-Route-Option": "preferred-route", Authorization: "must-not-be-forwarded", }, request_example: { image: ["https://api.vip98k.com/logo.png"], model: "gpt-image-2.5-flare", prompt: "Edit", }, }, invocations: [ { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", headers: {}, request_example: { image: ["https://api.vip98k.com/logo.png"], model: "gpt-image-2.5-flare", prompt: "Edit", }, }, ], }, ]); const metadata = preset?.capabilities?.vip98k as { invocation?: Vip98kTaskInvocation; invocations?: Vip98kTaskInvocation[]; }; expect(metadata.invocation?.headers).toEqual({ "X-Relay-Route-Option": "preferred-route", }); expect(metadata.invocations?.[0]?.headers).toEqual({ "X-Relay-Route-Option": "preferred-route", }); expect( selectVip98kTaskInvocation({ modelId: "gpt-image-2.5-flare", configuredInvocation: metadata.invocation || null, configuredInvocations: metadata.invocations, imageCount: 2, })?.headers, ).toEqual({ "X-Relay-Route-Option": "preferred-route" }); expect( normalizeVip98kInvocationHeaders({ "X-Relay-Route-Option": "route-2", Authorization: "blocked", }), ).toEqual({ "X-Relay-Route-Option": "route-2" }); }); test("keeps every restored BananaImage preset multi-reference capable", () => { expect(BANANAIMAGE_MODEL_PRESETS.map((preset) => preset.modelId)).toEqual([ "gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview-plus", "gemini-3.1-flash-image-preview-plus", "gpt-image-2", ]); expect( BANANAIMAGE_MODEL_PRESETS.every( (preset) => preset.supportsImageGen && preset.supportsVision && preset.capabilities?.supportsMultiImageReference === true && Number(preset.capabilities?.maxReferenceImages) >= 2, ), ).toBe(true); }); test("uses a stable cross-process cache key for staged references", () => { const source = "https://storage.example/uploads/reference.png"; const key = getVip98kReferenceStagingObjectKey(source); expect(key).toMatch(/^uploads\/vip98k-cache-v2\/[a-f0-9]{64}\.jpg$/); expect( getVip98kReferenceStagingObjectKey(`${source}?sg_media_retry=retry-2`), ).toBe(key); expect( isVip98kStagedReferenceUrl( `https://minio.example/bucket/${key}`, ), ).toBe(true); }); test("loads bounded database runtime settings with a Pro timeout override", () => { const defaults = normalizeVip98kRuntimeConfig(null); expect(resolveVip98kSubmissionTimeoutMs("nano-banana-2", defaults)).toBe( 120_000, ); expect(resolveVip98kSubmissionTimeoutMs("nano-banana-pro", defaults)).toBe( 240_000, ); expect(defaults.referenceCacheTtlMs).toBe(7 * 24 * 60 * 60 * 1000); const config = normalizeVip98kRuntimeConfig({ submissionTimeoutMs: 90_000, modelSubmissionTimeoutMs: { "nano-banana-pro": 300_000 }, maxSubmissionAttempts: 20, referenceCacheTtlMs: 1, }); expect(config.maxSubmissionAttempts).toBe(5); expect(config.referenceCacheTtlMs).toBe(5 * 60_000); expect(resolveVip98kSubmissionTimeoutMs("nano-banana-2", config)).toBe( 90_000, ); expect(resolveVip98kSubmissionTimeoutMs("NANO-BANANA-PRO", config)).toBe( 300_000, ); }); 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: 409, message: "A request with this idempotency key is still in progress.", attempt: 1, maxAttempts: 3 })).toBe(15_000); expect(getVip98kSubmissionRetryDelay({ status: 409, message: "Conflicting request parameters", attempt: 1, maxAttempts: 3 })).toBeNull(); expect(getVip98kSubmissionRetryDelay({ status: 409, message: "A request with this idempotency key is still in progress.", attempt: 3, maxAttempts: 3 })).toBeNull(); 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: "The request was sent, but its final outcome could not be confirmed. Do not retry with a new idempotency key.", attempt: 1, maxAttempts: 3, }), ).toBe(15_000); expect( getVip98kSubmissionRetryDelay({ status: 503, message: "pending safe reconciliation", attempt: 3, maxAttempts: 3, }), ).toBeNull(); expect( getVip98kSubmissionRetryDelay({ status: 502, message: "Bad Gateway", attempt: 1, maxAttempts: 3, }), ).toBe(15_000); expect( getVip98kSubmissionRetryDelay({ status: 504, message: "Gateway Timeout", attempt: 2, maxAttempts: 3, retryAfterMs: 6_000, }), ).toBe(6_000); }); 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("binds every reference image to an array-shaped image field", () => { const body: Record = { image: ["https://api.vip98k.com/logo.png"], model: "gpt-image-2-vip", prompt: "Edit", }; expect( bindVip98kImages(body, [ "https://example.com/a.png", "https://example.com/b.png", ]), ).toEqual(["image"]); expect(body.image).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", }, input_schema: { properties: { aspect_ratio: { type: "string" }, file_urls: { type: "array", maxItems: 8 }, resolution: { type: "string" }, size: { type: "string", enum: [ "auto", "1024x1024", "2048x2048", "4096x4096", ], }, }, }, result_method: "GET", result_path: "/extensions/v1/tasks/{task_id}/result", }, ], }, ]); expect(preset).toMatchObject({ supportsVision: true, supportsImageGen: true, capabilities: { supportsMultiImageReference: true, maxReferenceImages: 8, supportedImageSizes: ["1K", "2K", "4K"], supportedAspectRatios: [ "auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "4:5", "5:4", ], }, imageGenConfig: { supportsSize: true, supportedImageSizes: ["1K", "2K", "4K"], }, }); expect( (preset?.imageGenConfig?.vip98k as { invocations?: unknown[] })?.invocations, ).toHaveLength(2); expect( ( (preset?.imageGenConfig?.vip98k as { invocations?: Array }) ?.invocations?.[1]?.inputSchema as Record )?.properties, ).toBeTruthy(); }); 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 unverified text-only VIP98K models from the image picker", () => { const presets = buildVip98kModelPresets([ { id: "unverified-image-model", 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: "unverified-image-model", n: 1, prompt: "Generate" }, }, }, ]); expect(presets).toEqual([]); }); test("keeps gpt-image-2-vip when its published image field is an array", () => { const presets = buildVip98kModelPresets([ { id: "gpt-image-2-vip", type: "image", capabilities: [ "image_to_image", "openai.images.generations", "text_to_image", ], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { image: ["https://api.vip98k.com/logo.png"], model: "gpt-image-2-vip", n: 1, prompt: "Generate", }, input_schema: { properties: { image: { type: "array" }, size: { type: "string" }, }, }, }, }, ]); expect(presets).toHaveLength(1); expect(presets[0]).toMatchObject({ modelId: "gpt-image-2-vip", supportsVision: true, isHidden: false, capabilities: { supportsMultiImageReference: true, maxReferenceImages: 8, supportedImageSizes: ["1K", "2K", "4K"], }, imageGenConfig: { supportsSize: true, supports1K: true, supports2K: true, supports4K: true, supportedImageSizes: ["1K", "2K", "4K"], }, }); }); test("publishes only the verified GPT Image 2.5 multi-image variants", () => { const models = [ "gpt-image-2.5", "gpt-image-2.5-flare", "gpt-image-2.5-sunburst", ].map((modelId) => ({ id: modelId, type: "image", capabilities: ["image_to_image", "openai.images.generations"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { image: ["https://api.vip98k.com/logo.png"], model: modelId, n: 1, prompt: "Edit", quality: "auto", stream: false, }, }, })); expect(models.map((model) => isVip98kImageModelVisible(model))).toEqual([ true, true, false, ]); const presets = buildVip98kModelPresets(models); expect( presets.map((preset) => ({ modelId: preset.modelId, isHidden: preset.isHidden, supportsQuality: preset.imageGenConfig?.supportsQuality, quality: preset.defaultParams.quality, supportedAspectRatios: preset.imageGenConfig?.supportedAspectRatios, })), ).toEqual([ { modelId: "gpt-image-2.5", isHidden: false, supportsQuality: true, quality: "auto", supportedAspectRatios: ["auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "4:5", "5:4"], }, { modelId: "gpt-image-2.5-flare", isHidden: false, supportsQuality: true, quality: "auto", supportedAspectRatios: [ "auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", ], }, { modelId: "gpt-image-2.5-sunburst", isHidden: true, supportsQuality: true, quality: "auto", supportedAspectRatios: ["auto"], }, ]); }); test("repairs stale Flare catalog metadata with verified multi-image and size controls", () => { const [preset] = buildVip98kModelPresets([ { id: "gpt-image-2.5-flare", type: "image", capabilities: ["image_to_image", "openai.images.generations"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", headers: { "X-Relay-Route-Option": "flare-route" }, request_example: { model: "gpt-image-2.5-flare", n: 1, prompt: "Edit", quality: "auto", stream: false, }, }, }, ]); expect(preset).toMatchObject({ modelId: "gpt-image-2.5-flare", supportsVision: true, isHidden: false, capabilities: { supportsMultiImageReference: true, maxReferenceImages: 8, supportedImageSizes: ["1K", "2K", "4K"], supportedAspectRatios: [ "auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", ], }, defaultParams: { imageSize: "2K" }, imageGenConfig: { supportsSize: true, supports1K: true, supports2K: true, supports4K: true, supportedImageSizes: ["1K", "2K", "4K"], }, }); const metadata = preset?.capabilities?.vip98k as { invocation?: Vip98kTaskInvocation; }; const invocation = selectVip98kTaskInvocation({ modelId: "gpt-image-2.5-flare", configuredInvocation: metadata.invocation || null, imageCount: 2, }); expect(invocation?.headers).toEqual({ "X-Relay-Route-Option": "flare-route", }); expect(invocation?.requestExample.image).toEqual([]); expect(invocation?.inputSchema).toMatchObject({ properties: { aspect_ratio: { type: "string" }, image: { type: "array" }, size: { enum: ["1024x1024", "2048x2048", "2880x2880"], }, }, }); }); 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([]); }); test("excludes Lite and Fast even when catalog metadata claims image arrays", () => { const presets = buildVip98kModelPresets( ["nano-banana-2-lite", "nano-banana-fast"].map((modelId) => ({ id: modelId, type: "image", capabilities: ["text_to_image", "image_to_image"], invocation: { kind: "task", method: "POST", path: "/extensions/v1/tasks", content_type: "application/json", request_example: { file_urls: ["https://api.vip98k.com/logo.png"], model: modelId, prompt: "Edit", }, input_schema: { properties: { file_urls: { type: "array", maxItems: 8 }, }, }, }, })), ); expect(presets).toEqual([]); }); test("maps frontend ratio and resolution to the VIP98K V2 size contract", () => { expect( resolveVip98kImageTaskSize({ imageSize: "1K", aspectRatio: "16:9", fallbackSize: "auto", }), ).toBe("1024x576"); expect( resolveVip98kImageTaskSize({ imageSize: "886*1182", aspectRatio: "3:4", fallbackSize: "auto", }), ).toBe("886x1182"); expect( resolveVip98kImageTaskSize({ imageSize: "1K", aspectRatio: "auto", fallbackSize: "auto", }), ).toBe("auto"); expect( resolveVip98kImageTaskSize({ imageSize: "4K", aspectRatio: "1:1", fallbackSize: "auto", preserveResolutionBucket: true, }), ).toBe("4K"); expect( resolveVip98kImageTaskSize({ imageSize: "2K", aspectRatio: "1:1", fallbackSize: "1024x1024", preserveResolutionBucket: true, useGptImage25AspectSizing: true, }), ).toBe("2048x2048"); expect( resolveVip98kImageTaskSize({ imageSize: "2K", aspectRatio: "2:3", fallbackSize: "1024x1024", preserveResolutionBucket: true, useGptImage25AspectSizing: true, }), ).toBe("1664x2512"); expect( resolveVip98kImageTaskSize({ imageSize: "4K", aspectRatio: "2:3", fallbackSize: "1024x1024", preserveResolutionBucket: true, useGptImage25AspectSizing: true, }), ).toBe("2352x3520"); }); test("keeps rounded GPT 2K requests inside their pixel tier", () => { for (const ratio of ["16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4"]) { const size = resolveVip98kImageTaskSize({ imageSize: "2K", aspectRatio: ratio, useGptImage25AspectSizing: true }); const [w, h] = size!.split("x").map(Number); expect(w * h).toBeLessThanOrEqual(2048 * 2048); } expect(resolveVip98kImageTaskSize({ imageSize: "2K", aspectRatio: "16:9", useGptImage25AspectSizing: true })).toBe("2720x1536"); }); test("advertises verified Flare resolutions and aspect-ratio controls", () => { const [preset] = buildVip98kModelPresets([ { id: "gpt-image-2.5-flare", type: "image", capabilities: ["image_to_image"], supported_resolutions: ["1K", "2K", "4K"], invocation: { kind: "tool", method: "POST", path: "/v1/images/generations", content_type: "application/json", request_example: { image: ["https://api.vip98k.com/logo.png"], model: "gpt-image-2.5-flare", prompt: "Edit", }, input_schema: { properties: { image: { type: "array" }, size: { type: "string" }, }, }, }, }, ]); expect(preset?.imageGenConfig).toMatchObject({ supportsSize: true, supports1K: true, supports2K: true, supports4K: true, supportedImageSizes: ["1K", "2K", "4K"], supportedAspectRatios: [ "auto", "1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", ], }); }); }); test.describe("VIP98K reference byte limits", () => { test("compresses a valid upload above the provider limit without cropping", async () => { const source = await sharp(randomBytes(2300 * 2000 * 3), { raw: { width: 2300, height: 2000, channels: 3 }, }).png().toBuffer(); expect(source.byteLength).toBeGreaterThan(VIP98K_REFERENCE_MAX_BYTES); const normalized = await normalizeVip98kReferenceImage(source); expect(isVip98kReferenceSizeAllowed(normalized.buffer.byteLength)).toBe(true); const meta = await sharp(normalized.buffer).metadata(); expect(meta.format).toBe("jpeg"); expect(meta.width).toBe(1920); expect((meta.width || 0) / (meta.height || 1)).toBeCloseTo(2300 / 2000, 2); }); test("never falls back to unprocessed bytes after decode failure", async () => { await expect(normalizeVip98kReferenceImage(Buffer.alloc(VIP98K_REFERENCE_MAX_BYTES + 1))).rejects.toThrow(); expect(isVip98kReferenceSizeAllowed(VIP98K_REFERENCE_MAX_BYTES)).toBe(true); expect(isVip98kReferenceSizeAllowed(VIP98K_REFERENCE_MAX_BYTES + 1)).toBe(false); expect(isVip98kReferenceSizeAllowed(0)).toBe(false); }); test("does not retry a permanently oversized reference as propagation lag", () => { expect(getVip98kStagedMediaRetryDelay({ status: 400, message: "reference image at example.top is larger than 10 MiB", attempt: 1, maxAttempts: 3, stagedReferenceCount: 1, })).toBeNull(); }); }); test.describe("replacement run recovery after backgrounding", () => { const user = (id: string) => ({ id, role: "user", parts: [{ type: "text", text: "replace" }] }); const result = (id: string, run: string, card: string, failed = false) => ({ id, role: "assistant", parts: [{ type: "text", text: `\n\n${failed ? "生成失败,当前先沿用参考图" : "完成"}\n![image](https://example.com/${card}.png)` }], }); const pending = { requestMessageId: "new", userTurnCountAtStart: 1, requestedCardIds: ["a", "b"], expectedCount: 2 }; test("settles each card by ID when parallel responses arrive out of order", () => { const messages = [user("old"), result("old-a", "old", "a"), user("new"), result("new-b", "new", "b")]; const partial = recoverReplacementRun({ messages, ...pending }); expect(partial.completedCardIds).toEqual(["b"]); expect(partial.done).toBe(false); const finished = recoverReplacementRun({ messages: [...messages, result("new-a", "new", "a", true)], ...pending }); expect(finished.done).toBe(true); expect(finished.failedCardIds).toEqual(["a"]); }); test("recovers missed SSE results from persisted history after tab return", () => { const local = [user("old"), user("new")]; const history = [...local, result("a", "new", "a"), result("b", "new", "b", true)]; const merged = mergeGenerationHistory(local, history); expect(recoverReplacementRun({ messages: merged, ...pending }).done).toBe(true); expect(mergeGenerationHistory(merged, history)).toBe(merged); }); test("updates persisted content even if message IDs did not change", () => { const partial = { id: "a", role: "assistant", parts: [{ type: "text", text: "处理中" }] }; const final = result("a", "new", "a", true); expect(mergeGenerationHistory([partial], [final])).toEqual([final]); expect(mergeGenerationHistory([final], [partial])).toEqual([final]); }); test("does not use an old result or duplicate delivery to finish a rerun", () => { const stale = result("a-old", "old", "a"); const messages = [user("old"), stale, user("new"), stale, result("b", "new", "b"), result("b-copy", "new", "b")]; const progress = recoverReplacementRun({ messages, ...pending }); expect(progress.done).toBe(false); expect(progress.completedCardIds).toEqual(["b"]); }); test("recovers legacy pending runs without a request ID", () => { const messages = [user("old"), result("old-a", "old", "a"), user("new"), result("a", "new", "a")]; expect(recoverReplacementRun({ messages, userTurnCountAtStart: 1, requestedCardIds: ["a"], expectedCount: 1 }).done).toBe(true); }); });