Merge remote-tracking branch 'origin/farm/260d1f69/fix-alibaba-cn-quota-reporting'
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed `omp usage invalidate` to discard stale OAuth and API-key usage snapshots, then force a cache-bypassing, per-provider serialized refresh so upgraded subscriptions do not silently retain pre-change quota data.
|
||||
- Fixed quota reporting and Cookie capture guidance for China (Beijing) Alibaba Token Plan credentials ([#8509](https://github.com/can1357/oh-my-pi/issues/8509)).
|
||||
|
||||
## [17.3.3] - 2026-08-14
|
||||
|
||||
|
||||
@@ -86,9 +86,13 @@ export async function loginAlibabaTokenPlan(options: OAuthController): Promise<s
|
||||
fetch: options.fetch,
|
||||
});
|
||||
|
||||
const cookieRequestHost =
|
||||
baseUrl === ALIBABA_TOKEN_PLAN_CN_BASE_URL ? "bailian-cs.console.aliyun.com" : "cs-data.qwencloud.com";
|
||||
const rawCookie = await options.onPrompt({
|
||||
message:
|
||||
"Optional quota reporting: open browser DevTools → Network, reload the Token Plan page, filter for api.json, and select the cs-data.qwencloud.com/data/api.json request whose api query ends in /tokenplan/personal/api/v2/usage. Copy Request Headers → Cookie, then paste the complete name=value; ... value here, or press Enter to skip.",
|
||||
baseUrl === ALIBABA_TOKEN_PLAN_CN_BASE_URL
|
||||
? "Optional quota reporting: open browser DevTools → Network, reload the Token Plan page, filter for api.json, and select the bailian-cs.console.aliyun.com/data/api.json request whose api query ends in /tokenplan/personal/api/v2/usage. Copy Request Headers → Cookie, then paste the complete name=value; ... value here, or press Enter to skip."
|
||||
: "Optional quota reporting: open browser DevTools → Network, reload the Token Plan page, filter for api.json, and select the cs-data.qwencloud.com/data/api.json request whose api query ends in /tokenplan/personal/api/v2/usage. Copy Request Headers → Cookie, then paste the complete name=value; ... value here, or press Enter to skip.",
|
||||
placeholder: "name=value; name=value; ...",
|
||||
allowEmpty: true,
|
||||
});
|
||||
@@ -107,7 +111,7 @@ export async function loginAlibabaTokenPlan(options: OAuthController): Promise<s
|
||||
})
|
||||
) {
|
||||
throw new AIError.ConfigurationError(
|
||||
"Invalid QwenCloud Cookie header. Copy the complete Cookie request header from the cs-data.qwencloud.com usage request, not a single cookie value.",
|
||||
`Invalid QwenCloud Cookie header. Copy the complete Cookie request header from the ${cookieRequestHost} usage request, not a single cookie value.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
||||
import { parseAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
|
||||
import {
|
||||
ALIBABA_TOKEN_PLAN_CN_BASE_URL,
|
||||
parseAlibabaTokenPlanCredential,
|
||||
} from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
|
||||
import type {
|
||||
CredentialRankingStrategy,
|
||||
UsageFetchContext,
|
||||
@@ -12,21 +15,45 @@ import { isRecord } from "../utils";
|
||||
import { HOUR_MS, parsePositiveTimestamp, WEEK_MS } from "./shared";
|
||||
|
||||
const PROVIDER = "alibaba-token-plan";
|
||||
const CONSOLE_ORIGIN = "https://home.qwencloud.com";
|
||||
const DASHBOARD_URL = `${CONSOLE_ORIGIN}/billing/subscription/token-plan-individual`;
|
||||
const USER_INFO_URL = `${CONSOLE_ORIGIN}/tool/user/info.json`;
|
||||
const GATEWAY_ACTION = "IntlBroadScopeAspnGateway";
|
||||
const USAGE_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage";
|
||||
const USAGE_URL = `https://cs-data.qwencloud.com/data/api.json?product=sfm_bailian&action=${GATEWAY_ACTION}&api=${encodeURIComponent(USAGE_API)}`;
|
||||
const BROWSER_USER_AGENT =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
|
||||
const CONSOLE_CORNERSTONE_PARAM = {
|
||||
const INTERNATIONAL_CONSOLE = {
|
||||
origin: "https://home.qwencloud.com",
|
||||
dashboardUrl: "https://home.qwencloud.com/billing/subscription/token-plan-individual",
|
||||
sessionUrl: "https://home.qwencloud.com/tool/user/info.json",
|
||||
gatewayAction: "IntlBroadScopeAspnGateway",
|
||||
region: "ap-southeast-1",
|
||||
usageUrl: `https://cs-data.qwencloud.com/data/api.json?product=sfm_bailian&action=IntlBroadScopeAspnGateway&api=${encodeURIComponent(USAGE_API)}`,
|
||||
cornerstoneParam: {
|
||||
domain: "home.qwencloud.com",
|
||||
consoleSite: "QWENCLOUD",
|
||||
console: "ONE_CONSOLE",
|
||||
xsp_lang: "en-US",
|
||||
protocol: "V2",
|
||||
productCode: "p_efm",
|
||||
},
|
||||
} as const;
|
||||
const CHINA_CONSOLE = {
|
||||
origin: "https://bailian.console.aliyun.com",
|
||||
dashboardUrl: "https://bailian.console.aliyun.com/cn-beijing?tab=plan",
|
||||
sessionUrl: "https://bailian.console.aliyun.com/cn-beijing?tab=plan",
|
||||
gatewayAction: "BroadScopeAspnGateway",
|
||||
region: "cn-beijing",
|
||||
usageUrl: `https://bailian-cs.console.aliyun.com/data/api.json?action=BroadScopeAspnGateway&product=sfm_bailian&api=${encodeURIComponent(USAGE_API)}`,
|
||||
cornerstoneParam: {
|
||||
feURL: "https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/token-plan/personal",
|
||||
protocol: "V2",
|
||||
console: "ONE_CONSOLE",
|
||||
productCode: "p_efm",
|
||||
switchAgent: 12608464,
|
||||
switchUserType: 3,
|
||||
domain: "bailian.console.aliyun.com",
|
||||
consoleSite: "BAILIAN_ALIYUN",
|
||||
userNickName: "",
|
||||
userPrincipalName: "",
|
||||
xsp_lang: "zh-CN",
|
||||
},
|
||||
} as const;
|
||||
|
||||
function extractCookieValue(header: string, name: string): string | undefined {
|
||||
@@ -102,35 +129,56 @@ async function fetchAlibabaTokenPlanUsage(
|
||||
const credential = parseAlibabaTokenPlanCredential(params.credential.apiKey);
|
||||
if (!credential?.cookie) return null;
|
||||
const cookie = credential.cookie;
|
||||
const isChina = credential.baseUrl === ALIBABA_TOKEN_PLAN_CN_BASE_URL;
|
||||
const consoleConfig = isChina ? CHINA_CONSOLE : INTERNATIONAL_CONSOLE;
|
||||
|
||||
try {
|
||||
const userResponse = await ctx.fetch(USER_INFO_URL, {
|
||||
const sessionResponse = await ctx.fetch(consoleConfig.sessionUrl, {
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
Accept: isChina
|
||||
? "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8"
|
||||
: "application/json, text/plain, */*",
|
||||
Cookie: cookie,
|
||||
Referer: `${CONSOLE_ORIGIN}/`,
|
||||
Referer: `${consoleConfig.origin}/`,
|
||||
"User-Agent": BROWSER_USER_AGENT,
|
||||
},
|
||||
redirect: "manual",
|
||||
signal: params.signal,
|
||||
});
|
||||
if (!userResponse.ok) {
|
||||
ctx.logger?.warn("QwenCloud session lookup failed", { provider: PROVIDER, status: userResponse.status });
|
||||
if (!sessionResponse.ok) {
|
||||
ctx.logger?.warn("Alibaba Token Plan session lookup failed", {
|
||||
provider: PROVIDER,
|
||||
status: sessionResponse.status,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
const userPayload: unknown = await userResponse.json();
|
||||
|
||||
let secToken: string | undefined;
|
||||
let accountId: string | undefined;
|
||||
if (isChina) {
|
||||
const html = await sessionResponse.text();
|
||||
secToken = /\bSEC_TOKEN\s*:\s*"([^"]+)"/.exec(html)?.[1];
|
||||
if (!secToken) {
|
||||
ctx.logger?.warn("Alibaba Token Plan China session response invalid", { provider: PROVIDER });
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
const userPayload: unknown = await sessionResponse.json();
|
||||
if (!isRecord(userPayload) || !isRecord(userPayload.data) || typeof userPayload.data.secToken !== "string") {
|
||||
ctx.logger?.warn("QwenCloud session response invalid", { provider: PROVIDER });
|
||||
return null;
|
||||
}
|
||||
const secToken = userPayload.data.secToken;
|
||||
secToken = userPayload.data.secToken;
|
||||
accountId = accountIdFromUserData(userPayload.data);
|
||||
}
|
||||
|
||||
const csrf = extractCookieValue(cookie, "login_aliyunid_csrf") ?? extractCookieValue(cookie, "csrf");
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Cookie: cookie,
|
||||
Origin: CONSOLE_ORIGIN,
|
||||
Referer: DASHBOARD_URL,
|
||||
Origin: consoleConfig.origin,
|
||||
Referer: consoleConfig.dashboardUrl,
|
||||
"User-Agent": BROWSER_USER_AGENT,
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
};
|
||||
@@ -140,16 +188,21 @@ async function fetchAlibabaTokenPlanUsage(
|
||||
}
|
||||
const body = new URLSearchParams({
|
||||
product: "sfm_bailian",
|
||||
action: GATEWAY_ACTION,
|
||||
region: "ap-southeast-1",
|
||||
action: consoleConfig.gatewayAction,
|
||||
region: consoleConfig.region,
|
||||
sec_token: secToken,
|
||||
params: JSON.stringify({
|
||||
Api: USAGE_API,
|
||||
Data: { cornerstoneParam: CONSOLE_CORNERSTONE_PARAM },
|
||||
Data: {
|
||||
cornerstoneParam: {
|
||||
...(isChina ? { feTraceId: crypto.randomUUID() } : {}),
|
||||
...consoleConfig.cornerstoneParam,
|
||||
},
|
||||
},
|
||||
V: "1.0",
|
||||
}),
|
||||
});
|
||||
const usageResponse = await ctx.fetch(USAGE_URL, {
|
||||
const usageResponse = await ctx.fetch(consoleConfig.usageUrl, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
@@ -157,16 +210,18 @@ async function fetchAlibabaTokenPlanUsage(
|
||||
signal: params.signal,
|
||||
});
|
||||
if (!usageResponse.ok) {
|
||||
ctx.logger?.warn("QwenCloud usage fetch failed", { provider: PROVIDER, status: usageResponse.status });
|
||||
ctx.logger?.warn("Alibaba Token Plan usage fetch failed", {
|
||||
provider: PROVIDER,
|
||||
status: usageResponse.status,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
const payload: unknown = await usageResponse.json();
|
||||
if (!isRecord(payload) || payload.successResponse === false || !isRecord(payload.data)) {
|
||||
ctx.logger?.warn("QwenCloud usage response invalid", { provider: PROVIDER });
|
||||
ctx.logger?.warn("Alibaba Token Plan usage response invalid", { provider: PROVIDER });
|
||||
return null;
|
||||
}
|
||||
const responseData = unwrapGatewayData(payload.data);
|
||||
const accountId = accountIdFromUserData(userPayload.data);
|
||||
const limits = [
|
||||
buildLimit(
|
||||
"5h",
|
||||
@@ -190,10 +245,10 @@ async function fetchAlibabaTokenPlanUsage(
|
||||
provider: PROVIDER,
|
||||
fetchedAt: Date.now(),
|
||||
limits,
|
||||
metadata: { source: "qwencloud-console", ...(accountId ? { accountId } : {}) },
|
||||
metadata: { source: isChina ? "bailian-console" : "qwencloud-console", ...(accountId ? { accountId } : {}) },
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.logger?.warn("QwenCloud usage request failed", {
|
||||
ctx.logger?.warn("Alibaba Token Plan usage request failed", {
|
||||
provider: PROVIDER,
|
||||
error: error instanceof Error ? error.name : "unknown",
|
||||
});
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
alibabaTokenPlanRankingStrategy,
|
||||
alibabaTokenPlanUsageProvider,
|
||||
} from "@oh-my-pi/pi-ai/usage/alibaba-token-plan";
|
||||
import { serializeAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
|
||||
import {
|
||||
ALIBABA_TOKEN_PLAN_CN_BASE_URL,
|
||||
serializeAlibabaTokenPlanCredential,
|
||||
} from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
|
||||
|
||||
function params(apiKey: string): UsageFetchParams {
|
||||
return {
|
||||
@@ -102,6 +105,84 @@ describe("QwenCloud Token Plan opt-in usage", () => {
|
||||
expect(windows.secondary?.id).toBe("credits:7d");
|
||||
});
|
||||
|
||||
test("fetches China quota through the Beijing console gateway", async () => {
|
||||
const requests: { url: string; init?: RequestInit }[] = [];
|
||||
const fetchMock: FetchImpl = (input, init) => {
|
||||
requests.push({ url: String(input), init });
|
||||
if (requests.length === 1) {
|
||||
return Promise.resolve(
|
||||
new Response('<script>window.ALIYUN_CONSOLE_CONFIG = { SEC_TOKEN: "cn-sec-token" };</script>'),
|
||||
);
|
||||
}
|
||||
return Promise.resolve(
|
||||
Response.json({
|
||||
code: "200",
|
||||
data: {
|
||||
DataV2: {
|
||||
data: {
|
||||
data: {
|
||||
per1WeekPercentage: 0.7913113,
|
||||
per1WeekResetTime: 1_786_716_480_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
successResponse: true,
|
||||
}),
|
||||
);
|
||||
};
|
||||
const cookie = "login_aliyunid_csrf=cn-csrf; aliyun_lang=zh";
|
||||
const credential = serializeAlibabaTokenPlanCredential("sk-sp-beijing", cookie, ALIBABA_TOKEN_PLAN_CN_BASE_URL);
|
||||
|
||||
const report = await alibabaTokenPlanUsageProvider.fetchUsage(params(credential), { fetch: fetchMock });
|
||||
|
||||
expect(requests).toHaveLength(2);
|
||||
expect(requests[0]?.url).toBe("https://bailian.console.aliyun.com/cn-beijing?tab=plan");
|
||||
expect(new Headers(requests[0]?.init?.headers).get("Cookie")).toBe(cookie);
|
||||
expect(requests[1]?.url).toBe(
|
||||
"https://bailian-cs.console.aliyun.com/data/api.json?action=BroadScopeAspnGateway&product=sfm_bailian&api=zeldaHttp.apikeyMgr.%2Ftokenplan%2Fpersonal%2Fapi%2Fv2%2Fusage",
|
||||
);
|
||||
const usageHeaders = new Headers(requests[1]?.init?.headers);
|
||||
expect(usageHeaders.get("Origin")).toBe("https://bailian.console.aliyun.com");
|
||||
expect(usageHeaders.get("Referer")).toBe("https://bailian.console.aliyun.com/cn-beijing?tab=plan");
|
||||
const body = new URLSearchParams(String(requests[1]?.init?.body));
|
||||
expect(body.get("action")).toBe("BroadScopeAspnGateway");
|
||||
expect(body.get("region")).toBe("cn-beijing");
|
||||
expect(body.get("sec_token")).toBe("cn-sec-token");
|
||||
const gatewayParams: unknown = JSON.parse(body.get("params") ?? "null");
|
||||
expect(gatewayParams).toMatchObject({
|
||||
Api: "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage",
|
||||
Data: {
|
||||
cornerstoneParam: {
|
||||
feTraceId: expect.any(String),
|
||||
feURL: "https://bailian.console.aliyun.com/cn-beijing?tab=plan#/efm/subscription/token-plan/personal",
|
||||
protocol: "V2",
|
||||
console: "ONE_CONSOLE",
|
||||
productCode: "p_efm",
|
||||
switchAgent: 12608464,
|
||||
switchUserType: 3,
|
||||
domain: "bailian.console.aliyun.com",
|
||||
consoleSite: "BAILIAN_ALIYUN",
|
||||
userNickName: "",
|
||||
userPrincipalName: "",
|
||||
xsp_lang: "zh-CN",
|
||||
},
|
||||
},
|
||||
V: "1.0",
|
||||
});
|
||||
expect(report).toMatchObject({
|
||||
provider: "alibaba-token-plan",
|
||||
limits: [
|
||||
{
|
||||
id: "credits:7d",
|
||||
window: { id: "7d", durationMs: 604_800_000, resetsAt: 1_786_716_480_000 },
|
||||
amount: { usedFraction: 0.7913113, unit: "percent" },
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(report?.limits).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("does not claim quota support for API-key-only credentials", async () => {
|
||||
let fetched = false;
|
||||
const fetchMock: FetchImpl = () => {
|
||||
|
||||
@@ -36,10 +36,17 @@ describe("QwenCloud Token Plan login", () => {
|
||||
test("China (Beijing) region validates against and routes inference to cn-beijing", async () => {
|
||||
const authRequests: { url: string; instructions?: string }[] = [];
|
||||
let requestedUrl = "";
|
||||
let cookiePrompt = "";
|
||||
const prompts = ["2", "sk-sp-beijing"];
|
||||
const credential = await loginAlibabaTokenPlan({
|
||||
onAuth: request => authRequests.push(request),
|
||||
onPrompt: async prompt => (prompt.allowEmpty ? "" : (prompts.shift() ?? "")),
|
||||
onPrompt: async prompt => {
|
||||
if (prompt.allowEmpty) {
|
||||
cookiePrompt = prompt.message;
|
||||
return "";
|
||||
}
|
||||
return prompts.shift() ?? "";
|
||||
},
|
||||
fetch: input => {
|
||||
requestedUrl = String(input);
|
||||
return Promise.resolve(Response.json({ data: [{ id: "qwen3.7-plus" }] }));
|
||||
@@ -48,6 +55,7 @@ describe("QwenCloud Token Plan login", () => {
|
||||
|
||||
expect(requestedUrl).toBe("https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/models");
|
||||
expect(authRequests[0]?.url).toBe("https://www.aliyun.com/benefit/scene/tokenplan");
|
||||
expect(cookiePrompt).toContain("bailian-cs.console.aliyun.com/data/api.json");
|
||||
expect(JSON.parse(credential)).toEqual({
|
||||
token: "sk-sp-beijing",
|
||||
baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
|
||||
|
||||
Reference in New Issue
Block a user