import test from "node:test"; import assert from "node:assert/strict"; import { encryptSecret } from "../src/shared/auth.ts"; import { createStore } from "../src/store.ts"; import { invokeProvider, probeProviderModels, queryProvider } from "../src/adapters/provider.ts"; const png = Buffer.from("89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489", "hex"); function channel(store: ReturnType) { return { ...store.channels[0], providerType: "openai-images" as const, baseUrl: "https://1.1.1.1/v1", models: [ { requestModelId: "base-model", source: "manual" as const, enabled: true }, { requestModelId: "mapped-2k", source: "manual" as const, enabled: true }, { requestModelId: "供应商模型", source: "manual" as const, enabled: true }, ], modelMappings: [], secretRef: encryptSecret("1234567890123456", store.channelEncryptionKey), }; } test("OpenAI image generation uses the mapped model and normalized v1 endpoint", async () => { const store = createStore(); const requests: Array<{ url: string; body: unknown; headers: Headers }> = []; const previousFetch = globalThis.fetch; globalThis.fetch = async (input, init) => { requests.push({ url: String(input), body: init?.body, headers: new Headers(init?.headers) }); return new Response(JSON.stringify({ data: [{ b64_json: png.toString("base64") }] }), { status: 200, headers: { "content-type": "application/json", "x-request-id": "request-123" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "task-1", taskType: "image", requestModelId: "base-model", prompt: "一只猫", count: 1, params: { resolution: "2K" }, platformIdempotencyKey: "idem-1" }); assert.equal(result.status, "succeeded"); assert.equal(result.providerRequestId, "request-123"); assert.equal(result.outputs?.[0]?.mimeType, "image/png"); assert.equal(requests[0]?.url, "https://1.1.1.1/v1/images/generations"); assert.equal(requests[0]?.headers.get("authorization"), "Bearer 1234567890123456"); assert.equal(requests[0]?.headers.get("idempotency-key"), "idem-1"); assert.equal((JSON.parse(String(requests[0]?.body)) as Record).model, "base-model"); } finally { globalThis.fetch = previousFetch; } }); test("display model IDs resolve to distinct upstream request model IDs", async () => { const store = createStore(); const configured = { ...channel(store), modelMappings: [{ displayModelId: "平台模型", requestModelId: "供应商模型" }] }; const previousFetch = globalThis.fetch; globalThis.fetch = async (_input, init) => { assert.equal((JSON.parse(String(init?.body)) as Record).model, "供应商模型"); return new Response(JSON.stringify({ data: [{ b64_json: png.toString("base64") }] }), { status: 200, headers: { "content-type": "application/json" } }); }; try { const result = await invokeProvider(store, configured, { taskId: "mapping-task", taskType: "image", publicModelId: "平台模型", prompt: "测试", params: { resolution: "2K" }, platformIdempotencyKey: "mapping-idem" }); assert.equal(result.status, "succeeded"); } finally { globalThis.fetch = previousFetch; } }); test("OpenAI image edits preserve reference order with image[] fields", async () => { const store = createStore(); for (const [id, color] of [["ref-a", 0x11], ["ref-b", 0x22]] as const) { const bytes = Buffer.from(png); bytes[bytes.length - 1] ^= color; store.objects.set(id, { id, ownerId: "user-1", mimeType: "image/png", data: bytes.toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString() }); } const previousFetch = globalThis.fetch; globalThis.fetch = async (_input, init) => { const form = init?.body as FormData; const names = [...form.getAll("image[]")].map((value) => (value instanceof File ? value.name : "")); assert.deepEqual(names, ["reference-0.png", "reference-1.png"]); assert.equal(form.get("model"), "base-model"); assert.equal(new Headers(init?.headers).get("idempotency-key"), "idem-2"); return new Response(JSON.stringify({ data: [{ b64_json: png.toString("base64") }] }), { status: 200, headers: { "content-type": "application/json" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "task-2", taskType: "image", requestModelId: "base-model", prompt: "@图片一和@图片二", count: 1, referenceImages: [{ objectId: "ref-b", seq: 2, name: "图片二" }, { objectId: "ref-a", seq: 1, name: "图片一" }], platformIdempotencyKey: "idem-2" }); assert.equal(result.status, "succeeded"); } finally { globalThis.fetch = previousFetch; } }); test("invalid image base64 and empty model probes fail closed", async () => { const store = createStore(); const previousFetch = globalThis.fetch; globalThis.fetch = async (input) => { if (String(input).endsWith("/models")) return new Response(JSON.stringify({ data: [] }), { status: 200, headers: { "content-type": "application/json" } }); return new Response(JSON.stringify({ data: [{ b64_json: "not-base64" }] }), { status: 200, headers: { "content-type": "application/json" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "task-3", taskType: "image", requestModelId: "base-model", prompt: "测试", count: 1, platformIdempotencyKey: "idem-3" }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_INVALID_OUTPUT"); await assert.rejects(() => probeProviderModels("https://1.1.1.1/v1", "1234567890123456")); } finally { globalThis.fetch = previousFetch; } }); test("duplicate references are uploaded once and URL outputs are validated", async () => { const store = createStore(); store.objects.set("ref-a", { id: "ref-a", ownerId: "user-1", mimeType: "image/png", data: png.toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString() }); const previousFetch = globalThis.fetch; const urls: string[] = []; globalThis.fetch = async (input, init) => { urls.push(String(input)); if (String(input).endsWith("/images/edits")) { const form = init?.body as FormData; assert.equal(form.getAll("image[]").length, 1); assert.equal(form.get("prompt"), "使用第一张图"); return new Response(JSON.stringify({ data: [{ url: "https://1.1.1.1/result.png" }] }), { status: 200, headers: { "content-type": "application/json" } }); } return new Response(png, { status: 200, headers: { "content-type": "image/png" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "task-4", requestModelId: "base-model", taskType: "image", prompt: "使用@图片一", count: 1, referenceImages: [ { objectId: "ref-a", seq: 1, name: "图片一" }, { objectId: "ref-a", seq: 2, name: "图片二" }, ], platformIdempotencyKey: "idem-4", }); assert.equal(result.status, "succeeded"); assert.equal(result.outputs?.[0]?.mimeType, "image/png"); assert.deepEqual(urls, ["https://1.1.1.1/v1/images/edits", "https://1.1.1.1/result.png"]); } finally { globalThis.fetch = previousFetch; } }); test("unavailable reference objects fail without retrying the provider", async () => { const store = createStore(); const previousFetch = globalThis.fetch; let called = false; globalThis.fetch = async () => { called = true; return new Response(JSON.stringify({ data: [] }), { status: 200, headers: { "content-type": "application/json" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "task-5", requestModelId: "base-model", taskType: "image", prompt: "使用第一张图", count: 1, referenceImages: [{ objectId: "missing", seq: 1, name: "图片一" }], platformIdempotencyKey: "idem-5", }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_REFERENCE_UNAVAILABLE"); assert.equal(called, false); } finally { globalThis.fetch = previousFetch; } }); test("expired, revoked, and missing staging references fail deterministically", async () => { const store = createStore(); store.objects.set("expired", { id: "expired", ownerId: "user-1", mimeType: "image/png", data: png.toString("base64"), expiresAt: new Date(Date.now() - 1).toISOString() }); store.objects.set("revoked", { id: "revoked", ownerId: "user-1", mimeType: "image/png", data: png.toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString(), revoked: true }); store.objects.set("staging-missing", { id: "staging-missing", ownerId: "user-1", mimeType: "image/png", stagingKey: "missing/reference.png", expiresAt: new Date(Date.now() + 60_000).toISOString() }); const previousFetch = globalThis.fetch; let called = false; globalThis.fetch = async () => { called = true; return new Response(JSON.stringify({ data: [] }), { status: 200 }); }; try { for (const [index, objectId] of ["expired", "revoked", "staging-missing"].entries()) { const result = await invokeProvider(store, channel(store), { taskId: `reference-unavailable-${index}`, taskType: "image", requestModelId: "base-model", prompt: "使用第一张图", count: 1, referenceImages: [{ objectId, seq: 1, name: "图片一" }], platformIdempotencyKey: `reference-unavailable-${index}` }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_REFERENCE_UNAVAILABLE"); assert.equal(result.retryable, false); } assert.equal(called, false); } finally { globalThis.fetch = previousFetch; } }); test("invalid image output URLs are non-retryable provider failures", async () => { const store = createStore(); const previousFetch = globalThis.fetch; const outputs = [ "not a url", "https://127.0.0.1/result.png", "https://1.1.1.1/result.png", ]; try { for (const [index, outputUrl] of outputs.entries()) { globalThis.fetch = async (input) => { if (String(input).endsWith("/images/generations")) return new Response(JSON.stringify({ data: [{ url: outputUrl }] }), { status: 200, headers: { "content-type": "application/json" } }); return new Response(index === 2 ? png : "not an image", { status: index === 2 ? 404 : 200, headers: { "content-type": "image/png" } }); }; const result = await invokeProvider(store, channel(store), { taskId: `invalid-output-${index}`, taskType: "image", requestModelId: "base-model", prompt: "测试", count: 1, platformIdempotencyKey: `invalid-output-${index}` }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_INVALID_OUTPUT"); assert.equal(result.retryable, false); } } finally { globalThis.fetch = previousFetch; } }); test("redirected image output URLs are rejected as invalid output", async () => { const store = createStore(); const previousFetch = globalThis.fetch; let downloadRedirected = false; globalThis.fetch = async (input) => { if (String(input).endsWith("/images/generations")) return new Response(JSON.stringify({ data: [{ url: "https://1.1.1.1/result.png" }] }), { status: 200, headers: { "content-type": "application/json" } }); downloadRedirected = true; return new Response(null, { status: 302, headers: { location: "https://example.com/private.png" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "redirect-output", taskType: "image", requestModelId: "base-model", prompt: "测试", count: 1, platformIdempotencyKey: "redirect-output" }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_INVALID_OUTPUT"); assert.equal(result.retryable, false); assert.equal(downloadRedirected, true); } finally { globalThis.fetch = previousFetch; } }); test("image output metadata prefers dimensions decoded from the returned bytes", async () => { const store = createStore(); const previousFetch = globalThis.fetch; globalThis.fetch = async (input) => { if (String(input).endsWith("/images/generations")) return new Response(JSON.stringify({ data: [{ b64_json: png.toString("base64"), size: "999x999" }] }), { status: 200, headers: { "content-type": "application/json" } }); return new Response(null, { status: 500 }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "metadata-bytes", taskType: "image", requestModelId: "base-model", prompt: "测试", count: 1, platformIdempotencyKey: "metadata-bytes" }); assert.equal(result.status, "succeeded"); assert.deepEqual(result.outputs?.[0]?.metadata, { width: 1, height: 1, format: "png", size: "1x1", revisedPrompt: undefined, usage: undefined, source: "base64" }); } finally { globalThis.fetch = previousFetch; } }); test("image references validate mentions and add a default reference instruction", async () => { const store = createStore(); store.objects.set("ref-a", { id: "ref-a", ownerId: "user-1", mimeType: "image/png", data: png.toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString() }); const previousFetch = globalThis.fetch; const prompts: string[] = []; globalThis.fetch = async (_input, init) => { const form = init?.body as FormData; prompts.push(String(form.get("prompt"))); return new Response(JSON.stringify({ data: [{ b64_json: png.toString("base64") }] }), { status: 200, headers: { "content-type": "application/json" } }); }; try { const result = await invokeProvider(store, channel(store), { taskId: "task-6", taskType: "image", requestModelId: "base-model", prompt: "请保持主体", count: 1, referenceImages: [{ objectId: "ref-a", seq: 1, name: "图片一" }], platformIdempotencyKey: "idem-6" }); assert.equal(result.status, "succeeded"); assert.equal(prompts[0], "请保持主体\n基于以下参考图生成。"); const missing = await invokeProvider(store, channel(store), { taskId: "task-7", taskType: "image", requestModelId: "base-model", prompt: "使用@图片二", count: 1, referenceImages: [{ objectId: "ref-a", seq: 1, name: "图片一" }], platformIdempotencyKey: "idem-7" }); assert.equal(missing.status, "failed"); assert.equal(missing.errorCode, "PROVIDER_REFERENCE_UNAVAILABLE"); } finally { globalThis.fetch = previousFetch; } }); test("invalid provider reference sequences fail closed", async () => { const store = createStore(); const result = await invokeProvider(store, channel(store), { taskId: "task-8", taskType: "image", requestModelId: "base-model", prompt: "测试", count: 1, referenceImages: [{ objectId: "ref-a", seq: 17, name: "图片十七" }], platformIdempotencyKey: "idem-8" }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_REFERENCE_INVALID"); }); test("duplicate reference sequences are rejected instead of producing ambiguous 图片 names", async () => { const store = createStore(); for (const objectId of ["duplicate-seq-a", "duplicate-seq-b"]) store.objects.set(objectId, { id: objectId, ownerId: "user-1", mimeType: "image/png", data: png.toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString() }); const result = await invokeProvider(store, channel(store), { taskId: "duplicate-seq", taskType: "image", requestModelId: "base-model", prompt: "测试", count: 1, referenceImages: [{ objectId: "duplicate-seq-a", seq: 1, name: "图片一" }, { objectId: "duplicate-seq-b", seq: 1, name: "图片一" }], platformIdempotencyKey: "duplicate-seq" }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_REFERENCE_INVALID"); }); test("provider image requests reject non-positive or fractional counts", async () => { const store = createStore(); const previousFetch = globalThis.fetch; let called = false; globalThis.fetch = async () => { called = true; return new Response(JSON.stringify({ data: [] }), { status: 200 }); }; try { for (const count of [0, -1, 1.5]) { const result = await invokeProvider(store, channel(store), { taskId: `invalid-count-${count}`, taskType: "image", requestModelId: "base-model", prompt: "测试", count, platformIdempotencyKey: `invalid-count-${String(count).replace(".", "-")}` }); assert.equal(result.status, "failed"); assert.equal(result.errorCode, "PROVIDER_INVALID_REQUEST"); } assert.equal(called, false); } finally { globalThis.fetch = previousFetch; } }); test("OpenAI image edits accept the full sixteen-reference boundary and reject the seventeenth", async () => { const store = createStore(); const references = Array.from({ length: 16 }, (_, index) => { const objectId = `boundary-ref-${index + 1}`; store.objects.set(objectId, { id: objectId, ownerId: "user-1", mimeType: "image/png", data: png.toString("base64"), expiresAt: new Date(Date.now() + 60_000).toISOString() }); return { objectId, seq: index + 1, name: `图片${index + 1}` }; }); const previousFetch = globalThis.fetch; globalThis.fetch = async (_input, init) => { const form = init?.body as FormData; assert.equal(form.getAll("image[]").length, 16); return new Response(JSON.stringify({ data: [{ b64_json: png.toString("base64") }] }), { status: 200, headers: { "content-type": "application/json" } }); }; try { const accepted = await invokeProvider(store, channel(store), { taskId: "sixteen-references", taskType: "image", requestModelId: "base-model", prompt: "组合参考图", count: 1, referenceImages: references, platformIdempotencyKey: "sixteen-references" }); assert.equal(accepted.status, "succeeded"); const rejected = await invokeProvider(store, channel(store), { taskId: "seventeen-references", taskType: "image", requestModelId: "base-model", prompt: "组合参考图", count: 1, referenceImages: [...references, { objectId: "boundary-ref-1", seq: 17, name: "图片十七" }], platformIdempotencyKey: "seventeen-references" }); assert.equal(rejected.status, "failed"); assert.equal(rejected.errorCode, "PROVIDER_REFERENCE_LIMIT"); } finally { globalThis.fetch = previousFetch; } }); test("provider output metadata detects JPEG and WebP bytes instead of trusting a declared format", async () => { const store = createStore(); const jpeg = Buffer.from("ffd8ffc00011080001000101011100021100031100ffd9", "hex"); const webp = Buffer.alloc(30); webp.write("RIFF", 0, "ascii"); webp.writeUInt32LE(22, 4); webp.write("WEBP", 8, "ascii"); webp.write("VP8X", 12, "ascii"); webp.writeUInt32LE(10, 16); const previousFetch = globalThis.fetch; let requestedFormat = "jpeg"; globalThis.fetch = async (input) => { const url = String(input); if (url.endsWith("/images/generations")) return new Response(JSON.stringify({ data: [{ url: `https://1.1.1.1/result.${requestedFormat}` }] }), { status: 200, headers: { "content-type": "application/json" } }); const bytes = requestedFormat === "jpeg" ? jpeg : webp; return new Response(bytes, { status: 200, headers: { "content-type": requestedFormat === "jpeg" ? "image/jpg" : "image/webp" } }); }; try { for (const [index, suffix] of ["jpeg", "webp"].entries()) { requestedFormat = suffix; const result = await invokeProvider(store, channel(store), { taskId: `format-${suffix}`, taskType: "image", requestModelId: "base-model", prompt: "格式测试", count: 1, params: { output_format: suffix }, platformIdempotencyKey: `format-${index}` }); assert.equal(result.status, "succeeded"); assert.equal(result.outputs?.[0]?.mimeType, `image/${suffix}`); assert.deepEqual(result.outputs?.[0]?.metadata && { width: result.outputs[0].metadata.width, height: result.outputs[0].metadata.height, format: result.outputs[0].metadata.format }, { width: 1, height: 1, format: suffix }); } } finally { globalThis.fetch = previousFetch; } }); test("provider probes use GET and Bearer authentication, and transport failures become unknown", async () => { const store = createStore(); const previousFetch = globalThis.fetch; const probeRequests: Array<{ method: string; authorization: string | null }> = []; globalThis.fetch = async (input, init) => { if (String(input).endsWith("/models")) { probeRequests.push({ method: init?.method || "GET", authorization: new Headers(init?.headers).get("authorization") }); return new Response(JSON.stringify({ data: [{ id: "gpt-image-2" }] }), { status: 200, headers: { "content-type": "application/json" } }); } throw new Error("socket closed"); }; try { const probe = await probeProviderModels("https://1.1.1.1/v1", "1234567890123456"); assert.deepEqual(probe, { healthy: true, models: ["gpt-image-2"] }); assert.deepEqual(probeRequests, [{ method: "GET", authorization: "Bearer 1234567890123456" }]); const unknown = await invokeProvider(store, channel(store), { taskId: "transport-unknown", taskType: "image", requestModelId: "base-model", prompt: "网络中断", count: 1, platformIdempotencyKey: "transport-unknown" }); assert.equal(unknown.status, "unknown"); assert.equal(unknown.errorCode, "PROVIDER_TRANSPORT_UNKNOWN"); const abortedFetch = globalThis.fetch; globalThis.fetch = async () => { throw Object.assign(new Error("timeout"), { name: "AbortError" }); }; const timeout = await invokeProvider(store, channel(store), { taskId: "timeout-unknown", taskType: "image", requestModelId: "base-model", prompt: "超时", count: 1, platformIdempotencyKey: "timeout-unknown" }); assert.equal(timeout.status, "unknown"); assert.equal(timeout.errorCode, "PROVIDER_TIMEOUT_UNKNOWN"); globalThis.fetch = async () => { throw new Error("probe transport failed"); }; await assert.rejects(() => probeProviderModels("https://1.1.1.1/v1", "1234567890123456")); globalThis.fetch = abortedFetch; } finally { globalThis.fetch = previousFetch; } }); test("OpenAI image channels stay in manual reconciliation because no query endpoint is assumed", async () => { const store = createStore(); const result = await queryProvider(store, channel(store), "provider-request-1"); assert.deepEqual(result, { status: "unknown", providerRequestId: "provider-request-1", errorCode: "PROVIDER_QUERY_UNAVAILABLE" }); });