---
name: image-gen-openrouter
description: 当用户说"生成图片"、"画一张图"、"配图"、"OpenRouter 生图"、"nano banana"、"image-gen-openrouter"，或在 workflow 里要按提示词生成图片时使用。调用 OpenRouter 的 Chat Completions 接口（OpenAI 兼容 + image modalities），按提示词生成任意比例图片并落盘。
---

# image-gen-openrouter —— OpenRouter 文生图

> 这是一个**通用 skill**，不绑定特定场景。任何需要文生图的 workflow / 用户对话都能调它。

## 输入约定

调用方传一行 `key=value` 字段串，**字段顺序无要求**，例：

```
prompt=A cat reading a book in cyberpunk neon city output=./assets/img_0.png aspect=9:16 size=4K model=google/gemini-3-pro-image-preview key=sk-or-...
```

字段定义：
- **prompt**（必填）：图片提示词（建议英文，效果更好）。可包含空格 —— 用"剩余串"策略解析（先把其它已知字段抠出去，剩下的就是 prompt）
- **output**（必填）：目标图片路径（相对 cwd 或绝对）
- **aspect**（可选，默认 `1:1`）：宽高比；OpenRouter 支持 `1:1` / `2:3` / `3:2` / `3:4` / `4:3` / `4:5` / `5:4` / `9:16` / `16:9` / `21:9` 等
- **size**（可选，默认 `1K`）：分辨率；可选 `0.5K` / `1K` / `2K` / `4K`（部分模型不支持 4K，限流时自动降级）
- **model**（可选，默认 `google/gemini-3-pro-image-preview`）—— Nano Banana Pro，质量最好；备选 `google/gemini-3.1-flash-image-preview`（便宜）、`google/gemini-2.5-flash-image`（兜底）
- **key**（可选）：API key；不传则用环境变量 `OPENROUTER_API_KEY`

## 标准执行流程

### 1. 解析输入

仔细从 step.input 里提取 4 个字段。注意 prompt 可能包含空格，所以按"先取 output= / model= / key= 之外的部分作为 prompt"的策略最稳妥。

如果你拿不到 prompt 或 output，立刻报错并返回。

### 2. 取 API key

按优先级：
1. step.input 里的 `key=` 字段
2. 用 Bash 跑 `echo "$OPENROUTER_API_KEY"` 看环境变量

两者都空 → 报错 "OpenRouter API key 未配置（既不在 step.input 也不在 OPENROUTER_API_KEY 环境变量）" 并退出。

### 3. 调 OpenRouter API

用 Bash 跑 curl（**注意把变量替换成实际值**）：

```bash
curl -sS https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer <YOUR_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<MODEL>",
    "messages": [{"role": "user", "content": "<PROMPT>"}],
    "modalities": ["image", "text"],
    "image_config": {"aspect_ratio": "<ASPECT>", "image_size": "<SIZE>"}
  }' \
  -o /tmp/openrouter_resp.json
```

> 💡 prompt 里若含双引号/特殊字符，用 jq 拼 JSON（**推荐**）：
> ```bash
> jq -n --arg p "$PROMPT" --arg m "$MODEL" --arg a "$ASPECT" --arg s "$SIZE" \
>   '{model:$m, messages:[{role:"user", content:$p}], modalities:["image","text"], image_config:{aspect_ratio:$a, image_size:$s}}' \
>   | curl -sS https://openrouter.ai/api/v1/chat/completions \
>       -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
>       --data-binary @- -o /tmp/openrouter_resp.json
> ```

### 4. 提取 base64 并解码到 PNG

OpenRouter 返回结构（关键字段）：
```
.choices[0].message.images[0].image_url.url  →  "data:image/png;base64,iVBORw0KG..."
```

**Linux/macOS/git-bash on Windows：**
```bash
mkdir -p "$(dirname <OUTPUT>)"
jq -r '.choices[0].message.images[0].image_url.url' /tmp/openrouter_resp.json \
  | sed 's|^data:image/[a-z]*;base64,||' \
  | base64 -d > <OUTPUT>
```

**Windows PowerShell（base64 -d 不存在时）：**
```powershell
powershell -NoProfile -Command "$resp = Get-Content /tmp/openrouter_resp.json -Raw | ConvertFrom-Json; $url = $resp.choices[0].message.images[0].image_url.url; $b64 = $url -replace '^data:image/[a-z]+;base64,',''; [IO.File]::WriteAllBytes('<OUTPUT>', [Convert]::FromBase64String($b64))"
```

### 5. 校验产物

用 Read 工具读 `<OUTPUT>`（offset=0 limit=1）— 触发文件类型嗅探；如果工具报"二进制"或文件 size > 1KB 就视为成功，否则失败。

或者更直接：用 Bash 跑 `ls -la <OUTPUT> | awk '{print $5}'`，size > 10000 字节算成功。

### 6. 处理错误

如果 curl 失败 / OpenRouter 返回 error / base64 解码失败 / 产物太小：
- 打印 `/tmp/openrouter_resp.json` 头 500 字节（用 Bash `head -c 500`）帮助 debug
- 如果是 429 限流，sleep 5s 重试一次
- 仍然失败 → 输出 "image-gen-openrouter 失败：<原因>" 并返回（不要继续往下走）

### 7. 收尾输出

成功后输出**一行** summary（机器友好，方便上游 capture / regex 提取）：
```
saved: <OUTPUT> (model=<MODEL>, aspect=<ASPECT>, size=<BYTES>)
```

## 模型选型快速参考

| 模型 ID | 代号 | 单图成本 | 备注 |
|---|---|---|---|
| `google/gemini-3-pro-image-preview` | Nano Banana Pro | 高 | 4K，质量最好，默认 |
| `google/gemini-3.1-flash-image-preview` | Nano Banana 2 | 中 | 平衡，限流时降级 |
| `google/gemini-2.5-flash-image` | Nano Banana | 低 | 兜底 |

## 注意事项

- **不要把 API key 打到日志或最终输出里**，curl 错误信息也要小心 grep 掉
- **不要重试超过 1 次** — 429 / 5xx 各最多 1 次重试，避免烧钱
- 中文提示词模型可以理解，但效果不如英文 —— 如果调用方给的是中文 prompt，可以保留，但**不要主动翻译**（让上游负责）
- 调用方决定 `aspect` 和 `size`；本 skill 不做任何场景假设（不绑定竖屏/横屏）
