import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { createServer } from 'node:http'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, it } from 'node:test'; import { fileURLToPath } from 'node:url'; const repoRoot = fileURLToPath(new URL('..', import.meta.url)); const skillScriptsRoot = join(repoRoot, 'skills/gpt-image-playground-agent/scripts'); describe('Page form streaming defaults', () => { it('does not force stream=true for page generate JSON responses', async () => { let requestBody = ''; await withServer( async (request, response) => { if (request.url === '/api/agent/capabilities') { writePageCapabilities(response); return; } if (request.url === '/api/images') { requestBody = await readRequestText(request); writePageImageResult(response, 'generate.webp', 'generate-page-json-key'); return; } writeMissing(response); }, async (baseUrl) => { const result = await runSkillScript( 'generate-image.mjs', [ '--allow-billable', '--page-sse', '--format', 'webp', '--idempotency-key', 'generate-page-json-key', 'generate prompt' ], { GPT_IMAGE_PLAYGROUND_URL: baseUrl } ); assert.equal(result.status, 0); assert.equal(result.stderr.trim(), ''); const body = JSON.parse(result.stdout); assert.equal(body.images[0].filename, 'generate.webp'); assert.equal(body.images[0].absolute_path, `${baseUrl}/api/image/generate.webp`); assert.doesNotMatch(requestBody, /name="stream"/); } ); }); it('does not force stream=true for default page edit JSON responses', async () => { const tempRoot = mkdtempSync(join(tmpdir(), 'gpt-image-page-form-edit-')); try { const imagePath = join(tempRoot, 'source.png'); writeFileSync(imagePath, fakePngBuffer(2, 1)); let requestBody = ''; await withServer( async (request, response) => { if (request.url === '/api/agent/capabilities') { writePageCapabilities(response); return; } if (request.url === '/api/images') { requestBody = await readRequestText(request); writePageImageResult(response, 'edit.webp', 'edit-page-json-key'); return; } writeMissing(response); }, async (baseUrl) => { const result = await runSkillScript( 'edit-image.mjs', [ '--allow-billable', '--idempotency-key', 'edit-page-json-key', imagePath, 'edit prompt' ], { GPT_IMAGE_PLAYGROUND_URL: baseUrl } ); assert.equal(result.status, 0); assert.equal(result.stderr.trim(), ''); const body = JSON.parse(result.stdout); assert.equal(body.images[0].filename, 'edit.webp'); assert.equal(body.images[0].absolute_path, `${baseUrl}/api/image/edit.webp`); assert.doesNotMatch(requestBody, /name="stream"/); } ); } finally { rmSync(tempRoot, { recursive: true, force: true }); } }); it('does not force stream=true for batch page edit JSON responses', async () => { const tempRoot = mkdtempSync(join(tmpdir(), 'gpt-image-page-form-batch-')); try { const imagePath = join(tempRoot, 'source.png'); const inputPath = join(tempRoot, 'tasks.jsonl'); writeFileSync(imagePath, fakePngBuffer(2, 1)); writeFileSync( inputPath, JSON.stringify({ id: 'batch-page-json', mode: 'edit', prompt: 'batch edit prompt', image_path: imagePath, size: '1024x1024', idempotency_key: 'batch-page-json-key' }) ); let requestBody = ''; await withServer( async (request, response) => { if (request.url === '/api/agent/capabilities') { writePageCapabilities(response); return; } if (request.url === '/api/images') { requestBody = await readRequestText(request); writePageImageResult(response, 'batch.webp', 'batch-page-json-key'); return; } writeMissing(response); }, async (baseUrl) => { const result = await runSkillScript( 'batch-images.mjs', ['--allow-billable', '--input', inputPath], { GPT_IMAGE_PLAYGROUND_URL: baseUrl } ); assert.equal(result.status, 0); assert.equal(result.stderr.trim(), ''); const body = JSON.parse(result.stdout); assert.equal(body.results[0].status, 'succeeded'); assert.equal(body.results[0].response.images[0].filename, 'batch.webp'); assert.equal( body.results[0].response.images[0].absolute_path, `${baseUrl}/api/image/batch.webp` ); assert.doesNotMatch(requestBody, /name="stream"/); } ); } finally { rmSync(tempRoot, { recursive: true, force: true }); } }); }); function writePageCapabilities(response) { response.writeHead(200, { 'content-type': 'application/json' }); response.end( JSON.stringify({ agent_streaming: { page_sse: { supported: true, endpoint: '/api/images', auth: { required: false, schemes: [], form_field: 'passwordHash' } } }, agent_jobs: { supported: true, mode: 'job_polling' } }) ); } function writePageImageResult(response, filename, clientRequestId) { response.writeHead(200, { 'content-type': 'application/json' }); response.end( JSON.stringify({ images: [ { filename, path: `/api/image/${filename}`, output_format: 'webp', clientRequestId } ], clientRequestId }) ); } function writeMissing(response) { response.writeHead(404, { 'content-type': 'application/json' }); response.end(JSON.stringify({ error: 'missing' })); } function withServer(handler, run) { return new Promise((resolve, reject) => { const server = createServer((request, response) => { Promise.resolve(handler(request, response)).catch(reject); }); server.listen(0, '127.0.0.1', async () => { const address = server.address(); try { assert.equal(typeof address, 'object'); assert.ok(address); await run(`http://127.0.0.1:${address.port}`); resolve(); } catch (error) { reject(error); } finally { server.close(); } }); }); } function runSkillScript(filename, args, env) { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [join(skillScriptsRoot, filename), ...args], { cwd: repoRoot, env: buildTestEnvironment(env), stdio: ['ignore', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; const timeout = setTimeout(() => child.kill('SIGTERM'), 30_000); child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8'); child.stdout.on('data', (chunk) => { stdout += chunk; }); child.stderr.on('data', (chunk) => { stderr += chunk; }); child.once('error', (error) => { clearTimeout(timeout); reject(error); }); child.once('close', (status, signal) => { clearTimeout(timeout); resolve({ status, signal, stdout, stderr }); }); }); } function buildTestEnvironment(overrides) { const names = ['HOME', 'PATH', 'SystemRoot', 'TEMP', 'TMP', 'TMPDIR', 'USERPROFILE']; const environment = { GPT_IMAGE_AGENT_LOAD_ENV_FILE: '0' }; for (const name of names) { if (process.env[name] !== undefined) environment[name] = process.env[name]; } return { ...environment, ...overrides }; } function readRequestText(request) { return new Promise((resolve, reject) => { let body = ''; request.setEncoding('utf8'); request.on('data', (chunk) => { body += chunk; }); request.on('end', () => resolve(body)); request.on('error', reject); }); } function fakePngBuffer(width, height) { const buffer = Buffer.alloc(24); buffer[0] = 0x89; buffer.write('PNG', 1, 'ascii'); buffer[4] = 0x0d; buffer[5] = 0x0a; buffer[6] = 0x1a; buffer[7] = 0x0a; buffer.writeUInt32BE(13, 8); buffer.write('IHDR', 12, 'ascii'); buffer.writeUInt32BE(width, 16); buffer.writeUInt32BE(height, 20); return buffer; }