File size: 4,979 Bytes
c7d34c1
 
 
3e8ea5d
 
c7d34c1
 
ecb6084
 
 
 
 
c7d34c1
 
3e8ea5d
c7d34c1
 
 
 
 
 
 
ecb6084
 
 
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e8ea5d
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32151b6
 
 
 
 
 
 
 
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ecb6084
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32151b6
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env node
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();
}