mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
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:
@@ -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";
|
||||
|
||||
@@ -13,3 +13,4 @@ export type {
|
||||
AuthState,
|
||||
CredentialSource,
|
||||
} from "./types.ts";
|
||||
export { generateCLIAccessToken, refreshAccessToken } from "./refresh-token.ts";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user