import { PNG_BASE64, imageFormRequest, readSseEvents, startImagesJsonUpstream, startResponsesImageUpstream, startStreamingImageUpstream, startStreamingResponsesImageUpstream } from './route-test-helpers'; import { registerRouteTestLifecycle } from './route-test-setup'; import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; registerRouteTestLifecycle(); describe('POST /api/images streaming and Responses backends', { concurrency: false }, () => { it('keeps the stable SSE contract for streaming edits', async () => { const { POST } = await import('./route'); let upstreamUrl = ''; let upstreamBody = ''; const upstream = await startStreamingImageUpstream(async (body, url) => { upstreamUrl = url; upstreamBody = body; return [ { event: 'image_edit.partial_image', data: { type: 'image_edit.partial_image', b64_json: 'edit-partial-base64' } }, { event: 'image_edit.completed', data: { type: 'image_edit.completed', b64_json: PNG_BASE64, usage: { input_tokens: 3, output_tokens: 4, total_tokens: 7 } } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', mode: 'edit', stream: true }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['partial_image', 'completed', 'done'] ); assert.equal(events[0].b64_json, 'edit-partial-base64'); assert.equal(events[1].b64_json, PNG_BASE64); assert.equal(events[1].output_format, 'png'); assert.equal((events[2].images as Array>)[0].b64_json, PNG_BASE64); assert.equal(upstreamUrl, '/v1/images/edits'); assert.match(upstreamBody, /name="image\[\]"/); assert.match(upstreamBody, /name="stream"/); assert.match(upstreamBody, /name="partial_images"/); } finally { await upstream.close(); } }); it('accepts snake_case image streaming strategy on page SSE edit requests', async () => { const { POST } = await import('./route'); let upstreamUrl = ''; let upstreamBody = ''; const upstream = await startStreamingImageUpstream(async (body, url) => { upstreamUrl = url; upstreamBody = body; return [ { event: 'image_edit.completed', data: { type: 'image_edit.completed', b64_json: PNG_BASE64 } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', mode: 'edit', stream: true, imageStreamingStrategy: 'force-sse', imageStreamingStrategyField: 'image_streaming_strategy' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['completed', 'done'] ); assert.equal(upstreamUrl, '/v1/images/edits'); assert.match(upstreamBody, /name="stream"/); assert.match(upstreamBody, /name="partial_images"/); } finally { await upstream.close(); } }); it('rejects explicit image stream requests when the server strategy disables streaming', async () => { process.env.IMAGE_STREAMING_STRATEGY = 'off'; const { POST } = await import('./route'); let upstreamCalls = 0; const upstream = await startStreamingImageUpstream(async () => { upstreamCalls += 1; return []; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: true }) ); assert.equal(response.status, 400); const body = (await response.json()) as Record; assert.match(String(body.error), /流式兼容模式已关闭/); assert.equal(upstreamCalls, 0); } finally { await upstream.close(); } }); it('lets request streaming strategy override a disabled server strategy', async () => { process.env.IMAGE_STREAMING_STRATEGY = 'off'; const { POST } = await import('./route'); let upstreamBody = ''; const upstream = await startStreamingImageUpstream(async (body) => { upstreamBody = body; return [ { event: 'image_generation.completed', data: { type: 'image_generation.completed', b64_json: PNG_BASE64 } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: true, imageStreamingStrategy: 'newapi-keepalive-sse' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['completed', 'done'] ); const upstreamJson = JSON.parse(upstreamBody) as Record; assert.equal(upstreamJson.stream, true); assert.equal(upstreamJson.partial_images, 2); } finally { await upstream.close(); } }); it('uses force-sse to stream even when the request omits the legacy stream field', async () => { const { POST } = await import('./route'); let upstreamBody = ''; const upstream = await startStreamingImageUpstream(async (body) => { upstreamBody = body; return [ { event: 'image_generation.completed', data: { type: 'image_generation.completed', b64_json: PNG_BASE64 } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', imageStreamingStrategy: 'force-sse' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['completed', 'done'] ); const upstreamJson = JSON.parse(upstreamBody) as Record; assert.equal(upstreamJson.stream, true); assert.equal(upstreamJson.partial_images, 2); } finally { await upstream.close(); } }); it('normalizes gaoren JSON-as-SSE completed image payloads into the stable client SSE contract', async () => { const { POST } = await import('./route'); let upstreamBody = ''; const upstream = await startStreamingImageUpstream(async (body) => { upstreamBody = body; return [ { event: 'image_generation.completed', data: { type: 'image_generation.completed', data: [{ b64_json: PNG_BASE64 }] } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: true, imageStreamingStrategy: 'newapi-keepalive-sse' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['completed', 'done'] ); assert.equal(events[0].b64_json, PNG_BASE64); assert.equal((events[1].images as Array>)[0].b64_json, PNG_BASE64); const upstreamJson = JSON.parse(upstreamBody) as Record; assert.equal(upstreamJson.stream, true); assert.equal(upstreamJson.partial_images, 2); } finally { await upstream.close(); } }); it('rejects the experimental Responses API backend when the feature flag is disabled', async () => { const { POST } = await import('./route'); const response = await POST( imageFormRequest({ apiBaseUrl: 'http://127.0.0.1:1/v1', apiKey: 'test-key', stream: false, streamMode: 'non_stream', imageBackend: 'responses' }) ); assert.equal(response.status, 400); const body = (await response.json()) as Record; assert.match(String(body.error), /ENABLE_RESPONSES_IMAGE_BACKEND/); }); it('requires a separate Responses API top-level model for the experimental backend', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; const { POST } = await import('./route'); const response = await POST( imageFormRequest({ apiBaseUrl: 'http://127.0.0.1:1/v1', apiKey: 'test-key', stream: false, streamMode: 'non_stream', imageBackend: 'responses' }) ); assert.equal(response.status, 400); const body = (await response.json()) as Record; assert.match(String(body.error), /OPENAI_RESPONSES_API_MODEL|responsesModel/); }); it('rejects unsupported Responses API backend request shapes before contacting upstream', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); const multiImage = await POST( imageFormRequest({ apiBaseUrl: 'http://127.0.0.1:1/v1', apiKey: 'test-key', stream: false, streamMode: 'non_stream', imageBackend: 'responses', n: '2' }) ); assert.equal(multiImage.status, 400); assert.match(String(((await multiImage.json()) as Record).error), /单张生成/); }); it('rejects multi-image edit requests for the Responses API backend before contacting upstream', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); const edit = await POST( imageFormRequest({ apiBaseUrl: 'http://127.0.0.1:1/v1', apiKey: 'test-key', stream: false, streamMode: 'non_stream', imageBackend: 'responses', n: '2', mode: 'edit' }) ); assert.equal(edit.status, 400); assert.match(String(((await edit.json()) as Record).error), /单张编辑/); }); it('uses the Responses API image backend only when the flag and request opt-in are both present', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); let upstreamBody = ''; const upstream = await startResponsesImageUpstream(async (body) => { upstreamBody = body; return { output: [ { type: 'image_generation_call', status: 'completed', result: PNG_BASE64 } ], usage: { input_tokens: 1, output_tokens: 2, total_tokens: 3 } }; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: false, streamMode: 'non_stream', imageBackend: 'responses-image-generation' }) ); assert.equal(response.status, 200); const body = (await response.json()) as { images?: Array>; usage?: unknown }; assert.equal(body.images?.[0]?.b64_json, PNG_BASE64); assert.deepEqual(body.usage, { input_tokens: 1, output_tokens: 2, total_tokens: 3 }); const upstreamJson = JSON.parse(upstreamBody) as Record; assert.equal(upstreamJson.model, 'gpt-4.1'); assert.equal(upstreamJson.stream, false); assert.equal( upstreamJson.tool_choice && (upstreamJson.tool_choice as Record).type, 'image_generation' ); assert.equal(Array.isArray(upstreamJson.tools), true); } finally { await upstream.close(); } }); it('returns a 502 JSON error when non-streaming Responses image_generation fails upstream', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); const upstream = await startResponsesImageUpstream(async () => ({ output: [ { type: 'image_generation_call', status: 'failed', error: { code: 'content_policy_violation', message: 'blocked by upstream policy' } } ] })); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: false, streamMode: 'non_stream', imageBackend: 'responses-image-generation' }) ); assert.equal(response.status, 502); const body = (await response.json()) as Record; assert.match(String(body.error), /blocked by upstream policy/); } finally { await upstream.close(); } }); it('fails non-streaming Images API JSON results that only contain a remote URL', async () => { const { POST } = await import('./route'); const upstream = await startImagesJsonUpstream(async () => ({ data: [{ url: 'https://example.test/final.png' }] })); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: false, streamMode: 'non_stream' }) ); assert.equal(response.status, 502); const body = (await response.json()) as Record; assert.match(String(body.error), /同源/); assert.equal(JSON.stringify(body).includes('https://example.test/final.png'), false); } finally { await upstream.close(); } }); it('streams Responses API image_generation events through the stable page SSE contract', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); let upstreamBody = ''; const upstream = await startStreamingResponsesImageUpstream(async (body) => { upstreamBody = body; return [ { event: 'response.image_generation_call.partial_image', data: { type: 'response.image_generation_call.partial_image', partial_image_b64: 'responses-partial-base64', partial_image_index: 0 } }, { event: 'response.output_item.done', data: { type: 'response.output_item.done', item: { type: 'image_generation_call', status: 'completed', result: PNG_BASE64 } } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: true, imageBackend: 'responses', imageStreamingStrategy: 'responses-sse' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['partial_image', 'completed', 'done'] ); assert.equal(events[0].b64_json, 'responses-partial-base64'); assert.equal(events[1].b64_json, PNG_BASE64); assert.equal((events[2].images as Array>)[0].b64_json, PNG_BASE64); const upstreamJson = JSON.parse(upstreamBody) as Record; assert.equal(upstreamJson.model, 'gpt-4.1'); assert.equal(upstreamJson.stream, true); const tools = upstreamJson.tools as Array>; assert.equal(tools[0].type, 'image_generation'); assert.equal(tools[0].partial_images, 2); } finally { await upstream.close(); } }); it('rejects partial image counts outside the Responses streaming contract', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); const response = await POST( imageFormRequest({ apiBaseUrl: 'https://img.matsca.com/v1', apiKey: 'test-key', stream: true, imageBackend: 'responses', imageStreamingStrategy: 'responses-sse', partialImages: '4' }) ); assert.equal(response.status, 400); const body = (await response.json()) as Record; assert.match(String(body.error), /Responses API.*partial_images/); }); it('keeps separate Responses final items when their base64 payloads match', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); const upstream = await startStreamingResponsesImageUpstream(async () => [ { event: 'response.output_item.done', data: { type: 'response.output_item.done', item: { id: 'ig_same_payload_a', type: 'image_generation_call', status: 'completed', result: PNG_BASE64 } } }, { event: 'response.output_item.done', data: { type: 'response.output_item.done', item: { id: 'ig_same_payload_b', type: 'image_generation_call', status: 'completed', result: PNG_BASE64 } } } ]); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: true, imageBackend: 'responses', imageStreamingStrategy: 'responses-sse' }) ); assert.equal(response.status, 200); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['completed', 'completed', 'done'] ); assert.equal((events[2].images as Array>).length, 2); } finally { await upstream.close(); } }); it('uses force-sse for Responses image_generation without the legacy stream field', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); let upstreamBody = ''; const upstream = await startStreamingResponsesImageUpstream(async (body) => { upstreamBody = body; return [ { event: 'response.output_item.done', data: { type: 'response.output_item.done', item: { type: 'image_generation_call', status: 'completed', result: PNG_BASE64 } } } ]; }); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: false, imageBackend: 'responses-image-generation', imageStreamingStrategy: 'force-sse' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['completed', 'done'] ); assert.equal(events[0].b64_json, PNG_BASE64); const upstreamJson = JSON.parse(upstreamBody) as Record; assert.equal(upstreamJson.stream, true); assert.equal(upstreamJson.model, 'gpt-4.1'); const tools = upstreamJson.tools as Array>; assert.equal(tools[0].type, 'image_generation'); assert.equal(tools[0].partial_images, 2); } finally { await upstream.close(); } }); it('turns failed Responses image_generation calls into the stable page SSE error contract', async () => { process.env.ENABLE_RESPONSES_IMAGE_BACKEND = 'true'; process.env.OPENAI_RESPONSES_API_MODEL = 'gpt-4.1'; const { POST } = await import('./route'); const upstream = await startStreamingResponsesImageUpstream(async () => [ { event: 'response.output_item.done', data: { type: 'response.output_item.done', item: { type: 'image_generation_call', status: 'failed', error: { code: 'content_policy_violation', message: 'blocked by upstream policy' } } } } ]); try { const response = await POST( imageFormRequest({ apiBaseUrl: upstream.baseUrl, apiKey: 'test-key', stream: true, imageBackend: 'responses', imageStreamingStrategy: 'responses-sse' }) ); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'text/event-stream'); const events = await readSseEvents(response); assert.deepEqual( events.map((event) => event.type), ['error'] ); assert.equal(events[0].status, 502); assert.match(String(events[0].error), /blocked by upstream policy/); } finally { await upstream.close(); } }); });