Spaces:
Running
Running
| import http from 'node:http'; | |
| import { pathToFileURL } from 'node:url'; | |
| export const FIXTURE_IMAGE_BASE64 = | |
| 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII='; | |
| export const MAX_JSON_BODY_BYTES = 1024 * 1024; | |
| export const FIXTURE_IMAGE_PATH = '/api/storage/generations/fixture.png'; | |
| class PayloadTooLargeError extends Error { | |
| constructor() { | |
| super('Request body too large'); | |
| this.name = 'PayloadTooLargeError'; | |
| this.status = 413; | |
| } | |
| } | |
| function readJsonBody(request) { | |
| return new Promise((resolve, reject) => { | |
| let raw = ''; | |
| let byteLength = 0; | |
| let tooLarge = false; | |
| let settled = false; | |
| const cleanup = () => { | |
| request.off('data', onData); | |
| request.off('end', onEnd); | |
| request.off('error', onError); | |
| }; | |
| const fail = (error) => { | |
| if (settled) return; | |
| settled = true; | |
| cleanup(); | |
| reject(error); | |
| }; | |
| const finish = (value) => { | |
| if (settled) return; | |
| settled = true; | |
| cleanup(); | |
| resolve(value); | |
| }; | |
| const onData = (chunk) => { | |
| byteLength += Buffer.byteLength(chunk, 'utf8'); | |
| if (byteLength > MAX_JSON_BODY_BYTES) { | |
| tooLarge = true; | |
| return; | |
| } | |
| if (tooLarge) return; | |
| raw += chunk; | |
| }; | |
| const onEnd = () => { | |
| if (tooLarge) { | |
| fail(new PayloadTooLargeError()); | |
| return; | |
| } | |
| if (!raw.trim()) { | |
| finish({}); | |
| return; | |
| } | |
| try { | |
| finish(JSON.parse(raw)); | |
| } catch (error) { | |
| fail(error); | |
| } | |
| }; | |
| const onError = (error) => fail(error); | |
| request.setEncoding('utf8'); | |
| request.on('data', onData); | |
| request.on('end', onEnd); | |
| request.on('error', onError); | |
| }); | |
| } | |
| function sendJson(response, status, body) { | |
| response.writeHead(status, { 'Content-Type': 'application/json' }); | |
| response.end(JSON.stringify(body)); | |
| } | |
| function sendFixtureImage(response) { | |
| const buffer = Buffer.from(FIXTURE_IMAGE_BASE64, 'base64'); | |
| response.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': String(buffer.byteLength) }); | |
| response.end(buffer); | |
| } | |
| function writeSse(response, event, payload) { | |
| if (event) response.write(`event: ${event}\n`); | |
| response.write(`data: ${JSON.stringify(payload)}\n\n`); | |
| } | |
| function sendImagesStream(response) { | |
| response.writeHead(200, { | |
| 'Content-Type': 'text/event-stream', | |
| 'Cache-Control': 'no-cache', | |
| Connection: 'keep-alive' | |
| }); | |
| response.write(': keepalive\n\n'); | |
| writeSse(response, 'image_generation.partial_image', { | |
| type: 'image_generation.partial_image', | |
| partial_image_index: 0, | |
| b64_json: FIXTURE_IMAGE_BASE64 | |
| }); | |
| writeSse(response, 'image_generation.completed', { | |
| type: 'image_generation.completed', | |
| b64_json: FIXTURE_IMAGE_BASE64, | |
| output_format: 'png', | |
| size: '1024x1024', | |
| quality: 'low' | |
| }); | |
| response.end('data: [DONE]\n\n'); | |
| } | |
| function sendResponsesStream(response) { | |
| response.writeHead(200, { | |
| 'Content-Type': 'text/event-stream', | |
| 'Cache-Control': 'no-cache', | |
| Connection: 'keep-alive' | |
| }); | |
| writeSse(response, 'response.image_generation_call.partial_image', { | |
| type: 'response.image_generation_call.partial_image', | |
| partial_image_b64: FIXTURE_IMAGE_BASE64, | |
| partial_image_index: 0 | |
| }); | |
| writeSse(response, 'response.output_item.done', { | |
| type: 'response.output_item.done', | |
| item: { | |
| id: 'ig_fixture', | |
| type: 'image_generation_call', | |
| status: 'completed', | |
| result: FIXTURE_IMAGE_PATH | |
| } | |
| }); | |
| writeSse(response, 'response.completed', { | |
| type: 'response.completed', | |
| response: { | |
| output: [ | |
| { | |
| id: 'ig_fixture', | |
| type: 'image_generation_call', | |
| status: 'completed', | |
| result: FIXTURE_IMAGE_PATH | |
| } | |
| ] | |
| } | |
| }); | |
| response.end('data: [DONE]\n\n'); | |
| } | |
| function imagesResponse(body) { | |
| const created = Math.floor(Date.now() / 1000); | |
| return { | |
| created, | |
| data: [{ b64_json: FIXTURE_IMAGE_BASE64 }], | |
| usage: { | |
| input_tokens: 1, | |
| output_tokens: 1, | |
| total_tokens: 2 | |
| }, | |
| fixture_request: { | |
| model: typeof body.model === 'string' ? body.model : undefined, | |
| stream: body.stream === true | |
| } | |
| }; | |
| } | |
| function responsesJsonResponse(body) { | |
| return { | |
| id: 'resp_fixture', | |
| object: 'response', | |
| model: typeof body.model === 'string' ? body.model : 'gpt-5.4', | |
| output: [{ id: 'ig_fixture', type: 'image_generation_call', status: 'completed', result: FIXTURE_IMAGE_PATH }], | |
| usage: { | |
| input_tokens: 1, | |
| output_tokens: 1, | |
| total_tokens: 2 | |
| } | |
| }; | |
| } | |
| function modelsResponse() { | |
| return { | |
| object: 'list', | |
| data: [ | |
| { id: 'gpt-image-2', object: 'model', owned_by: 'fixture' }, | |
| { id: 'gpt-5.4', object: 'model', owned_by: 'fixture' } | |
| ] | |
| }; | |
| } | |
| async function handleRequest(request, response) { | |
| const url = new URL(request.url || '/', 'http://fixture.local'); | |
| if (request.method === 'GET' && url.pathname === '/health') { | |
| sendJson(response, 200, { ok: true }); | |
| return; | |
| } | |
| if (request.method === 'GET' && url.pathname === '/v1/models') { | |
| sendJson(response, 200, modelsResponse()); | |
| return; | |
| } | |
| if (request.method === 'GET' && url.pathname === FIXTURE_IMAGE_PATH) { | |
| sendFixtureImage(response); | |
| return; | |
| } | |
| if (request.method === 'POST' && url.pathname === '/v1/images/generations') { | |
| const body = await readJsonBody(request); | |
| if (body.stream === true) { | |
| sendImagesStream(response); | |
| return; | |
| } | |
| sendJson(response, 200, imagesResponse(body)); | |
| return; | |
| } | |
| if (request.method === 'POST' && url.pathname === '/v1/responses') { | |
| const body = await readJsonBody(request); | |
| if (body.stream === true) { | |
| sendResponsesStream(response); | |
| return; | |
| } | |
| sendJson(response, 200, responsesJsonResponse(body)); | |
| return; | |
| } | |
| sendJson(response, 404, { error: { message: `No fixture route for ${request.method} ${url.pathname}` } }); | |
| } | |
| export function createFixtureServer() { | |
| return http.createServer((request, response) => { | |
| handleRequest(request, response).catch((error) => { | |
| const status = error instanceof PayloadTooLargeError ? error.status : 500; | |
| sendJson(response, status, { error: { message: error instanceof Error ? error.message : String(error) } }); | |
| }); | |
| }); | |
| } | |
| export function startFixtureServer(port = Number(process.env.PORT || 19080), host = process.env.HOST || '127.0.0.1') { | |
| const server = createFixtureServer(); | |
| server.listen(port, host, () => { | |
| const address = server.address(); | |
| const resolvedPort = typeof address === 'object' && address ? address.port : port; | |
| console.log(`local image upstream fixture listening on http://${host}:${resolvedPort}`); | |
| }); | |
| return server; | |
| } | |
| if (import.meta.url === pathToFileURL(process.argv[1]).href) { | |
| startFixtureServer(); | |
| } | |