mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-14 06:39:48 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c995fcc24 | |||
| 151f1ec2a8 | |||
| 12e207dc3f | |||
| 00e74ab071 | |||
| 1653b8544b |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "1.79.2"
|
"version": "1.80.1"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
English | [中文](./CHANGELOG.zh.md)
|
English | [中文](./CHANGELOG.zh.md)
|
||||||
|
|
||||||
|
## 1.80.1 - 2026-03-24
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- `baoyu-image-gen`: use correct `prompt` field name for Jimeng API request
|
||||||
|
|
||||||
|
## 1.80.0 - 2026-03-24
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- `baoyu-image-gen`: add Azure OpenAI as independent image generation provider with flexible endpoint parsing, deployment-name resolution, quality mapping, and reference image validation
|
||||||
|
|
||||||
## 1.79.2 - 2026-03-23
|
## 1.79.2 - 2026-03-23
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
[English](./CHANGELOG.md) | 中文
|
[English](./CHANGELOG.md) | 中文
|
||||||
|
|
||||||
|
## 1.80.1 - 2026-03-24
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- `baoyu-image-gen`:修正即梦 API 请求中的 `prompt` 字段名
|
||||||
|
|
||||||
|
## 1.80.0 - 2026-03-24
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
- `baoyu-image-gen`:新增 Azure OpenAI 作为独立图像生成服务商,支持灵活的端点解析、部署名称推断、质量映射及参考图片格式校验
|
||||||
|
|
||||||
## 1.79.2 - 2026-03-23
|
## 1.79.2 - 2026-03-23
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
Claude Code marketplace plugin providing AI-powered content generation skills. Version: **1.79.2**.
|
Claude Code marketplace plugin providing AI-powered content generation skills. Version: **1.80.1**.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ Execute: `${BUN_X} skills/<skill>/scripts/main.ts [options]`
|
|||||||
|
|
||||||
- **Bun**: TypeScript runtime (`bun` preferred, fallback `npx -y bun`)
|
- **Bun**: TypeScript runtime (`bun` preferred, fallback `npx -y bun`)
|
||||||
- **Chrome**: Required for CDP-based skills (gemini-web, post-to-x/wechat/weibo, url-to-markdown). All CDP skills share a single profile, override via `BAOYU_CHROME_PROFILE_DIR` env var. Platform paths: [docs/chrome-profile.md](docs/chrome-profile.md)
|
- **Chrome**: Required for CDP-based skills (gemini-web, post-to-x/wechat/weibo, url-to-markdown). All CDP skills share a single profile, override via `BAOYU_CHROME_PROFILE_DIR` env var. Platform paths: [docs/chrome-profile.md](docs/chrome-profile.md)
|
||||||
- **Image generation APIs**: `baoyu-image-gen` requires API key (OpenAI, Google, OpenRouter, DashScope, or Replicate) configured in EXTEND.md
|
- **Image generation APIs**: `baoyu-image-gen` requires API key (OpenAI, Azure OpenAI, Google, OpenRouter, DashScope, or Replicate) configured in EXTEND.md
|
||||||
- **Gemini Web auth**: Browser cookies (first run opens Chrome for login, `--login` to refresh)
|
- **Gemini Web auth**: Browser cookies (first run opens Chrome for login, `--login` to refresh)
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ AI-powered generation backends.
|
|||||||
|
|
||||||
#### baoyu-image-gen
|
#### baoyu-image-gen
|
||||||
|
|
||||||
AI SDK-based image generation using OpenAI, Google, OpenRouter, DashScope (Aliyun Tongyi Wanxiang), Jimeng (即梦), Seedream (豆包), and Replicate APIs. Supports text-to-image, reference images, aspect ratios, and quality presets.
|
AI SDK-based image generation using OpenAI, Azure OpenAI, Google, OpenRouter, DashScope (Aliyun Tongyi Wanxiang), Jimeng (即梦), Seedream (豆包), and Replicate APIs. Supports text-to-image, reference images, aspect ratios, and quality presets.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Basic generation (auto-detect provider)
|
# Basic generation (auto-detect provider)
|
||||||
@@ -678,6 +678,9 @@ AI SDK-based image generation using OpenAI, Google, OpenRouter, DashScope (Aliyu
|
|||||||
# Specific provider
|
# Specific provider
|
||||||
/baoyu-image-gen --prompt "A cat" --image cat.png --provider openai
|
/baoyu-image-gen --prompt "A cat" --image cat.png --provider openai
|
||||||
|
|
||||||
|
# Azure OpenAI (model = deployment name)
|
||||||
|
/baoyu-image-gen --prompt "A cat" --image cat.png --provider azure --model gpt-image-1.5
|
||||||
|
|
||||||
# OpenRouter
|
# OpenRouter
|
||||||
/baoyu-image-gen --prompt "A cat" --image cat.png --provider openrouter
|
/baoyu-image-gen --prompt "A cat" --image cat.png --provider openrouter
|
||||||
|
|
||||||
@@ -693,7 +696,7 @@ AI SDK-based image generation using OpenAI, Google, OpenRouter, DashScope (Aliyu
|
|||||||
# Seedream (豆包)
|
# Seedream (豆包)
|
||||||
/baoyu-image-gen --prompt "一只可爱的猫" --image cat.png --provider seedream
|
/baoyu-image-gen --prompt "一只可爱的猫" --image cat.png --provider seedream
|
||||||
|
|
||||||
# With reference images (Google, OpenAI, OpenRouter, Replicate, or Seedream 5.0/4.5/4.0)
|
# With reference images (Google, OpenAI, Azure OpenAI, OpenRouter, Replicate, or Seedream 5.0/4.5/4.0)
|
||||||
/baoyu-image-gen --prompt "Make it blue" --image out.png --ref source.png
|
/baoyu-image-gen --prompt "Make it blue" --image out.png --ref source.png
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -663,7 +663,7 @@ AI 驱动的生成后端。
|
|||||||
|
|
||||||
#### baoyu-image-gen
|
#### baoyu-image-gen
|
||||||
|
|
||||||
基于 AI SDK 的图像生成,支持 OpenAI、Google、OpenRouter、DashScope(阿里通义万相)、即梦(Jimeng)、豆包(Seedream)和 Replicate API。支持文生图、参考图、宽高比和质量预设。
|
基于 AI SDK 的图像生成,支持 OpenAI、Azure OpenAI、Google、OpenRouter、DashScope(阿里通义万相)、即梦(Jimeng)、豆包(Seedream)和 Replicate API。支持文生图、参考图、宽高比和质量预设。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 基础生成(自动检测服务商)
|
# 基础生成(自动检测服务商)
|
||||||
@@ -678,6 +678,9 @@ AI 驱动的生成后端。
|
|||||||
# 指定服务商
|
# 指定服务商
|
||||||
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider openai
|
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider openai
|
||||||
|
|
||||||
|
# Azure OpenAI(model 为部署名称)
|
||||||
|
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider azure --model gpt-image-1.5
|
||||||
|
|
||||||
# OpenRouter
|
# OpenRouter
|
||||||
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider openrouter
|
/baoyu-image-gen --prompt "一只猫" --image cat.png --provider openrouter
|
||||||
|
|
||||||
@@ -693,7 +696,7 @@ AI 驱动的生成后端。
|
|||||||
# 豆包(Seedream)
|
# 豆包(Seedream)
|
||||||
/baoyu-image-gen --prompt "一只可爱的猫" --image cat.png --provider seedream
|
/baoyu-image-gen --prompt "一只可爱的猫" --image cat.png --provider seedream
|
||||||
|
|
||||||
# 带参考图(Google、OpenAI、OpenRouter、Replicate 或 Seedream 5.0/4.5/4.0)
|
# 带参考图(Google、OpenAI、Azure OpenAI、OpenRouter、Replicate 或 Seedream 5.0/4.5/4.0)
|
||||||
/baoyu-image-gen --prompt "把它变成蓝色" --image out.png --ref source.png
|
/baoyu-image-gen --prompt "把它变成蓝色" --image out.png --ref source.png
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: baoyu-image-gen
|
name: baoyu-image-gen
|
||||||
description: AI image generation with OpenAI, Google, OpenRouter, DashScope, 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: AI image generation with OpenAI, Azure OpenAI, Google, OpenRouter, DashScope, 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.3
|
version: 1.56.3
|
||||||
metadata:
|
metadata:
|
||||||
openclaw:
|
openclaw:
|
||||||
@@ -13,7 +13,7 @@ metadata:
|
|||||||
|
|
||||||
# Image Generation (AI SDK)
|
# Image Generation (AI SDK)
|
||||||
|
|
||||||
Official API-based image generation. Supports OpenAI, Google, OpenRouter, DashScope (阿里通义万象), Jimeng (即梦), Seedream (豆包) and Replicate providers.
|
Official API-based image generation. Supports OpenAI, Azure OpenAI, Google, OpenRouter, DashScope (阿里通义万象), Jimeng (即梦), Seedream (豆包) and Replicate providers.
|
||||||
|
|
||||||
## Script Directory
|
## Script Directory
|
||||||
|
|
||||||
@@ -74,12 +74,15 @@ ${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --quality 2k
|
|||||||
# From prompt files
|
# From prompt files
|
||||||
${BUN_X} {baseDir}/scripts/main.ts --promptfiles system.md content.md --image out.png
|
${BUN_X} {baseDir}/scripts/main.ts --promptfiles system.md content.md --image out.png
|
||||||
|
|
||||||
# With reference images (Google, OpenAI, OpenRouter, Replicate, or Seedream 4.0/4.5/5.0)
|
# With reference images (Google, OpenAI, Azure OpenAI, OpenRouter, Replicate, or Seedream 4.0/4.5/5.0)
|
||||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --ref source.png
|
${BUN_X} {baseDir}/scripts/main.ts --prompt "Make blue" --image out.png --ref source.png
|
||||||
|
|
||||||
# With reference images (explicit provider/model)
|
# 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
|
${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)
|
# OpenRouter (recommended default model)
|
||||||
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openrouter
|
${BUN_X} {baseDir}/scripts/main.ts --prompt "A cat" --image out.png --provider openrouter
|
||||||
|
|
||||||
@@ -147,13 +150,13 @@ Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch fi
|
|||||||
| `--image <path>` | Output image path (required in single-image mode) |
|
| `--image <path>` | Output image path (required in single-image mode) |
|
||||||
| `--batchfile <path>` | JSON batch file for multi-image generation |
|
| `--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) |
|
| `--jobs <count>` | Worker count for batch mode (default: auto, max from config, built-in default 10) |
|
||||||
| `--provider google\|openai\|openrouter\|dashscope\|jimeng\|seedream\|replicate` | Force provider (default: auto-detect) |
|
| `--provider google\|openai\|azure\|openrouter\|dashscope\|jimeng\|seedream\|replicate` | Force provider (default: auto-detect) |
|
||||||
| `--model <id>`, `-m` | Model ID (Google: `gemini-3-pro-image-preview`; OpenAI: `gpt-image-1.5`; OpenRouter: `google/gemini-3.1-flash-image-preview`; DashScope: `qwen-image-2.0-pro`) |
|
| `--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`) |
|
||||||
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
| `--ar <ratio>` | Aspect ratio (e.g., `16:9`, `1:1`, `4:3`) |
|
||||||
| `--size <WxH>` | Size (e.g., `1024x1024`) |
|
| `--size <WxH>` | Size (e.g., `1024x1024`) |
|
||||||
| `--quality normal\|2k` | Quality preset (default: `2k`) |
|
| `--quality normal\|2k` | Quality preset (default: `2k`) |
|
||||||
| `--imageSize 1K\|2K\|4K` | Image size for Google/OpenRouter (default: from quality) |
|
| `--imageSize 1K\|2K\|4K` | Image size for Google/OpenRouter (default: from quality) |
|
||||||
| `--ref <files...>` | Reference images. Supported by Google multimodal, OpenAI GPT Image edits, OpenRouter multimodal models, Replicate, and Seedream 5.0/4.5/4.0. Not supported by Jimeng, Seedream 3.0, or removed SeedEdit 3.0 |
|
| `--ref <files...>` | Reference images. Supported by Google multimodal, OpenAI GPT Image edits, Azure OpenAI edits (PNG/JPG only), OpenRouter multimodal models, Replicate, 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 |
|
| `--n <count>` | Number of images |
|
||||||
| `--json` | JSON output |
|
| `--json` | JSON output |
|
||||||
|
|
||||||
@@ -162,6 +165,7 @@ Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch fi
|
|||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `OPENAI_API_KEY` | OpenAI API key |
|
| `OPENAI_API_KEY` | OpenAI API key |
|
||||||
|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI API key |
|
||||||
| `OPENROUTER_API_KEY` | OpenRouter API key |
|
| `OPENROUTER_API_KEY` | OpenRouter API key |
|
||||||
| `GOOGLE_API_KEY` | Google API key |
|
| `GOOGLE_API_KEY` | Google API key |
|
||||||
| `DASHSCOPE_API_KEY` | DashScope API key (阿里云) |
|
| `DASHSCOPE_API_KEY` | DashScope API key (阿里云) |
|
||||||
@@ -170,6 +174,8 @@ Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch fi
|
|||||||
| `JIMENG_SECRET_ACCESS_KEY` | Jimeng (即梦) Volcengine secret key |
|
| `JIMENG_SECRET_ACCESS_KEY` | Jimeng (即梦) Volcengine secret key |
|
||||||
| `ARK_API_KEY` | Seedream (豆包) Volcengine ARK API key |
|
| `ARK_API_KEY` | Seedream (豆包) Volcengine ARK API key |
|
||||||
| `OPENAI_IMAGE_MODEL` | OpenAI model override |
|
| `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`) |
|
| `OPENROUTER_IMAGE_MODEL` | OpenRouter model override (default: `google/gemini-3.1-flash-image-preview`) |
|
||||||
| `GOOGLE_IMAGE_MODEL` | Google model override |
|
| `GOOGLE_IMAGE_MODEL` | Google model override |
|
||||||
| `DASHSCOPE_IMAGE_MODEL` | DashScope model override (default: `qwen-image-2.0-pro`) |
|
| `DASHSCOPE_IMAGE_MODEL` | DashScope model override (default: `qwen-image-2.0-pro`) |
|
||||||
@@ -177,6 +183,8 @@ Paths in `promptFiles`, `image`, and `ref` are resolved relative to the batch fi
|
|||||||
| `JIMENG_IMAGE_MODEL` | Jimeng model override (default: jimeng_t2i_v40) |
|
| `JIMENG_IMAGE_MODEL` | Jimeng model override (default: jimeng_t2i_v40) |
|
||||||
| `SEEDREAM_IMAGE_MODEL` | Seedream model override (default: doubao-seedream-5-0-260128) |
|
| `SEEDREAM_IMAGE_MODEL` | Seedream model override (default: doubao-seedream-5-0-260128) |
|
||||||
| `OPENAI_BASE_URL` | Custom OpenAI endpoint |
|
| `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_BASE_URL` | Custom OpenRouter endpoint (default: `https://openrouter.ai/api/v1`) |
|
||||||
| `OPENROUTER_HTTP_REFERER` | Optional app/site URL for OpenRouter attribution |
|
| `OPENROUTER_HTTP_REFERER` | Optional app/site URL for OpenRouter attribution |
|
||||||
| `OPENROUTER_TITLE` | Optional app name for OpenRouter attribution |
|
| `OPENROUTER_TITLE` | Optional app name for OpenRouter attribution |
|
||||||
@@ -201,6 +209,8 @@ Model priority (highest → lowest), applies to all providers:
|
|||||||
3. Env var: `<PROVIDER>_IMAGE_MODEL` (e.g., `GOOGLE_IMAGE_MODEL`)
|
3. Env var: `<PROVIDER>_IMAGE_MODEL` (e.g., `GOOGLE_IMAGE_MODEL`)
|
||||||
4. Built-in default
|
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.
|
||||||
|
|
||||||
**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 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.
|
||||||
|
|
||||||
**Agent MUST display model info** before each generation:
|
**Agent MUST display model info** before each generation:
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ options:
|
|||||||
description: "Gemini multimodal - high quality, reference images, flexible sizes"
|
description: "Gemini multimodal - high quality, reference images, flexible sizes"
|
||||||
- label: "OpenAI"
|
- label: "OpenAI"
|
||||||
description: "GPT Image - consistent quality, reliable output"
|
description: "GPT Image - consistent quality, reliable output"
|
||||||
|
- label: "Azure OpenAI"
|
||||||
|
description: "Azure-hosted GPT Image deployments with resource-specific routing"
|
||||||
- label: "OpenRouter"
|
- label: "OpenRouter"
|
||||||
description: "Router for Gemini/FLUX/OpenAI-compatible image models"
|
description: "Router for Gemini/FLUX/OpenAI-compatible image models"
|
||||||
- label: "DashScope"
|
- label: "DashScope"
|
||||||
@@ -87,6 +89,20 @@ options:
|
|||||||
description: "Strong text-to-image quality through OpenRouter"
|
description: "Strong text-to-image quality through OpenRouter"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Question 2c: Default Azure Deployment
|
||||||
|
|
||||||
|
Only show if user selected Azure OpenAI.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
header: "Azure Deploy"
|
||||||
|
question: "Default Azure image deployment name?"
|
||||||
|
options:
|
||||||
|
- label: "gpt-image-1.5 (Recommended)"
|
||||||
|
description: "Best default if your Azure deployment uses the same name"
|
||||||
|
- label: "gpt-image-1"
|
||||||
|
description: "Previous GPT Image deployment name"
|
||||||
|
```
|
||||||
|
|
||||||
### Question 3: Default Quality
|
### Question 3: Default Quality
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -130,6 +146,7 @@ default_image_size: null
|
|||||||
default_model:
|
default_model:
|
||||||
google: [selected google model or null]
|
google: [selected google model or null]
|
||||||
openai: null
|
openai: null
|
||||||
|
azure: [selected azure deployment or null]
|
||||||
openrouter: [selected openrouter model or null]
|
openrouter: [selected openrouter model or null]
|
||||||
dashscope: null
|
dashscope: null
|
||||||
replicate: null
|
replicate: null
|
||||||
@@ -166,6 +183,23 @@ options:
|
|||||||
description: "Previous generation GPT Image model"
|
description: "Previous generation GPT Image model"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Azure Deployment Selection
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
header: "Azure Deploy"
|
||||||
|
question: "Choose a default Azure image deployment name?"
|
||||||
|
options:
|
||||||
|
- label: "gpt-image-1.5 (Recommended)"
|
||||||
|
description: "Use when your Azure deployment name matches the GPT-image-1.5 model"
|
||||||
|
- label: "gpt-image-1"
|
||||||
|
description: "Use when your Azure deployment name matches GPT-image-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes for Azure setup:
|
||||||
|
|
||||||
|
- In `baoyu-image-gen`, Azure `--model` / `default_model.azure` should be the Azure deployment name, not just the underlying model family.
|
||||||
|
- If the deployment name is custom, save that exact deployment name in `default_model.azure`.
|
||||||
|
|
||||||
### OpenRouter Model Selection
|
### OpenRouter Model Selection
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -230,6 +264,7 @@ After user selects a model:
|
|||||||
default_model:
|
default_model:
|
||||||
google: [value or null]
|
google: [value or null]
|
||||||
openai: [value or null]
|
openai: [value or null]
|
||||||
|
azure: [value or null]
|
||||||
openrouter: [value or null]
|
openrouter: [value or null]
|
||||||
dashscope: [value or null]
|
dashscope: [value or null]
|
||||||
replicate: [value or null]
|
replicate: [value or null]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ description: EXTEND.md YAML schema for baoyu-image-gen user preferences
|
|||||||
---
|
---
|
||||||
version: 1
|
version: 1
|
||||||
|
|
||||||
default_provider: null # google|openai|openrouter|dashscope|replicate|null (null = auto-detect)
|
default_provider: null # google|openai|azure|openrouter|dashscope|replicate|null (null = auto-detect)
|
||||||
|
|
||||||
default_quality: null # normal|2k|null (null = use default: 2k)
|
default_quality: null # normal|2k|null (null = use default: 2k)
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ default_image_size: null # 1K|2K|4K|null (Google/OpenRouter, overrides qualit
|
|||||||
default_model:
|
default_model:
|
||||||
google: null # e.g., "gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview"
|
google: null # e.g., "gemini-3-pro-image-preview", "gemini-3.1-flash-image-preview"
|
||||||
openai: null # e.g., "gpt-image-1.5", "gpt-image-1"
|
openai: null # e.g., "gpt-image-1.5", "gpt-image-1"
|
||||||
|
azure: null # Azure deployment name, e.g., "gpt-image-1.5" or "image-prod"
|
||||||
openrouter: null # e.g., "google/gemini-3.1-flash-image-preview"
|
openrouter: null # e.g., "google/gemini-3.1-flash-image-preview"
|
||||||
dashscope: null # e.g., "qwen-image-2.0-pro"
|
dashscope: null # e.g., "qwen-image-2.0-pro"
|
||||||
replicate: null # e.g., "google/nano-banana-pro"
|
replicate: null # e.g., "google/nano-banana-pro"
|
||||||
@@ -38,6 +39,9 @@ batch:
|
|||||||
openai:
|
openai:
|
||||||
concurrency: 3
|
concurrency: 3
|
||||||
start_interval_ms: 1100
|
start_interval_ms: 1100
|
||||||
|
azure:
|
||||||
|
concurrency: 3
|
||||||
|
start_interval_ms: 1100
|
||||||
openrouter:
|
openrouter:
|
||||||
concurrency: 3
|
concurrency: 3
|
||||||
start_interval_ms: 1100
|
start_interval_ms: 1100
|
||||||
@@ -58,6 +62,7 @@ batch:
|
|||||||
| `default_image_size` | string\|null | null | Google/OpenRouter image size (overrides quality) |
|
| `default_image_size` | string\|null | null | Google/OpenRouter image size (overrides quality) |
|
||||||
| `default_model.google` | string\|null | null | Google default model |
|
| `default_model.google` | string\|null | null | Google default model |
|
||||||
| `default_model.openai` | string\|null | null | OpenAI default model |
|
| `default_model.openai` | string\|null | null | OpenAI default model |
|
||||||
|
| `default_model.azure` | string\|null | null | Azure default deployment name |
|
||||||
| `default_model.openrouter` | string\|null | null | OpenRouter default model |
|
| `default_model.openrouter` | string\|null | null | OpenRouter default model |
|
||||||
| `default_model.dashscope` | string\|null | null | DashScope default model |
|
| `default_model.dashscope` | string\|null | null | DashScope default model |
|
||||||
| `default_model.replicate` | string\|null | null | Replicate default model |
|
| `default_model.replicate` | string\|null | null | Replicate default model |
|
||||||
@@ -87,6 +92,7 @@ default_image_size: 2K
|
|||||||
default_model:
|
default_model:
|
||||||
google: "gemini-3-pro-image-preview"
|
google: "gemini-3-pro-image-preview"
|
||||||
openai: "gpt-image-1.5"
|
openai: "gpt-image-1.5"
|
||||||
|
azure: "gpt-image-1.5"
|
||||||
openrouter: "google/gemini-3.1-flash-image-preview"
|
openrouter: "google/gemini-3.1-flash-image-preview"
|
||||||
dashscope: "qwen-image-2.0-pro"
|
dashscope: "qwen-image-2.0-pro"
|
||||||
replicate: "google/nano-banana-pro"
|
replicate: "google/nano-banana-pro"
|
||||||
@@ -96,6 +102,9 @@ batch:
|
|||||||
replicate:
|
replicate:
|
||||||
concurrency: 5
|
concurrency: 5
|
||||||
start_interval_ms: 700
|
start_interval_ms: 700
|
||||||
|
azure:
|
||||||
|
concurrency: 3
|
||||||
|
start_interval_ms: 1100
|
||||||
openrouter:
|
openrouter:
|
||||||
concurrency: 3
|
concurrency: 3
|
||||||
start_interval_ms: 1100
|
start_interval_ms: 1100
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ default_image_size: 2K
|
|||||||
default_model:
|
default_model:
|
||||||
google: gemini-3-pro-image-preview
|
google: gemini-3-pro-image-preview
|
||||||
openai: gpt-image-1.5
|
openai: gpt-image-1.5
|
||||||
|
azure: image-prod
|
||||||
batch:
|
batch:
|
||||||
max_workers: 8
|
max_workers: 8
|
||||||
provider_limits:
|
provider_limits:
|
||||||
@@ -131,6 +132,9 @@ batch:
|
|||||||
start_interval_ms: 900
|
start_interval_ms: 900
|
||||||
openai:
|
openai:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
|
azure:
|
||||||
|
concurrency: 1
|
||||||
|
start_interval_ms: 1500
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const config = parseSimpleYaml(yaml);
|
const config = parseSimpleYaml(yaml);
|
||||||
@@ -142,6 +146,7 @@ batch:
|
|||||||
assert.equal(config.default_image_size, "2K");
|
assert.equal(config.default_image_size, "2K");
|
||||||
assert.equal(config.default_model?.google, "gemini-3-pro-image-preview");
|
assert.equal(config.default_model?.google, "gemini-3-pro-image-preview");
|
||||||
assert.equal(config.default_model?.openai, "gpt-image-1.5");
|
assert.equal(config.default_model?.openai, "gpt-image-1.5");
|
||||||
|
assert.equal(config.default_model?.azure, "image-prod");
|
||||||
assert.equal(config.batch?.max_workers, 8);
|
assert.equal(config.batch?.max_workers, 8);
|
||||||
assert.deepEqual(config.batch?.provider_limits?.google, {
|
assert.deepEqual(config.batch?.provider_limits?.google, {
|
||||||
concurrency: 2,
|
concurrency: 2,
|
||||||
@@ -150,6 +155,10 @@ batch:
|
|||||||
assert.deepEqual(config.batch?.provider_limits?.openai, {
|
assert.deepEqual(config.batch?.provider_limits?.openai, {
|
||||||
concurrency: 4,
|
concurrency: 4,
|
||||||
});
|
});
|
||||||
|
assert.deepEqual(config.batch?.provider_limits?.azure, {
|
||||||
|
concurrency: 1,
|
||||||
|
start_interval_ms: 1500,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("mergeConfig only fills values missing from CLI args", () => {
|
test("mergeConfig only fills values missing from CLI args", () => {
|
||||||
@@ -203,6 +212,8 @@ test("detectProvider selects an available ref-capable provider for reference-ima
|
|||||||
useEnv(t, {
|
useEnv(t, {
|
||||||
GOOGLE_API_KEY: null,
|
GOOGLE_API_KEY: null,
|
||||||
OPENAI_API_KEY: "openai-key",
|
OPENAI_API_KEY: "openai-key",
|
||||||
|
AZURE_OPENAI_API_KEY: null,
|
||||||
|
AZURE_OPENAI_BASE_URL: null,
|
||||||
OPENROUTER_API_KEY: null,
|
OPENROUTER_API_KEY: null,
|
||||||
DASHSCOPE_API_KEY: null,
|
DASHSCOPE_API_KEY: null,
|
||||||
REPLICATE_API_TOKEN: null,
|
REPLICATE_API_TOKEN: null,
|
||||||
@@ -216,6 +227,27 @@ test("detectProvider selects an available ref-capable provider for reference-ima
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("detectProvider selects Azure when only Azure credentials are configured", (t) => {
|
||||||
|
useEnv(t, {
|
||||||
|
GOOGLE_API_KEY: null,
|
||||||
|
OPENAI_API_KEY: null,
|
||||||
|
AZURE_OPENAI_API_KEY: "azure-key",
|
||||||
|
AZURE_OPENAI_BASE_URL: "https://example.openai.azure.com",
|
||||||
|
OPENROUTER_API_KEY: null,
|
||||||
|
DASHSCOPE_API_KEY: null,
|
||||||
|
REPLICATE_API_TOKEN: null,
|
||||||
|
JIMENG_ACCESS_KEY_ID: null,
|
||||||
|
JIMENG_SECRET_ACCESS_KEY: null,
|
||||||
|
ARK_API_KEY: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(detectProvider(makeArgs()), "azure");
|
||||||
|
assert.equal(
|
||||||
|
detectProvider(makeArgs({ referenceImages: ["ref.png"] })),
|
||||||
|
"azure",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("detectProvider infers Seedream from model id and allows Seedream reference-image workflows", (t) => {
|
test("detectProvider infers Seedream from model id and allows Seedream reference-image workflows", (t) => {
|
||||||
useEnv(t, {
|
useEnv(t, {
|
||||||
GOOGLE_API_KEY: null,
|
GOOGLE_API_KEY: null,
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const DEFAULT_PROVIDER_RATE_LIMITS: Record<Provider, ProviderRateLimit> = {
|
|||||||
dashscope: { concurrency: 3, startIntervalMs: 1100 },
|
dashscope: { concurrency: 3, startIntervalMs: 1100 },
|
||||||
jimeng: { concurrency: 3, startIntervalMs: 1100 },
|
jimeng: { concurrency: 3, startIntervalMs: 1100 },
|
||||||
seedream: { concurrency: 3, startIntervalMs: 1100 },
|
seedream: { concurrency: 3, startIntervalMs: 1100 },
|
||||||
|
azure: { concurrency: 3, startIntervalMs: 1100 },
|
||||||
};
|
};
|
||||||
|
|
||||||
function printUsage(): void {
|
function printUsage(): void {
|
||||||
@@ -74,13 +75,13 @@ Options:
|
|||||||
--image <path> Output image path (required in single-image mode)
|
--image <path> Output image path (required in single-image mode)
|
||||||
--batchfile <path> JSON batch file for multi-image generation
|
--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)
|
--jobs <count> Worker count for batch mode (default: auto, max from config, built-in default 10)
|
||||||
--provider google|openai|openrouter|dashscope|replicate|jimeng|seedream Force provider (auto-detect by default)
|
--provider google|openai|openrouter|dashscope|replicate|jimeng|seedream|azure Force provider (auto-detect by default)
|
||||||
-m, --model <id> Model ID
|
-m, --model <id> Model ID
|
||||||
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
|
--ar <ratio> Aspect ratio (e.g., 16:9, 1:1, 4:3)
|
||||||
--size <WxH> Size (e.g., 1024x1024)
|
--size <WxH> Size (e.g., 1024x1024)
|
||||||
--quality normal|2k Quality preset (default: 2k)
|
--quality normal|2k Quality preset (default: 2k)
|
||||||
--imageSize 1K|2K|4K Image size for Google/OpenRouter (default: from quality)
|
--imageSize 1K|2K|4K Image size for Google/OpenRouter (default: from quality)
|
||||||
--ref <files...> Reference images (Google, OpenAI, OpenRouter, Replicate, or Seedream 4.0/4.5/5.0)
|
--ref <files...> Reference images (Google, OpenAI, Azure, OpenRouter, Replicate, or Seedream 4.0/4.5/5.0)
|
||||||
--n <count> Number of images for the current task (default: 1)
|
--n <count> Number of images for the current task (default: 1)
|
||||||
--json JSON output
|
--json JSON output
|
||||||
-h, --help Show help
|
-h, --help Show help
|
||||||
@@ -131,6 +132,11 @@ Environment variables:
|
|||||||
DASHSCOPE_BASE_URL Custom DashScope endpoint
|
DASHSCOPE_BASE_URL Custom DashScope endpoint
|
||||||
REPLICATE_BASE_URL Custom Replicate endpoint
|
REPLICATE_BASE_URL Custom Replicate endpoint
|
||||||
JIMENG_BASE_URL Custom Jimeng endpoint
|
JIMENG_BASE_URL Custom Jimeng endpoint
|
||||||
|
AZURE_OPENAI_API_KEY Azure OpenAI API key
|
||||||
|
AZURE_OPENAI_BASE_URL Azure OpenAI resource or deployment endpoint
|
||||||
|
AZURE_OPENAI_DEPLOYMENT Default Azure deployment name
|
||||||
|
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
|
SEEDREAM_BASE_URL Custom Seedream endpoint
|
||||||
BAOYU_IMAGE_GEN_MAX_WORKERS Override batch worker cap
|
BAOYU_IMAGE_GEN_MAX_WORKERS Override batch worker cap
|
||||||
BAOYU_IMAGE_GEN_<PROVIDER>_CONCURRENCY Override provider concurrency
|
BAOYU_IMAGE_GEN_<PROVIDER>_CONCURRENCY Override provider concurrency
|
||||||
@@ -231,7 +237,8 @@ export function parseArgs(argv: string[]): CliArgs {
|
|||||||
v !== "dashscope" &&
|
v !== "dashscope" &&
|
||||||
v !== "replicate" &&
|
v !== "replicate" &&
|
||||||
v !== "jimeng" &&
|
v !== "jimeng" &&
|
||||||
v !== "seedream"
|
v !== "seedream" &&
|
||||||
|
v !== "azure"
|
||||||
) {
|
) {
|
||||||
throw new Error(`Invalid provider: ${v}`);
|
throw new Error(`Invalid provider: ${v}`);
|
||||||
}
|
}
|
||||||
@@ -386,6 +393,7 @@ export function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
|||||||
replicate: null,
|
replicate: null,
|
||||||
jimeng: null,
|
jimeng: null,
|
||||||
seedream: null,
|
seedream: null,
|
||||||
|
azure: null,
|
||||||
};
|
};
|
||||||
currentKey = "default_model";
|
currentKey = "default_model";
|
||||||
currentProvider = null;
|
currentProvider = null;
|
||||||
@@ -411,7 +419,8 @@ export function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
|||||||
key === "dashscope" ||
|
key === "dashscope" ||
|
||||||
key === "replicate" ||
|
key === "replicate" ||
|
||||||
key === "jimeng" ||
|
key === "jimeng" ||
|
||||||
key === "seedream"
|
key === "seedream" ||
|
||||||
|
key === "azure"
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
config.batch ??= {};
|
config.batch ??= {};
|
||||||
@@ -427,7 +436,8 @@ export function parseSimpleYaml(yaml: string): Partial<ExtendConfig> {
|
|||||||
key === "dashscope" ||
|
key === "dashscope" ||
|
||||||
key === "replicate" ||
|
key === "replicate" ||
|
||||||
key === "jimeng" ||
|
key === "jimeng" ||
|
||||||
key === "seedream"
|
key === "seedream" ||
|
||||||
|
key === "azure"
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const cleaned = value.replace(/['"]/g, "");
|
const cleaned = value.replace(/['"]/g, "");
|
||||||
@@ -520,9 +530,10 @@ export function getConfiguredProviderRateLimits(
|
|||||||
dashscope: { ...DEFAULT_PROVIDER_RATE_LIMITS.dashscope },
|
dashscope: { ...DEFAULT_PROVIDER_RATE_LIMITS.dashscope },
|
||||||
jimeng: { ...DEFAULT_PROVIDER_RATE_LIMITS.jimeng },
|
jimeng: { ...DEFAULT_PROVIDER_RATE_LIMITS.jimeng },
|
||||||
seedream: { ...DEFAULT_PROVIDER_RATE_LIMITS.seedream },
|
seedream: { ...DEFAULT_PROVIDER_RATE_LIMITS.seedream },
|
||||||
|
azure: { ...DEFAULT_PROVIDER_RATE_LIMITS.azure },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "jimeng", "seedream"] as Provider[]) {
|
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "jimeng", "seedream", "azure"] as Provider[]) {
|
||||||
const envPrefix = `BAOYU_IMAGE_GEN_${provider.toUpperCase()}`;
|
const envPrefix = `BAOYU_IMAGE_GEN_${provider.toUpperCase()}`;
|
||||||
const extendLimit = extendConfig.batch?.provider_limits?.[provider];
|
const extendLimit = extendConfig.batch?.provider_limits?.[provider];
|
||||||
configured[provider] = {
|
configured[provider] = {
|
||||||
@@ -581,18 +592,20 @@ export function detectProvider(args: CliArgs): Provider {
|
|||||||
args.provider &&
|
args.provider &&
|
||||||
args.provider !== "google" &&
|
args.provider !== "google" &&
|
||||||
args.provider !== "openai" &&
|
args.provider !== "openai" &&
|
||||||
|
args.provider !== "azure" &&
|
||||||
args.provider !== "openrouter" &&
|
args.provider !== "openrouter" &&
|
||||||
args.provider !== "replicate" &&
|
args.provider !== "replicate" &&
|
||||||
args.provider !== "seedream"
|
args.provider !== "seedream"
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Reference images require a ref-capable provider. Use --provider google (Gemini multimodal), --provider openai (GPT Image edits), --provider openrouter (OpenRouter multimodal), --provider replicate, or --provider seedream for supported Seedream models."
|
"Reference images require a ref-capable provider. Use --provider google (Gemini multimodal), --provider openai (GPT Image edits), --provider azure (Azure OpenAI), --provider openrouter (OpenRouter multimodal), --provider replicate, or --provider seedream for supported Seedream models."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.provider) return args.provider;
|
if (args.provider) return args.provider;
|
||||||
|
|
||||||
const hasGoogle = !!(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
|
const hasGoogle = !!(process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY);
|
||||||
|
const hasAzure = !!(process.env.AZURE_OPENAI_API_KEY && process.env.AZURE_OPENAI_BASE_URL);
|
||||||
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
const hasOpenai = !!process.env.OPENAI_API_KEY;
|
||||||
const hasOpenrouter = !!process.env.OPENROUTER_API_KEY;
|
const hasOpenrouter = !!process.env.OPENROUTER_API_KEY;
|
||||||
const hasDashscope = !!process.env.DASHSCOPE_API_KEY;
|
const hasDashscope = !!process.env.DASHSCOPE_API_KEY;
|
||||||
@@ -611,17 +624,19 @@ export function detectProvider(args: CliArgs): Provider {
|
|||||||
if (args.referenceImages.length > 0) {
|
if (args.referenceImages.length > 0) {
|
||||||
if (hasGoogle) return "google";
|
if (hasGoogle) return "google";
|
||||||
if (hasOpenai) return "openai";
|
if (hasOpenai) return "openai";
|
||||||
|
if (hasAzure) return "azure";
|
||||||
if (hasOpenrouter) return "openrouter";
|
if (hasOpenrouter) return "openrouter";
|
||||||
if (hasReplicate) return "replicate";
|
if (hasReplicate) return "replicate";
|
||||||
if (hasSeedream) return "seedream";
|
if (hasSeedream) return "seedream";
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Reference images require Google, OpenAI, OpenRouter, Replicate, or supported Seedream models. Set GOOGLE_API_KEY/GEMINI_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, REPLICATE_API_TOKEN, or ARK_API_KEY, or remove --ref."
|
"Reference images require Google, OpenAI, Azure, OpenRouter, Replicate, or supported Seedream models. Set GOOGLE_API_KEY/GEMINI_API_KEY, OPENAI_API_KEY, AZURE_OPENAI_API_KEY+AZURE_OPENAI_BASE_URL, OPENROUTER_API_KEY, REPLICATE_API_TOKEN, or ARK_API_KEY, or remove --ref."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const available = [
|
const available = [
|
||||||
hasGoogle && "google",
|
hasGoogle && "google",
|
||||||
hasOpenai && "openai",
|
hasOpenai && "openai",
|
||||||
|
hasAzure && "azure",
|
||||||
hasOpenrouter && "openrouter",
|
hasOpenrouter && "openrouter",
|
||||||
hasDashscope && "dashscope",
|
hasDashscope && "dashscope",
|
||||||
hasReplicate && "replicate",
|
hasReplicate && "replicate",
|
||||||
@@ -633,7 +648,7 @@ export function detectProvider(args: CliArgs): Provider {
|
|||||||
if (available.length > 1) return available[0]!;
|
if (available.length > 1) return available[0]!;
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"No API key found. Set GOOGLE_API_KEY, GEMINI_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, DASHSCOPE_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, REPLICATE_API_TOKEN, JIMENG keys, or ARK_API_KEY.\n" +
|
||||||
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
|
"Create ~/.baoyu-skills/.env or <cwd>/.baoyu-skills/.env with your keys."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -676,6 +691,7 @@ async function loadProviderModule(provider: Provider): Promise<ProviderModule> {
|
|||||||
if (provider === "openrouter") return (await import("./providers/openrouter")) as ProviderModule;
|
if (provider === "openrouter") return (await import("./providers/openrouter")) as ProviderModule;
|
||||||
if (provider === "jimeng") return (await import("./providers/jimeng")) as ProviderModule;
|
if (provider === "jimeng") return (await import("./providers/jimeng")) as ProviderModule;
|
||||||
if (provider === "seedream") return (await import("./providers/seedream")) as ProviderModule;
|
if (provider === "seedream") return (await import("./providers/seedream")) as ProviderModule;
|
||||||
|
if (provider === "azure") return (await import("./providers/azure")) as ProviderModule;
|
||||||
return (await import("./providers/openai")) as ProviderModule;
|
return (await import("./providers/openai")) as ProviderModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,6 +720,7 @@ function getModelForProvider(
|
|||||||
if (provider === "replicate" && extendConfig.default_model.replicate) return extendConfig.default_model.replicate;
|
if (provider === "replicate" && extendConfig.default_model.replicate) return extendConfig.default_model.replicate;
|
||||||
if (provider === "jimeng" && extendConfig.default_model.jimeng) return extendConfig.default_model.jimeng;
|
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 === "seedream" && extendConfig.default_model.seedream) return extendConfig.default_model.seedream;
|
||||||
|
if (provider === "azure" && extendConfig.default_model.azure) return extendConfig.default_model.azure;
|
||||||
}
|
}
|
||||||
return providerModule.getDefaultModel();
|
return providerModule.getDefaultModel();
|
||||||
}
|
}
|
||||||
@@ -923,7 +940,7 @@ async function runBatchTasks(
|
|||||||
const acquireProvider = createProviderGate(providerRateLimits);
|
const acquireProvider = createProviderGate(providerRateLimits);
|
||||||
const workerCount = getWorkerCount(tasks.length, jobs, maxWorkers);
|
const workerCount = getWorkerCount(tasks.length, jobs, maxWorkers);
|
||||||
console.error(`Batch mode: ${tasks.length} tasks, ${workerCount} workers, parallel mode enabled.`);
|
console.error(`Batch mode: ${tasks.length} tasks, ${workerCount} workers, parallel mode enabled.`);
|
||||||
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "jimeng", "seedream"] as Provider[]) {
|
for (const provider of ["replicate", "google", "openai", "openrouter", "dashscope", "jimeng", "seedream", "azure"] as Provider[]) {
|
||||||
const limit = providerRateLimits[provider];
|
const limit = providerRateLimits[provider];
|
||||||
console.error(`- ${provider}: concurrency=${limit.concurrency}, startIntervalMs=${limit.startIntervalMs}`);
|
console.error(`- ${provider}: concurrency=${limit.concurrency}, startIntervalMs=${limit.startIntervalMs}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import test, { type TestContext } from "node:test";
|
||||||
|
|
||||||
|
import type { CliArgs } from "../types.ts";
|
||||||
|
import {
|
||||||
|
generateImage,
|
||||||
|
getDefaultModel,
|
||||||
|
parseAzureBaseURL,
|
||||||
|
validateArgs,
|
||||||
|
} from "./azure.ts";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeTempDir(prefix: string): Promise<string> {
|
||||||
|
return fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Azure endpoint parsing and default deployment selection follow env precedence", (t) => {
|
||||||
|
assert.deepEqual(parseAzureBaseURL("https://example.openai.azure.com"), {
|
||||||
|
resourceBaseURL: "https://example.openai.azure.com/openai",
|
||||||
|
deployment: null,
|
||||||
|
});
|
||||||
|
assert.deepEqual(
|
||||||
|
parseAzureBaseURL("https://example.openai.azure.com/openai/deployments/from-url"),
|
||||||
|
{
|
||||||
|
resourceBaseURL: "https://example.openai.azure.com/openai",
|
||||||
|
deployment: "from-url",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
useEnv(t, {
|
||||||
|
AZURE_OPENAI_BASE_URL: "https://example.openai.azure.com/openai/deployments/from-url",
|
||||||
|
AZURE_OPENAI_DEPLOYMENT: "explicit-deploy",
|
||||||
|
AZURE_OPENAI_IMAGE_MODEL: "env-fallback",
|
||||||
|
});
|
||||||
|
assert.equal(getDefaultModel(), "explicit-deploy");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Azure validateArgs rejects unsupported edit input formats before the API call", () => {
|
||||||
|
assert.doesNotThrow(() =>
|
||||||
|
validateArgs("demo-deployment", makeArgs({ referenceImages: ["hero.png", "photo.jpeg"] })),
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => validateArgs("demo-deployment", makeArgs({ referenceImages: ["hero.webp"] })),
|
||||||
|
/PNG or JPG\/JPEG/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Azure image generation routes model to deployment and sends mapped quality", async (t) => {
|
||||||
|
useEnv(t, {
|
||||||
|
AZURE_OPENAI_API_KEY: "azure-key",
|
||||||
|
AZURE_OPENAI_BASE_URL: "https://example.openai.azure.com/openai/deployments/default-deploy",
|
||||||
|
AZURE_API_VERSION: null,
|
||||||
|
AZURE_OPENAI_DEPLOYMENT: null,
|
||||||
|
AZURE_OPENAI_IMAGE_MODEL: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
t.after(() => {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
const calls: Array<{ url: string; body: string }> = [];
|
||||||
|
globalThis.fetch = async (input, init) => {
|
||||||
|
calls.push({
|
||||||
|
url: String(input),
|
||||||
|
body: String(init?.body ?? ""),
|
||||||
|
});
|
||||||
|
return Response.json({
|
||||||
|
data: [{ b64_json: Buffer.from("azure-image").toString("base64") }],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const bytes = await generateImage(
|
||||||
|
"A calm lake at sunset",
|
||||||
|
"custom-deploy",
|
||||||
|
makeArgs({ quality: "normal" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(Buffer.from(bytes).toString("utf8"), "azure-image");
|
||||||
|
assert.equal(
|
||||||
|
calls[0]?.url,
|
||||||
|
"https://example.openai.azure.com/openai/deployments/custom-deploy/images/generations?api-version=2025-04-01-preview",
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = JSON.parse(calls[0]!.body) as Record<string, string>;
|
||||||
|
assert.equal(body.quality, "medium");
|
||||||
|
assert.equal(body.size, "1024x1024");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Azure image edits include quality in multipart requests", async (t) => {
|
||||||
|
const root = await makeTempDir("baoyu-image-gen-azure-");
|
||||||
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
const pngPath = path.join(root, "ref.png");
|
||||||
|
const jpgPath = path.join(root, "ref.jpg");
|
||||||
|
await fs.writeFile(pngPath, "png-bytes");
|
||||||
|
await fs.writeFile(jpgPath, "jpg-bytes");
|
||||||
|
|
||||||
|
useEnv(t, {
|
||||||
|
AZURE_OPENAI_API_KEY: "azure-key",
|
||||||
|
AZURE_OPENAI_BASE_URL: "https://example.openai.azure.com",
|
||||||
|
AZURE_API_VERSION: "2025-04-01-preview",
|
||||||
|
AZURE_OPENAI_DEPLOYMENT: null,
|
||||||
|
AZURE_OPENAI_IMAGE_MODEL: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
t.after(() => {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
const calls: Array<{ url: string; form: FormData }> = [];
|
||||||
|
globalThis.fetch = async (input, init) => {
|
||||||
|
calls.push({
|
||||||
|
url: String(input),
|
||||||
|
form: init?.body as FormData,
|
||||||
|
});
|
||||||
|
return Response.json({
|
||||||
|
data: [{ b64_json: Buffer.from("edited-image").toString("base64") }],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const bytes = await generateImage(
|
||||||
|
"Add warm lighting",
|
||||||
|
"edit-deploy",
|
||||||
|
makeArgs({
|
||||||
|
quality: "2k",
|
||||||
|
referenceImages: [pngPath, jpgPath],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(Buffer.from(bytes).toString("utf8"), "edited-image");
|
||||||
|
assert.equal(
|
||||||
|
calls[0]?.url,
|
||||||
|
"https://example.openai.azure.com/openai/deployments/edit-deploy/images/edits?api-version=2025-04-01-preview",
|
||||||
|
);
|
||||||
|
assert.equal(calls[0]?.form.get("quality"), "high");
|
||||||
|
assert.equal(calls[0]?.form.get("size"), "1024x1024");
|
||||||
|
assert.equal(calls[0]?.form.getAll("image[]").length, 2);
|
||||||
|
});
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
import type { CliArgs } from "../types";
|
||||||
|
import { getOpenAISize, extractImageFromResponse } from "./openai.ts";
|
||||||
|
|
||||||
|
type OpenAIImageResponse = { data: Array<{ url?: string; b64_json?: string }> };
|
||||||
|
type AzureEndpoint = {
|
||||||
|
resourceBaseURL: string;
|
||||||
|
deployment: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_AZURE_API_VERSION = "2025-04-01-preview";
|
||||||
|
const AZURE_EDIT_IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg"]);
|
||||||
|
|
||||||
|
export function parseAzureBaseURL(url: string): AzureEndpoint {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
const trimmedPath = parsed.pathname.replace(/\/+$/, "");
|
||||||
|
const deploymentMatch = trimmedPath.match(/^(.*?)(?:\/openai)?\/deployments\/([^/]+)$/);
|
||||||
|
|
||||||
|
if (deploymentMatch) {
|
||||||
|
parsed.pathname = `${deploymentMatch[1] || ""}/openai`;
|
||||||
|
return {
|
||||||
|
resourceBaseURL: parsed.toString().replace(/\/+$/, ""),
|
||||||
|
deployment: decodeURIComponent(deploymentMatch[2]!),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.pathname = trimmedPath.endsWith("/openai") ? trimmedPath : `${trimmedPath}/openai`;
|
||||||
|
return {
|
||||||
|
resourceBaseURL: parsed.toString().replace(/\/+$/, ""),
|
||||||
|
deployment: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultModel(): string {
|
||||||
|
const explicitDeployment = process.env.AZURE_OPENAI_DEPLOYMENT?.trim();
|
||||||
|
if (explicitDeployment) return explicitDeployment;
|
||||||
|
|
||||||
|
const baseURL = process.env.AZURE_OPENAI_BASE_URL;
|
||||||
|
if (baseURL) {
|
||||||
|
try {
|
||||||
|
const { deployment } = parseAzureBaseURL(baseURL);
|
||||||
|
if (deployment) return deployment;
|
||||||
|
} catch {
|
||||||
|
// Ignore invalid URLs here so the required-env check can raise the user-facing error later.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return process.env.AZURE_OPENAI_IMAGE_MODEL || "gpt-image-1.5";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEndpoint(): AzureEndpoint {
|
||||||
|
const url = process.env.AZURE_OPENAI_BASE_URL;
|
||||||
|
if (!url) {
|
||||||
|
throw new Error(
|
||||||
|
"AZURE_OPENAI_BASE_URL is required. Set it to your Azure resource or deployment endpoint, e.g.: https://your-resource.openai.azure.com or https://your-resource.openai.azure.com/openai/deployments/your-deployment"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return parseAzureBaseURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiKey(): string {
|
||||||
|
const key = process.env.AZURE_OPENAI_API_KEY;
|
||||||
|
if (!key) {
|
||||||
|
throw new Error(
|
||||||
|
"AZURE_OPENAI_API_KEY is required. Get it from Azure Portal → your OpenAI resource → Keys and Endpoint."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApiVersion(): string {
|
||||||
|
return process.env.AZURE_API_VERSION || DEFAULT_AZURE_API_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeployment(model: string): string {
|
||||||
|
const deployment = model.trim();
|
||||||
|
if (!deployment) {
|
||||||
|
throw new Error(
|
||||||
|
"Azure deployment name is required. Use --model <deployment>, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_IMAGE_MODEL, or embed the deployment in AZURE_OPENAI_BASE_URL."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return deployment;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildURL(deployment: string, pathSuffix: string): string {
|
||||||
|
const { resourceBaseURL } = getEndpoint();
|
||||||
|
return `${resourceBaseURL}/deployments/${encodeURIComponent(deployment)}${pathSuffix}?api-version=${getApiVersion()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function authHeaders(): Record<string, string> {
|
||||||
|
return { "api-key": getApiKey() };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAzureQuality(quality: CliArgs["quality"]): "medium" | "high" {
|
||||||
|
return quality === "2k" ? "high" : "medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateArgs(_model: string, args: CliArgs): void {
|
||||||
|
for (const refPath of args.referenceImages) {
|
||||||
|
const ext = path.extname(refPath).toLowerCase();
|
||||||
|
if (!AZURE_EDIT_IMAGE_EXTENSIONS.has(ext)) {
|
||||||
|
throw new Error(
|
||||||
|
`Azure OpenAI reference images must be PNG or JPG/JPEG. Unsupported file: ${refPath}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateImage(
|
||||||
|
prompt: string,
|
||||||
|
model: string,
|
||||||
|
args: CliArgs
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
const deployment = getDeployment(model);
|
||||||
|
const size = args.size || getOpenAISize(model, args.aspectRatio, args.quality);
|
||||||
|
|
||||||
|
if (args.referenceImages.length > 0) {
|
||||||
|
return generateWithAzureEdits(prompt, deployment, size, args.referenceImages, args.quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateWithAzureGenerations(prompt, deployment, size, args.quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateWithAzureGenerations(
|
||||||
|
prompt: string,
|
||||||
|
deployment: string,
|
||||||
|
size: string,
|
||||||
|
quality: CliArgs["quality"]
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
const body: Record<string, any> = {
|
||||||
|
prompt,
|
||||||
|
size,
|
||||||
|
n: 1,
|
||||||
|
quality: getAzureQuality(quality),
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch(buildURL(deployment, "/images/generations"), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
...authHeaders(),
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.text();
|
||||||
|
throw new Error(`Azure OpenAI API error: ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = (await res.json()) as OpenAIImageResponse;
|
||||||
|
return extractImageFromResponse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateWithAzureEdits(
|
||||||
|
prompt: string,
|
||||||
|
deployment: string,
|
||||||
|
size: string,
|
||||||
|
referenceImages: string[],
|
||||||
|
quality: CliArgs["quality"]
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
const form = new FormData();
|
||||||
|
form.append("prompt", prompt);
|
||||||
|
form.append("size", size);
|
||||||
|
form.append("n", "1");
|
||||||
|
form.append("quality", getAzureQuality(quality));
|
||||||
|
|
||||||
|
for (const refPath of referenceImages) {
|
||||||
|
const bytes = await readFile(refPath);
|
||||||
|
const filename = path.basename(refPath);
|
||||||
|
const mimeType = path.extname(filename).toLowerCase() === ".png" ? "image/png" : "image/jpeg";
|
||||||
|
const blob = new Blob([bytes], { type: mimeType });
|
||||||
|
form.append("image[]", blob, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(buildURL(deployment, "/images/edits"), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
...authHeaders(),
|
||||||
|
},
|
||||||
|
body: form,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const err = await res.text();
|
||||||
|
throw new Error(`Azure OpenAI edits API error: ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = (await res.json()) as OpenAIImageResponse;
|
||||||
|
return extractImageFromResponse(result);
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test, { type TestContext } from "node:test";
|
||||||
|
|
||||||
|
import type { CliArgs } from "../types.ts";
|
||||||
|
import { generateImage } from "./jimeng.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("Jimeng submit request uses prompt field expected by current API", async (t) => {
|
||||||
|
useEnv(t, {
|
||||||
|
JIMENG_ACCESS_KEY_ID: "test-access-key",
|
||||||
|
JIMENG_SECRET_ACCESS_KEY: "test-secret-key",
|
||||||
|
JIMENG_BASE_URL: null,
|
||||||
|
JIMENG_REGION: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
t.after(() => {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
});
|
||||||
|
|
||||||
|
const calls: Array<{
|
||||||
|
input: string;
|
||||||
|
init?: RequestInit;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
globalThis.fetch = async (input, init) => {
|
||||||
|
calls.push({
|
||||||
|
input: String(input),
|
||||||
|
init,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (calls.length === 1) {
|
||||||
|
return Response.json({
|
||||||
|
code: 10000,
|
||||||
|
data: {
|
||||||
|
task_id: "task-123",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
code: 10000,
|
||||||
|
data: {
|
||||||
|
status: "done",
|
||||||
|
binary_data_base64: [Buffer.from("jimeng-image").toString("base64")],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const image = await generateImage(
|
||||||
|
"A quiet bamboo forest",
|
||||||
|
"jimeng_t2i_v40",
|
||||||
|
makeArgs({ quality: "normal" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(Buffer.from(image).toString("utf8"), "jimeng-image");
|
||||||
|
assert.equal(calls.length, 2);
|
||||||
|
assert.equal(
|
||||||
|
calls[0]?.input,
|
||||||
|
"https://visual.volcengineapi.com/?Action=CVSync2AsyncSubmitTask&Version=2022-08-31",
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitBody = JSON.parse(String(calls[0]?.init?.body)) as Record<string, unknown>;
|
||||||
|
assert.equal(submitBody.req_key, "jimeng_t2i_v40");
|
||||||
|
assert.equal(submitBody.prompt, "A quiet bamboo forest");
|
||||||
|
assert.ok(!("prompt_text" in submitBody));
|
||||||
|
assert.equal(submitBody.width, 1024);
|
||||||
|
assert.equal(submitBody.height, 1024);
|
||||||
|
});
|
||||||
@@ -246,7 +246,7 @@ async function submitTask(
|
|||||||
const [width, height] = size.split("x").map(Number);
|
const [width, height] = size.split("x").map(Number);
|
||||||
const bodyObj = {
|
const bodyObj = {
|
||||||
req_key: model,
|
req_key: model,
|
||||||
prompt_text: prompt,
|
prompt,
|
||||||
// Use separate width and height parameters instead of size string
|
// Use separate width and height parameters instead of size string
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type Provider = "google" | "openai" | "openrouter" | "dashscope" | "replicate" | "jimeng" | "seedream";
|
export type Provider = "google" | "openai" | "openrouter" | "dashscope" | "replicate" | "jimeng" | "seedream" | "azure";
|
||||||
export type Quality = "normal" | "2k";
|
export type Quality = "normal" | "2k";
|
||||||
|
|
||||||
export type CliArgs = {
|
export type CliArgs = {
|
||||||
@@ -55,6 +55,7 @@ export type ExtendConfig = {
|
|||||||
replicate: string | null;
|
replicate: string | null;
|
||||||
jimeng: string | null;
|
jimeng: string | null;
|
||||||
seedream: string | null;
|
seedream: string | null;
|
||||||
|
azure: string | null;
|
||||||
};
|
};
|
||||||
batch?: {
|
batch?: {
|
||||||
max_workers?: number | null;
|
max_workers?: number | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user