Skip to content

用 Codex 生成图片(gpt-image-2)

费用说明: 通过 CodexZH 使用 gpt-image-2,每生成一张图片计费 $0.5,按次从周额度中扣除。

⚠️ 风险提示: 使用这种方式,可能会出现你只发了一个需求,但是生图的请求掉了几次。这是 Agent 自己掉的,它在生成完之后会自己检查,如果觉得不合适,会重新再去生成。所以,你会看到使用记录里面有好几条的调用。如果你只需要让它每一次只调用一次,那么请参考 AI HUB API 网页生图教程 使用。

codexzh-image 是平台自带的画图技能。装好后在 Codex 里输入 /image 就能调用,支持文生图(按文字描述出图)和图生图(改一张已有的图)。

💡 懒人小贴士: 不想手动建文件,可以让 Codex 自己装。把下面这句话复制给 Codex,它会读取本教程并自动配置好:

请打开这个页面 https://docs.codexzh.com/codex/image-2 ,按照里面"手动创建"一节,把 codexzh-image 技能的 4 个文件分别创建到对应位置(SKILL.md、openai-image.sh、openai-image.ps1,以及 ~/.codex/.env),内容严格照页面里的代码块填写。.env 里的 api_key 先留占位,最后提醒我换成自己的密钥。

技能要用到的文件

技能由 4 个文件组成,分别放在下面这些位置:

~/.codex/
├── .env                          # 接口地址和密钥
└── skills/
    └── codexzh-image/
        ├── SKILL.md              # 技能说明
        └── scripts/
            ├── openai-image.sh   # macOS / Linux 脚本
            └── openai-image.ps1  # Windows 脚本

~/.codex 这个目录在哪里?

上面的 ~/.codex 是个简写,~ 代表你电脑的「用户主目录」,.codex 是它下面一个以点开头的隐藏文件夹。它通常不会直接显示出来,按下面的方法找到它:

Mac:

  1. 打开「访达(Finder)」。
  2. 按下快捷键 Command + Shift + G,会弹出一个跳转输入框。
  3. 输入 ~/.codex 后回车,即可直接进入该目录。
  4. 如果提示文件夹不存在,说明还没创建——可以先按上面的目录结构手动新建,或直接交给 Codex 自动创建。

小贴士:在访达里按 Command + Shift + .(点号)可以切换显示/隐藏以点开头的文件。

Windows:

  1. 打开「文件资源管理器」。
  2. 点击顶部的地址栏,输入 %USERPROFILE%\.codex 后回车,即可进入该目录。 (%USERPROFILE% 就是你的用户文件夹,一般是 C:\Users\你的用户名。)
  3. 如果提示找不到,说明 .codex 文件夹还没创建——在用户文件夹下手动新建一个名为 .codex 的文件夹即可,或直接交给 Codex 自动创建。

小贴士:若要直接看到隐藏文件夹,可在资源管理器顶部「查看 → 显示 → 隐藏的项目」中打开。

手动安装

按上面的目录结构新建这 4 个文件,再展开下方对应区块,把内容复制进去保存。

装完后,把 ~/.codex/.env 里的 api_key 改成你自己的密钥(sk-...)。如果这个文件里已有 http_proxy 等代理设置,保留即可,互不影响。

~/.codex/.env —— 接口地址和密钥:

base_url=https://api.codexzh.com/v1
api_key=sk-xxx
~/.codex/skills/codexzh-image/SKILL.md(点击展开)
markdown
---
name: codexzh-image
description: 用 OpenAI 兼容接口(curl 命令行)生成或编辑图片。当用户想"生成图片/画一张图/文生图/做张配图/帮我画/create an image/generate image",或"改图/图生图/编辑这张图/换背景/给图片加东西/edit image"时使用本技能。它读取 ~/.codex/.env 里的 base_url 与 api_key,用 curl 调接口;Base64 结果会解码保存到本地,URL 结果会下载到 --out。无需安装任何 Python/环境。只要用户提到生成图片、画图、改图、图像编辑,即使没说"用 OpenAI 接口",也应优先考虑用本技能。

