import { expect, test } from "@playwright/test"; import { serverLoader } from "../helpers/canvas-download-server-loader"; import sharp from "sharp"; test("Alibaba terminal task errors preserve the nested provider reason and do not resubmit", async () => { const methods: string[] = []; const loader = serverLoader({ "@/lib/storage/image-archive": {}, "@/lib/storage/service": {}, setTimeout: (callback: () => void, ms: number) => setTimeout(callback, ms === 5000 ? 0 : ms), fetch: async (_url: string, init: RequestInit) => { methods.push(init.method || "GET"); return Response.json(methods.length === 1 ? { output: { task_id: "original-task", task_status: "PENDING" } } : { output: { task_id: "original-task", task_status: "FAILED", code: "InvalidParameter", message: "Failed to download image URL after retries." } }); }, }); const provider = loader("lib/ai/providers/alibaba-image.ts").createAlibabaImage({ apiKey: "fixture-key", baseURL: "https://dashscope.example/api/v1/services/aigc/multimodal-generation/generation" }); let error: unknown; try { await provider.chat("qwen-image-3.0").doGenerate({ messages: [{ role: "user", content: "fixture" }], imageParams: { aspectRatio: "1:1" } }); } catch (caught) { error = caught; } expect(String(error)).toContain("Failed to download image URL after retries."); expect(String(error)).toContain("InvalidParameter"); expect(String(error)).toContain("original-task"); expect(methods).toEqual(["POST", "GET"]); const failure = loader("lib/request-error.ts").getRequestFailureDetails(error); expect(failure).toMatchObject({ code: "PROVIDER_REFERENCE_FETCH_FAILED", message: "模型服务未能读取参考图。" }); expect(failure.recommendation).toContain("模型取图线路"); expect(failure.recommendation).not.toContain("稍后重试"); }); test("Qwen 3 sends reference bytes directly with the right MIME and keeps auto resolution", async () => { const png = await sharp({ create: { width: 32, height: 64, channels: 3, background: "blue" } }).png().toBuffer(); const jpeg = await sharp(png).jpeg().toBuffer(); const webp = await sharp(png).webp().toBuffer(); const originals = [png, jpeg, webp]; const downloads: number[] = []; let submitted: any; let posts = 0; const loader = serverLoader({ "@/lib/storage/image-archive": {}, "@/lib/storage/service": { uploadFile() { throw new Error("Reference transport must not upload an intermediate file"); } }, setTimeout: (callback: () => void, ms: number) => setTimeout(callback, ms === 5000 ? 0 : ms), fetch: async (url: string, init: RequestInit = {}) => { if (init.method === "POST") { posts++; submitted = JSON.parse(String(init.body)); return Response.json({ output: { task_id: "inline-task", task_status: "PENDING" } }); } if (url.includes("/tasks/")) return Response.json({ output: { task_status: "FAILED", message: "transport-inspection-complete" } }); const index = Number(new URL(url).pathname.slice(1)); downloads.push(index); return new Response(originals[index], { headers: { "content-type": ["image/png", "image/jpeg", "image/webp"][index] } }); }, }); const provider = loader("lib/ai/providers/alibaba-image.ts").createAlibabaImage({ apiKey: "fixture-key", baseURL: "https://dashscope.example" }); await expect(provider.chat("qwen-image-3.0").doGenerate({ messages: [{ role: "user", content: [ { type: "text", text: "fixture" }, ...originals.map((_bytes, index) => ({ type: "image", image: `https://source.example/${index}` })), ] }], imageParams: { aspectRatio: "auto", imageSize: "2K" }, referenceImageBuffers: new Map([["https://source.example/0", png]]) })).rejects.toThrow("transport-inspection-complete"); expect(posts).toBe(1); expect(downloads).toEqual([1, 2]); expect(submitted.parameters.size).toBe("1024*2048"); const images = submitted.input.messages[0].content.filter((part: any) => part.image).map((part: any) => part.image); expect(images).toHaveLength(3); images.forEach((value: string, index: number) => { expect(value.split(";", 1)[0]).toBe(["data:image/png", "data:image/jpeg", "data:image/webp"][index]); expect(Buffer.from(value.split(",")[1], "base64").equals(originals[index])).toBe(true); }); }); test("a timed out reference read is safe to retry because no generation was submitted", async () => { const methods: string[] = []; const loader = serverLoader({ "@/lib/storage/image-archive": {}, "@/lib/storage/service": {}, setTimeout: (callback: () => void, ms: number) => setTimeout(callback, ms === 500 ? 0 : ms), fetch: async (_url: string, init: RequestInit = {}) => { methods.push(init.method || "GET"); throw new DOMException("This operation was aborted", "AbortError"); }, }); const provider = loader("lib/ai/providers/alibaba-image.ts").createAlibabaImage({ apiKey: "fixture-key", baseURL: "https://dashscope.example" }); const error = await provider.chat("qwen-image-3.0").doGenerate({ messages: [{ role: "user", content: [ { type: "text", text: "fixture" }, { type: "image", image: "https://source.example/slow.png" }, ] }] }).catch((error: unknown) => error); expect(methods).toEqual(["GET", "GET"]); expect(error).toMatchObject({ confirmedNoResult: true }); expect(loader("lib/request-error.ts").getRequestFailureDetails(error)).toMatchObject({ code: "IMAGE_REFERENCE_READ_TIMEOUT", outcome: "failed", recommendation: "可以稍后重试。", }); }); test("a failed status lookup after Alibaba accepts the request keeps its outcome unknown", async () => { const methods: string[] = []; const loader = serverLoader({ "@/lib/storage/image-archive": {}, "@/lib/storage/service": {}, setTimeout: (callback: () => void, ms: number) => setTimeout(callback, ms === 5000 ? 0 : ms), fetch: async (_url: string, init: RequestInit = {}) => { methods.push(init.method || "GET"); return init.method === "POST" ? Response.json({ output: { task_id: "accepted-task", task_status: "PENDING" } }) : Response.json({ code: "NotFound", message: "Task lookup failed" }, { status: 404 }); }, }); const provider = loader("lib/ai/providers/alibaba-image.ts").createAlibabaImage({ apiKey: "fixture-key", baseURL: "https://dashscope.example" }); const error = await provider.chat("qwen-image-3.0").doGenerate({ messages: [{ role: "user", content: "fixture" }] }).catch((error: unknown) => error); expect(methods).toEqual(["POST", "GET"]); expect(String(error)).toContain("accepted-task"); expect(loader("lib/request-error.ts").getRequestFailureDetails(error)).toMatchObject({ code: "RESULT_UNCONFIRMED", outcome: "unknown" }); });