Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and

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

## [1.11.2] - 2026-07-28

### Changed

- MCP tools and WebSearch now provide activation guidance and direct marketplace links when Bailian reports that the corresponding service is not activated. WebSearch also guides users with legacy SSE connections to reactivate the service using Streamable HTTP.

### Fixed

- Fixed text chat and API Key validation compatibility failures caused by sending unsupported `enable_thinking` values. Text chat now sends the parameter only when thinking is explicitly enabled, while validation uses a compatible model without sending it.

## [1.11.1] - 2026-07-28

### Added
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@

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

## [1.11.2] - 2026-07-28

### 变更

- MCP 工具或 WebSearch 因对应服务未开通而不可用时,CLI 现在会提供开通指引和市场直达链接;对于使用旧版 SSE 连接的 WebSearch,还会提示重新开通以切换至 Streamable HTTP。

### 修复

- 修复文本对话与 API Key 登录校验因传递不受支持的 `enable_thinking` 参数值而产生的兼容性错误。文本对话仅在用户明确开启思考模式时传递该参数,登录校验则改用兼容模型且不再传递该参数。

## [1.11.1] - 2026-07-28

### 新增
Expand Down
2 changes: 2 additions & 0 deletions docs/agents/url-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ runtime/src/urls.ts ← 用户面控制台 URL(cn-only)
BAILIAN_CONSOLE BAILIAN_CONSOLE_ROOT/cn-beijing
API_KEY_PAGE BAILIAN_CONSOLE/?tab=app#/api-key
TOKEN_PLAN_PAGE BAILIAN_CONSOLE_ROOT/cn-beijing?tab=plan#/efm/subscription/overview
MCP_WEBSEARCH_PAGE mcpMarketplaceDetailPage("WebSearch")
mcpMarketplaceDetailPage BAILIAN_CONSOLE?tab=mcp#/mcp-market/detail/<serverCode>
core/files/upload.ts ← 文件上传 endpoint(cn-pinned)
UPLOAD_API ${REGIONS.cn}/api/v1/uploads
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bailian-cli",
"version": "1.11.1",
"version": "1.11.2",
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
"keywords": [
"agent",
Expand Down
2 changes: 1 addition & 1 deletion packages/commands/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bailian-cli-commands",
"version": "1.11.1",
"version": "1.11.2",
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
"homepage": "https://bailian.console.aliyun.com/cli",
"bugs": {
Expand Down
3 changes: 1 addition & 2 deletions packages/commands/src/commands/auth/login-api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function validateAndPersistApiKey(
const persistBaseUrl = profile.persistBaseUrl
? normalizeModelBaseUrl(profile.persistBaseUrl)
: undefined;
const validationModel = profile.defaultTextModel || "qwen3.7-max";
const validationModel = "qwen3.7-max";
const requestOpts = {
url: baseUrl + chatPath(),
method: "POST",
Expand All @@ -68,7 +68,6 @@ export async function validateAndPersistApiKey(
messages: [{ role: "user", content: "hi" }],
max_tokens: 1,
stream: false,
enable_thinking: validationModel === "qwen3.8-max-preview",
},
};

Expand Down
39 changes: 39 additions & 0 deletions packages/commands/src/commands/mcp/activate-hint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { BailianError } from "bailian-cli-core";
import { mcpMarketplaceDetailPage } from "bailian-cli-runtime";

/** Detect MCP-not-activated / invalid 404 errors (CLI-wrapped server message). */
export function isMcpNotActivated(error: unknown): boolean {
if (!(error instanceof BailianError)) return false;
const message = error.message;
if (!/MCP request failed:\s*404\b/i.test(message)) return false;
return /未开通|MCP不存在|MCP_IS_INVALID/i.test(message);
}

/** Activation hint; URL from runtime/urls.ts. */
export function mcpActivateHint(serverCode: string): string {
const lines = [
`Activate (or re-activate) the ${serverCode} MCP in the Bailian MCP marketplace, then retry.`,
];
if (serverCode === "WebSearch") {
lines.push(
"If it was previously on SSE, cancel and activate again to upgrade to Streamable HTTP.",
);
}
lines.push(`Open: ${mcpMarketplaceDetailPage(serverCode)}`);
return lines.join("\n");
}

/**
* For not-activated errors, keep the original message / exitCode and append a hint only.
* Do not replace the server error message.
*/
export function rethrowWithMcpActivateHint(error: unknown, serverCode: string): never {
if (isMcpNotActivated(error) && error instanceof BailianError && !error.hint) {
throw new BailianError(error.message, error.exitCode, mcpActivateHint(serverCode), {
cause: error,
api: error.api,
rawResponse: error.rawResponse,
});
}
throw error;
}
22 changes: 15 additions & 7 deletions packages/commands/src/commands/mcp/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type ParsedFlags,
} from "bailian-cli-core";
import { emitResult } from "bailian-cli-runtime";
import { rethrowWithMcpActivateHint } from "./activate-hint.ts";

const CALL_FLAGS = {
target: {
Expand Down Expand Up @@ -130,14 +131,21 @@ export default defineCommand({
}

const client = ctx.client.mcp(url);
await client.initialize();
const result = await client.callTool(toolName, toolArgs);
try {
await client.initialize();
const result = await client.callTool(toolName, toolArgs);

if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
throw new BailianError(`Tool error: ${errText}`);
}
if (result.isError) {
const errText = result.content.map((c) => c.text || "").join("\n");
throw new BailianError(`Tool error: ${errText}`);
}

emitResult(result, format);
emitResult(result, format);
} catch (error) {
if (!flags.url) {
rethrowWithMcpActivateHint(error, serverCode);
}
throw error;
}
},
});
14 changes: 11 additions & 3 deletions packages/commands/src/commands/mcp/tools.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineCommand, bailianMcpPath, detectOutputFormat } from "bailian-cli-core";
import { emitResult } from "bailian-cli-runtime";
import { rethrowWithMcpActivateHint } from "./activate-hint.ts";