---

# OpenAI 图像生成 / 编辑(curl 方式)

通过 OpenAI 兼容的 `/images/generations`(文生图)和 `/images/edits`(图生图 / 编辑)接口生成图片。
所有调用都用 `curl`(Windows 用 PowerShell)。如果接口返回非空 `b64_json`,脚本会解码并保存到本地;如果没有非空 `b64_json` 但有 `url`,脚本会下载到 `--out` 指定路径。**不依赖 Python,无需装环境。**

技能自带两个脚本,直接调用即可,不要重写:

- `scripts/openai-image.sh` —— macOS / Linux
- `scripts/openai-image.ps1` —— Windows

## 工作流程

### 第 1 步:确认配置存在

脚本会读取 `~/.codex/.env`(Windows:`%USERPROFILE%\.codex\.env`)里的两行:

```
base_url=https://api.xbai.top/v1
api_key=sk-你的密钥
```

先快速检查这两个键是否存在:

```bash
grep -E '^\s*(base_url|api_key)\s*=' ~/.codex/.env 2>/dev/null
```

**如果缺少 base_url 或 api_key**,引导用户配置——告诉用户在 `~/.codex/.env` 里加上这两行,并给出 base_url 的两个推荐选项:

- `https://api.xbai.top/v1`
- `https://api.codexzh.com/v1`
- 如果用户用的是别的中转站 / 官方接口,让用户把自己的 base_url 给你,或自行填写。

`api_key` 必须由用户自己提供(不要编造)。可以用 AskUserQuestion 让用户选 base_url,但密钥一定让用户自己填进文件,确认后再继续。

> 注意:该 `.env` 里可能已有 http_proxy 等代理设置,互不影响,保留即可。

### 第 2 步:判断任务类型并收集参数

- **文生图(generate)**:只有文字描述 → 用 `generate`
- **图生图 / 编辑(edit)**:用户给了一张或多张已有图片,想改它 → 用 `edit`,把图片路径作为 `--image` 传入。

需要的参数:

- `--prompt`(必填):图像描述,尽量把用户的意图写清楚、具体。
- `--model`:默认 `gpt-image-2`。用户指定别的就用别的。
- `--size`:默认 `1024x1024`。gpt-image 还支持 `1536x1024`(横)、`1024x1536`(竖)、`auto`
- `--n`:生成数量,默认 1。
- `--out`:图片保存路径,默认当前目录 `image-时间戳.png`。多张时自动加 `_1``_2` 后缀。URL 返回通常有有效期,脚本会立即下载到该路径。

### 第 3 步:调用脚本

**macOS / Linux:**

```bash
# 文生图
bash ~/.codex/skills/codexzh-image/scripts/openai-image.sh generate \
  --prompt "一只戴贝雷帽、坐在木桌上的小水獭,柔和光线" \
  --size 1024x1024 --out ./otter.png

# 图生图 / 编辑(可多张 --image,可选 --mask 局部重绘)
bash ~/.codex/skills/codexzh-image/scripts/openai-image.sh edit \
  --prompt "把背景换成下雪的雪山" \
  --image ./otter.png --out ./otter-snow.png
```

**Windows(PowerShell):**

```powershell
# 文生图
powershell -ExecutionPolicy Bypass -File "$HOME\.codex\skills\codexzh-image\scripts\openai-image.ps1" `
  generate -Prompt "一只戴贝雷帽的小水獭" -Out .\otter.png

# 图生图 / 编辑
powershell -ExecutionPolicy Bypass -File "$HOME\.codex\skills\codexzh-image\scripts\openai-image.ps1" `
  edit -Prompt "把背景换成雪山" -Image .\otter.png -Out .\otter-snow.png
```

