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]; 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)); 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" } })).rejects.toThrow("transport-inspection-complete"); expect(posts).toBe(1); 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); }); });