Skip to content

Commit 403a98d

Browse files
authored
Merge pull request #290 from modelstudioai/feat/mcp-call-aigc
Feat/mcp call aigc
2 parents 691482c + a2a5997 commit 403a98d

30 files changed

Lines changed: 4423 additions & 1470 deletions

‎docs/agents/telemetry-change.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
- `channel` 与 `t1` 是当前固定口径
6565
- `User-Agent` 是独立标识:`bl` 为 `bailian-cli/<version>`,`kscli` 为 `knowledge-studio-cli/<version>`
6666
- `X-Dashscope-Service: bailian-cli` 与 source-config 一起由 `trackingHeaders()` 注入;凡走该函数的百炼 / DashScope 请求(含 MCP 注册与 JSON-RPC)都会带上。OSS、结果文件下载、npm / Skill 仍不发送。
67+
- **AIGC auto MCP 例外**:AIGC 命令解析出的模型为保留值 `auto` 时,经 `withAigcMcpSession` 调用的 4 个生成类 MCP server(TextGenerateImage / VideoGenerate / TextGenerateSpeech / SpeechGenerateText)把 `X-Dashscope-Service` 覆写为 `bailian-cli-call`(常量 `MCP_CALL_SERVICE`),供网关单独归因 CLI 发起的 MCP 生成流量;此时 `source-config` 的 `channel` 仍是 `bailian-cli`,只有 Service 头变。其它 MCP 命令(`search web`、`mcp tools`、`mcp call`)与所有 REST 请求保持 `bailian-cli`。覆写经 `connectBailianMcp(serverCode, urlOverride?, serviceSource?)` → `connectBailianMcpWithFallback` → `SdkMcpAdapter` → `trackingHeaders(identity, service = CHANNEL)` 逐层透传,未传时回落默认 `CHANNEL`。
6768

6869
source-config 只用于百炼 / DashScope API 侧消费,不发送到通用网络传输:
6970

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

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ import {
2222
ASYNC_FLAG,
2323
CONCURRENT_FLAG,
2424
redactDataUri,
25+
bailianMcpPath,
26+
AIGC_MCP_SERVER,
27+
AIGC_MCP_TOOL,
28+
buildImageEditArgs,
29+
parseAigcImageResults,
2530
} from "bailian-cli-core";
2631
import { poll } from "bailian-cli-runtime";
2732
import { downloadFile } from "bailian-cli-runtime";
@@ -30,6 +35,15 @@ import { emitResult, emitBare } from "bailian-cli-runtime";
3035
import { resolveImageSize } from "bailian-cli-runtime";
3136
import { join } from "path";
3237
import { BOOL_FLAG_PROMPT_EXTEND_CLI_TRUE, BOOL_FLAG_WATERMARK } from "bailian-cli-runtime";
38+
import {
39+
isAutoModel,
40+
assertAutoMcpAvailable,
41+
assertMcpCompatible,
42+
withAigcMcpSession,
43+
resolveAutoImageInput,
44+
mcpSingleImageOnlyError,
45+
type Localize,
46+
} from "../shared/aigc-mcp.ts";
3347

