Skip to content

Commit 0f23527

Browse files
lishengzxcclaude
andcommitted
feat(core): move generateCLIAccessToken to core and auto-refresh on NotLogined
Move the GenerateCLIAccessToken API call logic into core/auth/refresh-token.ts so it can be reused across packages. Add refreshAccessToken() which reads AK/SK from config, calls the API, and persists the new access_token. Client.console() now catches NotLogined errors and automatically retries with a refreshed token when AK/SK are available in config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8e3f858 commit 0f23527

4 files changed

Lines changed: 118 additions & 63 deletions

File tree

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1 @@
1-
import { Client, REGIONS, type Identity, type Region, type Settings } from "bailian-cli-core";
2-
3-
const API_VERSION = "2026-02-10";
4-
const API_ACTION = "GenerateCLIAccessToken";
5-
const API_PATH = "/modelstudio/cli/generateAccessToken";
6-
7-
const MODEL_STUDIO_HOSTS: Partial<Record<Region, string>> = {
8-
cn: "modelstudio.cn-beijing.aliyuncs.com",
9-
intl: "modelstudio.ap-southeast-1.aliyuncs.com",
10-
};
11-
12-
function resolveRegion(baseUrl: string): Region {
13-
for (const [region, url] of Object.entries(REGIONS) as Array<[Region, string]>) {
14-
if (baseUrl === url || baseUrl.startsWith(`${url}/`)) return region;
15-
}
16-
return "cn";
17-
}
18-
19-
function modelStudioHost(baseUrl: string): string {
20-
const region = resolveRegion(baseUrl);
21-
return MODEL_STUDIO_HOSTS[region] ?? MODEL_STUDIO_HOSTS.cn!;
22-
}
23-
24-
interface GenerateCLIAccessTokenResponse {
25-
Success?: boolean;
26-
Code?: string;
27-
Message?: string;
28-
Data?: Record<string, unknown>;
29-
}
30-
31-
export async function generateCLIAccessToken(opts: {
32-
identity: Identity;
33-
settings: Settings;
34-
baseUrl: string;
35-
accessKeyId: string;
36-
accessKeySecret: string;
37-
}): Promise<any> {
38-
const { identity, settings, baseUrl, accessKeyId, accessKeySecret } = opts;
39-
40-
const client = new Client({
41-
identity,
42-
settings,
43-
baseUrl,
44-
openApiCred: { accessKeyId, accessKeySecret, source: "flag" },
45-
});
46-
47-
const host = modelStudioHost(baseUrl);
48-
49-
return client.openApiQueryJson<GenerateCLIAccessTokenResponse>({
50-
host,
51-
path: API_PATH,
52-
action: API_ACTION,
53-
version: API_VERSION,
54-
method: "POST",
55-
queryParams: {},
56-
});
57-
}
1+
export { generateCLIAccessToken } from "bailian-cli-core";