> Windows 的 `edit`(图生图)依赖 PowerShell 7+ 的 `Invoke-RestMethod -Form`。若用户是自带的 PowerShell 5,文生图可用,图生图建议在 macOS/Linux 上或装 PowerShell 7。

### 第 4 步:报告结果

脚本会把 Base64 或 URL 返回的图片保存到本地,并打印 `✅ 已保存:路径`。把保存的文件路径告诉用户,并尽量展示本地图片。
如果下载或解码失败,脚本会退出并打印明确错误;不要把 0B 文件当作成功结果。

## 接口与参数速查(来自 OpenAI Images API)

| 接口                  | 方法             | 用途                 | 关键参数                                                     |
| --------------------- | ---------------- | -------------------- | ------------------------------------------------------------ |
| `/images/generations` | POST (JSON)      | 文生图               | `model` `prompt` `n` `size` `quality`                        |
| `/images/edits`       | POST (multipart) | 图生图 / 编辑 / 扩展 | `image[]`(必填,可多张)`prompt` `mask`(可选,局部重绘)`n` `size` |

要点:

- `gpt-image` 系列接口通常返回 `b64_json`(base64),脚本会自动解码落地;`dall-e` 系列或部分中转站可能返回 `url`,脚本会立即下载保存。两种都已兼容。
- `dall-e-3` **不支持** `/images/edits` 编辑端点;要做图生图请用 `gpt-image-2`(默认)或 `dall-e-2`
- `mask`(掩码):透明区域表示要重绘的位置,必须是与原图同尺寸的 PNG。仅在用户要"局部修改某个区域"时才用。

## 常见问题

- **报错"未找到 base_url 或 api_key"** → 回到第 1 步引导用户在 `.env` 里补全。
- **接口报错(余额 / 模型不存在 / 鉴权失败)** → 脚本会原样打印接口返回的 error,把它转述给用户,常见是 api_key 错误、余额不足或该中转站不支持所选 model。
- **保存出 0B 文件或下载失败** → 旧脚本可能把空 `b64_json` 当成功;现在会优先使用非空 `b64_json`,否则回退下载 `url`,并在文件为空时显式失败。
- **想换模型** → 加 `--model 模型名`,例如 `--model dall-e-3`(仅文生图)。
~/.codex/skills/codexzh-image/scripts/openai-image.sh(点击展开)
bash
#!/usr/bin/env bash
# openai-image.sh — 通过 OpenAI 兼容接口用 curl 生成 / 编辑图片
# 依赖:curl、base64、grep、sed(macOS / Linux 自带,无需安装任何环境)
#
# 用法:
#   生成(文生图):
#     openai-image.sh generate --prompt "一只戴贝雷帽的小水獭" \
#       [--model gpt-image-2] [--size 1024x1024] [--n 1] [--quality auto] [--out 路径.png]
#   编辑 / 图生图:
#     openai-image.sh edit --prompt "把背景换成雪山" --image in.png \
#       [--image 第二张.png ...] [--mask mask.png] \
#       [--model gpt-image-2] [--size 1024x1024] [--n 1] [--out 路径.png]
#
# 配置:默认读取 $HOME/.codex/.env 中的 base_url 和 api_key
#       (可用环境变量 OPENAI_IMAGE_ENV 覆盖配置文件路径)

set -euo pipefail

ENV_FILE="${OPENAI_IMAGE_ENV:-$HOME/.codex/.env}"
DEFAULT_MODEL="gpt-image-2"

err() { printf '%s\n' "$*" >&2; }

# ---------- 读取配置 ----------
read_cfg() {
  # 读取 key=value,忽略前后空白与包裹的引号;找不到时安全返回空
  local key="$1" line
  [ -f "$ENV_FILE" ] || return 0
  line="$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$ENV_FILE" 2>/dev/null | head -1)" || return 0
  [ -n "$line" ] || return 0
  line="${line#*=}"                                              # 去掉 key= 前缀
  line="$(printf '%s' "$line" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"  # 先去首尾空白
  line="$(printf '%s' "$line" | sed -E 's/^"(.*)"$/\1/; s/^'\''(.*)'\''$/\1/')"  # 再去包裹引号
  printf '%s' "$line"
}

