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>
This commit is contained in:
lisheng.lisheng
2026-07-10 00:00:57 +08:00
parent 8e3f8586b0
commit 0f23527bfc
4 changed files with 118 additions and 63 deletions
@@ -1,57 +1 @@
import { Client, REGIONS, type Identity, type Region, type Settings } from "bailian-cli-core";
const API_VERSION = "2026-02-10";
const API_ACTION = "GenerateCLIAccessToken";
const API_PATH = "/modelstudio/cli/generateAccessToken";
const MODEL_STUDIO_HOSTS: Partial<Record<Region, string>> = {
cn: "modelstudio.cn-beijing.aliyuncs.com",
intl: "modelstudio.ap-southeast-1.aliyuncs.com",
};
function resolveRegion(baseUrl: string): Region {
for (const [region, url] of Object.entries(REGIONS) as Array<[Region, string]>) {
if (baseUrl === url || baseUrl.startsWith(`${url}/`)) return region;
}
return "cn";
}
function modelStudioHost(baseUrl: string): string {
const region = resolveRegion(baseUrl);
return MODEL_STUDIO_HOSTS[region] ?? MODEL_STUDIO_HOSTS.cn!;
}
interface GenerateCLIAccessTokenResponse {
Success?: boolean;
Code?: string;
Message?: string;
Data?: Record<string, unknown>;
}
export async function generateCLIAccessToken(opts: {
identity: Identity;
settings: Settings;
baseUrl: string;
accessKeyId: string;
accessKeySecret: string;
}): Promise<any> {
const { identity, settings, baseUrl, accessKeyId, accessKeySecret } = opts;
const client = new Client({
identity,
settings,
baseUrl,
openApiCred: { accessKeyId, accessKeySecret, source: "flag" },
});
const host = modelStudioHost(baseUrl);
return client.openApiQueryJson<GenerateCLIAccessTokenResponse>({
host,
path: API_PATH,
action: API_ACTION,
version: API_VERSION,
method: "POST",
queryParams: {},
});
}
export { generateCLIAccessToken } from "bailian-cli-core";
+1
View File
@@ -13,3 +13,4 @@ export type {
AuthState,
CredentialSource,
} from "./types.ts";
export { generateCLIAccessToken, refreshAccessToken } from "./refresh-token.ts";
+89
View File
@@ -0,0 +1,89 @@
import { REGIONS, type Region } from "../config/schema.ts";
import type { Identity, Settings } from "../config/schema.ts";
import { readConfigFile, writeConfigFile } from "../config/loader.ts";
import { Client } from "../client/client.ts";
const API_VERSION = "2026-02-10";
const API_ACTION = "GenerateCLIAccessToken";
const API_PATH = "/modelstudio/cli/generateAccessToken";
const MODEL_STUDIO_HOSTS: Partial<Record<Region, string>> = {
cn: "modelstudio.cn-beijing.aliyuncs.com",
intl: "modelstudio.ap-southeast-1.aliyuncs.com",
};
function resolveRegion(baseUrl: string): Region {
for (const [region, url] of Object.entries(REGIONS) as Array<[Region, string]>) {
if (baseUrl === url || baseUrl.startsWith(`${url}/`)) return region;
}
return "cn";
}
function modelStudioHost(baseUrl: string): string {
const region = resolveRegion(baseUrl);
return MODEL_STUDIO_HOSTS[region] ?? MODEL_STUDIO_HOSTS.cn!;
}
export async function generateCLIAccessToken(opts: {
identity: Identity;
settings: Settings;
baseUrl: string;
accessKeyId: string;
accessKeySecret: string;
}): Promise<any> {
const { identity, settings, baseUrl, accessKeyId, accessKeySecret } = opts;
const client = new Client({
identity,
settings,
baseUrl,
openApiCred: { accessKeyId, accessKeySecret, source: "flag" },
});
const host = modelStudioHost(baseUrl);
return client.openApiQueryJson({
host,
path: API_PATH,
action: API_ACTION,
version: API_VERSION,
method: "POST",
queryParams: {},
});
}
/**
* Try to refresh the console access_token using stored AK/SK.
* Returns the new token on success, or null if AK/SK are not available.
*/
export async function refreshAccessToken(opts: {
identity: Identity;
settings: Settings;
baseUrl: string;
}): Promise<string | null> {
const config = readConfigFile();
const accessKeyId = config.access_key_id;
const accessKeySecret = config.access_key_secret;
if (!accessKeyId || !accessKeySecret) return null;
if (opts.settings.verbose) {
process.stderr.write("Refreshing access token...\n");
}
const resp = await generateCLIAccessToken({
identity: opts.identity,
settings: opts.settings,
baseUrl: opts.baseUrl,
accessKeyId,
accessKeySecret,
});
const token: string | undefined = resp.cliAccessToken;
if (!token) return null;
const existing = readConfigFile() as Record<string, unknown>;
existing.access_token = token;
await writeConfigFile(existing);
return token;
}
+27 -6
View File
@@ -7,6 +7,7 @@ import { buildAcsCanonicalQuery, signAcsRequest, type AcsQueryParams } from "./a
import { isLocalFile, resolveFileUrl } from "../files/upload.ts";
import { McpClient } from "./mcp.ts";
import { callConsoleGateway } from "../console/gateway.ts";
import { refreshAccessToken } from "../auth/refresh-token.ts";
import { maskToken } from "../utils/token.ts";
import { trackingHeaders } from "./headers.ts";
@@ -112,15 +113,35 @@ export class Client {
return new McpClient(this.http, url, this.deps.apiCred?.token);
}
console<T>(api: string, data: Record<string, unknown>): Promise<T> {
async console<T>(api: string, data: Record<string, unknown>): Promise<T> {
if (!this.deps.consoleCred) {
throw new BailianError("This command needs a console access token.", ExitCode.AUTH);
}
// region / site / switchAgent 已解析在 consoleCred 里,gateway 不再回读 config。
return callConsoleGateway(this.deps.consoleCred, this.deps.settings.timeout, {
api,
data,
}) as Promise<T>;
try {
return (await callConsoleGateway(this.deps.consoleCred, this.deps.settings.timeout, {
api,
data,
})) as T;
} catch (err) {
if (
!(err instanceof BailianError) ||
err.exitCode !== ExitCode.AUTH ||
!err.message.includes("not logged in")
) {
throw err;
}
const newToken = await refreshAccessToken({
identity: this.deps.identity,
settings: this.deps.settings,
baseUrl: this.deps.baseUrl,
});
if (!newToken) throw err;
return (await callConsoleGateway(
{ ...this.deps.consoleCred, token: newToken },
this.deps.settings.timeout,
{ api, data },
)) as T;
}
}
async openApiQueryJson<T extends OpenApiResponse>(opts: ClientOpenApiQueryOpts): Promise<T> {