packages/core/src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export type {
1313
AuthState,
1414
CredentialSource,
1515
} from "./types.ts";
16+
export { generateCLIAccessToken, refreshAccessToken } from "./refresh-token.ts";
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { REGIONS, type Region } from "../config/schema.ts";
2+
import type { Identity, Settings } from "../config/schema.ts";
3+
import { readConfigFile, writeConfigFile } from "../config/loader.ts";
4+
import { Client } from "../client/client.ts";
5+
6+
const API_VERSION = "2026-02-10";
7+
const API_ACTION = "GenerateCLIAccessToken";
8+
const API_PATH = "/modelstudio/cli/generateAccessToken";
9+
10+
const MODEL_STUDIO_HOSTS: Partial<Record<Region, string>> = {
11+
cn: "modelstudio.cn-beijing.aliyuncs.com",
12+
intl: "modelstudio.ap-southeast-1.aliyuncs.com",
13+
};
14+
15+
function resolveRegion(baseUrl: string): Region {
16+
for (const [region, url] of Object.entries(REGIONS) as Array<[Region, string]>) {
17+
if (baseUrl === url || baseUrl.startsWith(`${url}/`)) return region;
18+
}
19+
return "cn";
20+
}
21+
22+
function modelStudioHost(baseUrl: string): string {
23+
const region = resolveRegion(baseUrl);
24+
return MODEL_STUDIO_HOSTS[region] ?? MODEL_STUDIO_HOSTS.cn!;
25+
}
26+
27+
export async function generateCLIAccessToken(opts: {
28+
identity: Identity;
29+
settings: Settings;
30+
baseUrl: string;
31+
accessKeyId: string;
32+
accessKeySecret: string;
33+
}): Promise<any> {
34+
const { identity, settings, baseUrl, accessKeyId, accessKeySecret } = opts;
35+
36+
const client = new Client({
37+
identity,
38+
settings,
39+
baseUrl,
40+
openApiCred: { accessKeyId, accessKeySecret, source: "flag" },
41+
});
42+
43+
const host = modelStudioHost(baseUrl);
44+
45+
return client.openApiQueryJson({
46+
host,
47+
path: API_PATH,
48+
action: API_ACTION,
49+
version: API_VERSION,
50+
method: "POST",
51+
queryParams: {},
52+
});
53+
}
54+
55+
/**
56+
* Try to refresh the console access_token using stored AK/SK.
57+
* Returns the new token on success, or null if AK/SK are not available.
58+
*/
59+
export async function refreshAccessToken(opts: {
60+
identity: Identity;
61+
settings: Settings;
62+
baseUrl: string;
63+
}): Promise<string | null> {
64+
const config = readConfigFile();
65+
const accessKeyId = config.access_key_id;
66+
const accessKeySecret = config.access_key_secret;
67+
if (!accessKeyId || !accessKeySecret) return null;
68+
69+
if (opts.settings.verbose) {
70+
process.stderr.write("Refreshing access token...\n");
71+
}
72+
73+
const resp = await generateCLIAccessToken({
74+
identity: opts.identity,
75+
settings: opts.settings,
76+
baseUrl: opts.baseUrl,
77+
accessKeyId,
78+
accessKeySecret,
79+
});
80+
81+
const token: string | undefined = resp.cliAccessToken;
82+
if (!token) return null;
83+
84+
const existing = readConfigFile() as Record<string, unknown>;
85+
existing.access_token = token;
86+
await writeConfigFile(existing);
87+
88+
return token;
89+
}

packages/core/src/client/client.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { buildAcsCanonicalQuery, signAcsRequest, type AcsQueryParams } from "./a
77
import { isLocalFile, resolveFileUrl } from "../files/upload.ts";
88
import { McpClient } from "./mcp.ts";
99
import { callConsoleGateway } from "../console/gateway.ts";
10+
import { refreshAccessToken } from "../auth/refresh-token.ts";
1011
import { maskToken } from "../utils/token.ts";
1112
import { trackingHeaders } from "./headers.ts";
1213

@@ -112,15 +113,35 @@ export class Client {
112113
return new McpClient(this.http, url, this.deps.apiCred?.token);
113114
}
114115

115-
console<T>(api: string, data: Record<string, unknown>): Promise<T> {
116+
async console<T>(api: string, data: Record<string, unknown>): Promise<T> {
116117
if (!this.deps.consoleCred) {
117118
throw new BailianError("This command needs a console access token.", ExitCode.AUTH);
118119
}
119-
// region / site / switchAgent 已解析在 consoleCred 里,gateway 不再回读 config。
120-
return callConsoleGateway(this.deps.consoleCred, this.deps.settings.timeout, {
121-
api,
122-
data,
123-
}) as Promise<T>;
120+
try {
121+
return (await callConsoleGateway(this.deps.consoleCred, this.deps.settings.timeout, {
122+
api,
123+
data,
124+
})) as T;
125+
} catch (err) {
126+
if (
127+
!(err instanceof BailianError) ||
128+
err.exitCode !== ExitCode.AUTH ||
129+
!err.message.includes("not logged in")
130+
) {
131+
throw err;
132+
}
133+
const newToken = await refreshAccessToken({
134+
identity: this.deps.identity,
135+
settings: this.deps.settings,
136+
baseUrl: this.deps.baseUrl,
137+
});
138+
if (!newToken) throw err;
139+
return (await callConsoleGateway(
140+
{ ...this.deps.consoleCred, token: newToken },
141+
this.deps.settings.timeout,
142+
{ api, data },
143+
)) as T;
144+
}
124145
}
125146

126147
async openApiQueryJson<T extends OpenApiResponse>(opts: ClientOpenApiQueryOpts): Promise<T> {

0 commit comments

Comments
 (0)