BASE_URL="$(read_cfg base_url)"
API_KEY="$(read_cfg api_key)"

if [ -z "$BASE_URL" ] || [ -z "$API_KEY" ]; then
  err "❌ 未在 $ENV_FILE 中找到 base_url 或 api_key。"
  err ""
  err "请在该文件中补充以下两行(任选一个 base_url,或填你自己的):"
  err "  base_url=https://api.xbai.top/v1"
  err "  api_key=sk-你的密钥"
  err ""
  err "可选的另一个站点: base_url=https://api.codexzh.com/v1"
  exit 2
fi

# 去掉 base_url 结尾多余的斜杠
BASE_URL="${BASE_URL%/}"

# ---------- 解析参数 ----------
MODE="${1:-}"; shift || true
PROMPT=""; MODEL="$DEFAULT_MODEL"; SIZE="1024x1024"; N="1"; QUALITY=""; OUT=""
MASK=""; IMAGES=()

while [ $# -gt 0 ]; do
  case "$1" in
    --prompt)  PROMPT="$2"; shift 2 ;;
    --model)   MODEL="$2"; shift 2 ;;
    --size)    SIZE="$2"; shift 2 ;;
    --n)       N="$2"; shift 2 ;;
    --quality) QUALITY="$2"; shift 2 ;;
    --out)     OUT="$2"; shift 2 ;;
    --mask)    MASK="$2"; shift 2 ;;
    --image)   IMAGES+=("$2"); shift 2 ;;
    *) err "未知参数:$1"; exit 2 ;;
  esac
done

[ -n "$PROMPT" ] || { err "缺少 --prompt"; exit 2; }

# 输出文件名(默认按时间戳放当前目录)
if [ -z "$OUT" ]; then
  OUT="./image-$(date +%Y%m%d-%H%M%S).png"
fi
OUT_DIR="$(dirname "$OUT")"
mkdir -p "$OUT_DIR"

TMP="$(mktemp -t openai-image.XXXXXX.json)"
trap 'rm -f "$TMP"' EXIT

# ---------- JSON 字符串转义(仅生成接口用)----------
json_escape() {
  printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' '
}

# ---------- 处理响应里的图片 ----------
output_path_for_index() {
  local i="$1" name ext base suffix
  name="${OUT##*/}"
  suffix=""
  if [ "$name" != "${name%.*}" ]; then
    ext="${OUT##*.}"
    base="${OUT%.*}"
    suffix=".$ext"
  else
    base="$OUT"
  fi
  if [ "$i" -eq 0 ]; then
    printf '%s' "$OUT"
  else
    printf '%s_%s%s' "$base" "$i" "$suffix"
  fi
}

json_unescape_url() {
  # curl only needs the common JSON escapes that appear in URLs from these APIs.
  printf '%s' "$1" | sed -E 's#\\/#/#g; s#\\u0026#\&#g'
}

commit_image_file() {
  local tmp_file="$1" final_file="$2"
  if [ ! -s "$tmp_file" ]; then
    rm -f "$tmp_file"
    err "❌ 保存失败:生成的文件为空:$final_file"
    return 1
  fi
  mv "$tmp_file" "$final_file"
  printf '✅ 已保存:%s\n' "$final_file"
}

save_b64_image() {
  local b64="$1" final_file="$2" tmp_file
  tmp_file="$(mktemp "${final_file}.XXXXXX")"
  if printf '%s' "$b64" | base64 -d > "$tmp_file" 2>/dev/null; then
    :
  elif printf '%s' "$b64" | base64 -D > "$tmp_file" 2>/dev/null; then
    :
  else
    rm -f "$tmp_file"
    err "❌ Base64 解码失败:$final_file"
    return 1
  fi
  commit_image_file "$tmp_file" "$final_file"
}

