visual-journal / scripts /local-image-upstream-fixture.mjs
misonL's picture
Deploy 76de60b to Docker Space
e445b51 verified
Raw
History Blame Contribute Delete
7.75 kB
#!/usr/bin/env node
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();
}