1
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: gpt-image-2-apimart
|
||||
description: Generate or inspect project image assets through this repository's APIMart OpenAI-compatible gpt-image-2 workflow. Use when Codex needs to create puzzle template sample images, reproduce the server-rs gpt-image-2 request body, dry-run image prompts, batch-generate local project thumbnails, or debug APIMART_BASE_URL / APIMART_API_KEY image-generation configuration without exposing secrets.
|
||||
description: Generate or inspect project image assets through this repository's VectorEngine gpt-image-2 workflow. Use when Codex needs to create puzzle template sample images, reproduce the server-rs gpt-image-2 request body, dry-run image prompts, batch-generate local project thumbnails, or debug VECTOR_ENGINE_BASE_URL / VECTOR_ENGINE_API_KEY image-generation configuration without exposing secrets. The directory name is historical.
|
||||
---
|
||||
|
||||
# gpt-image-2 APIMart
|
||||
# gpt-image-2 VectorEngine
|
||||
|
||||
Use this skill for project-local image asset generation that must match the repository's `server-rs` APIMart `gpt-image-2` path.
|
||||
Use this skill for project-local image asset generation that must match the repository's `server-rs` VectorEngine `gpt-image-2-all` path. The folder still contains `apimart` in its name for compatibility with existing local plugin references.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -24,15 +24,15 @@ Use this skill for project-local image asset generation that must match the repo
|
||||
```
|
||||
|
||||
5. Save final project assets under `public/` or another explicitly requested workspace path.
|
||||
6. Never print `APIMART_API_KEY`. Report only whether configuration exists.
|
||||
6. Never print `VECTOR_ENGINE_API_KEY`. Report only whether configuration exists.
|
||||
|
||||
## Request Contract
|
||||
|
||||
The repository image path uses:
|
||||
|
||||
```text
|
||||
POST {APIMART_BASE_URL}/images/generations
|
||||
Authorization: Bearer {APIMART_API_KEY}
|
||||
POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations
|
||||
Authorization: Bearer {VECTOR_ENGINE_API_KEY}
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
@@ -40,11 +40,10 @@ Default body:
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-image-2",
|
||||
"model": "gpt-image-2-all",
|
||||
"prompt": "<prompt>",
|
||||
"n": 1,
|
||||
"official_fallback": true,
|
||||
"size": "1:1"
|
||||
"size": "1024x1024"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -52,17 +51,11 @@ For a reference image, add:
|
||||
|
||||
```json
|
||||
{
|
||||
"image_urls": ["data:image/png;base64,..."]
|
||||
"image": ["data:image/png;base64,..."]
|
||||
}
|
||||
```
|
||||
|
||||
Poll async responses with:
|
||||
|
||||
```text
|
||||
GET {APIMART_BASE_URL}/tasks/{task_id}
|
||||
```
|
||||
|
||||
Accept image output from `data[].url`, `data[].b64_json`, direct nested `url` fields, or async task results.
|
||||
Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine GPT-image-2-all currently returns synchronously; do not poll APIMart task endpoints.
|
||||
|
||||
## Environment
|
||||
|
||||
@@ -70,12 +63,12 @@ Load environment values from process env first, then `.env.secrets.local`, `.env
|
||||
|
||||
Required for live generation:
|
||||
|
||||
- `APIMART_BASE_URL`
|
||||
- `APIMART_API_KEY`
|
||||
- `VECTOR_ENGINE_BASE_URL`
|
||||
- `VECTOR_ENGINE_API_KEY`
|
||||
|
||||
Optional:
|
||||
|
||||
- `APIMART_IMAGE_REQUEST_TIMEOUT_MS`
|
||||
- `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`
|
||||
|
||||
If the key or base URL is missing, stop after dry-run or explain the missing configuration. Do not ask the user to paste the key in chat.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
interface:
|
||||
display_name: "GPT Image 2 APIMart"
|
||||
short_description: "Generate project thumbnails through APIMart"
|
||||
display_name: "GPT Image 2 VectorEngine"
|
||||
short_description: "Generate project thumbnails through VectorEngine"
|
||||
brand_color: "#10B981"
|
||||
default_prompt: "Use $gpt-image-2-apimart to dry-run or generate puzzle template thumbnails through APIMart."
|
||||
default_prompt: "Use $gpt-image-2-apimart to dry-run or generate puzzle template thumbnails through VectorEngine."
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
@@ -14,7 +14,6 @@ const promptsPath = path.join(
|
||||
);
|
||||
const defaultOutDir = path.join(repoRoot, 'public', 'puzzle-creation-templates');
|
||||
const defaultTimeoutMs = 180000;
|
||||
const pollDelayMs = 3000;
|
||||
|
||||
const args = new Map();
|
||||
for (let index = 2; index < process.argv.length; index += 1) {
|
||||
@@ -66,15 +65,23 @@ function resolveEnv() {
|
||||
...process.env,
|
||||
};
|
||||
return {
|
||||
baseUrl: String(loaded.APIMART_BASE_URL || '').trim().replace(/\/+$/u, ''),
|
||||
apiKey: String(loaded.APIMART_API_KEY || '').trim(),
|
||||
baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '')
|
||||
.trim()
|
||||
.replace(/\/+$/u, ''),
|
||||
apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(),
|
||||
timeoutMs: Number.parseInt(
|
||||
String(loaded.APIMART_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs),
|
||||
String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs),
|
||||
10,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function buildVectorEngineImagesGenerationUrl(baseUrl) {
|
||||
return baseUrl.endsWith('/v1')
|
||||
? `${baseUrl}/images/generations`
|
||||
: `${baseUrl}/v1/images/generations`;
|
||||
}
|
||||
|
||||
function buildPrompt(template) {
|
||||
return [
|
||||
'请生成一张高清 1:1 方形插画,用作拼图创作模板样例图。',
|
||||
@@ -124,14 +131,6 @@ function extractBase64Images(payload) {
|
||||
return values;
|
||||
}
|
||||
|
||||
function extractTaskId(payload) {
|
||||
const ids = [];
|
||||
collectStringsByKey(payload, 'task_id', ids);
|
||||
collectStringsByKey(payload, 'taskId', ids);
|
||||
collectStringsByKey(payload, 'id', ids);
|
||||
return ids[0] || null;
|
||||
}
|
||||
|
||||
function inferExtensionFromContentType(contentType) {
|
||||
const normalized = contentType.split(';')[0]?.trim().toLowerCase();
|
||||
if (normalized === 'image/png') {
|
||||
@@ -172,7 +171,7 @@ async function fetchJson(url, options, timeoutMs) {
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(`APIMart ${response.status}: ${text.slice(0, 600)}`);
|
||||
throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`);
|
||||
}
|
||||
return JSON.parse(text);
|
||||
} finally {
|
||||
@@ -200,52 +199,20 @@ async function downloadUrl(url, timeoutMs) {
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForTask(env, taskId) {
|
||||
const deadline = Date.now() + env.timeoutMs;
|
||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
const payload = await fetchJson(
|
||||
`${env.baseUrl}/tasks/${encodeURIComponent(taskId)}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.apiKey}`,
|
||||
},
|
||||
},
|
||||
env.timeoutMs,
|
||||
);
|
||||
const statuses = [];
|
||||
collectStringsByKey(payload, 'status', statuses);
|
||||
collectStringsByKey(payload, 'task_status', statuses);
|
||||
const status = String(statuses[0] || '').trim().toLowerCase();
|
||||
|
||||
if (['completed', 'succeeded', 'success'].includes(status)) {
|
||||
return payload;
|
||||
}
|
||||
if (['failed', 'error', 'canceled', 'cancelled', 'unknown'].includes(status)) {
|
||||
throw new Error(`APIMart task ${taskId} failed: ${JSON.stringify(payload).slice(0, 600)}`);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, pollDelayMs));
|
||||
}
|
||||
|
||||
throw new Error(`APIMart task ${taskId} timed out`);
|
||||
}
|
||||
|
||||
async function generateOne(env, template, outDir) {
|
||||
const requestBody = {
|
||||
model: 'gpt-image-2',
|
||||
model: 'gpt-image-2-all',
|
||||
prompt: buildPrompt(template),
|
||||
n: 1,
|
||||
official_fallback: true,
|
||||
size: '1:1',
|
||||
size: '1024x1024',
|
||||
};
|
||||
const payload = await fetchJson(
|
||||
`${env.baseUrl}/images/generations`,
|
||||
buildVectorEngineImagesGenerationUrl(env.baseUrl),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
@@ -253,12 +220,8 @@ async function generateOne(env, template, outDir) {
|
||||
env.timeoutMs,
|
||||
);
|
||||
|
||||
const resolvedPayload =
|
||||
extractImageUrls(payload).length || extractBase64Images(payload).length
|
||||
? payload
|
||||
: await waitForTask(env, extractTaskId(payload));
|
||||
const urls = extractImageUrls(resolvedPayload);
|
||||
const b64Images = extractBase64Images(resolvedPayload);
|
||||
const urls = extractImageUrls(payload);
|
||||
const b64Images = extractBase64Images(payload);
|
||||
|
||||
let image;
|
||||
if (urls[0]) {
|
||||
@@ -270,7 +233,7 @@ async function generateOne(env, template, outDir) {
|
||||
extension: inferExtensionFromBytes(bytes),
|
||||
};
|
||||
} else {
|
||||
throw new Error(`APIMart returned no image for ${template.id}`);
|
||||
throw new Error(`VectorEngine returned no image for ${template.id}`);
|
||||
}
|
||||
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
@@ -302,11 +265,10 @@ if (dryRun) {
|
||||
id: template.id,
|
||||
title: template.title,
|
||||
body: {
|
||||
model: 'gpt-image-2',
|
||||
model: 'gpt-image-2-all',
|
||||
prompt: buildPrompt(template),
|
||||
n: 1,
|
||||
official_fallback: true,
|
||||
size: '1:1',
|
||||
size: '1024x1024',
|
||||
},
|
||||
})),
|
||||
},
|
||||
@@ -322,7 +284,7 @@ if (!env.baseUrl || !env.apiKey) {
|
||||
console.error(
|
||||
JSON.stringify({
|
||||
ok: false,
|
||||
error: 'Missing APIMART_BASE_URL or APIMART_API_KEY',
|
||||
error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY',
|
||||
hasBaseUrl: Boolean(env.baseUrl),
|
||||
hasApiKey: Boolean(env.apiKey),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user