save_url_image() {
  local url="$1" final_file="$2" tmp_file
  tmp_file="$(mktemp "${final_file}.XXXXXX")"
  if ! curl -fsSL "$url" -o "$tmp_file"; then
    rm -f "$tmp_file"
    err "❌ URL 下载失败:$url"
    return 1
  fi
  commit_image_file "$tmp_file" "$final_file"
}

extract_json_field() {
  local obj="$1" field="$2" match
  match="$(
    printf '%s' "$obj" \
      | grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \
      | head -1
  )" || true
  [ -n "$match" ] || return 0
  printf '%s' "$match" | sed -E "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"//; s/\"$//"
}

save_outputs() {
  local resp="$1" compact objects i obj b64 url f
  compact="$(tr -d '\n' < "$resp")"
  objects="$(printf '%s' "$compact" | grep -o '{[^{}]*}' || true)"
  i=0

  if [ -n "$objects" ]; then
    while IFS= read -r obj; do
      [ -n "$obj" ] || continue
      b64="$(extract_json_field "$obj" "b64_json")"
      url="$(extract_json_field "$obj" "url")"
      f="$(output_path_for_index "$i")"

      if [ -n "$b64" ]; then
        save_b64_image "$b64" "$f" || exit 1
        i=$((i+1))
      elif [ -n "$url" ]; then
        url="$(json_unescape_url "$url")"
        save_url_image "$url" "$f" || exit 1
        i=$((i+1))
      fi
    done <<EOF
$objects
EOF
  fi

  if [ "$i" -eq 0 ]; then
    err "❌ 接口返回异常,原始响应:"
    cat "$resp" >&2
    exit 1
  fi
}

# ---------- 调用接口 ----------
case "$MODE" in
  generate)
    esc="$(json_escape "$PROMPT")"
    body="$(printf '{"model":"%s","prompt":"%s","n":%s,"size":"%s"' "$MODEL" "$esc" "$N" "$SIZE")"
    [ -n "$QUALITY" ] && body="${body},\"quality\":\"${QUALITY}\""
    body="${body}}"
    curl -sS "$BASE_URL/images/generations" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $API_KEY" \
      -d "$body" -o "$TMP"
    ;;
  edit)
    [ "${#IMAGES[@]}" -gt 0 ] || { err "edit 模式至少需要一个 --image"; exit 2; }
    args=( -sS "$BASE_URL/images/edits"
           -H "Authorization: Bearer $API_KEY"
           -F "model=$MODEL"
           -F "prompt=$PROMPT"
           -F "n=$N"
           -F "size=$SIZE" )
    for img in "${IMAGES[@]}"; do
      [ -f "$img" ] || { err "图片不存在:$img"; exit 2; }
      # gpt-image 系列用 image[],多图支持;dall-e-2 也兼容
      args+=( -F "image[]=@${img}" )
    done
    [ -n "$MASK" ] && args+=( -F "mask=@${MASK}" )
    [ -n "$QUALITY" ] && args+=( -F "quality=$QUALITY" )
    curl "${args[@]}" -o "$TMP"
    ;;
  *)
    err "用法:openai-image.sh {generate|edit} --prompt \"...\" [选项]"
    exit 2
    ;;
esac

# ---------- 出错检测 ----------
if grep -q '"error"' "$TMP" && ! grep -q '"b64_json"\|"url"' "$TMP"; then
  err "❌ 接口报错:"
  cat "$TMP" >&2
  exit 1
fi

save_outputs "$TMP"
~/.codex/skills/codexzh-image/scripts/openai-image.ps1(点击展开)
powershell
<#+
openai-image.ps1 - Generate or edit images through an OpenAI-compatible Images API.

Configuration is read from $HOME\.codex\.env by default, or from OPENAI_IMAGE_ENV when set.
Required keys:
  base_url=https://api.example.com/v1
  api_key=sk-...
