Skip to content

Commit cbeb2bf

Browse files
authored
Merge branch 'main' into feat/update-intent-model
2 parents 8cb24b7 + 1fd08fe commit cbeb2bf

51 files changed

Lines changed: 825 additions & 404 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ CLI 只为「自己能权威解释的错误」发出语义化信号,服务端的
9393

9494
不要扮演服务端错误的翻译官——我们没有最新的错误码体系认知,二次包装只会撒谎(详见 `docs/agents/error-hint-change.md` 中的反面 case)。
9595

96+
### 4. Console Gateway 命令必须声明 console 全局 flags
97+
98+
如果新命令使用了 `callConsoleGateway`,必须在 `options` 中添加以下三个全局 flag 的说明,以便 `--help` 中展示:
99+
100+
```ts
101+
{ flag: "--console-region <region>", description: "Console region" },
102+
{ flag: "--console-site <site>", description: "Console site: domestic, international" },
103+
{ flag: "--console-switch-agent <uid>", description: "Switch agent UID", type: "number" },
104+
```
105+
106+
这些 flag 已在 `GLOBAL_OPTIONS``packages/core/src/types/command.ts`)中注册,由 `loadConfig` 写入 `config.consoleRegion` / `config.consoleSite` / `config.consoleSwitchAgent``callConsoleGateway` 自动读取——命令无需手动提取或传递。
107+
96108
## 完成改动后的快速验证
97109

98110
```sh

INSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ bl auth status --output json
102102
bl text chat --message "ping" --non-interactive --output json
103103
```
104104

105-
若失败:根据 stderr / JSON 中的 `hint``message` 排查(网络、Key 无效、region 等)。全局 region:`--region cn|us|intl`,默认 `cn`
105+
若失败:根据 stderr / JSON 中的 `hint``message` 排查(网络、Key 无效、`base_url` 等)。DashScope 端点:使用 `--base-url` / `bl config set --key base_url` / `DASHSCOPE_BASE_URL`,默认中国大陆 `https://dashscope.aliyuncs.com`
106106

107107
---
108108

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
172172
bl config show
173173

174174
# Set defaults
175-
bl config set --key region --value us
175+
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
176176
bl config set --key default_text_model --value qwen-turbo
177177
bl config set --key timeout --value 600
178178

README.zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
167167
bl config show
168168

169169
# 设置默认值
170-
bl config set --key region --value us
170+
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
171171
bl config set --key default_text_model --value qwen-turbo
172172
bl config set --key timeout --value 600
173173

packages/cli/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
172172
bl config show
173173

174174
# Set defaults
175-
bl config set --key region --value us
175+
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
176176
bl config set --key default_text_model --value qwen-turbo
177177
bl config set --key timeout --value 600
178178

packages/cli/README.zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export BAILIAN_WORKSPACE_ID=ws-...
167167
bl config show
168168

169169
# 设置默认值
170-
bl config set --key region --value us
170+
bl config set --key base_url --value https://dashscope-us.aliyuncs.com
171171
bl config set --key default_text_model --value qwen-turbo
172172
bl config set --key timeout --value 600
173173

