Skip to content

Commit 81fa5b5

Browse files
Merge pull request #123 from modelstudioai/fix/default-model
Fix/default model
2 parents 634d704 + df987ad commit 81fa5b5

21 files changed

Lines changed: 1134 additions & 256 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
66

77
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
88

9+
## [1.11.1] - 2026-07-28
10+
11+
### Added
12+
13+
- `bl image edit` now supports `--function` for specifying edit operations with Wanx image-edit models such as `wanx2.1-imageedit`.
14+
15+
### Fixed
16+
17+
- Fixed image generation and editing failures and incorrect size parameters for some image models, improving compatibility with Qwen-Image, Wan/Wanx, Z-Image, and dated `wanx-v1` variants.
18+
919
## [1.11.0] - 2026-07-28
1020

1121
### Added

CHANGELOG.zh.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
88

9+
## [1.11.1] - 2026-07-28
10+
11+
### 新增
12+
13+
- `bl image edit` 新增 `--function` 参数,支持为万相图片编辑模型(如 `wanx2.1-imageedit`)指定编辑功能。
14+
15+
### 修复
16+
17+
- 修复部分图片模型在图片生成与编辑时的调用失败和尺寸参数错误,并完善 Qwen-Image、Wan/Wanx、Z-Image 系列及 `wanx-v1` 日期版本的兼容性。
18+
919
## [1.11.0] - 2026-07-28
1020

