From 166977baf0b11261c8c648eec73938c46918687f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 21 Sep 2026 22:01:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=B4=A0=E6=9D=90=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E6=94=B9=E7=94=A8=20concrete=20model=20=E4=B8=8E=20Ti?= =?UTF-8?q?antoken=20=E5=87=AD=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - generate-template-samples 与 generate-anthro-cat-illustrations 的 preferredImageModel 改为 gpt-image-2.5-flare-c - 两个脚本的 base URL / API key / 超时改为只读 TIANTOKEN_BASE_URL、TIANTOKEN_API_KEY、TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS,不再回退 VectorEngine - 脚本内 URL 构造函数、错误文案与错误字段名从 VectorEngine 改为 provider 中性的 Tiantoken 口径 - SKILL.md 请求契约、环境变量与模型说明同步到 concrete model + Tiantoken,并说明带参考图的编辑走 gpt-image-2.5-sunburst-c --- .codex/skills/gpt-image-2-apimart/SKILL.md | 34 ++++++++++------- .../generate-anthro-cat-illustrations.mjs | 38 ++++++++++--------- .../scripts/generate-template-samples.mjs | 38 ++++++++++--------- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/.codex/skills/gpt-image-2-apimart/SKILL.md b/.codex/skills/gpt-image-2-apimart/SKILL.md index 38baf072c..651193668 100644 --- a/.codex/skills/gpt-image-2-apimart/SKILL.md +++ b/.codex/skills/gpt-image-2-apimart/SKILL.md @@ -1,11 +1,17 @@ --- name: gpt-image-2-apimart -description: Generate or inspect project image assets through this repository's image workflow using the GPT Image 2.5 business model. Use when Codex needs to create puzzle template sample images, reproduce the server-rs image 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. +description: Generate or inspect project image assets through this repository's Tiantoken GPT Image 2.5 workflow. Use when Codex needs to create puzzle template sample images, reproduce the server-rs image request body, dry-run image prompts, batch-generate local project thumbnails, or debug TIANTOKEN_BASE_URL / TIANTOKEN_API_KEY image-generation configuration without exposing secrets. The directory name is historical. --- # GPT Image 2.5 project image workflow -Use this skill for project-local image asset generation that must match the repository's image request contract. Use the business model identifier `gpt-image-2.5`; provider concrete model routing is owned by `server-rs`, and this client must not perform a cross-model fallback. The folder still contains `apimart` in its name for compatibility with existing local plugin references. +Use this skill for project-local image asset generation that must match the repository's image request contract. Provider routing is owned by `server-rs`; when this skill talks to the provider directly it must send the concrete provider model and the matching provider credentials, because the provider side only accepts concrete models: + +- generation (no reference images): `gpt-image-2.5-flare-c` through Tiantoken +- edits (any reference image): `gpt-image-2.5-sunburst-c` through Tiantoken +- nanobanana (`gemini-3.1-flash-image-preview`) stays on VectorEngine + +The business model name `gpt-image-2.5` is resolved to a concrete key by `server-rs` at the task boundary and is never sent to a provider directly. This client must not perform a cross-model fallback. The folder still contains `apimart` in its name for compatibility with existing local plugin references. ## Workflow @@ -24,15 +30,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 `VECTOR_ENGINE_API_KEY`. Report only whether configuration exists. +6. Never print `TIANTOKEN_API_KEY`. Report only whether configuration exists. ## Request Contract The repository image path uses: ```text -POST {VECTOR_ENGINE_BASE_URL}/v1/images/generations -Authorization: Bearer {VECTOR_ENGINE_API_KEY} +POST {TIANTOKEN_BASE_URL}/v1/images/generations +Authorization: Bearer {TIANTOKEN_API_KEY} Content-Type: application/json ``` @@ -40,7 +46,7 @@ Default body: ```json { - "model": "gpt-image-2.5", + "model": "gpt-image-2.5-flare-c", "prompt": "", "n": 1, "size": "1024x1024" @@ -50,22 +56,22 @@ Default body: For visual references, use the edit endpoint instead of the create endpoint: ```text -POST {VECTOR_ENGINE_BASE_URL}/v1/images/edits -Authorization: Bearer {VECTOR_ENGINE_API_KEY} +POST {TIANTOKEN_BASE_URL}/v1/images/edits +Authorization: Bearer {TIANTOKEN_API_KEY} Content-Type: multipart/form-data ``` Multipart fields: ```text -model=gpt-image-2.5 +model=gpt-image-2.5-sunburst-c prompt= n=1 size=1024x1024 image=@reference.png ``` -In this repository, calls with no reference images use `POST /v1/images/generations`; calls with any reference image use `POST /v1/images/edits` and pass references as one or more `image` form parts. Both paths send the business model `gpt-image-2.5`; provider routing and retry policy remain server-owned. Match3D container UI generation embeds `public/match3d-background-references/pot-fused-reference.png` into the edit request as an `image` part. +In this repository, calls with no reference images use `POST /v1/images/generations` with `gpt-image-2.5-flare-c`; calls with any reference image use `POST /v1/images/edits` with `gpt-image-2.5-sunburst-c` and pass references as one or more `image` form parts. Server-side calls still submit the business model `gpt-image-2.5` and let `server-rs` resolve the concrete key; provider routing and retry policy remain server-owned, and pricing follows the same reference-image rule (generation with a reference image is charged at the edit tier). Match3D container UI generation embeds `public/match3d-background-references/pot-fused-reference.png` into the edit request as an `image` part. Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine image generation currently returns synchronously; do not poll APIMart task endpoints. @@ -75,12 +81,14 @@ Load environment values from process env first, then `.env.secrets.local`, `.env Required for live generation: -- `VECTOR_ENGINE_BASE_URL` -- `VECTOR_ENGINE_API_KEY` +- `TIANTOKEN_BASE_URL` +- `TIANTOKEN_API_KEY` Optional: -- `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` +- `TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS` + +`VECTOR_ENGINE_*` values no longer serve GPT Image 2.5: those credentials belong to VectorEngine, which only serves nanobanana. Do not fall back from `TIANTOKEN_*` to `VECTOR_ENGINE_*`. 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. diff --git a/.codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs b/.codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs index 0ab4af282..c38a99436 100644 --- a/.codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs +++ b/.codex/skills/gpt-image-2-apimart/scripts/generate-anthro-cat-illustrations.mjs @@ -9,7 +9,9 @@ const skillRoot = path.resolve(__dirname, '..'); const repoRoot = path.resolve(skillRoot, '..', '..', '..'); const defaultOutDir = path.join(repoRoot, 'public', 'anthro-cat-illustrations'); const defaultTimeoutMs = 1000000; -const preferredImageModel = 'gpt-image-2.5'; +// GPT Image 2.5 生成任务只接受 concrete provider model;业务模型名 `gpt-image-2.5` +// 由 server-rs 在任务边界解析,脚本直连 provider 时必须自己给出 concrete key。 +const preferredImageModel = 'gpt-image-2.5-flare-c'; const prompts = [ { @@ -100,18 +102,18 @@ function resolveEnv() { ...process.env, }; return { - baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '') + baseUrl: String(loaded.TIANTOKEN_BASE_URL || '') .trim() .replace(/\/+$/u, ''), - apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(), + apiKey: String(loaded.TIANTOKEN_API_KEY || '').trim(), timeoutMs: Number.parseInt( - String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs), + String(loaded.TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs), 10, ), }; } -function buildVectorEngineImagesGenerationUrl(baseUrl) { +function buildTiantokenImagesGenerationUrl(baseUrl) { return baseUrl.endsWith('/v1') ? `${baseUrl}/images/generations` : `${baseUrl}/v1/images/generations`; @@ -232,22 +234,22 @@ async function fetchJson(url, options, timeoutMs) { const text = await response.text(); if (!response.ok) { const error = new Error( - `VectorEngine ${response.status}: ${text.slice(0, 600)}`, + `Tiantoken ${response.status}: ${text.slice(0, 600)}`, ); - error.vectorEngineStatus = response.status; - error.vectorEngineBody = text; + error.providerStatus = response.status; + error.providerBody = text; throw error; } try { return JSON.parse(text); } catch (error) { - error.vectorEngineResponseParse = true; - error.vectorEngineBody = text; + error.providerResponseParse = true; + error.providerBody = text; throw error; } } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`); + throw new Error(`Tiantoken request timed out after ${timeoutMs}ms`); } throw error; } finally { @@ -264,7 +266,7 @@ async function requestImagePayload(env, entry) { size: '1024x1024', }; const payload = await fetchJson( - buildVectorEngineImagesGenerationUrl(env.baseUrl), + buildTiantokenImagesGenerationUrl(env.baseUrl), { method: 'POST', headers: { @@ -280,9 +282,9 @@ async function requestImagePayload(env, entry) { if (extractImageUrls(payload)[0] || base64Image) { return payload; } - const error = new Error(`VectorEngine returned no image for ${entry.id}`); - error.vectorEngineResponseParse = true; - error.vectorEngineBody = JSON.stringify(payload).slice(0, 600); + const error = new Error(`Tiantoken returned no image for ${entry.id}`); + error.providerResponseParse = true; + error.providerBody = JSON.stringify(payload).slice(0, 600); throw error; } @@ -326,7 +328,7 @@ async function generateOne(env, entry, outDir) { const bytes = decodeStrictBase64Image(b64Images[0]); if (!bytes) { throw new Error( - `VectorEngine returned invalid base64 image for ${entry.id}`, + `Tiantoken returned invalid base64 image for ${entry.id}`, ); } image = { @@ -334,7 +336,7 @@ async function generateOne(env, entry, outDir) { extension: inferExtensionFromBytes(bytes), }; } else { - throw new Error(`VectorEngine returned no image for ${entry.id}`); + throw new Error(`Tiantoken returned no image for ${entry.id}`); } mkdirSync(outDir, { recursive: true }); @@ -381,7 +383,7 @@ if (!env.baseUrl || !env.apiKey) { console.error( JSON.stringify({ ok: false, - error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY', + error: 'Missing TIANTOKEN_BASE_URL or TIANTOKEN_API_KEY', hasBaseUrl: Boolean(env.baseUrl), hasApiKey: Boolean(env.apiKey), }), diff --git a/.codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs b/.codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs index 796745998..28363fcba 100644 --- a/.codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs +++ b/.codex/skills/gpt-image-2-apimart/scripts/generate-template-samples.mjs @@ -18,7 +18,9 @@ const defaultOutDir = path.join( 'puzzle-creation-templates', ); const defaultTimeoutMs = 1000000; -const preferredImageModel = 'gpt-image-2.5'; +// GPT Image 2.5 生成任务只接受 concrete provider model;业务模型名 `gpt-image-2.5` +// 由 server-rs 在任务边界解析,脚本直连 provider 时必须自己给出 concrete key。 +const preferredImageModel = 'gpt-image-2.5-flare-c'; const args = new Map(); for (let index = 2; index < process.argv.length; index += 1) { @@ -70,18 +72,18 @@ function resolveEnv() { ...process.env, }; return { - baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '') + baseUrl: String(loaded.TIANTOKEN_BASE_URL || '') .trim() .replace(/\/+$/u, ''), - apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(), + apiKey: String(loaded.TIANTOKEN_API_KEY || '').trim(), timeoutMs: Number.parseInt( - String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs), + String(loaded.TIANTOKEN_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs), 10, ), }; } -function buildVectorEngineImagesGenerationUrl(baseUrl) { +function buildTiantokenImagesGenerationUrl(baseUrl) { return baseUrl.endsWith('/v1') ? `${baseUrl}/images/generations` : `${baseUrl}/v1/images/generations`; @@ -202,22 +204,22 @@ async function fetchJson(url, options, timeoutMs) { const text = await response.text(); if (!response.ok) { const error = new Error( - `VectorEngine ${response.status}: ${text.slice(0, 600)}`, + `Tiantoken ${response.status}: ${text.slice(0, 600)}`, ); - error.vectorEngineStatus = response.status; - error.vectorEngineBody = text; + error.providerStatus = response.status; + error.providerBody = text; throw error; } try { return JSON.parse(text); } catch (error) { - error.vectorEngineResponseParse = true; - error.vectorEngineBody = text; + error.providerResponseParse = true; + error.providerBody = text; throw error; } } catch (error) { if (error?.name === 'AbortError') { - throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`); + throw new Error(`Tiantoken request timed out after ${timeoutMs}ms`); } throw error; } finally { @@ -234,7 +236,7 @@ async function requestImagePayload(env, template) { size: '1024x1024', }; const payload = await fetchJson( - buildVectorEngineImagesGenerationUrl(env.baseUrl), + buildTiantokenImagesGenerationUrl(env.baseUrl), { method: 'POST', headers: { @@ -250,9 +252,9 @@ async function requestImagePayload(env, template) { if (extractImageUrls(payload)[0] || base64Image) { return payload; } - const error = new Error(`VectorEngine returned no image for ${template.id}`); - error.vectorEngineResponseParse = true; - error.vectorEngineBody = JSON.stringify(payload).slice(0, 600); + const error = new Error(`Tiantoken returned no image for ${template.id}`); + error.providerResponseParse = true; + error.providerBody = JSON.stringify(payload).slice(0, 600); throw error; } @@ -296,7 +298,7 @@ async function generateOne(env, template, outDir) { const bytes = decodeStrictBase64Image(b64Images[0]); if (!bytes) { throw new Error( - `VectorEngine returned invalid base64 image for ${template.id}`, + `Tiantoken returned invalid base64 image for ${template.id}`, ); } image = { @@ -304,7 +306,7 @@ async function generateOne(env, template, outDir) { extension: inferExtensionFromBytes(bytes), }; } else { - throw new Error(`VectorEngine returned no image for ${template.id}`); + throw new Error(`Tiantoken returned no image for ${template.id}`); } mkdirSync(outDir, { recursive: true }); @@ -355,7 +357,7 @@ if (!env.baseUrl || !env.apiKey) { console.error( JSON.stringify({ ok: false, - error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY', + error: 'Missing TIANTOKEN_BASE_URL or TIANTOKEN_API_KEY', hasBaseUrl: Boolean(env.baseUrl), hasApiKey: Boolean(env.apiKey), }),