Optional proxy keys are also honored:
  http_proxy=http://127.0.0.1:7890
  https_proxy=http://127.0.0.1:7890

Examples:
  powershell -ExecutionPolicy Bypass -File openai-image.ps1 generate -Prompt "A small otter" -Out .\otter.png
  powershell -ExecutionPolicy Bypass -File openai-image.ps1 edit -Prompt "Change background to snow" -Image .\otter.png -Out .\otter-snow.png
#>

param(
  [Parameter(Position = 0, Mandatory = $true)]
  [ValidateSet('generate', 'edit')]
  [string]$Mode,

  [string]$Prompt,
  [string]$Model = 'gpt-image-2',
  [string]$Size = '1024x1024',
  [int]$N = 1,
  [string]$Quality = '',
  [string]$Out = '',
  [string]$Image = '',
  [string]$Image2 = '',
  [string]$Mask = ''
)

$ErrorActionPreference = 'Stop'

$EnvFile = if ($env:OPENAI_IMAGE_ENV) { $env:OPENAI_IMAGE_ENV } else { Join-Path $HOME '.codex\.env' }

function Read-Cfg([string]$Key) {
  if (-not (Test-Path -LiteralPath $EnvFile)) { return '' }

  foreach ($line in Get-Content -LiteralPath $EnvFile) {
    if ($line -match "^\s*$([regex]::Escape($Key))\s*=\s*(.*)$") {
      return $Matches[1].Trim().Trim('"').Trim("'")
    }
  }

  return ''
}

function Set-OptionalProxy([string]$Key, [string]$EnvNameLower, [string]$EnvNameUpper) {
  $value = Read-Cfg $Key
  if ($value) {
    [Environment]::SetEnvironmentVariable($EnvNameLower, $value, 'Process')
    [Environment]::SetEnvironmentVariable($EnvNameUpper, $value, 'Process')
  }
}

$BaseUrl = Read-Cfg 'base_url'
$ApiKey = Read-Cfg 'api_key'

if (-not $BaseUrl -or -not $ApiKey) {
  Write-Error @"
Missing base_url or api_key in $EnvFile.
Add both lines, for example:
  base_url=https://api.xbai.top/v1
  api_key=sk-your-key
"@
  exit 2
}

if (-not $Prompt) {
  Write-Error 'Missing -Prompt.'
  exit 2
}

if ($N -lt 1) {
  Write-Error '-N must be at least 1.'
  exit 2
}

$BaseUrl = $BaseUrl.TrimEnd('/')
Set-OptionalProxy 'http_proxy' 'http_proxy' 'HTTP_PROXY'
Set-OptionalProxy 'https_proxy' 'https_proxy' 'HTTPS_PROXY'

if (-not $Out) {
  $Out = ".\image-$(Get-Date -Format yyyyMMdd-HHmmss).png"
}

$OutDir = Split-Path -Parent $Out
if ($OutDir -and -not (Test-Path -LiteralPath $OutDir)) {
  New-Item -ItemType Directory -Path $OutDir | Out-Null
}

$Headers = @{ Authorization = "Bearer $ApiKey" }

function Get-OutputPath([int]$Index) {
  $extension = [System.IO.Path]::GetExtension($Out)
  $baseName = if ($extension) { $Out.Substring(0, $Out.Length - $extension.Length) } else { $Out }

  if ($Index -eq 0) { return $Out }
  if ($extension) { return "${baseName}_$Index$extension" }
  return "${baseName}_$Index"
}

function Commit-ImageFile([string]$TempFile, [string]$FinalFile) {
  if (-not (Test-Path -LiteralPath $TempFile) -or (Get-Item -LiteralPath $TempFile).Length -le 0) {
    Remove-Item -LiteralPath $TempFile -Force -ErrorAction SilentlyContinue
    throw "Save failed: generated file is empty for $FinalFile."
  }

  Move-Item -LiteralPath $TempFile -Destination $FinalFile -Force
  Write-Host "Saved: $FinalFile"
}