packages/cli/src/commands/app/list.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ export default defineCommand({
3030
description: "Results per page (default: 30)",
3131
type: "number",
3232
},
33+
{ flag: "--console-region <region>", description: "Console region" },
3334
{
34-
flag: "--region <region>",
35-
description: "API region (default: cn-beijing)",
35+
flag: "--console-site <site>",
36+
description: "Console site: domestic, international",
37+
},
38+
{
39+
flag: "--console-switch-agent <uid>",
40+
description: "Switch agent UID",
41+
type: "number",
3642
},
3743
],
3844
examples: [
@@ -45,7 +51,6 @@ export default defineCommand({
4551
const name = (flags.name as string) || "";
4652
const pageNo = (flags.page as number) || 1;
4753
const pageSize = (flags.pageSize as number) || 30;
48-
const region = (flags.region as string) || "cn-beijing";
4954
const format = detectOutputFormat(config.output);
5055

5156
const credential = await resolveConsoleGatewayCredential(config);
@@ -62,17 +67,13 @@ export default defineCommand({
6267
};
6368

6469
if (config.dryRun) {
65-
emitResult(
66-
{ api: APP_LIST_API, data, region, token: credential.token.slice(0, 8) + "..." },
67-
format,
68-
);
70+
emitResult({ api: APP_LIST_API, data, token: credential.token.slice(0, 8) + "..." }, format);
6971
return;
7072
}
7173

7274
const result = (await callConsoleGateway(config, credential.token, {
7375
api: APP_LIST_API,
7476
data,
75-
region,
7677
})) as any;
7778

7879
const list: unknown[] = result?.data?.DataV2?.data?.data?.list ?? [];

packages/cli/src/commands/auth/login-console.ts

Lines changed: 193 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@ import http from "node:http";
55
import {
66
BailianError,
77
ExitCode,
8+
chatEndpoint,
89
getConfigPath,
910
readConfigFile,
11+
requestJson,
1012
writeConfigFile,
13+
type Config,
1114
} from "bailian-cli-core";
1215

1316
const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
1417
const MAX_AUTH_CALLBACK_BODY = 65536;
1518

16-
const DEFAULT_CONSOLE_ORIGIN = "https://bailian.console.aliyun.com";
19+
const CONSOLE_ORIGINS: Record<string, string> = {
20+
domestic: "https://bailian.console.aliyun.com",
21+
international: "https://modelstudio.console.alibabacloud.com",
22+
};
1723

18-
export function resolveConsoleOrigin(): string {
19-
return process.env.BAILIAN_CONSOLE_ORIGIN || DEFAULT_CONSOLE_ORIGIN;
24+
export function resolveConsoleOrigin(site?: string): string {
25+
return (site && CONSOLE_ORIGINS[site]) || CONSOLE_ORIGINS.domestic!;
2026
}
2127

2228
function readBodyBounded(req: http.IncomingMessage): Promise<string> {
@@ -210,9 +216,76 @@ function parseApiKeyFromRawBody(raw: string, contentType: string): string | null
210216
return null;
211217
}
212218

219+
type CallbackExtras = Pick<
220+
CallbackCredentials,
221+
"baseUrl" | "consoleSite" | "consoleRegion" | "consoleSwitchAgent" | "workspaceId"
222+
>;
223+
224+
function stringField(o: Record<string, unknown>, ...keys: string[]): string | null {
225+
for (const k of keys) {
226+
const v = o[k];
227+
if (typeof v === "string" && v.trim()) return v.trim();
228+
}
229+
return null;
230+
}
231+
232+
function parseExtrasFromRawBody(raw: string, contentType: string): CallbackExtras {
233+
const empty: CallbackExtras = {
234+
baseUrl: null,
235+
consoleSite: null,
236+
consoleRegion: null,
237+
consoleSwitchAgent: null,
238+
workspaceId: null,
239+
};
240+
if (!raw.trim()) return empty;
241+
242+
let obj: Record<string, unknown> | null = null;
243+
244+
const ct = contentType.toLowerCase();
245+
if (ct.includes("application/json") || ct.includes("text/json")) {
246+
try {
247+
const parsed = JSON.parse(raw.trim());
248+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) obj = parsed;
249+
} catch {
250+
/* */
251+
}
252+
}
253+
if (!obj && ct.includes("application/x-www-form-urlencoded")) {
254+
try {
255+
const params = new URLSearchParams(raw.trim());
256+
obj = Object.fromEntries(params);
257+
} catch {
258+
/* */
259+
}
260+
}
261+
if (!obj) {
262+
try {
263+
const parsed = JSON.parse(raw.trim());
264+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) obj = parsed;
265+
} catch {
266+
/* */
267+
}
268+
}
269+
270+
if (!obj) return empty;
271+
272+
return {
273+
baseUrl: stringField(obj, "base_url", "baseUrl"),
274+
consoleSite: stringField(obj, "console_site", "consoleSite"),
275+
consoleRegion: stringField(obj, "console_region", "consoleRegion"),
276+
consoleSwitchAgent: stringField(obj, "console_switch_agent", "consoleSwitchAgent"),
277+
workspaceId: stringField(obj, "workspace_id", "workspaceId"),
278+
};
279+
}
280+
213281
interface CallbackCredentials {
214282
accessToken: string | null;
215283
apiKey: string | null;
284+
baseUrl: string | null;
285+
consoleSite: string | null;
286+
consoleRegion: string | null;
287+
consoleSwitchAgent: string | null;
288+
workspaceId: string | null;
216289
}
217290

218291
async function extractCredentialsFromRequest(
@@ -222,12 +295,30 @@ async function extractCredentialsFromRequest(
222295
const accessTokenFromQuery =
223296
u.searchParams.get("access_token") ?? u.searchParams.get("accessToken");
224297
const apiKeyFromQuery = u.searchParams.get("api_key") ?? u.searchParams.get("apiKey");
298+
const baseUrlFromQuery = u.searchParams.get("base_url") ?? u.searchParams.get("baseUrl");
299+
const consoleSiteFromQuery =
300+
u.searchParams.get("console_site") ?? u.searchParams.get("consoleSite");
301+
const consoleRegionFromQuery =
302+
u.searchParams.get("console_region") ?? u.searchParams.get("consoleRegion");
303+
const consoleSwitchAgentFromQuery =
304+
u.searchParams.get("console_switch_agent") ?? u.searchParams.get("consoleSwitchAgent");
305+
const workspaceIdFromQuery =
306+
u.searchParams.get("workspace_id") ?? u.searchParams.get("workspaceId");
307+
308+
const extras = {
309+
baseUrl: baseUrlFromQuery?.trim() || null,
310+
consoleSite: consoleSiteFromQuery?.trim() || null,
311+
consoleRegion: consoleRegionFromQuery?.trim() || null,
312+
consoleSwitchAgent: consoleSwitchAgentFromQuery?.trim() || null,
313+
workspaceId: workspaceIdFromQuery?.trim() || null,
314+
};
225315

226316
const m = req.method ?? "GET";
227317
if (m !== "POST" && m !== "PUT" && m !== "PATCH") {
228318
return {
229319
accessToken: accessTokenFromQuery?.trim() || null,
230320
apiKey: apiKeyFromQuery?.trim() || null,
321+
...extras,
231322
};
232323
}
233324

@@ -239,12 +330,24 @@ async function extractCredentialsFromRequest(
239330
return {
240331
accessToken: accessTokenFromQuery?.trim() || null,
241332
apiKey: apiKeyFromQuery?.trim() || null,
333+
...extras,
242334
};
243335
}
244336

245337
const accessToken = accessTokenFromQuery?.trim() || parseAccessTokenFromRawBody(raw, contentType);
246338
const apiKey = apiKeyFromQuery?.trim() || parseApiKeyFromRawBody(raw, contentType);
247-
return { accessToken, apiKey };
339+
340+
const bodyExtras = parseExtrasFromRawBody(raw, contentType);
341+
342+
return {
343+
accessToken,
344+
apiKey,
345+
baseUrl: extras.baseUrl || bodyExtras.baseUrl,
346+
consoleSite: extras.consoleSite || bodyExtras.consoleSite,
347+
consoleRegion: extras.consoleRegion || bodyExtras.consoleRegion,
348+
consoleSwitchAgent: extras.consoleSwitchAgent || bodyExtras.consoleSwitchAgent,
349+
workspaceId: extras.workspaceId || bodyExtras.workspaceId,
350+
};
248351
}
249352

250353
function listenServerOnFreeLocalPort(server: http.Server): Promise<number> {
@@ -276,9 +379,69 @@ function openInBrowser(url: string): Promise<void> {
276379
});
277380
}
278381

382+
const RETRY_DELAY_BASE_MS = 500;
383+
384+
function canRetry(err: unknown): boolean {
385+
if (err instanceof BailianError) {
386+
if (err.exitCode === ExitCode.NETWORK || err.exitCode === ExitCode.TIMEOUT) return true;
387+
const status = err.api?.httpStatus;
388+
return status === 401 || (status !== undefined && status >= 500);
389+
}
390+
if (err instanceof Error) {
391+
return (
392+
err.name === "AbortError" ||
393+
err.name === "TimeoutError" ||
394+
err.message.includes("timed out") ||
395+
err.message === "fetch failed"
396+
);
397+
}
398+
return false;
399+
}
400+
401+
export async function validateAndPersistApiKey(
402+
config: Config,
403+
key: string,
404+
baseUrl: string,
405+
): Promise<void> {
406+
process.stderr.write("Testing key... ");
407+
const testConfig = { ...config, apiKey: key, baseUrl };
408+
const requestOpts = {
409+
url: chatEndpoint(testConfig.baseUrl),
410+
method: "POST",
411+
timeout: Math.min(config.timeout, 30),
412+
body: {
413+
model: "qwen3.7-max",
414+
messages: [{ role: "user", content: "hi" }],
415+
max_tokens: 1,
416+
},
417+
};
418+
419+
for (let attempt = 1; attempt <= 3; attempt++) {
420+
try {
421+
await requestJson<unknown>(testConfig, requestOpts);
422+
break;
423+
} catch (err) {
424+
if (attempt >= 3 || !canRetry(err)) {
425+
process.stderr.write("Failed\n");
426+
throw new BailianError("API key validation failed", ExitCode.AUTH, "Invalid API key.", {
427+
cause: err,
428+
});
429+
}
430+
const delayMs = RETRY_DELAY_BASE_MS * 2 ** (attempt - 1);
431+
await new Promise((resolve) => setTimeout(resolve, delayMs));
432+
}
433+
}
434+
435+
process.stderr.write("Valid\n");
436+
const existing = readConfigFile() as Record<string, unknown>;
437+
existing.api_key = key;
438+
await writeConfigFile(existing);
439+
}
440+
279441
export async function runConsoleLogin(
280442
consoleOrigin: string,
281-
opts?: { needApiKey?: boolean; onApiKey?: (key: string) => Promise<void> },
443+
config: Config,
444+
opts?: { needApiKey?: boolean },
282445
): Promise<void> {
283446
const state = randomBytes(16).toString("hex");
284447
let callbackError: unknown;
@@ -301,18 +464,35 @@ export async function runConsoleLogin(
301464
return;
302465
}
303466

304-
const { accessToken, apiKey } = await extractCredentialsFromRequest(req);
467+
const {
468+
accessToken,
469+
apiKey,
470+
baseUrl,
471+
consoleSite,
472+
consoleRegion,
473+
consoleSwitchAgent,
474+
workspaceId,
475+
} = await extractCredentialsFromRequest(req);
476+
477+
const hasConfig =
478+
accessToken || baseUrl || consoleSite || consoleRegion || consoleSwitchAgent || workspaceId;
305479

306-
if (accessToken || apiKey) {
480+
if (hasConfig || apiKey) {
307481
try {
308-
if (accessToken) {
482+
if (hasConfig) {
309483
const existing = readConfigFile() as Record<string, unknown>;
310-
existing.access_token = accessToken;
484+
if (accessToken) existing.access_token = accessToken;
485+
if (baseUrl) existing.base_url = baseUrl;
486+
if (consoleSite) existing.console_site = consoleSite;
487+
if (consoleRegion) existing.console_region = consoleRegion;
488+
if (consoleSwitchAgent) existing.console_switch_agent = Number(consoleSwitchAgent);
489+
if (workspaceId) existing.workspace_id = workspaceId;
311490
await writeConfigFile(existing);
312-
process.stderr.write(`access_token saved to ${getConfigPath()}\n`);
491+
process.stderr.write(`Config saved to ${getConfigPath()}\n`);
313492
}
314-
if (apiKey && opts?.onApiKey) {
315-
await opts.onApiKey(apiKey);
493+
if (apiKey) {
494+
const testBaseUrl = baseUrl || config.baseUrl;
495+
await validateAndPersistApiKey(config, apiKey, testBaseUrl);
316496
}
317497
} catch (err: unknown) {
318498
callbackError = err;
@@ -329,7 +509,7 @@ export async function runConsoleLogin(
329509
});
330510
res.end("OK\n");
331511

332-
if (accessToken || apiKey) {
512+
if (hasConfig || apiKey) {
333513
server.close();
334514
}
335515
} catch {

0 commit comments

Comments
 (0)