Files
Genarrative/.codex/skills/genarrative-external-editor-api/SKILL.md
T
lhk229 aecabdacfd 新抠图算法 (#85)
Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/85
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-07-16 18:33:06 +08:00

17 KiB

name, description
name description
genarrative-external-editor-api Guide use of Genarrative's external editor/canvas OpenAPI. Use when a user describes a canvas/editor integration need and Codex must infer the right `/api/external/v1` API automatically, prepare canvas and asset-library context, abstract reusable art specs before generating assets, upload references, draft curl/HTTP/SDK requests, or set up and safely handle a Genarrative developer API Key.

Genarrative External Editor API

Use the live OpenAPI contract as the source of truth: GET https://www.genarrative.world/api/external/v1/openapi.json. In this repository, the same contract is docs/openapi/genarrative-external-v1.openapi.json. If exact fields or enums matter, read the contract before emitting final code.

Prefer the bundled Python helper for runnable examples: scripts/genarrative_external_api.py. It uses only Python stdlib, reads the local JSON API Key file, fixes the production base URL, and wraps upload/confirm/generation routes.

Workflow

  1. At the start of a new conversation, ask the user for the canvas name before the first generation call unless an existing session is already provided. Create or use a project with that name and an asset-library folder with the same name. Keep canvasName, projectId, assetFolderId, and the current art spec in conversation state.
  2. Before any art asset generation, abstract the user's request into a reusable art spec. Ask only for missing spec fields required by the selected asset type. If a current spec already exists and the user does not request a new style/spec, reuse it automatically.
  3. Classify the user's natural-language intent. Do not ask the user to choose an API:
    • "生成/生图/做一张图" -> image generation
    • "重绘/修改这张图" -> image edit
    • "用这张参考图/基于本地图生成" -> upload local reference image, then generation or edit
    • "上传本地素材" -> upload ticket, OSS form upload, object confirm
    • "保存画板/更新布局" -> canvas save
    • "读取私有素材" -> signed read URL
  4. Ask only for missing inputs that affect the request body or an actually ambiguous route:
    • credentials JSON path only if the user cannot use the default local path
    • canvas name when no current canvas session exists; existing projectId, folder/resource IDs only when resuming a known project
    • media type, prompt, references, dimensions, model, ratio, duration, and resolution
    • whether referenced media is already uploaded as objectKey or still local
  5. Every external generation must write to both the canvas and the asset library. Include projectId, assetFolderId, a display label, and canvasCompletion whenever the target endpoint supports them. For character animation, use the helper's two-step fallback: generate with projectId + canvasCompletion, then create a library asset from the first returned frame in the session folder.
  6. If the user lacks an API Key, guide setup before request design.
  7. Read references/api-selection.md before finalizing any request. Use the core table below for fast routing, then verify details in the reference.
  8. Use scripts/genarrative_external_api.py when the user wants runnable Python, reference image upload, canvas/folder session setup, art-spec carrying, or a chain that should execute with fewer hand-written curl steps.
  9. Keep to /api/external/v1 unless the user explicitly asks for internal profile/admin APIs.

Core Routes

Intent Method and path Required fields
List/create projects GET/POST /api/external/v1/editor/projects create: optional title
Save canvas PATCH /api/external/v1/editor/projects/{projectId}/canvas viewport, layers
Upload local media POST /api/external/v1/assets/direct-upload-tickets -> OSS form -> POST /api/external/v1/assets/objects/confirm ticket: legacyPrefix, fileName; confirm: objectKey, assetKind
Read private media GET /api/external/v1/assets/read-url objectKey or legacyPublicPath
Image generation POST /api/external/v1/editor/images/generations prompt
Image edit/redraw POST /api/external/v1/editor/images/edits prompt, sourceImageSrc
Icon spritesheet POST /api/external/v1/editor/icon-spritesheets/generations referenceImageSrc, iconDescriptions
UI asset extraction POST /api/external/v1/editor/ui-designs/assets/extractions sourceImageSrc, aspectRatio, imageSize; use assetFolderId for library folder
Character animation POST /api/external/v1/editor/character-animations/generations sourceLayerId, sourceImageSrc, sourceWidth, sourceHeight, promptText, resolution, ratio, frameCount, durationSeconds, model
Video generation POST /api/external/v1/editor/videos/generations prompt, model, aspectRatio, durationSeconds, resolution, mode, sound
Sound effect POST /api/external/v1/editor/audios/sound-effects/generations prompt, duration
Background music POST /api/external/v1/editor/audios/background-music/generations gptDescriptionPrompt, makeInstrumental

Art Spec Interface

Maintain one current art spec per conversation. A compact spec is enough:

{
  "assetType": "character | background | prop | ui | icon | animation | video | audio",
  "subject": "要生成的主体",
  "style": "画风/材质/时代/参考风格",
  "palette": "主色与禁用色",
  "composition": "构图、镜头、姿态或布局",
  "format": "比例、尺寸、分辨率、帧数、时长",
  "constraints": "必须保留/禁止出现/透明或绿幕要求",
  "references": ["objectKey 或本地路径说明"]
}

For a first spec, infer fields from the user's words and ask only for missing fields that block the selected API. Examples: character animation needs source image/layer, dimensions, motion, ratio, frame count, and duration; UI extraction needs source design image plus target density; icon spritesheet needs reference image and icon descriptions. After a spec exists, reuse it for later assets unless the user changes style, subject family, palette, format, or constraints.

API Key

The external OpenAPI uses:

Authorization: Bearer <tnr_sk_...>

The OpenAPI JSON endpoint is public; every other external endpoint requires the Bearer API Key.

Use this fixed production base URL:

https://www.genarrative.world/

Guide the user to create a key from the logged-in product UI under 开发者 API Key. The raw key is shown only once; never ask the user to paste it into chat. Tell them to store it in this local private JSON file, outside the repository:

~/.config/genarrative/external-editor-api.json
{
  "apiKey": "tnr_sk_..."
}

Set the file readable only by the current user where possible: chmod 600 ~/.config/genarrative/external-editor-api.json. Do not use environment variables for this API.

Smoke test by reading the JSON file, without printing the key:

api_key="$(node -e 'const fs=require("fs"); const p=process.argv[1]; const c=JSON.parse(fs.readFileSync(p,"utf8")); process.stdout.write(c.apiKey || "");' "$HOME/.config/genarrative/external-editor-api.json")"
curl -fsS "https://www.genarrative.world/api/external/v1/editor/projects" \
  -H "Authorization: Bearer $api_key"

For generated client code, read apiKey from the JSON file, fail with a clear missing-config error, and redact keys in logs.

Python smoke without printing the key:

python3 .codex/skills/genarrative-external-editor-api/scripts/genarrative_external_api.py list-projects

Request Patterns

For Python callers, prefer:

from genarrative_external_api import GenarrativeExternalClient

client = GenarrativeExternalClient()
session = client.prepare_canvas_session("新画板")
art_spec = {
    "assetType": "background",
    "subject": "幻想森林主视觉",
    "style": "手绘游戏概念图",
    "palette": "翡翠绿、金色光斑,避免低饱和灰",
    "composition": "16:9 横版,中心留出角色站位",
    "format": "16:9, 1K",
    "constraints": "无文字、无 UI 按钮",
    "references": [],
}
client.generate_image(
    "生成幻想森林背景",
    canvasSession=session,
    assetLabel="森林背景",
    aspectRatio="16:9",
    imageSize="1K",
    artSpec=art_spec,
)

Use the helper directly from this skill path, or copy it into the caller's project. Do not change the fixed base URL or move the API Key into environment variables.

Use this shared base:

api="https://www.genarrative.world"
credentials_file="$HOME/.config/genarrative/external-editor-api.json"
api_key="$(node -e 'const fs=require("fs"); const p=process.argv[1]; const c=JSON.parse(fs.readFileSync(p,"utf8")); process.stdout.write(c.apiKey || "");' "$credentials_file")"
auth=(-H "Authorization: Bearer $api_key")
json=(-H "Content-Type: application/json")

Create a project:

curl -fsS "$api/api/external/v1/editor/projects" \
  "${auth[@]}" "${json[@]}" \
  -d '{"title":"新画板"}'

Generate an image and save it into both the canvas and the asset-library folder:

{
  "prompt": "一张横版幻想森林背景,适合游戏主视觉",
  "kind": "spec",
  "aspectRatio": "16:9",
  "imageSize": "1K",
  "projectId": "<projectId>",
  "assetFolderId": "<assetFolderId>",
  "assetLabel": "森林背景",
  "generationInputs": {
    "artSpec": {
      "assetType": "background",
      "style": "手绘游戏概念图"
    }
  },
  "canvasCompletion": {
    "title": "森林背景",
    "placeholder": {
      "x": 0,
      "y": 0,
      "width": 1024,
      "height": 576,
      "originalWidth": 1024,
      "originalHeight": 576
    }
  }
}

Then call POST /api/external/v1/editor/images/generations.

For direct HTTP/curl, create or find the folder first with GET /api/external/v1/editor/assets/library and POST /api/external/v1/editor/assets/folders. The folder label should match the canvas name.

Reference Images

When the user provides a local reference image path/file, upload it first; do not ask the user to convert it to base64.

Python helper path:

from genarrative_external_api import GenarrativeExternalClient

client = GenarrativeExternalClient()
session = client.prepare_canvas_session("新画板")
ref = client.upload_reference_image("/path/to/reference.png")
client.generate_image(
    "基于参考图生成一张 16:9 游戏背景",
    canvasSession=session,
    assetLabel="参考图背景",
    aspectRatio="16:9",
    imageSize="1K",
    referenceImageSrcs=[ref["objectKey"]],
)

Use the normal upload flow with:

{
  "legacyPrefix": "generated-character-drafts",
  "pathSegments": ["editor", "external-editor-references"],
  "fileName": "<original-file-name>",
  "contentType": "image/png",
  "access": "private"
}

After OSS form upload, confirm the object with assetKind: "editor_reference_image". Put the returned objectKey into the generation request:

  • image generation: referenceImageSrcs
  • image edit/redraw: sourceImageSrc; extra references go in referenceImageSrcs
  • icon spritesheet: referenceImageSrc
  • UI asset extraction: sourceImageSrc; extra references go in referenceImageSrcs
  • character animation: sourceImageSrc
  • video generation image references: referenceImageSrcs

Use signedUrl only for display/download. For generation requests, use objectKey, project resource ID, asset ID, public URL, or Data URL as the endpoint allows; prefer uploaded objectKey for local/private reference images.

OSS form upload shape, using the ticket response saved as ticket.json. The default response has upload; if the caller explicitly requested the API response envelope, use data.upload:

node - <<'NODE' ticket.json /path/to/reference.png
const fs = require('fs');
const path = require('path');

(async () => {
  const body = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
  const ticket = body.upload || body.data?.upload;
  if (!ticket) throw new Error('Upload ticket response missing upload payload');
  const filePath = process.argv[3];
  const form = new FormData();
  for (const [key, value] of Object.entries(ticket.formFields)) {
    if (value != null) form.append(key, value);
  }
  const bytes = fs.readFileSync(filePath);
  form.append(
    'file',
    new Blob([bytes], { type: ticket.contentType || 'application/octet-stream' }),
    path.basename(filePath),
  );
  const response = await fetch(ticket.host, { method: 'POST', body: form });
  if (!response.ok) {
    throw new Error(`OSS upload failed: ${response.status} ${await response.text()}`);
  }
})().catch((error) => {
  console.error(error.message);
  process.exit(1);
});
NODE

Then confirm with contentLength:

{
  "objectKey": "<ticket upload.objectKey>",
  "contentType": "image/png",
  "contentLength": 12345,
  "assetKind": "editor_reference_image",
  "accessPolicy": "private"
}

contentLength is a JSON number from the local file byte size, not a quoted string.

For character animation from an uploaded local image, set:

{
  "sourceLayerId": "external-reference-hero",
  "sourceImageSrc": "<uploaded objectKey>",
  "sourceWidth": 720,
  "sourceHeight": 1280,
  "promptText": "让角色自然呼吸并轻微转身",
  "resolution": "720p",
  "ratio": "9:16",
  "frameCount": 40,
  "durationSeconds": 5,
  "model": "seedance2.0-fast"
}

Use an existing canvas layer ID when the image came from a project layer. If it came only from a local upload, derive a stable synthetic sourceLayerId from the file name, for example external-reference-hero. Read sourceWidth and sourceHeight from the actual image before upload; ask the user only if the dimensions cannot be determined.

The helper uses a 420 second timeout for generation calls, including character animation and video. Direct HTTP clients should not use a 70 second request timeout for animation.

Character animation currently returns canvas completion data but not a direct asset payload. To keep the "canvas + asset library" invariant, call client.animate_character(..., canvasSession=session, canvasTitle="..."); the helper creates a library asset from the first returned frame in the session folder after the animation call succeeds.

For video generation, always include mode: "std". When using image/video/audio references, default to model: "seedance2.0-fast" unless the user asks for another listed model, because reference media support is limited to the Seedance 2.0 family.

For image edit/redraw that should replace an existing canvas layer, pass projectId and targetLayerId. If the user instead gives an explicit canvasCompletion, let that placement win.

For sound effects and BGM, assetFolderId and assetLabel can write the generated audio to the account asset library, same as image/video generation.

Successful Responses with Warnings

Character image generation (including character redraw through kind: "character"), icon spritesheet generation, and UI asset extraction can return HTTP 2xx with an optional structured warning. A 2xx response means the task completed, but it does not guarantee that every requested post-processed derivative exists.

  • Apply the returned project and media snapshots before interpreting optional derivatives: character responses use resource / asset, while icon spritesheet and UI extraction responses use spritesheetResource / spritesheetAsset. When warning.code is postprocess-failed-source-preserved, the saved provider source image is the authoritative main result. Character output has no transparent derivative; icon spritesheet and UI extraction output have neither a transparent spritesheet nor slices. Display warning.reason directly, and do not synthesize missing derivatives or restart generation.
  • sliceWarning is a separate condition used only when transparent spritesheet post-processing succeeded but automatic slicing failed. Keep sliceWarning.reason as the original diagnostic and continue using the complete transparent spritesheet; a UI may add context when displaying it, but must not rewrite the stored reason.
  • The service contract keeps warning and sliceWarning mutually exclusive. As defensive handling for a malformed response containing both, treat the general warning as authoritative and do not misclassify the source-preserved result as a slicing-only warning.

Guardrails

  • Do not invent endpoints outside the OpenAPI, especially internal worker or runtime task-list routes.
  • Do not omit canvas/library context for generation. New generated assets should enter both the canvas and the asset-library folder named after the canvas.
  • Do not put API Keys in repository files, generated project files, command history snippets with literal secrets, logs, docs, commits, or screenshots. The only default storage is the user's local private JSON credentials file.
  • Do not use account JWT endpoints as the default external integration path. The profile API can create/revoke keys for logged-in product users, but it is not part of the external editor OpenAPI.
  • When an endpoint returns project, resource, or asset, treat those as the authoritative updated project/resource/asset snapshots.

Resources

  • references/api-selection.md: intent routing and required-field cheat sheet.
  • scripts/genarrative_external_api.py: stdlib Python helper for OpenAPI fetch, API Key loading, local reference upload, object confirm, project/canvas calls, and generation requests.