function Save-B64Image([string]$B64, [string]$FinalFile) {
  $tempFile = [System.IO.Path]::GetTempFileName()

  try {
    [System.IO.File]::WriteAllBytes($tempFile, [System.Convert]::FromBase64String($B64))
  } catch {
    Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue
    throw "Base64 decode failed for $FinalFile. $($_.Exception.Message)"
  }

  Commit-ImageFile $tempFile $FinalFile
}

function Save-UrlImage([string]$Url, [string]$FinalFile) {
  $tempFile = [System.IO.Path]::GetTempFileName()
  $client = New-Object System.Net.WebClient

  try {
    $client.DownloadFile($Url, $tempFile)
  } catch {
    Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue
    throw "URL download failed for $Url. $($_.Exception.Message)"
  } finally {
    $client.Dispose()
  }

  Commit-ImageFile $tempFile $FinalFile
}

function Save-Outputs($Data) {
  $savedCount = 0

  foreach ($item in @($Data)) {
    if ($null -eq $item) { continue }

    $outputFile = Get-OutputPath $savedCount
    $b64 = if ($null -ne $item.b64_json) { [string]$item.b64_json } else { '' }
    $url = if ($null -ne $item.url) { [string]$item.url } else { '' }

    if ($b64.Trim().Length -gt 0) {
      Save-B64Image $b64 $outputFile
      $savedCount++
      continue
    }

    if ($url.Trim().Length -gt 0) {
      Save-UrlImage $url.Trim() $outputFile
      $savedCount++
      continue
    }

    throw "API response item $($savedCount + 1) has no non-empty b64_json or url."
  }

  if ($savedCount -eq 0) {
    throw 'API response did not contain image data.'
  }
}

if ($Mode -eq 'generate') {
  $bodyObject = @{
    model = $Model
    prompt = $Prompt
    n = $N
    size = $Size
  }
  if ($Quality) { $bodyObject.quality = $Quality }

  $body = $bodyObject | ConvertTo-Json -Compress
  $response = Invoke-RestMethod -Method Post -Uri "$BaseUrl/images/generations" -Headers $Headers -ContentType 'application/json' -Body $body
  Save-Outputs $response.data
} elseif ($Mode -eq 'edit') {
  if (-not $Image) {
    Write-Error 'Edit mode requires -Image.'
    exit 2
  }

  if (-not (Test-Path -LiteralPath $Image)) {
    Write-Error "Image file not found: $Image"
    exit 2
  }

  if ($Image2 -and -not (Test-Path -LiteralPath $Image2)) {
    Write-Error "Image2 file not found: $Image2"
    exit 2
  }

  if ($Mask -and -not (Test-Path -LiteralPath $Mask)) {
    Write-Error "Mask file not found: $Mask"
    exit 2
  }

  if ($PSVersionTable.PSVersion.Major -lt 7) {
    Write-Error 'Edit mode requires PowerShell 7+ because Windows PowerShell 5 does not support Invoke-RestMethod -Form.'
    exit 2
  }

  $form = @{
    model = $Model
    prompt = $Prompt
    n = "$N"
    size = $Size
  }

  $images = @(Get-Item -LiteralPath $Image)
  if ($Image2) { $images += Get-Item -LiteralPath $Image2 }
  $form['image[]'] = $images
  if ($Mask) { $form['mask'] = Get-Item -LiteralPath $Mask }
  if ($Quality) { $form['quality'] = $Quality }

  $response = Invoke-RestMethod -Method Post -Uri "$BaseUrl/images/edits" -Headers $Headers -Form $form
  Save-Outputs $response.data
}

开始使用

四个文件就位后,重启 Codex,输入 /image 选择 codexzh-image 技能,再描述你想要的图片即可。效果如下:

codexzh-image 技能调用效果