export default defineCommand({
description: "List tools exposed by an MCP server (tools/list)",
Expand Down Expand Up @@ -36,8 +37,15 @@ export default defineCommand({
}

const client = ctx.client.mcp(url);
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
try {
await client.initialize();
const tools = await client.listTools();
emitResult({ server: code, url, tools }, format);
} catch (error) {
if (!flags.url) {
rethrowWithMcpActivateHint(error, code);
}
throw error;
}
},
});
18 changes: 18 additions & 0 deletions packages/commands/src/commands/search/web-activate-hint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
isMcpNotActivated,
mcpActivateHint,
rethrowWithMcpActivateHint,
} from "../mcp/activate-hint.ts";

/** Detect WebSearch MCP not-activated / invalid 404 errors. */
export const isWebSearchMcpNotActivated = isMcpNotActivated;

/** WebSearch activation hint. */
export function webSearchActivateHint(): string {
return mcpActivateHint("WebSearch");
}

/** Keep the original message; append a hint for WebSearch not-activated errors. */
export function rethrowWithWebSearchActivateHint(error: unknown): never {
rethrowWithMcpActivateHint(error, "WebSearch");
}
19 changes: 11 additions & 8 deletions packages/commands/src/commands/search/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
mcpWebSearchPath,
type FlagsDef,
} from "bailian-cli-core";
import { createSpinner } from "bailian-cli-runtime";
import { emitResult } from "bailian-cli-runtime";
import { createSpinner, emitResult } from "bailian-cli-runtime";
import { rethrowWithWebSearchActivateHint } from "./web-activate-hint.ts";