3448
const EDIT_FLAGS = {
3549
image: {
@@ -181,6 +195,14 @@ export default defineCommand({
181195
"zh-CN": '--image ./photo.png --prompt "将背景替换为海滩" --watermark false',
182196
},
183197
],
198+
notes: [
199+
{
200+
"en-US":
201+
'Model "auto" (via --model auto or a config default model of "auto") routes to the official Bailian MCP with a reduced parameter set; MCP-incompatible flags fail fast with a usage error instead of falling back to REST. Auto edit accepts a single --image only.',
202+
"zh-CN":
203+
'模型 "auto"(通过 --model auto 或将配置默认模型设为 "auto")改走百炼官方 MCP,参数面更小;MCP 不支持的参数会直接报用法错误,而不回退 REST。auto 编辑仅支持单个 --image。',
204+
},
205+
],
184206
async run(ctx) {
185207
const { settings, flags } = ctx;
186208
// Normalize --image to string array (supports both single and repeated flags)
@@ -192,7 +214,20 @@ export default defineCommand({
192214
}
193215
const prompt = flags.prompt;
194216

195-
const model = flags.model || settings.defaultImageModel || "qwen-image-3.0";
217+
const resolvedModel = flags.model || settings.defaultImageModel;
218+
if (isAutoModel(resolvedModel)) {
219+
await handleAutoMcpMode(
220+
ctx.client,
221+
ctx.localize,
222+
settings,
223+
flags,
224+
rawImages,
225+
detectOutputFormat(settings.output),
226+
);
227+
return;
228+
}
229+
230+
const model = resolvedModel || "qwen-image-3.0";
196231
const route = resolveImageEditApi(model);
197232

198233
// Auto-upload local files (resolve all images in parallel)
@@ -329,6 +364,70 @@ export default defineCommand({
329364
},
330365
});
331366

367+
async function handleAutoMcpMode(
368+
client: Client,
369+
localize: Localize,
370+
settings: Settings,
371+
flags: EditFlags,
372+
rawImages: string[],
373+
format: OutputFormat,
374+
): Promise<void> {
375+
assertAutoMcpAvailable(client, localize);
376+
assertMcpCompatible("imageEdit", flags, localize);
377+
378+
// bailian_image_edit takes a single source image; bailian_image_fusion is out
379+
// of scope, so multi-image merge is rejected in auto mode.
380+
if (rawImages.length > 1) {
381+
throw mcpSingleImageOnlyError(localize);
382+
}
383+
const source = rawImages[0];
384+
if (!source) {
385+
throw new BailianError("Provide one --image to edit.", ExitCode.USAGE);
386+
}
387+
388+
const imageUrl = resolveAutoImageInput(source);
389+
const concurrent = getConcurrency(flags);
390+
const args = buildImageEditArgs({
391+
imageUrl,
392+
prompt: flags.prompt,
393+
negativePrompt: flags.negativePrompt || undefined,
394+
});
395+
396+
if (settings.dryRun) {
397+
emitResult(
398+
{
399+
server: AIGC_MCP_SERVER.image,
400+
url: client.url(bailianMcpPath(AIGC_MCP_SERVER.image)),
401+
tool: AIGC_MCP_TOOL.imageEdit,
402+
arguments: { ...args, image_url: redactDataUri(imageUrl) },
403+
mode: "mcp",
404+
concurrent,
405+
},
406+
format,
407+
);
408+
return;
409+
}
410+
411+
if (!settings.quiet) {
412+
process.stderr.write("[Model: auto] [Mode: mcp] [Images: 1]\n");
413+
}
414+
415+
// One shared MCP session; --concurrent fans out parallel tools/call on it.
416+
await withAigcMcpSession(client, AIGC_MCP_SERVER.image, localize, async (callTool) => {
417+
const perRequest = await runConcurrent(concurrent, settings, async () => {
418+
const inner = await callTool(AIGC_MCP_TOOL.imageEdit, args);
419+
return parseAigcImageResults(inner);
420+
});
421+
422+
const imageUrls = perRequest.flat();
423+
if (imageUrls.length === 0) {
424+
throw new BailianError("Edit completed but no images returned.", ExitCode.GENERAL);
425+
}
426+
427+
await saveImages(imageUrls, flags, settings, format);
428+
});
429+
}
430+
332431
async function handleSyncMode(
333432
client: Client,
334433
settings: Settings,

‎packages/commands/src/commands/image/generate.ts‎

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,26 @@ import {
2020
resolveImageGenerateApi,
2121
ASYNC_FLAG,
2222
CONCURRENT_FLAG,
23+
bailianMcpPath,
24+
AIGC_MCP_SERVER,
25+
AIGC_MCP_TOOL,
26+
buildImageGenArgs,
27+
parseAigcImageResults,
2328
} from "bailian-cli-core";
2429
import { poll } from "bailian-cli-runtime";
2530
import { downloadFile } from "bailian-cli-runtime";
2631
import { runConcurrent, downloadParallel, getConcurrency } from "bailian-cli-runtime";
2732
import { emitResult, emitBare } from "bailian-cli-runtime";
2833
import { resolveImageSize } from "bailian-cli-runtime";
2934
import { BOOL_FLAG_PROMPT_EXTEND_IMAGE_GENERATE, BOOL_FLAG_WATERMARK } from "bailian-cli-runtime";
35+
import {
36+
isAutoModel,
37+
assertAutoMcpAvailable,
38+
assertMcpCompatible,
39+
resolveAutoImageSize,
40+
withAigcMcpSession,
41+
type Localize,
42+
} from "../shared/aigc-mcp.ts";
3043

3144
import { join } from "path";
3245

@@ -167,11 +180,31 @@ export default defineCommand({
167180
"zh-CN": '--prompt "产品摄影" --n 2 --concurrent 3 # 并行生成 6 张图片',
168181
},
169182
],
183+
notes: [
184+
{
185+
"en-US":
186+
'Model "auto" (via --model auto or a config default model of "auto") routes to the official Bailian MCP with a reduced parameter set; MCP-incompatible flags fail fast with a usage error instead of falling back to REST.',
187+
"zh-CN":
188+
'模型 "auto"(通过 --model auto 或将配置默认模型设为 "auto")改走百炼官方 MCP,参数面更小;MCP 不支持的参数会直接报用法错误,而不回退 REST。',
189+
},
190+
],
170191
async run(ctx) {
171192
const { settings, flags } = ctx;
172193
const prompt = flags.prompt;
173194

174-
const model = flags.model || settings.defaultImageModel || "qwen-image-3.0";
195+
const resolvedModel = flags.model || settings.defaultImageModel;
196+
if (isAutoModel(resolvedModel)) {
197+
await handleAutoMcpMode(
198+
ctx.client,
199+
ctx.localize,
200+
settings,
201+
flags,
202+
detectOutputFormat(settings.output),
203+
);
204+
return;
205+
}
206+
207+
const model = resolvedModel || "qwen-image-3.0";
175208
const route = resolveImageGenerateApi(model);
176209
const defaultSize = "1:1";
177210
const sizeInput = flags.size || defaultSize;
@@ -238,6 +271,62 @@ export default defineCommand({
238271
},
239272
});
240273

274+
// ---- Auto mode: Bailian AIGC MCP (bailian_image_gen) ----
275+
276+
async function handleAutoMcpMode(
277+
client: Client,
278+
localize: Localize,
279+
settings: Settings,
280+
flags: GenerateFlags,
281+
format: string,
282+
): Promise<void> {
283+
assertAutoMcpAvailable(client, localize);
284+
assertMcpCompatible("imageGen", flags, localize);
285+
286+
const concurrent = getConcurrency(flags);
287+
const args = buildImageGenArgs({
288+
prompt: flags.prompt,
289+
size: resolveAutoImageSize(flags.size, localize),
290+
n: flags.n,
291+
negativePrompt: flags.negativePrompt || undefined,
292+
promptExtend: flags.promptExtend,
293+
});
294+
295+
if (settings.dryRun) {
296+
emitResult(
297+
{
298+
server: AIGC_MCP_SERVER.image,
299+
url: client.url(bailianMcpPath(AIGC_MCP_SERVER.image)),
300+
tool: AIGC_MCP_TOOL.imageGen,
301+
arguments: args,
302+
mode: "mcp",
303+
concurrent,
304+
},
305+
format as OutputFormat,
306+
);
307+
return;
308+
}
309+
310+
if (!settings.quiet) {
311+
process.stderr.write("[Model: auto] [Mode: mcp]\n");
312+
}
313+
314+
// One shared MCP session; --concurrent fans out parallel tools/call on it.
315+
await withAigcMcpSession(client, AIGC_MCP_SERVER.image, localize, async (callTool) => {
316+
const perRequest = await runConcurrent(concurrent, settings, async () => {
317+
const inner = await callTool(AIGC_MCP_TOOL.imageGen, args);
318+
return parseAigcImageResults(inner);
319+
});
320+
321+
const imageUrls = perRequest.flat();
322+
if (imageUrls.length === 0) {
323+
throw new BailianError("Generation completed but no images returned.", ExitCode.GENERAL);
324+
}
325+
326+
await saveImages(imageUrls, flags, settings, format);
327+
});
328+
}
329+
241330
// ---- Sync mode: qwen-image / wan2.7-image / z-image ----
242331

243332
async function handleSyncMode(

‎packages/commands/src/commands/search/web.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
detectOutputFormat,
55
mcpWebSearchPath,
66
type FlagsDef,
7+
type McpConnectedClient,
78
} from "bailian-cli-core";
89
import { createSpinner, emitResult } from "bailian-cli-runtime";
910
import { rethrowWithWebSearchActivateHint } from "./web-activate-hint.ts";
@@ -66,13 +67,18 @@ export default defineCommand({
6667
return;
6768
}
6869

70+
let client: McpConnectedClient | undefined;
6971
try {
70-
const client = ctx.client.mcp(mcpWebSearchPath());
72+
client = ctx.client.mcp(mcpWebSearchPath());
7173
await client.initialize();
7274
const tools = await client.listTools();
7375
emitResult({ tools }, format);
7476
} catch (error) {
7577
rethrowWithWebSearchActivateHint(error);
78+
} finally {
79+
// The SDK transport keeps a live SSE stream / pending sockets open;
80+
// without closing it the process never exits after emitting results.
81+
client?.close?.();
7682
}
7783
return;
7884
}
@@ -152,6 +158,9 @@ export default defineCommand({
152158
} catch (error) {
153159
spinner.stop("Failed.");
154160
rethrowWithWebSearchActivateHint(error);
161+
} finally {
162+
// Close on both paths: an open transport stream keeps the process alive.
163+
client.close?.();
155164
}
156165
},
157166
});

0 commit comments

Comments
 (0)