1121
### 新增

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli",
3-
"version": "1.11.0",
3+
"version": "1.11.1",
44
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
55
"keywords": [
66
"agent",

packages/commands/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli-commands",
3-
"version": "1.11.0",
3+
"version": "1.11.1",
44
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
55
"homepage": "https://bailian.console.aliyun.com/cli",
66
"bugs": {

packages/commands/src/commands/image/edit.ts

Lines changed: 125 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import {
22
defineCommand,
3-
imagePath,
4-
imageSyncPath,
53
taskPath,
64
detectOutputFormat,
75
resolveOutputDir,
@@ -20,6 +18,7 @@ import {
2018
BailianError,
2119
resolveBooleanFlag,
2220
resolveWatermark,
21+
resolveImageEditApi,
2322
ASYNC_FLAG,
2423
CONCURRENT_FLAG,
2524
redactDataUri,
@@ -32,17 +31,6 @@ import { resolveImageSize } from "bailian-cli-runtime";
3231
import { join } from "path";
3332
import { BOOL_FLAG_PROMPT_EXTEND_CLI_TRUE, BOOL_FLAG_WATERMARK } from "bailian-cli-runtime";
3433

35-
const SYNC_MODEL_PREFIXES = ["qwen-image-2.0", "qwen-image-max", "wan2.7-image"];
36-
const PROMPT_EXTEND_DEFAULT_PREFIXES = ["qwen-image-2.0", "qwen-image-max"];
37-
38-
function isSyncModel(model: string): boolean {
39-
return SYNC_MODEL_PREFIXES.some((prefix) => model.startsWith(prefix));
40-
}
41-
42-
function enablesPromptExtendByDefault(model: string): boolean {
43-
return PROMPT_EXTEND_DEFAULT_PREFIXES.some((prefix) => model.startsWith(prefix));
44-
}
45-
4634
const EDIT_FLAGS = {
4735
image: {
4836
type: "array",
@@ -77,6 +65,12 @@ const EDIT_FLAGS = {
7765
valueHint: "<text>",
7866
description: "Negative prompt to exclude unwanted content",
7967
},
68+
function: {
69+
type: "string",
70+
valueHint: "<name>",
71+
description:
72+
"wanx*-imageedit function (default: description_edit). Examples: stylization_all, description_edit",
73+
},
8074
promptExtend: {
8175
type: "boolean",
8276
valueHint: "<bool>",
@@ -114,6 +108,8 @@ export default defineCommand({
114108
'--image ./a.png --image ./b.png --prompt "Merge two images into one collage"',
115109
'--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro',
116110
'--image ./photo.png --prompt "Change the style" --model wan2.7-image',
111+
'--image ./photo.png --prompt "Place the subject on a table" --model wan2.5-i2i-preview',
112+
'--image ./photo.png --prompt "转换成绘本风格" --model wanx2.1-imageedit --function stylization_all',
117113
'--image ./photo.png --prompt "Replace the background with a beach" --watermark false',
118114
],
119115
async run(ctx) {
@@ -128,7 +124,7 @@ export default defineCommand({
128124
const prompt = flags.prompt;
129125

130126
const model = flags.model || settings.defaultImageModel || "qwen-image-2.0";
131-
const useSync = isSyncModel(model);
127+
const route = resolveImageEditApi(model);
132128

133129
// Auto-upload local files (resolve all images in parallel)
134130
const resolvedImages = await Promise.all(
@@ -138,94 +134,152 @@ export default defineCommand({
138134

139135
const promptExtend = resolveBooleanFlag(
140136
flags.promptExtend,
141-
enablesPromptExtendByDefault(model) ? true : undefined,
137+
route.promptExtendDefault,
142138
"prompt-extend",
143139
);
144140

145-
// Build content: all images first, then text prompt
146-
const contentItems: Array<{ image?: string; text?: string }> = resolvedImages.map(
147-
(u: string) => ({ image: u }),
148-
);
149-
contentItems.push({ text: prompt });
150-
151141
const watermark = resolveWatermark(flags.watermark);
152142

153-
const body: DashScopeImageRequest = {
154-
model,
155-
input: {
156-
messages: [
157-
{
158-
role: "user",
159-
content: contentItems,
160-
},
161-
],
162-
},
163-
parameters: {
164-
size: resolveImageSize(flags.size, useSync),
165-
n,
166-
seed: flags.seed,
167-
prompt_extend: promptExtend,
168-
watermark,
169-
negative_prompt: flags.negativePrompt || undefined,
170-
},
143+
const parameters: NonNullable<DashScopeImageRequest["parameters"]> = {
144+
size: resolveImageSize(flags.size, route.sizeProfile),
145+
n,
146+
seed: flags.seed,
147+
prompt_extend: promptExtend,
148+
watermark,
171149
};
172150

151+
let body: DashScopeImageRequest;
152+
if (route.inputStyle === "function-base-image") {
153+
const baseImageUrl = resolvedImages[0];
154+
if (!baseImageUrl) {
155+
throw new BailianError(
156+
"wanx*-imageedit requires at least one --image as base_image_url.",
157+
ExitCode.USAGE,
158+
);
159+
}
160+
body = {
161+
model,
162+
input: {
163+
function: flags.function || "description_edit",
164+
prompt,
165+
base_image_url: baseImageUrl,
166+
},
167+
parameters,
168+
};
169+
} else if (route.inputStyle === "prompt-images") {
170+
body = {
171+
model,
172+
input: {
173+
prompt,
174+
images: resolvedImages,
175+
negative_prompt: flags.negativePrompt || undefined,
176+
},
177+
parameters,
178+
};
179+
} else {
180+
const contentItems: Array<{ image?: string; text?: string }> = resolvedImages.map(
181+
(imageUrl: string) => ({ image: imageUrl }),
182+
);
183+
contentItems.push({ text: prompt });
184+
body = {
185+
model,
186+
input: {
187+
messages: [
188+
{
189+
role: "user",
190+
content: contentItems,
191+
},
192+
],
193+
},
194+
parameters: {
195+
...parameters,
196+
negative_prompt: flags.negativePrompt || undefined,
197+
},
198+
};
199+
}
200+
173201
// Remove undefined parameters
174202
stripUndefined(body.parameters as Record<string, unknown>);
175203

176204
const format = detectOutputFormat(settings.output);
177205

178206
if (settings.dryRun) {
179-
const previewBody = {
180-
...body,
181-
input: {
182-
messages: body.input.messages.map((message) => ({
183-
...message,
184-
content: message.content.map((item) =>
185-
item.image ? { ...item, image: redactDataUri(item.image) } : item,
186-
),
187-
})),
188-
},
189-
};
190-
emitResult({ request: previewBody, mode: useSync ? "sync" : "async" }, format);
207+
let previewBody: DashScopeImageRequest = body;
208+
if ("messages" in body.input) {
209+
previewBody = {
210+
...body,
211+
input: {
212+
messages: body.input.messages.map((message) => ({
213+
...message,
214+
content: message.content.map((item) =>
215+
item.image ? { ...item, image: redactDataUri(item.image) } : item,
216+
),
217+
})),
218+
},
219+
};
220+
} else if ("images" in body.input) {
221+
previewBody = {
222+
...body,
223+
input: {
224+
...body.input,
225+
images: body.input.images?.map((imageUrl) => redactDataUri(imageUrl)),
226+
},
227+
};
228+
} else if ("base_image_url" in body.input) {
229+
previewBody = {
230+
...body,
231+
input: {
232+
...body.input,
233+
base_image_url: redactDataUri(body.input.base_image_url),
234+
mask_image_url: body.input.mask_image_url
235+
? redactDataUri(body.input.mask_image_url)
236+
: undefined,
237+
},
238+
};
239+
}
240+
emitResult(
241+
{ request: previewBody, mode: route.useSync ? "sync" : "async", path: route.path },
242+
format,
243+
);
191244
return;
192245
}
193246

194247
if (!settings.quiet) {
195248
process.stderr.write(
196-
`[Model: ${model}] [Mode: ${useSync ? "sync" : "async"}] [Images: ${resolvedImages.length}]\n`,
249+
`[Model: ${model}] [Mode: ${route.useSync ? "sync" : "async"}] [Images: ${resolvedImages.length}]\n`,
197250
);
198251
}
199252

200253
const concurrent = getConcurrency(flags);
201254

202-
if (useSync) {
203-
await handleSyncMode(ctx.client, settings, body, flags, format, concurrent);
255+
if (route.useSync) {
256+
await handleSyncMode(ctx.client, settings, route.path, body, flags, format, concurrent);
204257
} else {
205-
await handleAsyncMode(ctx.client, settings, body, flags, format, concurrent);
258+
await handleAsyncMode(ctx.client, settings, route.path, body, flags, format, concurrent);
206259
}
207260
},
208261
});
209262

210263
async function handleSyncMode(
211264
client: Client,
212265
settings: Settings,
266+
path: string,
213267
body: DashScopeImageRequest,
214268
flags: EditFlags,
215269
format: OutputFormat,
216270
concurrent: number,
217271
): Promise<void> {
218272
const results = await runConcurrent(concurrent, settings, () =>
219273
client.requestJson<DashScopeImageSyncResponse>({
220-
path: imageSyncPath(),
274+
path,
221275
method: "POST",
222276
body,
223277
}),
224278
);
225279

226280
const imageUrls = results
227-
.flatMap((r) => r.output.choices || [])
228-
.flatMap((c) => c.message?.content || [])
281+
.flatMap((result) => result.output.choices || [])
282+
.flatMap((choice) => choice.message?.content || [])
229283
.map((item) => item.image)
230284
.filter(Boolean);
231285

@@ -239,6 +293,7 @@ async function handleSyncMode(
239293
async function handleAsyncMode(
240294
client: Client,
241295
settings: Settings,
296+
path: string,
242297
body: DashScopeImageRequest,
243298
flags: EditFlags,
244299
format: OutputFormat,
@@ -249,14 +304,14 @@ async function handleAsyncMode(
249304
settings,
250305
() =>
251306
client.requestJson<DashScopeAsyncResponse>({
252-
path: imagePath(),
307+
path,
253308
method: "POST",
254309
body,
255310
async: true,
256311
}),
257312
"tasks",
258313
);
259-
const taskIds = responses.map((r) => r.output.task_id);
314+
const taskIds = responses.map((response) => response.output.task_id);
260315

261316
if (flags.async) {
262317
emitResult({ task_ids: taskIds }, format);
@@ -269,12 +324,12 @@ async function handleAsyncMode(
269324
url: client.url(taskPath(taskId)),
270325
intervalSec: pollInterval,
271326
timeoutSec: settings.timeout,
272-
isComplete: (d) => (d as DashScopeTaskResponse).output.task_status === "SUCCEEDED",
273-
isFailed: (d) => (d as DashScopeTaskResponse).output.task_status === "FAILED",
274-
getStatus: (d) => (d as DashScopeTaskResponse).output.task_status,
275-
getErrorMessage: (d) => {
276-
const o = (d as DashScopeTaskResponse).output;
277-
return o.message || o.code || undefined;
327+
isComplete: (data) => (data as DashScopeTaskResponse).output.task_status === "SUCCEEDED",
328+
isFailed: (data) => (data as DashScopeTaskResponse).output.task_status === "FAILED",
329+
getStatus: (data) => (data as DashScopeTaskResponse).output.task_status,
330+
getErrorMessage: (data) => {
331+
const output = (data as DashScopeTaskResponse).output;
332+
return output.message || output.code || undefined;
278333
},
279334
}),
280335
);
@@ -285,13 +340,13 @@ async function handleAsyncMode(
285340
for (const result of results) {
286341
if (result.output.choices) {
287342
const urls = result.output.choices
288-
.flatMap((c) => c.message?.content || [])
343+
.flatMap((choice) => choice.message?.content || [])
289344
.map((item) => item.image)
290345
.filter(Boolean);
291346
imageUrls.push(...urls);
292347
}
293348
if (result.output.results) {
294-
const urls = result.output.results.map((r) => r.url).filter(Boolean);
349+
const urls = result.output.results.map((item) => item.url).filter(Boolean);
295350
if (urls.length > 0 && imageUrls.length === 0) {
296351
imageUrls.push(...urls);
297352
}
@@ -321,8 +376,8 @@ async function saveImages(
321376
// Parallel download all images
322377
const items =
323378
imageUrls.length > 1
324-
? imageUrls.map((url, i) => {
325-
const filename = `${prefix}_${String(i + 1).padStart(3, "0")}.png`;
379+
? imageUrls.map((url, index) => {
380+
const filename = `${prefix}_${String(index + 1).padStart(3, "0")}.png`;
326381
return { url, destPath: join(outDir, filename) };
327382
})
328383
: [{ url: imageUrls[0], destPath: join(outDir, `${prefix}.png`) }];

0 commit comments

Comments
 (0)