const WEB_SEARCH_FLAGS = {
query: { type: "string", valueHint: "<text>", description: "Search query text" },
Expand Down Expand Up @@ -41,11 +41,14 @@ export default defineCommand({
return;
}

const client = ctx.client.mcp(mcpWebSearchPath());
await client.initialize();
const tools = await client.listTools();

emitResult({ tools }, format);
try {
const client = ctx.client.mcp(mcpWebSearchPath());
await client.initialize();
const tools = await client.listTools();
emitResult({ tools }, format);
} catch (error) {
rethrowWithWebSearchActivateHint(error);
}
return;
}

Expand Down Expand Up @@ -123,7 +126,7 @@ export default defineCommand({
}
} catch (error) {
spinner.stop("Failed.");
throw error;
rethrowWithWebSearchActivateHint(error);
}
},
});
5 changes: 0 additions & 5 deletions packages/commands/src/commands/text/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,6 @@ export default defineCommand({
if (flags.thinkingBudget !== undefined) {
body.thinking_budget = flags.thinkingBudget;
}
} else if (!shouldStream) {
// DashScope qwen3 models default to enable_thinking=true server-side, but
// non-streaming calls require it to be explicitly false. Stream calls
// support thinking, so leave the field unset there (server handles it).
body.enable_thinking = false;
}

if (flags.tool) {
Expand Down
4 changes: 1 addition & 3 deletions packages/commands/tests/e2e/auth.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ describe("e2e: auth", () => {
body: {
model: "qwen3.7-max",
stream: false,
enable_thinking: false,
},
});

Expand Down Expand Up @@ -315,9 +314,8 @@ describe("e2e: auth", () => {
authorization: "Bearer sk-sp-e2e-placeholder",
sourceConfig: expect.any(String),
body: {
model: "qwen3.8-max-preview",
model: "qwen3.7-max",
stream: false,
enable_thinking: true,
},
});

Expand Down
81 changes: 81 additions & 0 deletions packages/commands/tests/mcp-activate-hint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, expect, test } from "vite-plus/test";
import { BailianError, ExitCode } from "bailian-cli-core";
import { mcpMarketplaceDetailPage } from "bailian-cli-runtime";
import {
isMcpNotActivated,
mcpActivateHint,
rethrowWithMcpActivateHint,
} from "../src/commands/mcp/activate-hint.ts";

describe("mcp-activate-hint", () => {
test("识别 404 + 未开通 / MCP不存在 / MCP_IS_INVALID", () => {
expect(
isMcpNotActivated(new BailianError("MCP request failed: 404 Not Found - MCP不存在或未开通")),
).toBe(true);
expect(
isMcpNotActivated(new BailianError("MCP request failed: 404 - MCP不存在或未开通")),
).toBe(true);
expect(
isMcpNotActivated(new BailianError("MCP request failed: 404 Not Found - MCP_IS_INVALID")),
).toBe(true);
});

test("裸 404 或非 MCP 错误不加开通判定", () => {
expect(isMcpNotActivated(new BailianError("MCP request failed: 404 Not Found"))).toBe(false);
expect(isMcpNotActivated(new BailianError("MCP request failed: 405 Method Not Allowed"))).toBe(
false,
);
expect(isMcpNotActivated(new Error("MCP不存在或未开通"))).toBe(false);
});

test("hint 含对应 server 的 MCP 广场深链", () => {
const serverCode = "market-cmapi00073529";
expect(mcpActivateHint(serverCode)).toContain(mcpMarketplaceDetailPage(serverCode));
expect(mcpActivateHint(serverCode)).toMatch(/Activate|re-activate/i);
});

test("WebSearch hint 含 SSE 升级说明", () => {
expect(mcpActivateHint("WebSearch")).toMatch(/SSE|Streamable HTTP/i);
});

test("rethrow 保留原 message,补 hint", () => {
const serverCode = "market-cmapi00073529";
const original = new BailianError(
"MCP request failed: 404 Not Found - MCP不存在或未开通",
ExitCode.GENERAL,
);
try {
rethrowWithMcpActivateHint(original, serverCode);
expect.unreachable("should throw");
} catch (error) {
expect(error).toBeInstanceOf(BailianError);
const wrapped = error as BailianError;
expect(wrapped.message).toBe(original.message);
expect(wrapped.exitCode).toBe(ExitCode.GENERAL);
expect(wrapped.hint).toContain(mcpMarketplaceDetailPage(serverCode));
expect(wrapped.cause).toBe(original);
}
});

test("已有 hint 或非未开通错误原样抛出", () => {
const withHint = new BailianError(
"MCP request failed: 404 Not Found - MCP不存在或未开通",
ExitCode.GENERAL,
"already hinted",
);
try {
rethrowWithMcpActivateHint(withHint, "WebSearch");
expect.unreachable("should throw");
} catch (error) {
expect(error).toBe(withHint);
}

const other = new BailianError("MCP request failed: 401 Unauthorized");
try {
rethrowWithMcpActivateHint(other, "WebSearch");
expect.unreachable("should throw");
} catch (error) {
expect(error).toBe(other);
}
});
});
Loading