Spaces:
Running
Running
| import { spawnSync } from 'node:child_process'; | |
| import { readPositiveIntegerEnv } from './env-utils.mjs'; | |
| const imageName = process.env.HF_SPACE_SMOKE_IMAGE || 'gpt-image-playground:hf-space-memory-smoke'; | |
| const containerName = process.env.HF_SPACE_SMOKE_CONTAINER || 'gpt-image-playground-hf-space-smoke'; | |
| const parsedHostPort = readPositiveIntegerEnv('HF_SPACE_SMOKE_PORT', 4785); | |
| if (parsedHostPort > 65_535) { | |
| throw new Error('HF_SPACE_SMOKE_PORT must be less than or equal to 65535'); | |
| } | |
| const hostPort = String(parsedHostPort); | |
| const token = process.env.HF_SPACE_SMOKE_AGENT_TOKEN || 'hf-space-smoke-token'; | |
| const baseUrl = `http://127.0.0.1:${hostPort}`; | |
| const readyTimeoutMs = readPositiveIntegerEnv('HF_SPACE_SMOKE_READY_TIMEOUT_MS', 45_000); | |
| function run(command, args, options = {}) { | |
| const result = spawnSync(command, args, { | |
| stdio: options.capture ? 'pipe' : 'inherit', | |
| encoding: 'utf8', | |
| env: { ...process.env, ...(options.env || {}) } | |
| }); | |
| if (result.error) { | |
| throw new Error(`${command} ${args.join(' ')} failed to start: ${result.error.message}`); | |
| } | |
| if (result.status !== 0) { | |
| const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim(); | |
| throw new Error(`${command} ${args.join(' ')} failed${output ? `\n${output}` : ''}`); | |
| } | |
| return result.stdout || ''; | |
| } | |
| async function fetchJson(path, options = {}) { | |
| const response = await fetch(`${baseUrl}${path}`, options); | |
| const text = await response.text(); | |
| let body; | |
| try { | |
| body = JSON.parse(text); | |
| } catch { | |
| throw new Error(`${path} returned non-JSON response: ${text.slice(0, 200)}`); | |
| } | |
| if (!response.ok) { | |
| throw new Error(`${path} failed with ${response.status}: ${JSON.stringify(body)}`); | |
| } | |
| return body; | |
| } | |
| async function waitForReady() { | |
| const deadline = Date.now() + readyTimeoutMs; | |
| let lastError; | |
| while (Date.now() < deadline) { | |
| try { | |
| const response = await fetch(baseUrl, { | |
| headers: { | |
| 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148' | |
| } | |
| }); | |
| if (response.ok) return; | |
| lastError = new Error(`HTTP ${response.status}`); | |
| } catch (error) { | |
| lastError = error; | |
| } | |
| await new Promise((resolve) => setTimeout(resolve, 1000)); | |
| } | |
| throw lastError || new Error('container did not become ready'); | |
| } | |
| function cleanup() { | |
| spawnSync('docker', ['rm', '-f', containerName], { stdio: 'ignore' }); | |
| } | |
| function assertEqual(actual, expected, label) { | |
| if (actual !== expected) { | |
| throw new Error(`${label}: expected ${expected}, got ${actual}`); | |
| } | |
| } | |
| function assertJsonEqual(actual, expected, label) { | |
| const actualJson = JSON.stringify(actual); | |
| const expectedJson = JSON.stringify(expected); | |
| if (actualJson !== expectedJson) { | |
| throw new Error(`${label}: expected ${expectedJson}, got ${actualJson}`); | |
| } | |
| } | |
| cleanup(); | |
| try { | |
| run('docker', [ | |
| 'build', | |
| '--build-arg', | |
| 'NEXT_PUBLIC_IMAGE_STORAGE_MODE=indexeddb', | |
| '-t', | |
| imageName, | |
| '.' | |
| ]); | |
| run('docker', [ | |
| 'run', | |
| '-d', | |
| '--name', | |
| containerName, | |
| '-p', | |
| `127.0.0.1:${hostPort}:4783`, | |
| '-e', | |
| 'AGENT_STATE_BACKEND=memory', | |
| '-e', | |
| 'AGENT_SQLITE_PATH=', | |
| '-e', | |
| 'AGENT_DATABASE_URL=', | |
| '-e', | |
| 'AGENT_DB_PASSWORD=', | |
| '-e', | |
| 'AGENT_DB_PASSWORD_FILE=', | |
| '-e', | |
| 'NEXT_PUBLIC_IMAGE_STORAGE_MODE=indexeddb', | |
| '-e', | |
| 'APP_LOG_LEVEL=warn', | |
| '-e', | |
| `AGENT_API_TOKEN=${token}`, | |
| imageName | |
| ]); | |
| await waitForReady(); | |
| const capabilities = await fetchJson('/api/agent/capabilities'); | |
| assertEqual(capabilities.auth?.required, true, 'Agent auth required flag'); | |
| assertJsonEqual(capabilities.auth?.schemes, ['bearer'], 'Agent auth schemes'); | |
| assertEqual(capabilities.defaults?.state_backend, 'memory', 'Agent state backend'); | |
| assertEqual(capabilities.storage?.image_storage_mode, 'indexeddb', 'Image storage mode'); | |
| assertEqual(capabilities.storage?.postgres_configured, false, 'PostgreSQL configured flag'); | |
| const runtime = await fetchJson('/api/runtime-capabilities'); | |
| assertEqual(typeof runtime.streamingBatch?.enabled, 'boolean', 'Runtime capabilities shape'); | |
| run('node', ['skills/gpt-image-playground-agent/scripts/generate-image.mjs'], { | |
| env: { | |
| GPT_IMAGE_PLAYGROUND_URL: baseUrl, | |
| GPT_IMAGE_AGENT_TOKEN: token, | |
| GPT_IMAGE_AGENT_CONTRACT_CHECK: '1' | |
| } | |
| }); | |
| run('node', ['skills/gpt-image-playground-agent/scripts/edit-image.mjs'], { | |
| env: { | |
| GPT_IMAGE_PLAYGROUND_URL: baseUrl, | |
| GPT_IMAGE_AGENT_TOKEN: token, | |
| GPT_IMAGE_AGENT_CONTRACT_CHECK: '1' | |
| } | |
| }); | |
| console.log( | |
| JSON.stringify( | |
| { | |
| ok: true, | |
| baseUrl, | |
| state_backend: capabilities.defaults.state_backend, | |
| image_storage_mode: capabilities.storage.image_storage_mode, | |
| agent_contract_check: true | |
| }, | |
| null, | |
| 2 | |
| ) | |
| ); | |
| } finally { | |
| cleanup(); | |
| } | |