mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-08-07 09:23:04 +08:00
[codex] Refactor skills into focused references (#135)
* docs: add runtime-neutral User Input Tools convention across skills Introduce docs/user-input-tools.md as the author-side canonical source and inline the tool-selection rule into every SKILL.md that prompts the user. Also add Skill Self-Containment and User Input Tools sections to CLAUDE.md and the copy-verbatim template to docs/creating-skills.md, so skills stay portable across Claude Code, Codex, Hermes, and other runtimes. * feat: runtime-neutral image generation convention across skills - Introduce inline `## Image Generation Tools` rule in every rendering SKILL.md so skills delegate backend choice instead of hard-coding one; author-side canonical copy lives in docs/image-generation-tools.md. - Add `## Reference Images` support (`--ref`, frontmatter `references:` with direct/style/palette usage) to all seven image-rendering skills. - Move build-batch.ts (with ref propagation into batch JSON) from baoyu-article-illustrator to baoyu-imagine so non-backend skills don't own backend-specific scripts; update baoyu-image-gen stub in sync and relax the CLAUDE.md deprecation note accordingly. * refactor: slim heavy SKILL.md files and move detail to references/ Trim the four largest active skills and move presets, option tables, and confirmation scripts into per-skill references/ so SKILL.md stays focused on the decision flow. - baoyu-slide-deck: 761→258, + styles-gallery.md, confirmation.md - baoyu-image-cards: 657→280, + gallery.md, confirmation.md - baoyu-post-to-wechat: 518→267, + multi-account.md, api-setup.md - baoyu-imagine: 500→230, + providers/, usage-examples.md Also un-deprecate baoyu-image-gen (drop stub warning) so it stays functional alongside baoyu-imagine, and update CLAUDE.md to reflect that both superseded skills are kept in sync rather than stubbed. * refactor: slim four medium SKILL.md files into references/ Continue the P2 pattern on the next tier of skills — move option catalogs, per-provider/adapter detail, and repeated EXTEND.md path boilerplate into their own references so SKILL.md stays focused on the decision flow. - baoyu-comic: 380→297 (art/tone/preset tables → auto-selection.md; Step 7 expanded detail → workflow.md) - baoyu-infographic: 312→207 (layouts/styles/combinations/keywords → gallery.md; ASCII box tables → markdown tables) - baoyu-format-markdown: 376→296 (title + summary generation → title-summary.md; ASCII box tables → markdown tables) - baoyu-url-to-markdown: 334→169 (quality gate + recovery → quality-gate.md; adapters + media download → adapters.md) * chore: sync deprecated skills with their replacements Per project policy, baoyu-xhs-images and baoyu-image-gen are kept functional alongside the active skills they were superseded by. Sync their SKILL.md bodies and references/ to the slimmed baoyu-image-cards and baoyu-imagine versions respectively, so cross-cutting fixes stay consistent. Only the frontmatter (name, description, version, homepage) differs — content is identical. - baoyu-xhs-images: 657→281 (synced with baoyu-image-cards + new confirmation.md, gallery.md) - baoyu-image-gen: 408→231 (synced with baoyu-imagine + new providers/, usage-examples.md) * refactor: collapse EXTEND.md boilerplate into priority tables Replace the dual bash/powershell existence-check blocks and ASCII box art with a single markdown priority table across nine SKILL.md files. The runtime-neutral phrasing removes shell-specific snippets without losing the priority semantics. * fix: address refactor-skills branch review findings - image-gen: restore EXTEND.md paths to baoyu-image-gen (were pointing at baoyu-imagine) and mark descriptions of both deprecated skills as [Deprecated]. - xhs-images: sync neon/warm palettes with image-cards to add the "do not render color names/hex as visible text" safety sentence. - infographic: restore Layout Gallery (21), Style Gallery (21), Recommended Combinations, and Keyword Shortcuts inline (previous refactor split them out but SKILL.md still depended on them), and add the missing references/config/first-time-setup.md + preferences-schema.md. - image-cards / xhs-images / slide-deck / format-markdown: restore the sections that got over-slimmed into references/ (galleries, presets, dimensions, auto-selection, style x layout matrix, title/summary flow) and drop the now-empty shell files. - docs/image-generation-tools.md: note that backend skills themselves (baoyu-imagine, baoyu-image-gen, baoyu-danger-gemini-web) are exempt from the ## Image Generation Tools section requirement. * feat(image-gen): sync Z.AI GLM-Image provider from baoyu-imagine Add Z.AI as a full provider in the deprecated baoyu-image-gen skill so it stays in sync with baoyu-imagine's provider list. - new scripts/providers/zai.ts + zai.test.ts (verbatim port; test factory trimmed to match image-gen's CliArgs shape). - types.ts: "zai" added to Provider union and default_model. - main.ts: rate-limit defaults, provider help text, env var help, --provider validation, loadProviderModule, detectProvider auto-detect chain, getModelForProvider, YAML parser allow-lists. - references/config: Q2e Z.AI model question + zai slot in the preferences schema and batch.provider_limits. Scope is intentionally limited to the Z.AI chain; unrelated drift between image-gen and imagine (OpenAI image-API dialect, aspectRatioSource, imageSizeSource) is left alone. * docs: align inline-convention wording and note backend-skill exemption - docs/user-input-tools.md: fix stale "links here" wording so it matches the inline convention already enforced everywhere else. - CLAUDE.md §Image Generation Tools: inline the backend-skill exemption so readers don't need to cross-reference docs/image-generation-tools.md.
This commit is contained in:
+112
-291
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: baoyu-image-gen
|
||||
description: AI image generation with OpenAI, Azure OpenAI, Google, OpenRouter, DashScope, MiniMax, Jimeng, Seedream and Replicate APIs. Supports text-to-image, reference images, aspect ratios, and batch generation from saved prompt files. Sequential by default; use batch parallel generation when the user already has multiple prompts or wants stable multi-image throughput. Use when user asks to generate, create, or draw images.
|
||||
description: "[Deprecated: use baoyu-imagine] AI image generation with OpenAI, Azure OpenAI, Google, OpenRouter, DashScope, Z.AI GLM-Image, MiniMax, Jimeng, Seedream and Replicate APIs. Supports text-to-image, reference images, aspect ratios, and batch generation from saved prompt files. Sequential by default; use batch parallel generation when the user already has multiple prompts or wants stable multi-image throughput. Use when user asks to generate, create, or draw images."
|
||||
version: 1.56.4
|
||||
metadata:
|
||||
openclaw:
|
||||
@@ -11,147 +11,66 @@ metadata:
|
||||
- npx
|
||||
---
|
||||
|
||||
> **⚠️ DEPRECATED**: This skill has been migrated to `baoyu-imagine`. Print a bold red warning to the user **in the user's language** (use ANSI escape `\033[1;31m`) stating that `baoyu-image-gen` has been migrated to `baoyu-imagine` and they should use `baoyu-imagine` instead. Then STOP — do NOT continue executing this skill.
|
||||
|
||||
# Image Generation (AI SDK)
|
||||
|
||||
Official API-based image generation. Supports OpenAI, Azure OpenAI, Google, OpenRouter, DashScope (阿里通义万象), MiniMax, Jimeng (即梦), Seedream (豆包) and Replicate providers.
|
||||
Official API-based image generation. Supports OpenAI, Azure OpenAI, Google, OpenRouter, DashScope (阿里通义万象), Z.AI GLM-Image, MiniMax, Jimeng (即梦), Seedream (豆包) and Replicate.
|
||||
|
||||
## User Input Tools
|
||||
|
||||
When this skill prompts the user, follow this tool-selection rule (priority order):
|
||||
|
||||
1. **Prefer built-in user-input tools** exposed by the current agent runtime — e.g., `AskUserQuestion`, `request_user_input`, `clarify`, `ask_user`, or any equivalent.
|
||||
2. **Fallback**: if no such tool exists, emit a numbered plain-text message and ask the user to reply with the chosen number/answer for each question.
|
||||
3. **Batching**: if the tool supports multiple questions per call, combine all applicable questions into a single call; if only single-question, ask them one at a time in priority order.
|
||||
|
||||
Concrete `AskUserQuestion` references below are examples — substitute the local equivalent in other runtimes.
|
||||
|
||||
## Script Directory
|
||||
|
||||
**Agent Execution**:
|
||||
1. `{baseDir}` = this SKILL.md file's directory
|
||||
2. Script path = `{baseDir}/scripts/main.ts`
|
||||
3. Resolve `${BUN_X}` runtime: if `bun` installed → `bun`; if `npx` available → `npx -y bun`; else suggest installing bun
|
||||
`{baseDir}` = this SKILL.md's directory. Main script: `{baseDir}/scripts/main.ts`. Resolve `${BUN_X}`: prefer `bun`; else `npx -y bun`; else suggest `brew install oven-sh/bun/bun`.
|
||||
|
||||
## Step 0: Load Preferences ⛔ BLOCKING
|
||||
|
||||
**CRITICAL**: This step MUST complete BEFORE any image generation. Do NOT skip or defer.
|
||||
This step MUST complete before any image generation — generation is blocked until EXTEND.md exists.
|
||||
|
||||
Check EXTEND.md existence (priority: project → user):
|
||||
Check these paths in order; first hit wins:
|
||||
|
||||
```bash
|
||||
# macOS, Linux, WSL, Git Bash
|
||||
test -f .baoyu-skills/baoyu-image-gen/EXTEND.md && echo "project"
|
||||
test -f "${XDG_CONFIG_HOME:-$HOME/.config}/baoyu-skills/baoyu-image-gen/EXTEND.md" && echo "xdg"
|
||||
test -f "$HOME/.baoyu-skills/baoyu-image-gen/EXTEND.md" && echo "user"
|
||||
```
|
||||
|
||||
```powershell
|
||||
# PowerShell (Windows)
|
||||
if (Test-Path .baoyu-skills/baoyu-image-gen/EXTEND.md) { "project" }
|
||||
$xdg = if ($env:XDG_CONFIG_HOME) { $env:XDG_CONFIG_HOME } else { "$HOME/.config" }
|
||||
if (Test-Path "$xdg/baoyu-skills/baoyu-image-gen/EXTEND.md") { "xdg" }
|
||||
if (Test-Path "$HOME/.baoyu-skills/baoyu-image-gen/EXTEND.md") { "user" }
|
||||
```
|
||||
|
||||
| Result | Action |
|
||||
|--------|--------|
|
||||
| Found | Load, parse, apply settings. If `default_model.[provider]` is null → ask model only (Flow 2) |
|
||||
| Not found | ⛔ Run first-time setup ([references/config/first-time-setup.md](references/config/first-time-setup.md)) → Save EXTEND.md → Then continue |
|
||||
|
||||
**CRITICAL**: If not found, complete the full setup (provider + model + quality + save location) using AskUserQuestion BEFORE generating any images. Generation is BLOCKED until EXTEND.md is created.
|
||||
|
||||
| Path | Location |
|
||||
|------|----------|
|
||||
| `.baoyu-skills/baoyu-image-gen/EXTEND.md` | Project directory |
|
||||
| Path | Scope |
|
||||
|------|-------|
|
||||
| `.baoyu-skills/baoyu-image-gen/EXTEND.md` | Project |
|
||||
| `${XDG_CONFIG_HOME:-$HOME/.config}/baoyu-skills/baoyu-image-gen/EXTEND.md` | XDG |
|
||||
| `$HOME/.baoyu-skills/baoyu-image-gen/EXTEND.md` | User home |
|
||||
|
||||
**EXTEND.md Supports**: Default provider | Default quality | Default aspect ratio | Default image size | Default models | Batch worker cap | Provider-specific batch limits
|
||||
- **Found** → load, parse, apply. If `default_model.[provider]` is null → ask model only.
|
||||
- **Not found** → run first-time setup (`references/config/first-time-setup.md`) using AskUserQuestion to collect provider + model + quality + save location. Save EXTEND.md, then continue. Do not generate images before this completes.
|
||||
|
||||
Schema: `references/config/preferences-schema.md`
|
||||
**EXTEND.md keys**: default provider, default quality, default aspect ratio, default image size, OpenAI image API dialect, default models, batch worker cap, provider-specific batch limits. Schema: `references/config/preferences-schema.md`.
|
||||
|
||||
## Usage
|
||||
|
||||
Minimum working examples — see `references/usage-examples.md` for the full set including per-provider invocations and batch mode.
|
||||
|
||||
```bash
|
||||
# Basic
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image cat.png
|
||||
|
||||
# With aspect ratio
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A landscape" --image out.png --ar 16:9
|
||||
# With aspect ratio and high quality
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A landscape" --image out.png --ar 16:9 --quality 2k
|
||||
|
||||
# High quality
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --quality 2k
|
||||
|
||||
# From prompt files
|
||||
# Prompt from files
|
||||
${BUN_X} {baseDir}/scripts/main.ts --promptfiles system.md content.md --image out.png
|
||||
|
||||
# With reference images (Google, OpenAI, Azure OpenAI, OpenRouter, Replicate, MiniMax, or Seedream 4.0/4.5/5.0)
|
||||
# With reference image
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --ref source.png
|
||||
|
||||
# With reference images (explicit provider/model)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --provider google --model gemini-3-pro-image-preview --ref source.png
|
||||
|
||||
# Azure OpenAI (model means deployment name)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider azure --model gpt-image-1.5
|
||||
|
||||
# OpenRouter (recommended default model)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openrouter
|
||||
|
||||
# OpenRouter with reference images
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --provider openrouter --model google/gemini-3.1-flash-image-preview --ref source.png
|
||||
|
||||
# Specific provider
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openai
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider dashscope --model qwen-image-2.0-pro
|
||||
|
||||
# DashScope (阿里通义万象)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "一只可爱的猫" --image out.png --provider dashscope
|
||||
|
||||
# DashScope Qwen-Image 2.0 Pro (recommended for custom sizes and text rendering)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "为咖啡品牌设计一张 21:9 横幅海报,包含清晰中文标题" --image out.png --provider dashscope --model qwen-image-2.0-pro --size 2048x872
|
||||
|
||||
# DashScope legacy Qwen fixed-size model
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "一张电影感海报" --image out.png --provider dashscope --model qwen-image-max --size 1664x928
|
||||
|
||||
# MiniMax
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A fashion editorial portrait by a bright studio window" --image out.jpg --provider minimax
|
||||
|
||||
# MiniMax with subject reference (best for character/portrait consistency)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A girl stands by the library window, cinematic lighting" --image out.jpg --provider minimax --model image-01 --ref portrait.png --ar 16:9
|
||||
|
||||
# MiniMax with custom size (documented for image-01)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cinematic poster" --image out.jpg --provider minimax --model image-01 --size 1536x1024
|
||||
|
||||
# Replicate (google/nano-banana-pro)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate
|
||||
|
||||
# Replicate with specific model
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate --model google/nano-banana
|
||||
|
||||
# Batch mode with saved prompt files
|
||||
${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json
|
||||
|
||||
# Batch mode with explicit worker count
|
||||
${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json --jobs 4 --json
|
||||
# Batch mode
|
||||
${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json --jobs 4
|
||||
```
|
||||
|
||||
### Batch File Format
|
||||
|
||||
```json
|
||||
{
|
||||
"jobs": 4,
|
||||
"tasks": [
|
||||
{
|
||||
"id": "hero",
|
||||
"promptFiles": ["prompts/hero.md"],
|
||||
"image": "out/hero.png",
|
||||
"provider": "replicate",
|
||||
"model": "google/nano-banana-pro",
|
||||
"ar": "16:9",
|
||||
"quality": "2k"
|
||||
},
|
||||
{
|
||||
"id": "diagram",
|
||||
"promptFiles": ["prompts/diagram.md"],
|
||||
"image": "out/diagram.png",
|
||||
"ref": ["references/original.png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch file's directory. `jobs` is optional (overridden by CLI `--jobs`). Top-level array format (without `jobs` wrapper) is also accepted.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
@@ -161,14 +80,15 @@ Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch fi
|
||||
| `--image <path>` | Output image path (required in single-image mode) |
|
||||
| `--batchfile <path>` | JSON batch file for multi-image generation |
|
||||
| `--jobs <count>` | Worker count for batch mode (default: auto, max from config, built-in default 10) |
|
||||
| `--provider google\|openai\|azure\|openrouter\|dashscope\|minimax\|jimeng\|seedream\|replicate` | Force provider (default: auto-detect) |
|
||||
| `--model <id>`, `-m` | Model ID (Google: `gemini-3-pro-image-preview`; OpenAI: `gpt-image-1.5`; Azure: deployment name such as `gpt-image-1.5` or `image-prod`; OpenRouter: `google/gemini-3.1-flash-image-preview`; DashScope: `qwen-image-2.0-pro`; MiniMax: `image-01`) |
|
||||
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
||||
| `--size <WxH>` | Size (e.g., `1024x1024`) |
|
||||
| `--provider google\|openai\|azure\|openrouter\|dashscope\|zai\|minimax\|jimeng\|seedream\|replicate` | Force provider (default: auto-detect) |
|
||||
| `--model <id>`, `-m` | Model ID — see provider references for defaults and allowed values |
|
||||
| `--ar <ratio>` | Aspect ratio (`16:9`, `1:1`, `4:3`, …) |
|
||||
| `--size <WxH>` | Explicit size (e.g., `1024x1024`) |
|
||||
| `--quality normal\|2k` | Quality preset (default: `2k`) |
|
||||
| `--imageSize 1K\|2K\|4K` | Image size for Google/OpenRouter (default: from quality) |
|
||||
| `--ref <files...>` | Reference images. Supported by Google multimodal, OpenAI GPT Image edits, Azure OpenAI edits (PNG/JPG only), OpenRouter multimodal models, Replicate, MiniMax subject-reference, and Seedream 5.0/4.5/4.0. Not supported by Jimeng, Seedream 3.0, or removed SeedEdit 3.0 |
|
||||
| `--n <count>` | Number of images |
|
||||
| `--imageApiDialect openai-native\|ratio-metadata` | OpenAI-compatible endpoint dialect — use `ratio-metadata` for gateways that expect aspect-ratio `size` plus `metadata.resolution` |
|
||||
| `--ref <files...>` | Reference images. Supported by Google multimodal, OpenAI GPT Image edits, Azure OpenAI edits (PNG/JPG only), OpenRouter multimodal models, Replicate supported families, MiniMax subject-reference, Seedream 5.0/4.5/4.0. Not supported by Jimeng, Seedream 3.0, SeedEdit 3.0 |
|
||||
| `--n <count>` | Number of images. Replicate requires `--n 1` (single-output save semantics) |
|
||||
| `--json` | JSON output |
|
||||
|
||||
## Environment Variables
|
||||
@@ -179,221 +99,109 @@ Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch fi
|
||||
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key |
|
||||
| `OPENROUTER_API_KEY` | OpenRouter API key |
|
||||
| `GOOGLE_API_KEY` | Google API key |
|
||||
| `DASHSCOPE_API_KEY` | DashScope API key (阿里云) |
|
||||
| `DASHSCOPE_API_KEY` | DashScope API key |
|
||||
| `ZAI_API_KEY` (alias `BIGMODEL_API_KEY`) | Z.AI API key |
|
||||
| `MINIMAX_API_KEY` | MiniMax API key |
|
||||
| `REPLICATE_API_TOKEN` | Replicate API token |
|
||||
| `JIMENG_ACCESS_KEY_ID` | Jimeng (即梦) Volcengine access key |
|
||||
| `JIMENG_SECRET_ACCESS_KEY` | Jimeng (即梦) Volcengine secret key |
|
||||
| `JIMENG_ACCESS_KEY_ID`, `JIMENG_SECRET_ACCESS_KEY` | Jimeng (即梦) Volcengine credentials |
|
||||
| `ARK_API_KEY` | Seedream (豆包) Volcengine ARK API key |
|
||||
| `OPENAI_IMAGE_MODEL` | OpenAI model override |
|
||||
| `AZURE_OPENAI_DEPLOYMENT` | Azure default deployment name |
|
||||
| `AZURE_OPENAI_IMAGE_MODEL` | Backward-compatible alias for Azure default deployment/model name |
|
||||
| `OPENROUTER_IMAGE_MODEL` | OpenRouter model override (default: `google/gemini-3.1-flash-image-preview`) |
|
||||
| `GOOGLE_IMAGE_MODEL` | Google model override |
|
||||
| `DASHSCOPE_IMAGE_MODEL` | DashScope model override (default: `qwen-image-2.0-pro`) |
|
||||
| `MINIMAX_IMAGE_MODEL` | MiniMax model override (default: `image-01`) |
|
||||
| `REPLICATE_IMAGE_MODEL` | Replicate model override (default: google/nano-banana-pro) |
|
||||
| `JIMENG_IMAGE_MODEL` | Jimeng model override (default: jimeng_t2i_v40) |
|
||||
| `SEEDREAM_IMAGE_MODEL` | Seedream model override (default: doubao-seedream-5-0-260128) |
|
||||
| `OPENAI_BASE_URL` | Custom OpenAI endpoint |
|
||||
| `AZURE_OPENAI_BASE_URL` | Azure resource endpoint or deployment endpoint |
|
||||
| `AZURE_API_VERSION` | Azure image API version (default: `2025-04-01-preview`) |
|
||||
| `OPENROUTER_BASE_URL` | Custom OpenRouter endpoint (default: `https://openrouter.ai/api/v1`) |
|
||||
| `OPENROUTER_HTTP_REFERER` | Optional app/site URL for OpenRouter attribution |
|
||||
| `OPENROUTER_TITLE` | Optional app name for OpenRouter attribution |
|
||||
| `GOOGLE_BASE_URL` | Custom Google endpoint |
|
||||
| `DASHSCOPE_BASE_URL` | Custom DashScope endpoint |
|
||||
| `MINIMAX_BASE_URL` | Custom MiniMax endpoint (default: `https://api.minimax.io`) |
|
||||
| `REPLICATE_BASE_URL` | Custom Replicate endpoint |
|
||||
| `JIMENG_BASE_URL` | Custom Jimeng endpoint (default: `https://visual.volcengineapi.com`) |
|
||||
| `JIMENG_REGION` | Jimeng region (default: `cn-north-1`) |
|
||||
| `SEEDREAM_BASE_URL` | Custom Seedream endpoint (default: `https://ark.cn-beijing.volces.com/api/v3`) |
|
||||
| `<PROVIDER>_IMAGE_MODEL` | Per-provider model override (`OPENAI_IMAGE_MODEL`, `GOOGLE_IMAGE_MODEL`, `DASHSCOPE_IMAGE_MODEL`, `ZAI_IMAGE_MODEL`/`BIGMODEL_IMAGE_MODEL`, `MINIMAX_IMAGE_MODEL`, `OPENROUTER_IMAGE_MODEL`, `REPLICATE_IMAGE_MODEL`, `JIMENG_IMAGE_MODEL`, `SEEDREAM_IMAGE_MODEL`) |
|
||||
| `AZURE_OPENAI_DEPLOYMENT` (alias `AZURE_OPENAI_IMAGE_MODEL`) | Azure default deployment |
|
||||
| `<PROVIDER>_BASE_URL` | Per-provider endpoint override |
|
||||
| `AZURE_API_VERSION` | Azure image API version (default `2025-04-01-preview`) |
|
||||
| `JIMENG_REGION` | Jimeng region (default `cn-north-1`) |
|
||||
| `OPENAI_IMAGE_API_DIALECT` | `openai-native` \| `ratio-metadata` |
|
||||
| `OPENROUTER_HTTP_REFERER`, `OPENROUTER_TITLE` | Optional OpenRouter attribution |
|
||||
| `BAOYU_IMAGE_GEN_MAX_WORKERS` | Override batch worker cap |
|
||||
| `BAOYU_IMAGE_GEN_<PROVIDER>_CONCURRENCY` | Override provider concurrency, e.g. `BAOYU_IMAGE_GEN_REPLICATE_CONCURRENCY` |
|
||||
| `BAOYU_IMAGE_GEN_<PROVIDER>_START_INTERVAL_MS` | Override provider start gap, e.g. `BAOYU_IMAGE_GEN_REPLICATE_START_INTERVAL_MS` |
|
||||
| `BAOYU_IMAGE_GEN_<PROVIDER>_CONCURRENCY` | Per-provider concurrency (e.g., `BAOYU_IMAGE_GEN_REPLICATE_CONCURRENCY`) |
|
||||
| `BAOYU_IMAGE_GEN_<PROVIDER>_START_INTERVAL_MS` | Per-provider start-gap |
|
||||
|
||||
**Load Priority**: CLI args > EXTEND.md > env vars > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
|
||||
**Load priority**: CLI args > EXTEND.md > env vars > `<cwd>/.baoyu-skills/.env` > `~/.baoyu-skills/.env`
|
||||
|
||||
## Model Resolution
|
||||
|
||||
Model priority (highest → lowest), applies to all providers:
|
||||
Priority (highest → lowest) applies to every provider:
|
||||
|
||||
1. CLI flag: `--model <id>`
|
||||
2. EXTEND.md: `default_model.[provider]`
|
||||
3. Env var: `<PROVIDER>_IMAGE_MODEL` (e.g., `GOOGLE_IMAGE_MODEL`)
|
||||
1. CLI flag `--model <id>`
|
||||
2. EXTEND.md `default_model.[provider]`
|
||||
3. Env var `<PROVIDER>_IMAGE_MODEL`
|
||||
4. Built-in default
|
||||
|
||||
For Azure, `--model` / `default_model.azure` should be the Azure deployment name. `AZURE_OPENAI_DEPLOYMENT` is the preferred env var, and `AZURE_OPENAI_IMAGE_MODEL` remains as a backward-compatible alias.
|
||||
For Azure, `--model` / `default_model.azure` is the Azure deployment name. `AZURE_OPENAI_DEPLOYMENT` is the preferred env var; `AZURE_OPENAI_IMAGE_MODEL` is kept as a backward-compatible alias.
|
||||
|
||||
**EXTEND.md overrides env vars**. If both EXTEND.md `default_model.google: "gemini-3-pro-image-preview"` and env var `GOOGLE_IMAGE_MODEL=gemini-3.1-flash-image-preview` exist, EXTEND.md wins.
|
||||
EXTEND.md overrides env vars: if EXTEND.md sets `default_model.google: "gemini-3-pro-image-preview"` and the env var sets `GOOGLE_IMAGE_MODEL=gemini-3.1-flash-image-preview`, EXTEND.md wins.
|
||||
|
||||
**Agent MUST display model info** before each generation:
|
||||
- Show: `Using [provider] / [model]`
|
||||
- Show switch hint: `Switch model: --model <id> | EXTEND.md default_model.[provider] | env <PROVIDER>_IMAGE_MODEL`
|
||||
**Display model info before each generation**:
|
||||
|
||||
### DashScope Models
|
||||
- `Using [provider] / [model]`
|
||||
- `Switch model: --model <id> | EXTEND.md default_model.[provider] | env <PROVIDER>_IMAGE_MODEL`
|
||||
|
||||
Use `--model qwen-image-2.0-pro` or set `default_model.dashscope` / `DASHSCOPE_IMAGE_MODEL` when the user wants official Qwen-Image behavior.
|
||||
## OpenAI-Compatible Gateway Dialects
|
||||
|
||||
Official DashScope model families:
|
||||
`provider=openai` means the auth and routing entrypoint is OpenAI-compatible. It does **not** guarantee the upstream image API uses OpenAI native semantics. When a gateway expects a different wire format, set `default_image_api_dialect` in EXTEND.md, `OPENAI_IMAGE_API_DIALECT`, or `--imageApiDialect`:
|
||||
|
||||
- `qwen-image-2.0-pro`, `qwen-image-2.0-pro-2026-03-03`, `qwen-image-2.0`, `qwen-image-2.0-2026-03-03`
|
||||
- Free-form `size` in `宽*高` format
|
||||
- Total pixels must stay between `512*512` and `2048*2048`
|
||||
- Default size is approximately `1024*1024`
|
||||
- Best choice for custom ratios such as `21:9` and text-heavy Chinese/English layouts
|
||||
- `qwen-image-max`, `qwen-image-max-2025-12-30`, `qwen-image-plus`, `qwen-image-plus-2026-01-09`, `qwen-image`
|
||||
- Fixed sizes only: `1664*928`, `1472*1104`, `1328*1328`, `1104*1472`, `928*1664`
|
||||
- Default size is `1664*928`
|
||||
- `qwen-image` currently has the same capability as `qwen-image-plus`
|
||||
- Legacy DashScope models such as `z-image-turbo`, `z-image-ultra`, `wanx-v1`
|
||||
- Keep using them only when the user explicitly asks for legacy behavior or compatibility
|
||||
- `openai-native`: pixel `size` (`1536x1024`) and native OpenAI quality fields
|
||||
- `ratio-metadata`: aspect-ratio `size` (`16:9`) plus `metadata.resolution` (`1K|2K|4K`) and `metadata.orientation`
|
||||
|
||||
When translating CLI args into DashScope behavior:
|
||||
Use `openai-native` for the OpenAI native API or strict clones; try `ratio-metadata` for compatibility gateways in front of Gemini or similar models. Current limitation: `ratio-metadata` applies only to text-to-image; reference-image edits still need `openai-native` or a provider with first-class edit support.
|
||||
|
||||
- `--size` wins over `--ar`
|
||||
- For `qwen-image-2.0*`, prefer explicit `--size`; otherwise infer from `--ar` and use the official recommended resolutions below
|
||||
- For `qwen-image-max/plus/image`, only use the five official fixed sizes; if the requested ratio is not covered, switch to `qwen-image-2.0-pro`
|
||||
- `--quality` is a baoyu-image-gen compatibility preset, not a native DashScope API field. Mapping `normal` / `2k` onto the `qwen-image-2.0*` table below is an implementation inference, not an official API guarantee
|
||||
## Provider-Specific Guides
|
||||
|
||||
Recommended `qwen-image-2.0*` sizes for common aspect ratios:
|
||||
Each provider has its own quirks (model families, size rules, ref support, limits). Read these when the user picks that provider or asks for non-default behavior:
|
||||
|
||||
| Ratio | `normal` | `2k` |
|
||||
|-------|----------|------|
|
||||
| `1:1` | `1024*1024` | `1536*1536` |
|
||||
| `2:3` | `768*1152` | `1024*1536` |
|
||||
| `3:2` | `1152*768` | `1536*1024` |
|
||||
| `3:4` | `960*1280` | `1080*1440` |
|
||||
| `4:3` | `1280*960` | `1440*1080` |
|
||||
| `9:16` | `720*1280` | `1080*1920` |
|
||||
| `16:9` | `1280*720` | `1920*1080` |
|
||||
| `21:9` | `1344*576` | `2048*872` |
|
||||
|
||||
DashScope official APIs also expose `negative_prompt`, `prompt_extend`, and `watermark`, but `baoyu-image-gen` does not expose them as dedicated CLI flags today.
|
||||
|
||||
Official references:
|
||||
|
||||
- [Qwen-Image API](https://help.aliyun.com/zh/model-studio/qwen-image-api)
|
||||
- [Text-to-image guide](https://help.aliyun.com/zh/model-studio/text-to-image)
|
||||
- [Qwen-Image Edit API](https://help.aliyun.com/zh/model-studio/qwen-image-edit-api)
|
||||
|
||||
### MiniMax Models
|
||||
|
||||
Use `--model image-01` or set `default_model.minimax` / `MINIMAX_IMAGE_MODEL` when the user wants MiniMax image generation.
|
||||
|
||||
Official MiniMax image model options currently documented in the API reference:
|
||||
|
||||
- `image-01` (recommended default)
|
||||
- Supports text-to-image and subject-reference image generation
|
||||
- Supports official `aspect_ratio` values: `1:1`, `16:9`, `4:3`, `3:2`, `2:3`, `3:4`, `9:16`, `21:9`
|
||||
- Supports documented custom `width` / `height` output sizes when using `--size <WxH>`
|
||||
- `width` and `height` must both be between `512` and `2048`, and both must be divisible by `8`
|
||||
- `image-01-live`
|
||||
- Lower-latency variant
|
||||
- Use `--ar` for sizing; MiniMax documents custom `width` / `height` as only effective for `image-01`
|
||||
|
||||
MiniMax subject reference notes:
|
||||
|
||||
- `--ref` files are sent as MiniMax `subject_reference`
|
||||
- MiniMax docs currently describe `subject_reference[].type` as `character`
|
||||
- Official docs say `image_file` supports public URLs or Base64 Data URLs; `baoyu-image-gen` sends local refs as Data URLs
|
||||
- Official docs recommend front-facing portrait references in JPG/JPEG/PNG under 10MB
|
||||
|
||||
Official references:
|
||||
|
||||
- [MiniMax Image Generation Guide](https://platform.minimax.io/docs/guides/image-generation)
|
||||
- [MiniMax Text-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-t2i)
|
||||
- [MiniMax Image-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-i2i)
|
||||
|
||||
### OpenRouter Models
|
||||
|
||||
Use full OpenRouter model IDs, e.g.:
|
||||
|
||||
- `google/gemini-3.1-flash-image-preview` (recommended, supports image output and reference-image workflows)
|
||||
- `google/gemini-2.5-flash-image-preview`
|
||||
- `black-forest-labs/flux.2-pro`
|
||||
- Other OpenRouter image-capable model IDs
|
||||
|
||||
Notes:
|
||||
|
||||
- OpenRouter image generation uses `/chat/completions`, not the OpenAI `/images` endpoints
|
||||
- If `--ref` is used, choose a multimodal model that supports image input and image output
|
||||
- `--imageSize` maps to OpenRouter `imageGenerationOptions.size`; `--size <WxH>` is converted to the nearest OpenRouter size and inferred aspect ratio when possible
|
||||
|
||||
### Replicate Models
|
||||
|
||||
Supported model formats:
|
||||
|
||||
- `owner/name` (recommended for official models), e.g. `google/nano-banana-pro`
|
||||
- `owner/name:version` (community models by version), e.g. `stability-ai/sdxl:<version>`
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Use Replicate default model
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate
|
||||
|
||||
# Override model explicitly
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate --model google/nano-banana
|
||||
```
|
||||
| Provider | Reference |
|
||||
|----------|-----------|
|
||||
| DashScope (Qwen-Image families, custom sizes) | `references/providers/dashscope.md` |
|
||||
| Z.AI (GLM-Image, cogview-4) | `references/providers/zai.md` |
|
||||
| MiniMax (image-01, subject-reference) | `references/providers/minimax.md` |
|
||||
| OpenRouter (multimodal models, `/chat/completions` flow) | `references/providers/openrouter.md` |
|
||||
| Replicate (nano-banana, Seedream, Wan) | `references/providers/replicate.md` |
|
||||
|
||||
## Provider Selection
|
||||
|
||||
1. `--ref` provided + no `--provider` → auto-select Google first, then OpenAI, then Azure, then OpenRouter, then Replicate, then Seedream, then MiniMax (MiniMax subject reference is more specialized toward character/portrait consistency)
|
||||
2. `--provider` specified → use it (if `--ref`, must be `google`, `openai`, `azure`, `openrouter`, `replicate`, `seedream`, or `minimax`)
|
||||
3. Only one API key available → use that provider
|
||||
4. Multiple available → default to Google
|
||||
1. `--ref` provided + no `--provider` → auto-select Google → OpenAI → Azure → OpenRouter → Replicate → Seedream → MiniMax (MiniMax's subject reference is more specialized toward character/portrait consistency)
|
||||
2. `--provider` specified → use it (if `--ref`, must be google/openai/azure/openrouter/replicate/seedream/minimax)
|
||||
3. Only one API key present → use that provider
|
||||
4. Multiple keys → default priority: Google → OpenAI → Azure → OpenRouter → DashScope → Z.AI → MiniMax → Replicate → Jimeng → Seedream
|
||||
|
||||
## Quality Presets
|
||||
|
||||
| Preset | Google imageSize | OpenAI Size | OpenRouter size | Replicate resolution | Use Case |
|
||||
| Preset | Google imageSize | OpenAI size | OpenRouter size | Replicate resolution | Use case |
|
||||
|--------|------------------|-------------|-----------------|----------------------|----------|
|
||||
| `normal` | 1K | 1024px | 1K | 1K | Quick previews |
|
||||
| `2k` (default) | 2K | 2048px | 2K | 2K | Covers, illustrations, infographics |
|
||||
|
||||
**Google/OpenRouter imageSize**: Can be overridden with `--imageSize 1K|2K|4K`
|
||||
Google/OpenRouter `imageSize` can be overridden with `--imageSize 1K|2K|4K`.
|
||||
|
||||
## Aspect Ratios
|
||||
|
||||
Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1`
|
||||
Supported: `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `2.35:1`.
|
||||
|
||||
- Google multimodal: uses `imageConfig.aspectRatio`
|
||||
- OpenAI: maps to closest supported size
|
||||
- OpenRouter: sends `imageGenerationOptions.aspect_ratio`; if only `--size <WxH>` is given, aspect ratio is inferred automatically
|
||||
- Replicate: passes `aspect_ratio` to model; when `--ref` is provided without `--ar`, defaults to `match_input_image`
|
||||
- MiniMax: sends official `aspect_ratio` values directly; if `--size <WxH>` is given without `--ar`, `width` / `height` are sent for `image-01`
|
||||
- Google multimodal: `imageConfig.aspectRatio`
|
||||
- OpenAI: closest supported size
|
||||
- OpenRouter: `imageGenerationOptions.aspect_ratio`; if only `--size <WxH>` is given, the ratio is inferred
|
||||
- Replicate: behavior is model-specific — `google/nano-banana*` uses `aspect_ratio`, `bytedance/seedream-*` uses documented Replicate ratios, Wan 2.7 maps `--ar` to a concrete `size`
|
||||
- MiniMax: official `aspect_ratio` values; if `--size <WxH>` is given without `--ar`, sends `width`/`height` for `image-01`
|
||||
|
||||
## Generation Mode
|
||||
|
||||
**Default**: Sequential generation.
|
||||
**Default**: sequential. **Batch parallel**: enabled automatically when `--batchfile` contains 2+ pending tasks.
|
||||
|
||||
**Batch Parallel Generation**: When `--batchfile` contains 2 or more pending tasks, the script automatically enables parallel generation.
|
||||
| Situation | Prefer | Why |
|
||||
|-----------|--------|-----|
|
||||
| One image, or 1-2 simple images | Sequential | Lower coordination overhead, easier debugging |
|
||||
| Multiple images with saved prompt files | Batch (`--batchfile`) | Reuses finalized prompts, applies shared throttling/retries, predictable throughput |
|
||||
| Each image still needs its own reasoning / prompt writing / style exploration | Subagents | Work is still exploratory, each needs independent analysis |
|
||||
| Input is `outline.md` + `prompts/` (e.g. from `baoyu-article-illustrator`) | Batch — use `scripts/build-batch.ts` to assemble the payload | The outline + prompt files already contain everything needed |
|
||||
|
||||
| Mode | When to Use |
|
||||
|------|-------------|
|
||||
| Sequential (default) | Normal usage, single images, small batches |
|
||||
| Parallel batch | Batch mode with 2+ tasks |
|
||||
Rule of thumb: once prompt files are saved and the task is "generate all of these", prefer batch over subagents. Use subagents only when generation is coupled with per-image thinking or divergent creative exploration.
|
||||
|
||||
Execution choice:
|
||||
|
||||
| Situation | Preferred approach | Why |
|
||||
|-----------|--------------------|-----|
|
||||
| One image, or 1-2 simple images | Sequential | Lower coordination overhead and easier debugging |
|
||||
| Multiple images already have saved prompt files | Batch (`--batchfile`) | Reuses finalized prompts, applies shared throttling/retries, and gives predictable throughput |
|
||||
| Each image still needs separate reasoning, prompt writing, or style exploration | Subagents | The work is still exploratory, so each image may need independent analysis before generation |
|
||||
| Output comes from `baoyu-article-illustrator` with `outline.md` + `prompts/` | Batch (`build-batch.ts` -> `--batchfile`) | That workflow already produces prompt files, so direct batch execution is the intended path |
|
||||
|
||||
Rule of thumb:
|
||||
|
||||
- Prefer batch over subagents once prompt files are already saved and the task is "generate all of these"
|
||||
- Use subagents only when generation is coupled with per-image thinking, rewriting, or divergent creative exploration
|
||||
|
||||
Parallel behavior:
|
||||
**Parallel behavior**:
|
||||
|
||||
- Default worker count is automatic, capped by config, built-in default 10
|
||||
- Provider-specific throttling is applied only in batch mode, and the built-in defaults are tuned for faster throughput while still avoiding obvious RPM bursts
|
||||
- You can override worker count with `--jobs <count>`
|
||||
- Each image retries automatically up to 3 attempts
|
||||
- Provider-specific throttling applies only in batch mode; defaults are tuned for throughput while avoiding RPM bursts
|
||||
- Override with `--jobs <count>`
|
||||
- Each image retries up to 3 attempts
|
||||
- Final output includes success count, failure count, and per-image failure reasons
|
||||
|
||||
## Error Handling
|
||||
@@ -403,6 +211,19 @@ Parallel behavior:
|
||||
- Invalid aspect ratio → warning, proceed with default
|
||||
- Reference images with unsupported provider/model → error with fix hint
|
||||
|
||||
## References
|
||||
|
||||
| File | Content |
|
||||
|------|---------|
|
||||
| `references/usage-examples.md` | Extended CLI examples across providers and batch mode |
|
||||
| `references/providers/dashscope.md` | DashScope families, sizes, limits |
|
||||
| `references/providers/zai.md` | Z.AI GLM-image / cogview-4 |
|
||||
| `references/providers/minimax.md` | MiniMax image-01 + subject reference |
|
||||
| `references/providers/openrouter.md` | OpenRouter multimodal flow |
|
||||
| `references/providers/replicate.md` | Replicate supported families + guardrails |
|
||||
| `references/config/preferences-schema.md` | EXTEND.md schema |
|
||||
| `references/config/first-time-setup.md` | First-time setup flow |
|
||||
|
||||
## Extension Support
|
||||
|
||||
Custom configurations via EXTEND.md. See **Preferences** section for paths and supported options.
|
||||
Custom configurations via EXTEND.md. See Step 0 for paths and schema.
|
||||
|
||||
@@ -57,6 +57,8 @@ options:
|
||||
description: "MiniMax image generation with subject-reference character workflows"
|
||||
- label: "Replicate"
|
||||
description: "Community models - nano-banana-pro, flexible model selection"
|
||||
- label: "Z.AI"
|
||||
description: "GLM-Image - text-to-image with recommended aspect sizes"
|
||||
```
|
||||
|
||||
### Question 2: Default Google Model
|
||||
@@ -119,6 +121,22 @@ options:
|
||||
description: "Faster variant, use aspect ratio instead of custom size"
|
||||
```
|
||||
|
||||
### Question 2e: Default Z.AI Model
|
||||
|
||||
Only show if user selected Z.AI.
|
||||
|
||||
```yaml
|
||||
header: "Z.AI Model"
|
||||
question: "Default Z.AI image generation model?"
|
||||
options:
|
||||
- label: "glm-image (Recommended)"
|
||||
description: "Latest GLM-Image, best aspect-ratio coverage and text rendering"
|
||||
- label: "cogview-4-250304"
|
||||
description: "Legacy CogView-4 model with 16-pixel size stepping"
|
||||
- label: "cogview-4"
|
||||
description: "Previous CogView-4 snapshot for compatibility"
|
||||
```
|
||||
|
||||
### Question 3: Default Quality
|
||||
|
||||
```yaml
|
||||
@@ -167,6 +185,7 @@ default_model:
|
||||
dashscope: null
|
||||
minimax: [selected minimax model or null]
|
||||
replicate: null
|
||||
zai: [selected zai model or null]
|
||||
---
|
||||
```
|
||||
|
||||
@@ -287,6 +306,27 @@ Notes for MiniMax setup:
|
||||
- `image-01-live` is useful when the user prefers faster generation and can work with aspect-ratio-based sizing.
|
||||
- MiniMax subject reference currently uses `subject_reference[].type = character`; docs recommend front-facing portrait references in JPG/JPEG/PNG under 10MB.
|
||||
|
||||
### Z.AI Model Selection
|
||||
|
||||
```yaml
|
||||
header: "Z.AI Model"
|
||||
question: "Choose a default Z.AI image generation model?"
|
||||
options:
|
||||
- label: "glm-image (Recommended)"
|
||||
description: "Latest GLM-Image; pixels round to multiples of 32 and cap at 2^22"
|
||||
- label: "cogview-4-250304"
|
||||
description: "Legacy CogView-4 snapshot with 16-pixel size stepping"
|
||||
- label: "cogview-4"
|
||||
description: "Earlier CogView-4 snapshot for compatibility"
|
||||
```
|
||||
|
||||
Notes for Z.AI setup:
|
||||
|
||||
- Set `ZAI_API_KEY` (or legacy `BIGMODEL_API_KEY`) from https://docs.z.ai/.
|
||||
- `glm-image` supports recommended aspect sizes (1280x1280, 1728x960, 1568x1056, …); uncommon ratios auto-fit to the 2^22 pixel budget on multiples of 32.
|
||||
- Legacy CogView models use 16-pixel stepping and cap at 2^21 pixels per image.
|
||||
- Z.AI does not accept reference images or `n > 1` in `baoyu-image-gen`; use Google/OpenAI providers for those workflows.
|
||||
|
||||
### Update EXTEND.md
|
||||
|
||||
After user selects a model:
|
||||
@@ -304,6 +344,7 @@ default_model:
|
||||
dashscope: [value or null]
|
||||
minimax: [value or null]
|
||||
replicate: [value or null]
|
||||
zai: [value or null]
|
||||
```
|
||||
|
||||
Only set the selected provider's model; leave others as their current value or null.
|
||||
|
||||
@@ -11,7 +11,7 @@ description: EXTEND.md YAML schema for baoyu-image-gen user preferences
|
||||
---
|
||||
version: 1
|
||||
|
||||
default_provider: null # google|openai|azure|openrouter|dashscope|minimax|replicate|null (null = auto-detect)
|
||||
default_provider: null # google|openai|azure|openrouter|dashscope|minimax|replicate|zai|null (null = auto-detect)
|
||||
|
||||
default_quality: null # normal|2k|null (null = use default: 2k)
|
||||
|
||||
@@ -27,6 +27,7 @@ default_model:
|
||||
dashscope: null # e.g., "qwen-image-2.0-pro"
|
||||
minimax: null # e.g., "image-01"
|
||||
replicate: null # e.g., "google/nano-banana-pro"
|
||||
zai: null # e.g., "glm-image", "cogview-4-250304"
|
||||
|
||||
batch:
|
||||
max_workers: 10
|
||||
@@ -52,6 +53,9 @@ batch:
|
||||
minimax:
|
||||
concurrency: 3
|
||||
start_interval_ms: 1100
|
||||
zai:
|
||||
concurrency: 3
|
||||
start_interval_ms: 1100
|
||||
---
|
||||
```
|
||||
|
||||
@@ -71,6 +75,7 @@ batch:
|
||||
| `default_model.dashscope` | string\|null | null | DashScope default model |
|
||||
| `default_model.minimax` | string\|null | null | MiniMax default model |
|
||||
| `default_model.replicate` | string\|null | null | Replicate default model |
|
||||
| `default_model.zai` | string\|null | null | Z.AI default model (glm-image / cogview-4-*) |
|
||||
| `batch.max_workers` | int\|null | 10 | Batch worker cap |
|
||||
| `batch.provider_limits.<provider>.concurrency` | int\|null | provider default | Max simultaneous requests per provider |
|
||||
| `batch.provider_limits.<provider>.start_interval_ms` | int\|null | provider default | Minimum gap between request starts per provider |
|
||||
@@ -102,6 +107,7 @@ default_model:
|
||||
dashscope: "qwen-image-2.0-pro"
|
||||
minimax: "image-01"
|
||||
replicate: "google/nano-banana-pro"
|
||||
zai: "glm-image"
|
||||
batch:
|
||||
max_workers: 10
|
||||
provider_limits:
|
||||
@@ -117,5 +123,8 @@ batch:
|
||||
minimax:
|
||||
concurrency: 3
|
||||
start_interval_ms: 1100
|
||||
zai:
|
||||
concurrency: 3
|
||||
start_interval_ms: 1100
|
||||
---
|
||||
```
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# DashScope (阿里通义万象)
|
||||
|
||||
Read when the user picks `--provider dashscope`, sets `default_model.dashscope`, or asks for Qwen-Image behavior. The SKILL.md only names the default — this file covers model families, sizing rules, and limits.
|
||||
|
||||
## Model Families
|
||||
|
||||
**`qwen-image-2.0*`** — recommended modern family. Members: `qwen-image-2.0-pro`, `qwen-image-2.0-pro-2026-03-03`, `qwen-image-2.0`, `qwen-image-2.0-2026-03-03`.
|
||||
|
||||
- Free-form `size` in `宽*高` format
|
||||
- Total pixels must be between `512*512` and `2048*2048`
|
||||
- Default ≈ `1024*1024`
|
||||
- Best choice for custom ratios (e.g. `21:9`) and text-heavy Chinese/English layouts
|
||||
|
||||
**Fixed-size family** — `qwen-image-max`, `qwen-image-max-2025-12-30`, `qwen-image-plus`, `qwen-image-plus-2026-01-09`, `qwen-image`.
|
||||
|
||||
- Only five sizes allowed: `1664*928`, `1472*1104`, `1328*1328`, `1104*1472`, `928*1664`
|
||||
- Default is `1664*928`
|
||||
- `qwen-image` currently has the same capability as `qwen-image-plus`
|
||||
|
||||
**Legacy** — `z-image-turbo`, `z-image-ultra`, `wanx-v1`. Only use when the user explicitly asks for legacy behavior.
|
||||
|
||||
## Size Resolution
|
||||
|
||||
- `--size` wins over `--ar`
|
||||
- For `qwen-image-2.0*`: prefer explicit `--size`; otherwise infer from `--ar` using the recommended table below
|
||||
- For `qwen-image-max/plus/image`: only use the five fixed sizes; if the requested ratio doesn't fit, switch to `qwen-image-2.0-pro`
|
||||
- `--quality` is a baoyu-imagine preset, not an official DashScope field. The mapping of `normal`/`2k` onto the `qwen-image-2.0*` table is an implementation choice, not an API guarantee
|
||||
|
||||
### Recommended `qwen-image-2.0*` sizes
|
||||
|
||||
| Ratio | `normal` | `2k` |
|
||||
|-------|----------|------|
|
||||
| `1:1` | `1024*1024` | `1536*1536` |
|
||||
| `2:3` | `768*1152` | `1024*1536` |
|
||||
| `3:2` | `1152*768` | `1536*1024` |
|
||||
| `3:4` | `960*1280` | `1080*1440` |
|
||||
| `4:3` | `1280*960` | `1440*1080` |
|
||||
| `9:16` | `720*1280` | `1080*1920` |
|
||||
| `16:9` | `1280*720` | `1920*1080` |
|
||||
| `21:9` | `1344*576` | `2048*872` |
|
||||
|
||||
## Not Exposed
|
||||
|
||||
DashScope APIs also support `negative_prompt`, `prompt_extend`, and `watermark`, but `baoyu-imagine` does not expose them as CLI flags today.
|
||||
|
||||
## Official References
|
||||
|
||||
- [Qwen-Image API](https://help.aliyun.com/zh/model-studio/qwen-image-api)
|
||||
- [Text-to-image guide](https://help.aliyun.com/zh/model-studio/text-to-image)
|
||||
- [Qwen-Image Edit API](https://help.aliyun.com/zh/model-studio/qwen-image-edit-api)
|
||||
@@ -0,0 +1,29 @@
|
||||
# MiniMax
|
||||
|
||||
Read when the user picks `--provider minimax` or sets `default_model.minimax`. Default model is `image-01`.
|
||||
|
||||
## Models
|
||||
|
||||
**`image-01`** (recommended default)
|
||||
|
||||
- Supports text-to-image and subject-reference image generation
|
||||
- Supports official `aspect_ratio` values: `1:1`, `16:9`, `4:3`, `3:2`, `2:3`, `3:4`, `9:16`, `21:9`
|
||||
- Supports documented custom `width` / `height` via `--size <WxH>`
|
||||
- Both width and height must be in `[512, 2048]` and divisible by `8`
|
||||
|
||||
**`image-01-live`** — lower-latency variant
|
||||
|
||||
- Use `--ar` for sizing; MiniMax documents custom `width`/`height` only for `image-01`
|
||||
|
||||
## Subject Reference
|
||||
|
||||
- `--ref` files are sent as MiniMax `subject_reference`
|
||||
- `subject_reference[].type` is currently `character`
|
||||
- Official docs say `image_file` supports public URLs or Base64 Data URLs; baoyu-imagine sends local refs as Data URLs
|
||||
- Recommended refs: front-facing portraits, JPG/JPEG/PNG, under 10MB
|
||||
|
||||
## Official References
|
||||
|
||||
- [Image Generation Guide](https://platform.minimax.io/docs/guides/image-generation)
|
||||
- [Text-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-t2i)
|
||||
- [Image-to-Image API](https://platform.minimax.io/docs/api-reference/image-generation-i2i)
|
||||
@@ -0,0 +1,19 @@
|
||||
# OpenRouter
|
||||
|
||||
Read when the user picks `--provider openrouter`. Default model is `google/gemini-3.1-flash-image-preview`.
|
||||
|
||||
## Common Models
|
||||
|
||||
Use full OpenRouter model IDs:
|
||||
|
||||
- `google/gemini-3.1-flash-image-preview` (recommended — supports image output and reference-image workflows)
|
||||
- `google/gemini-2.5-flash-image-preview`
|
||||
- `black-forest-labs/flux.2-pro`
|
||||
- Any other OpenRouter image-capable model ID
|
||||
|
||||
## Behavior Notes
|
||||
|
||||
- OpenRouter image generation uses `/chat/completions`, not the OpenAI `/images` endpoints
|
||||
- `--ref` requires a multimodal model that supports both image input and image output
|
||||
- `--imageSize` maps to `imageGenerationOptions.size`
|
||||
- `--size <WxH>` is converted to the nearest supported OpenRouter size, and the aspect ratio is inferred when possible
|
||||
@@ -0,0 +1,50 @@
|
||||
# Replicate
|
||||
|
||||
Read when the user picks `--provider replicate`. Replicate support is intentionally scoped to model families baoyu-imagine can validate locally and save without dropping outputs.
|
||||
|
||||
## Supported Families
|
||||
|
||||
**`google/nano-banana*`** (default: `google/nano-banana-2`)
|
||||
|
||||
- Supports prompt-only and reference-image generation
|
||||
- Uses Replicate `aspect_ratio`, `resolution`, and `output_format`
|
||||
- `--size <WxH>` is accepted only as a shorthand for a documented `aspect_ratio` plus `1K` / `2K`
|
||||
|
||||
**`bytedance/seedream-4.5`**
|
||||
|
||||
- Supports prompt-only and reference-image generation
|
||||
- Uses Replicate `size`, `aspect_ratio`, and `image_input`
|
||||
- Local validation blocks unsupported `1K` requests before the API call
|
||||
|
||||
**`bytedance/seedream-5-lite`**
|
||||
|
||||
- Supports prompt-only and reference-image generation
|
||||
- Uses Replicate `size`, `aspect_ratio`, and `image_input`
|
||||
- Local validation currently accepts `2K` / `3K` only
|
||||
|
||||
**`wan-video/wan-2.7-image`**
|
||||
|
||||
- Supports prompt-only and reference-image generation
|
||||
- Uses Replicate `size` and `images`
|
||||
- Max output is 2K
|
||||
|
||||
**`wan-video/wan-2.7-image-pro`**
|
||||
|
||||
- Supports prompt-only and reference-image generation
|
||||
- Uses Replicate `size` and `images`
|
||||
- 4K is allowed only for text-to-image; local validation blocks `4K + --ref`
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Replicate currently supports only single-output save semantics in this tool — keep `--n 1`
|
||||
- If a model is outside the compatibility list above, baoyu-imagine treats it as prompt-only and rejects advanced local options instead of guessing a nano-banana-style schema
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Default model
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate
|
||||
|
||||
# Explicit model
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate --model google/nano-banana
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
# Z.AI GLM-Image
|
||||
|
||||
Read when the user picks `--provider zai` or sets `default_model.zai`. Default model is `glm-image`.
|
||||
|
||||
## Models
|
||||
|
||||
**`glm-image`** (recommended default)
|
||||
|
||||
- Text-to-image only in baoyu-imagine (no `--ref` support yet)
|
||||
- Native `quality` options are `hd` and `standard`; this skill maps `2k → hd` and `normal → standard`
|
||||
- Recommended sizes: `1280x1280`, `1568x1056`, `1056x1568`, `1472x1088`, `1088x1472`, `1728x960`, `960x1728`
|
||||
- Custom `--size` requires width/height in `[1024, 2048]`, divisible by `32`, total pixels ≤ `2^22`
|
||||
|
||||
**`cogview-4-250304`** (legacy family, same endpoint)
|
||||
|
||||
- Custom `--size` requires width/height in `[512, 2048]`, divisible by `16`, total pixels ≤ `2^21`
|
||||
|
||||
## Behavior Notes
|
||||
|
||||
- The sync API returns a temporary URL; baoyu-imagine downloads it and writes locally
|
||||
- `--ref` is not supported for Z.AI in this skill yet
|
||||
- The sync API returns a single image, so `--n > 1` is rejected
|
||||
|
||||
## Official References
|
||||
|
||||
- [GLM-Image Guide](https://docs.z.ai/guides/image/glm-image)
|
||||
- [Generate Image API](https://docs.z.ai/api-reference/image/generate-image)
|
||||
@@ -0,0 +1,108 @@
|
||||
# Usage Examples
|
||||
|
||||
Extended CLI examples. SKILL.md shows the minimum set; read this file when the user asks about provider-specific invocation, batch generation, or less-common flags.
|
||||
|
||||
## Core Patterns
|
||||
|
||||
```bash
|
||||
# Basic text-to-image
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image cat.png
|
||||
|
||||
# With aspect ratio
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A landscape" --image out.png --ar 16:9
|
||||
|
||||
# High quality
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --quality 2k
|
||||
|
||||
# Prompt from files
|
||||
${BUN_X} {baseDir}/scripts/main.ts --promptfiles system.md content.md --image out.png
|
||||
|
||||
# With reference images (any provider family that supports refs)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --ref source.png
|
||||
```
|
||||
|
||||
## Per-Provider
|
||||
|
||||
```bash
|
||||
# OpenAI
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openai
|
||||
|
||||
# Azure OpenAI (model = deployment name)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider azure --model gpt-image-1.5
|
||||
|
||||
# Google with explicit model
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --provider google --model gemini-3-pro-image-preview --ref source.png
|
||||
|
||||
# OpenRouter (recommended default)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openrouter
|
||||
|
||||
# OpenRouter with reference
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --provider openrouter --model google/gemini-3.1-flash-image-preview --ref source.png
|
||||
|
||||
# DashScope (default model)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "一只可爱的猫" --image out.png --provider dashscope
|
||||
|
||||
# DashScope Qwen-Image 2.0 Pro (custom size, Chinese text)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "为咖啡品牌设计一张 21:9 横幅海报,包含清晰中文标题" --image out.png --provider dashscope --model qwen-image-2.0-pro --size 2048x872
|
||||
|
||||
# DashScope legacy fixed-size
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "一张电影感海报" --image out.png --provider dashscope --model qwen-image-max --size 1664x928
|
||||
|
||||
# Z.AI GLM-image
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "一张带清晰中文标题的科技海报" --image out.png --provider zai
|
||||
|
||||
# Z.AI with custom size
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A science illustration with labels" --image out.png --provider zai --model glm-image --size 1472x1088
|
||||
|
||||
# MiniMax
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A fashion editorial portrait" --image out.jpg --provider minimax
|
||||
|
||||
# MiniMax with subject reference (character/portrait consistency)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A girl by the library window" --image out.jpg --provider minimax --model image-01 --ref portrait.png --ar 16:9
|
||||
|
||||
# Replicate (default: google/nano-banana-2)
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider replicate
|
||||
|
||||
# Replicate Seedream 4.5
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cinematic portrait" --image out.png --provider replicate --model bytedance/seedream-4.5 --ar 3:2
|
||||
|
||||
# Replicate Wan 2.7 Image Pro
|
||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A concept frame" --image out.png --provider replicate --model wan-video/wan-2.7-image-pro --size 2048x1152
|
||||
```
|
||||
|
||||
## Batch Mode
|
||||
|
||||
```bash
|
||||
# Batch from saved prompt files
|
||||
${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json
|
||||
|
||||
# Batch with explicit worker count
|
||||
${BUN_X} {baseDir}/scripts/main.ts --batchfile batch.json --jobs 4 --json
|
||||
```
|
||||
|
||||
### Batch File Format
|
||||
|
||||
```json
|
||||
{
|
||||
"jobs": 4,
|
||||
"tasks": [
|
||||
{
|
||||
"id": "hero",
|
||||
"promptFiles": ["prompts/hero.md"],
|
||||
"image": "out/hero.png",
|
||||
"provider": "replicate",
|
||||
"model": "google/nano-banana-2",
|
||||
"ar": "16:9",
|
||||
"quality": "2k"
|
||||
},
|
||||
{
|
||||
"id": "diagram",
|
||||
"promptFiles": ["prompts/diagram.md"],
|
||||
"image": "out/diagram.png",
|
||||
"ref": ["references/original.png"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch file's directory. `jobs` is optional (overridden by CLI `--jobs`). A top-level array without the `jobs` wrapper is also accepted.
|
||||
@@ -62,6 +62,7 @@ const DEFAULT_PROVIDER_RATE_LIMITS: Record<Provider, ProviderRateLimit> = {
|
||||
jimeng: { concurrency: 3, startIntervalMs: 1100 },
|
||||
seedream: { concurrency: 3, startIntervalMs: 1100 },
|
||||
azure: { concurrency: 3, startIntervalMs: 1100 },
|
||||
zai: { concurrency: 3, startIntervalMs: 1100 },
|
||||
};
|
||||
|
||||
function printUsage(): void {
|
||||
@@ -76,7 +77,7 @@ Options:
|
||||
--image <path> Output image path (required in single-image mode)
|
||||
--batchfile <path> JSON batch file for multi-image generation
|
||||
--jobs <count> Worker count for batch mode (default: auto, max from config, built-in default 10)
|
||||
--provider google|openai|openrouter|dashscope|minimax|replicate|jimeng|seedream|azure Force provider (auto-detect by default)
|
||||
--provider google|openai|openrouter|dashscope|minimax|replicate|jimeng|seedream|azure|zai Force provider (auto-detect by default)
|
||||
-m, --model <id> Model ID
|
||||
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
|
||||
--size <WxH> Size (e.g., 1024x1024)
|
||||
@@ -118,6 +119,8 @@ Environment variables:
|
||||
JIMENG_ACCESS_KEY_ID Jimeng Access Key ID
|
||||
JIMENG_SECRET_ACCESS_KEY Jimeng Secret Access Key
|
||||
ARK_API_KEY Seedream/Ark API key
|
||||
ZAI_API_KEY Z.AI API key (alias: BIGMODEL_API_KEY)
|
||||
BIGMODEL_API_KEY Z.AI API key alias (legacy BigModel credentials)
|
||||
OPENAI_IMAGE_MODEL Default OpenAI model (gpt-image-1.5)
|
||||
OPENROUTER_IMAGE_MODEL Default OpenRouter model (google/gemini-3.1-flash-image-preview)
|
||||
GOOGLE_IMAGE_MODEL Default Google model (gemini-3-pro-image-preview)
|
||||
@@ -126,6 +129,8 @@ Environment variables:
|
||||
REPLICATE_IMAGE_MODEL Default Replicate model (google/nano-banana-pro)
|
||||
JIMENG_IMAGE_MODEL Default Jimeng model (jimeng_t2i_v40)
|
||||
SEEDREAM_IMAGE_MODEL Default Seedream model (doubao-seedream-5-0-260128)
|
||||
ZAI_IMAGE_MODEL Default Z.AI model (glm-image)
|
||||
BIGMODEL_IMAGE_MODEL Z.AI model alias (legacy BigModel variable)
|
||||
OPENAI_BASE_URL Custom OpenAI endpoint
|
||||
OPENAI_IMAGE_USE_CHAT Use /chat/completions instead of /images/generations (true|false)
|
||||
OPENROUTER_BASE_URL Custom OpenRouter endpoint
|
||||
@@ -142,6 +147,8 @@ Environment variables:
|
||||
AZURE_API_VERSION Azure API version (default: 2025-04-01-preview)
|
||||
AZURE_OPENAI_IMAGE_MODEL Backward-compatible Azure deployment/model alias (defaults to gpt-image-1.5)
|
||||
SEEDREAM_BASE_URL Custom Seedream endpoint
|
||||
ZAI_BASE_URL Custom Z.AI endpoint (defaults to https://api.z.ai/api/paas/v4)
|
||||
BIGMODEL_BASE_URL Z.AI endpoint alias (legacy BigModel variable)
|
||||
BAOYU_IMAGE_GEN_MAX_WORKERS Override batch worker cap
|
||||
BAOYU_IMAGE_GEN_<PROVIDER>_CONCURRENCY Override provider concurrency
|
||||
BAOYU_IMAGE_GEN_<PROVIDER>_START_INTERVAL_MS Override provider start gap in ms
|
||||
@@ -243,7 +250,8 @@ export function parseArgs(argv: string[]): CliArgs {
|
||||
v !== "replicate" &&
|
||||
v !== "jimeng" &&
|
||||
v !== "seedream" &&
|
||||
v !== "azure"
|
||||
v !== "azure" &&
|
||||
v !== "zai"
|
||||
) {
|
||||
throw new Error(`Invalid provider: ${v}`);
|
||||
}
|
||||
@@ -400,6 +408,7 @@ export function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
||||
jimeng: null,
|
||||
seedream: null,
|
||||
azure: null,
|
||||
zai: null,
|
||||
};
|
||||
currentKey = "default_model";
|
||||
currentProvider = null;
|
||||
@@ -427,7 +436,8 @@ export function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
||||
key === "replicate" ||
|
||||
key === "jimeng" ||
|
||||
key === "seedream" ||
|
||||
key === "azure"
|
||||
key === "azure" ||
|
||||
key === "zai"
|
||||
)
|
||||
) {
|
||||
config.batch ??= {};
|
||||
@@ -445,7 +455,8 @@ export function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
||||
key === "replicate" ||
|
||||
key === "jimeng" ||
|
||||
key === "seedream" ||
|
||||
key === "azure"
|
||||
key === "azure" ||
|
||||
key === "zai"
|
||||
)
|
||||
) {
|
||||
const cleaned = value.replace(/['"]/g, "");
|
||||
@@ -540,9 +551,10 @@ export function getConfiguredProviderRateLimits(
|
||||
jimeng: { ...DEFAULT_PROVIDER_RATE_LIMITS.jimeng },
|
||||
seedream: { ...DEFAULT_PROVIDER_RATE_LIMITS.seedream },
|
||||
azure: { ...DEFAULT_PROVIDER_RATE_LIMITS.azure },
|
||||
zai: { ...DEFAULT_PROVIDER_RATE_LIMITS.zai },
|
||||
};
|
||||
|
||||
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "minimax", "jimeng", "seedream", "azure"] as Provider[]) {
|
||||
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "minimax", "jimeng", "seedream", "azure", "zai"] as Provider[]) {
|
||||
const envPrefix = `BAOYU_IMAGE_GEN_${provider.toUpperCase()}`;
|
||||
const extendLimit = extendConfig.batch?.provider_limits?.[provider];
|
||||
configured[provider] = {
|
||||
@@ -625,6 +637,7 @@ export function detectProvider(args: CliArgs): Provider {
|
||||
const hasReplicate = !!process.env.REPLICATE_API_TOKEN;
|
||||
const hasJimeng = !!(process.env.JIMENG_ACCESS_KEY_ID && process.env.JIMENG_SECRET_ACCESS_KEY);
|
||||
const hasSeedream = !!process.env.ARK_API_KEY;
|
||||
const hasZai = !!(process.env.ZAI_API_KEY || process.env.BIGMODEL_API_KEY);
|
||||
const modelProvider = inferProviderFromModel(args.model);
|
||||
|
||||
if (modelProvider === "seedream") {
|
||||
@@ -664,13 +677,14 @@ export function detectProvider(args: CliArgs): Provider {
|
||||
hasReplicate && "replicate",
|
||||
hasJimeng && "jimeng",
|
||||
hasSeedream && "seedream",
|
||||
hasZai && "zai",
|
||||
].filter(Boolean) as Provider[];
|
||||
|
||||
if (available.length === 1) return available[0]!;
|
||||
if (available.length > 1) return available[0]!;
|
||||
|
||||
throw new Error(
|
||||
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY+AZURE_OPENAI_BASE_URL, OPENROUTER_API_KEY, DASHSCOPE_API_KEY, MINIMAX_API_KEY, REPLICATE_API_TOKEN, JIMENG keys, or ARK_API_KEY.\n" +
|
||||
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY+AZURE_OPENAI_BASE_URL, OPENROUTER_API_KEY, DASHSCOPE_API_KEY, MINIMAX_API_KEY, REPLICATE_API_TOKEN, JIMENG keys, ARK_API_KEY, or ZAI_API_KEY/BIGMODEL_API_KEY.\n" +
|
||||
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
|
||||
);
|
||||
}
|
||||
@@ -715,6 +729,7 @@ async function loadProviderModule(provider: Provider): Promise<ProviderModule> {
|
||||
if (provider === "jimeng") return (await import("./providers/jimeng")) as ProviderModule;
|
||||
if (provider === "seedream") return (await import("./providers/seedream")) as ProviderModule;
|
||||
if (provider === "azure") return (await import("./providers/azure")) as ProviderModule;
|
||||
if (provider === "zai") return (await import("./providers/zai")) as ProviderModule;
|
||||
return (await import("./providers/openai")) as ProviderModule;
|
||||
}
|
||||
|
||||
@@ -745,6 +760,7 @@ function getModelForProvider(
|
||||
if (provider === "jimeng" && extendConfig.default_model.jimeng) return extendConfig.default_model.jimeng;
|
||||
if (provider === "seedream" && extendConfig.default_model.seedream) return extendConfig.default_model.seedream;
|
||||
if (provider === "azure" && extendConfig.default_model.azure) return extendConfig.default_model.azure;
|
||||
if (provider === "zai" && extendConfig.default_model.zai) return extendConfig.default_model.zai;
|
||||
}
|
||||
return providerModule.getDefaultModel();
|
||||
}
|
||||
@@ -964,7 +980,7 @@ async function runBatchTasks(
|
||||
const acquireProvider = createProviderGate(providerRateLimits);
|
||||
const workerCount = getWorkerCount(tasks.length, jobs, maxWorkers);
|
||||
console.error(`Batch mode: ${tasks.length} tasks, ${workerCount} workers, parallel mode enabled.`);
|
||||
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "jimeng", "seedream", "azure"] as Provider[]) {
|
||||
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "jimeng", "seedream", "azure", "zai"] as Provider[]) {
|
||||
const limit = providerRateLimits[provider];
|
||||
console.error(`- ${provider}: concurrency=${limit.concurrency}, startIntervalMs=${limit.startIntervalMs}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test, { type TestContext } from "node:test";
|
||||
|
||||
import type { CliArgs } from "../types.ts";
|
||||
import {
|
||||
buildRequestBody,
|
||||
buildZaiUrl,
|
||||
extractImageFromResponse,
|
||||
getDefaultModel,
|
||||
getModelFamily,
|
||||
parseAspectRatio,
|
||||
parseSize,
|
||||
resolveSizeForModel,
|
||||
validateArgs,
|
||||
} from "./zai.ts";
|
||||
|
||||
function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||
return {
|
||||
prompt: null,
|
||||
promptFiles: [],
|
||||
imagePath: null,
|
||||
provider: null,
|
||||
model: null,
|
||||
aspectRatio: null,
|
||||
size: null,
|
||||
quality: null,
|
||||
imageSize: null,
|
||||
referenceImages: [],
|
||||
n: 1,
|
||||
batchFile: null,
|
||||
jobs: null,
|
||||
json: false,
|
||||
help: false,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function useEnv(
|
||||
t: TestContext,
|
||||
values: Record<string, string | null>,
|
||||
): void {
|
||||
const previous = new Map<string, string | undefined>();
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
previous.set(key, process.env[key]);
|
||||
if (value == null) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
t.after(() => {
|
||||
for (const [key, value] of previous.entries()) {
|
||||
if (value == null) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test("Z.AI default model prefers env override and otherwise uses glm-image", (t) => {
|
||||
useEnv(t, {
|
||||
ZAI_IMAGE_MODEL: null,
|
||||
BIGMODEL_IMAGE_MODEL: null,
|
||||
});
|
||||
assert.equal(getDefaultModel(), "glm-image");
|
||||
|
||||
process.env.BIGMODEL_IMAGE_MODEL = "cogview-4-250304";
|
||||
assert.equal(getDefaultModel(), "cogview-4-250304");
|
||||
});
|
||||
|
||||
test("Z.AI URL builder normalizes host, v4 base, and full endpoint inputs", (t) => {
|
||||
useEnv(t, { ZAI_BASE_URL: "https://api.z.ai" });
|
||||
assert.equal(buildZaiUrl(), "https://api.z.ai/api/paas/v4/images/generations");
|
||||
|
||||
process.env.ZAI_BASE_URL = "https://proxy.example.com/api/paas/v4/";
|
||||
assert.equal(buildZaiUrl(), "https://proxy.example.com/api/paas/v4/images/generations");
|
||||
|
||||
process.env.ZAI_BASE_URL = "https://proxy.example.com/custom/images/generations";
|
||||
assert.equal(buildZaiUrl(), "https://proxy.example.com/custom/images/generations");
|
||||
});
|
||||
|
||||
test("Z.AI model family and parsing helpers recognize documented formats", () => {
|
||||
assert.equal(getModelFamily("glm-image"), "glm");
|
||||
assert.equal(getModelFamily("cogview-4-250304"), "legacy");
|
||||
assert.deepEqual(parseAspectRatio("16:9"), { width: 16, height: 9 });
|
||||
assert.equal(parseAspectRatio("wide"), null);
|
||||
assert.deepEqual(parseSize("1280x1280"), { width: 1280, height: 1280 });
|
||||
assert.deepEqual(parseSize("1472*1088"), { width: 1472, height: 1088 });
|
||||
assert.equal(parseSize("big"), null);
|
||||
});
|
||||
|
||||
test("Z.AI size resolution follows documented recommended ratios and validates custom sizes", () => {
|
||||
assert.equal(
|
||||
resolveSizeForModel("glm-image", makeArgs({ aspectRatio: "16:9", quality: "2k" })),
|
||||
"1728x960",
|
||||
);
|
||||
assert.equal(
|
||||
resolveSizeForModel("cogview-4-250304", makeArgs({ aspectRatio: "4:3", quality: "normal" })),
|
||||
"1152x864",
|
||||
);
|
||||
assert.equal(
|
||||
resolveSizeForModel("glm-image", makeArgs({ size: "1568x1056", quality: "2k" })),
|
||||
"1568x1056",
|
||||
);
|
||||
|
||||
const uncommon = resolveSizeForModel(
|
||||
"glm-image",
|
||||
makeArgs({ aspectRatio: "5:2", quality: "normal" }),
|
||||
);
|
||||
const parsed = parseSize(uncommon);
|
||||
assert.ok(parsed);
|
||||
assert.ok(parsed.width % 32 === 0);
|
||||
assert.ok(parsed.height % 32 === 0);
|
||||
assert.ok(parsed.width * parsed.height <= 2 ** 22);
|
||||
|
||||
assert.throws(
|
||||
() => resolveSizeForModel("glm-image", makeArgs({ size: "1000x1000", quality: "2k" })),
|
||||
/between 1024 and 2048/,
|
||||
);
|
||||
assert.throws(
|
||||
() => resolveSizeForModel("glm-image", makeArgs({ size: "1280x1260", quality: "2k" })),
|
||||
/divisible by 32/,
|
||||
);
|
||||
assert.throws(
|
||||
() => resolveSizeForModel("cogview-4-250304", makeArgs({ size: "2048x2048", quality: "2k" })),
|
||||
/must not exceed 2\^21 total pixels/,
|
||||
);
|
||||
});
|
||||
|
||||
test("Z.AI validation rejects unsupported refs and multi-image requests", () => {
|
||||
assert.throws(
|
||||
() => validateArgs("glm-image", makeArgs({ referenceImages: ["ref.png"] })),
|
||||
/text-to-image only/,
|
||||
);
|
||||
assert.throws(
|
||||
() => validateArgs("glm-image", makeArgs({ n: 2 })),
|
||||
/single image per request/,
|
||||
);
|
||||
});
|
||||
|
||||
test("Z.AI request body maps skill quality and resolved size into provider fields", () => {
|
||||
const body = buildRequestBody(
|
||||
"A cinematic science poster",
|
||||
"glm-image",
|
||||
makeArgs({ aspectRatio: "4:3", quality: "normal" }),
|
||||
);
|
||||
|
||||
assert.deepEqual(body, {
|
||||
model: "glm-image",
|
||||
prompt: "A cinematic science poster",
|
||||
quality: "standard",
|
||||
size: "1472x1088",
|
||||
});
|
||||
});
|
||||
|
||||
test("Z.AI response extraction downloads the returned image URL", async (t) => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
t.after(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
globalThis.fetch = async () =>
|
||||
new Response(Uint8Array.from([1, 2, 3]), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "image/png" },
|
||||
});
|
||||
|
||||
const image = await extractImageFromResponse({
|
||||
data: [{ url: "https://cdn.example.com/glm-image.png" }],
|
||||
});
|
||||
assert.deepEqual([...image], [1, 2, 3]);
|
||||
|
||||
await assert.rejects(
|
||||
() => extractImageFromResponse({ data: [{}] }),
|
||||
/No image URL/,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,306 @@
|
||||
import type { CliArgs, Quality } from "../types";
|
||||
|
||||
type ZaiModelFamily = "glm" | "legacy";
|
||||
|
||||
type ZaiRequestBody = {
|
||||
model: string;
|
||||
prompt: string;
|
||||
quality: "hd" | "standard";
|
||||
size: string;
|
||||
};
|
||||
|
||||
type ZaiResponse = {
|
||||
data?: Array<{ url?: string }>;
|
||||
};
|
||||
|
||||
const DEFAULT_MODEL = "glm-image";
|
||||
const GLM_MAX_PIXELS = 2 ** 22;
|
||||
const LEGACY_MAX_PIXELS = 2 ** 21;
|
||||
const GLM_SIZE_STEP = 32;
|
||||
const LEGACY_SIZE_STEP = 16;
|
||||
|
||||
const GLM_RECOMMENDED_SIZES: Record<string, string> = {
|
||||
"1:1": "1280x1280",
|
||||
"3:2": "1568x1056",
|
||||
"2:3": "1056x1568",
|
||||
"4:3": "1472x1088",
|
||||
"3:4": "1088x1472",
|
||||
"16:9": "1728x960",
|
||||
"9:16": "960x1728",
|
||||
};
|
||||
|
||||
const LEGACY_RECOMMENDED_SIZES: Record<string, string> = {
|
||||
"1:1": "1024x1024",
|
||||
"9:16": "768x1344",
|
||||
"3:4": "864x1152",
|
||||
"16:9": "1344x768",
|
||||
"4:3": "1152x864",
|
||||
"2:1": "1440x720",
|
||||
"1:2": "720x1440",
|
||||
};
|
||||
|
||||
export function getDefaultModel(): string {
|
||||
return process.env.ZAI_IMAGE_MODEL || process.env.BIGMODEL_IMAGE_MODEL || DEFAULT_MODEL;
|
||||
}
|
||||
|
||||
function getApiKey(): string | null {
|
||||
return process.env.ZAI_API_KEY || process.env.BIGMODEL_API_KEY || null;
|
||||
}
|
||||
|
||||
export function buildZaiUrl(): string {
|
||||
const base = (process.env.ZAI_BASE_URL || process.env.BIGMODEL_BASE_URL || "https://api.z.ai/api/paas/v4")
|
||||
.replace(/\/+$/g, "");
|
||||
if (base.endsWith("/images/generations")) return base;
|
||||
if (base.endsWith("/api/paas/v4")) return `${base}/images/generations`;
|
||||
if (base.endsWith("/v4")) return `${base}/images/generations`;
|
||||
return `${base}/api/paas/v4/images/generations`;
|
||||
}
|
||||
|
||||
export function getModelFamily(model: string): ZaiModelFamily {
|
||||
return model.trim().toLowerCase() === "glm-image" ? "glm" : "legacy";
|
||||
}
|
||||
|
||||
export function parseAspectRatio(ar: string): { width: number; height: number } | null {
|
||||
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
||||
if (!match) return null;
|
||||
const width = Number(match[1]);
|
||||
const height = Number(match[2]);
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
||||
return null;
|
||||
}
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
export function parseSize(size: string): { width: number; height: number } | null {
|
||||
const match = size.trim().match(/^(\d+)\s*[xX*]\s*(\d+)$/);
|
||||
if (!match) return null;
|
||||
const width = parseInt(match[1]!, 10);
|
||||
const height = parseInt(match[2]!, 10);
|
||||
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
||||
return null;
|
||||
}
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
function formatSize(width: number, height: number): string {
|
||||
return `${width}x${height}`;
|
||||
}
|
||||
|
||||
function roundToStep(value: number, step: number): number {
|
||||
return Math.max(step, Math.round(value / step) * step);
|
||||
}
|
||||
|
||||
function getRatioValue(ar: string): number | null {
|
||||
const parsed = parseAspectRatio(ar);
|
||||
if (!parsed) return null;
|
||||
return parsed.width / parsed.height;
|
||||
}
|
||||
|
||||
function findClosestRatioKey(ar: string, candidates: string[]): string | null {
|
||||
const targetRatio = getRatioValue(ar);
|
||||
if (targetRatio == null) return null;
|
||||
|
||||
let bestKey: string | null = null;
|
||||
let bestDiff = Infinity;
|
||||
for (const candidate of candidates) {
|
||||
const candidateRatio = getRatioValue(candidate);
|
||||
if (candidateRatio == null) continue;
|
||||
const diff = Math.abs(candidateRatio - targetRatio);
|
||||
if (diff < bestDiff) {
|
||||
bestDiff = diff;
|
||||
bestKey = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDiff <= 0.05 ? bestKey : null;
|
||||
}
|
||||
|
||||
function getTargetPixels(quality: Quality): number {
|
||||
return quality === "normal" ? 1024 * 1024 : 1536 * 1536;
|
||||
}
|
||||
|
||||
function fitToPixelBudget(
|
||||
width: number,
|
||||
height: number,
|
||||
targetPixels: number,
|
||||
maxPixels: number,
|
||||
step: number,
|
||||
): { width: number; height: number } {
|
||||
let nextWidth = width;
|
||||
let nextHeight = height;
|
||||
const pixels = nextWidth * nextHeight;
|
||||
|
||||
if (pixels > maxPixels) {
|
||||
const scale = Math.sqrt(maxPixels / pixels);
|
||||
nextWidth *= scale;
|
||||
nextHeight *= scale;
|
||||
} else {
|
||||
const scale = Math.sqrt(targetPixels / pixels);
|
||||
nextWidth *= scale;
|
||||
nextHeight *= scale;
|
||||
}
|
||||
|
||||
let roundedWidth = roundToStep(nextWidth, step);
|
||||
let roundedHeight = roundToStep(nextHeight, step);
|
||||
let roundedPixels = roundedWidth * roundedHeight;
|
||||
|
||||
while (roundedPixels > maxPixels && (roundedWidth > step || roundedHeight > step)) {
|
||||
if (roundedWidth >= roundedHeight && roundedWidth > step) {
|
||||
roundedWidth -= step;
|
||||
} else if (roundedHeight > step) {
|
||||
roundedHeight -= step;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
roundedPixels = roundedWidth * roundedHeight;
|
||||
}
|
||||
|
||||
return { width: roundedWidth, height: roundedHeight };
|
||||
}
|
||||
|
||||
function validateCustomSize(
|
||||
size: string,
|
||||
family: ZaiModelFamily,
|
||||
): string {
|
||||
const parsed = parseSize(size);
|
||||
if (!parsed) {
|
||||
throw new Error("Z.AI --size must be in WxH format, for example 1280x1280.");
|
||||
}
|
||||
|
||||
const widthStep = family === "glm" ? GLM_SIZE_STEP : LEGACY_SIZE_STEP;
|
||||
const minEdge = family === "glm" ? 1024 : 512;
|
||||
const maxPixels = family === "glm" ? GLM_MAX_PIXELS : LEGACY_MAX_PIXELS;
|
||||
|
||||
if (parsed.width < minEdge || parsed.width > 2048 || parsed.height < minEdge || parsed.height > 2048) {
|
||||
throw new Error(
|
||||
family === "glm"
|
||||
? "GLM-image custom size requires width and height between 1024 and 2048."
|
||||
: "Z.AI legacy image models require width and height between 512 and 2048."
|
||||
);
|
||||
}
|
||||
|
||||
if (parsed.width % widthStep !== 0 || parsed.height % widthStep !== 0) {
|
||||
throw new Error(
|
||||
family === "glm"
|
||||
? "GLM-image custom size requires width and height divisible by 32."
|
||||
: "Z.AI legacy image models require width and height divisible by 16."
|
||||
);
|
||||
}
|
||||
|
||||
if (parsed.width * parsed.height > maxPixels) {
|
||||
throw new Error(
|
||||
family === "glm"
|
||||
? "GLM-image custom size must not exceed 2^22 total pixels."
|
||||
: "Z.AI legacy image size must not exceed 2^21 total pixels."
|
||||
);
|
||||
}
|
||||
|
||||
return formatSize(parsed.width, parsed.height);
|
||||
}
|
||||
|
||||
export function resolveSizeForModel(
|
||||
model: string,
|
||||
args: Pick<CliArgs, "size" | "aspectRatio" | "quality">,
|
||||
): string {
|
||||
const family = getModelFamily(model);
|
||||
const quality = args.quality === "normal" ? "normal" : "2k";
|
||||
|
||||
if (args.size) {
|
||||
return validateCustomSize(args.size, family);
|
||||
}
|
||||
|
||||
const recommended = family === "glm" ? GLM_RECOMMENDED_SIZES : LEGACY_RECOMMENDED_SIZES;
|
||||
const defaultSize = family === "glm" ? "1280x1280" : "1024x1024";
|
||||
|
||||
if (!args.aspectRatio) return defaultSize;
|
||||
|
||||
const recommendedRatio = findClosestRatioKey(args.aspectRatio, Object.keys(recommended));
|
||||
if (recommendedRatio) {
|
||||
return recommended[recommendedRatio]!;
|
||||
}
|
||||
|
||||
const parsedRatio = parseAspectRatio(args.aspectRatio);
|
||||
if (!parsedRatio) return defaultSize;
|
||||
|
||||
const targetPixels = getTargetPixels(quality);
|
||||
const maxPixels = family === "glm" ? GLM_MAX_PIXELS : LEGACY_MAX_PIXELS;
|
||||
const step = family === "glm" ? GLM_SIZE_STEP : LEGACY_SIZE_STEP;
|
||||
const fit = fitToPixelBudget(
|
||||
parsedRatio.width,
|
||||
parsedRatio.height,
|
||||
targetPixels,
|
||||
maxPixels,
|
||||
step,
|
||||
);
|
||||
return formatSize(fit.width, fit.height);
|
||||
}
|
||||
|
||||
function getZaiQuality(quality: CliArgs["quality"]): "hd" | "standard" {
|
||||
return quality === "normal" ? "standard" : "hd";
|
||||
}
|
||||
|
||||
export function validateArgs(_model: string, args: CliArgs): void {
|
||||
if (args.referenceImages.length > 0) {
|
||||
throw new Error("Z.AI GLM-image currently supports text-to-image only in baoyu-image-gen. Remove --ref or choose another provider.");
|
||||
}
|
||||
|
||||
if (args.n > 1) {
|
||||
throw new Error("Z.AI image generation currently returns a single image per request in baoyu-image-gen.");
|
||||
}
|
||||
}
|
||||
|
||||
export function buildRequestBody(
|
||||
prompt: string,
|
||||
model: string,
|
||||
args: CliArgs,
|
||||
): ZaiRequestBody {
|
||||
validateArgs(model, args);
|
||||
return {
|
||||
model,
|
||||
prompt,
|
||||
quality: getZaiQuality(args.quality),
|
||||
size: resolveSizeForModel(model, args),
|
||||
};
|
||||
}
|
||||
|
||||
export async function extractImageFromResponse(result: ZaiResponse): Promise<Uint8Array> {
|
||||
const url = result.data?.[0]?.url;
|
||||
if (!url) {
|
||||
throw new Error("No image URL in Z.AI response");
|
||||
}
|
||||
|
||||
const imageResponse = await fetch(url);
|
||||
if (!imageResponse.ok) {
|
||||
throw new Error(`Failed to download image from Z.AI: ${imageResponse.status}`);
|
||||
}
|
||||
|
||||
return new Uint8Array(await imageResponse.arrayBuffer());
|
||||
}
|
||||
|
||||
export async function generateImage(
|
||||
prompt: string,
|
||||
model: string,
|
||||
args: CliArgs,
|
||||
): Promise<Uint8Array> {
|
||||
const apiKey = getApiKey();
|
||||
if (!apiKey) {
|
||||
throw new Error("ZAI_API_KEY is required. Get one from https://docs.z.ai/.");
|
||||
}
|
||||
|
||||
const response = await fetch(buildZaiUrl(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify(buildRequestBody(prompt, model, args)),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.text();
|
||||
throw new Error(`Z.AI API error (${response.status}): ${err}`);
|
||||
}
|
||||
|
||||
const result = (await response.json()) as ZaiResponse;
|
||||
return extractImageFromResponse(result);
|
||||
}
|
||||
@@ -7,7 +7,8 @@ export type Provider =
|
||||
| "replicate"
|
||||
| "jimeng"
|
||||
| "seedream"
|
||||
| "azure";
|
||||
| "azure"
|
||||
| "zai";
|
||||
export type Quality = "normal" | "2k";
|
||||
|
||||
export type CliArgs = {
|
||||
@@ -66,6 +67,7 @@ export type ExtendConfig = {
|
||||
jimeng: string | null;
|
||||
seedream: string | null;
|
||||
azure: string | null;
|
||||
zai: string | null;
|
||||
};
|
||||
batch?: {
|
||||
max_workers?: number | null;
|
||||
|
||||
Reference in New Issue
Block a user