Merge PR #6521: fix(ai): repair Alibaba Token Plan usage reporting (@eggpeat)

This commit is contained in:
can1357
2026-07-26 15:38:52 +02:00
6 changed files with 113 additions and 27 deletions
+1
View File
@@ -45,6 +45,7 @@
- Fixed every Claude (`anthropic-messages`) model on the `opencode-zen` provider failing with `401 Missing API key`: the gateway requires `x-api-key`, so `opencode-zen` now uses X-Api-Key auth like `opencode-go`/`umans` instead of bearer-only, and no longer sends the `context_management` field its Anthropic proxy rejects on thinking requests ([#6510](https://github.com/can1357/oh-my-pi/issues/6510)).
- Fixed Anthropic native server-tool blocks being dropped from persisted assistant turns, preserving signed web-search continuations in their original response order ([#6495](https://github.com/can1357/oh-my-pi/issues/6495))
- Fixed statusless provider capacity errors such as `no_capacity` and high-demand responses being treated as terminal instead of retryable. ([#6503](https://github.com/can1357/oh-my-pi/issues/6503))
- Fixed QwenCloud Token Plan quota reporting to call the current console usage RPC and document how to capture its optional Cookie during login.
## [17.1.1] - 2026-07-24
+20 -4
View File
@@ -9,7 +9,8 @@ const TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com
const loginApiKey = createApiKeyLogin({
providerLabel: "QwenCloud Token Plan",
authUrl: "https://home.qwencloud.com/billing/subscription/token-plan-individual",
instructions: "Subscribe to Token Plan Individual and copy its dedicated API key",
instructions:
"Subscribe to Token Plan Individual and copy its dedicated API key. Keep this page open; the next prompt explains how to enable optional quota reporting.",
promptMessage: "Paste your QwenCloud Token Plan API key",
placeholder: "sk-sp-...",
validation: {
@@ -24,15 +25,30 @@ export async function loginAlibabaTokenPlan(options: OAuthController): Promise<s
throw new AIError.OnPromptRequiredError("QwenCloud Token Plan");
}
const apiKey = await loginApiKey(options);
const cookie = await options.onPrompt({
const rawCookie = await options.onPrompt({
message:
"Paste the Cookie request header from home.qwencloud.com for optional quota reporting, or press Enter to skip",
placeholder: "login_aliyunid_csrf=...; ...",
"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,
});
const cookie = rawCookie
.trim()
.replace(/^Cookie:\s*/i, "")
.trim();
if (options.signal?.aborted) {
throw new AIError.LoginCancelledError();
}
if (
cookie &&
!cookie.split(";").some(segment => {
const separator = segment.indexOf("=");
return separator > 0 && Boolean(segment.slice(0, separator).trim() && segment.slice(separator + 1).trim());
})
) {
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.",
);
}
return serializeAlibabaTokenPlanCredential(apiKey, cookie);
}
+34 -6
View File
@@ -14,13 +14,21 @@ 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 USAGE_URL = `${CONSOLE_ORIGIN}/data/api.json?product=sfm_bailian&action=IntlBroadScopeAspnGateway`;
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 FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
const CONSOLE_CORNERSTONE_PARAM = {
domain: "home.qwencloud.com",
consoleSite: "QWENCLOUD",
console: "ONE_CONSOLE",
xsp_lang: "en-US",
protocol: "V2",
productCode: "p_efm",
} as const;
function extractCookieValue(header: string, name: string): string | undefined {
for (const segment of header.split(";")) {
@@ -32,6 +40,21 @@ function extractCookieValue(header: string, name: string): string | undefined {
return undefined;
}
function unwrapGatewayData(value: Record<string, unknown>): Record<string, unknown> {
let current = value;
if (typeof current.Data === "string") {
try {
const parsed: unknown = JSON.parse(current.Data);
if (isRecord(parsed)) current = parsed;
} catch {
return current;
}
}
if (isRecord(current.DataV2) && isRecord(current.DataV2.data)) current = current.DataV2.data;
if (isRecord(current.data)) current = current.data;
return current;
}
function parseResetTime(value: unknown): number | undefined {
const parsed = toNumber(value);
if (parsed === undefined || parsed <= 0) return undefined;
@@ -127,7 +150,11 @@ async function fetchAlibabaTokenPlanUsage(
action: GATEWAY_ACTION,
region: "ap-southeast-1",
sec_token: secToken,
params: JSON.stringify({ Api: USAGE_API, Data: {} }),
params: JSON.stringify({
Api: USAGE_API,
Data: { cornerstoneParam: CONSOLE_CORNERSTONE_PARAM },
V: "1.0",
}),
});
const usageResponse = await ctx.fetch(USAGE_URL, {
method: "POST",
@@ -145,22 +172,23 @@ async function fetchAlibabaTokenPlanUsage(
ctx.logger?.warn("QwenCloud usage response invalid", { provider: PROVIDER });
return null;
}
const responseData = unwrapGatewayData(payload.data);
const accountId = accountIdFromUserData(userPayload.data);
const limits = [
buildLimit(
"5h",
"5 Hour Credits",
FIVE_HOURS_MS,
parseUsedFraction(payload.data.per5HourPercentage),
parseResetTime(payload.data.per5HourResetTime),
parseUsedFraction(responseData.per5HourPercentage),
parseResetTime(responseData.per5HourResetTime),
accountId,
),
buildLimit(
"7d",
"7 Day Credits",
SEVEN_DAYS_MS,
parseUsedFraction(payload.data.per1WeekPercentage),
parseResetTime(payload.data.per1WeekResetTime),
parseUsedFraction(responseData.per1WeekPercentage),
parseResetTime(responseData.per1WeekResetTime),
accountId,
),
].filter((limit): limit is UsageLimit => limit !== undefined);
@@ -27,13 +27,17 @@ describe("QwenCloud Token Plan opt-in usage", () => {
}
return Promise.resolve(
Response.json({
code: "200",
successResponse: true,
data: {
per5HourPercentage: 0.25,
per5HourResetTime: 1_800_000_000_000,
per1WeekPercentage: 0.5,
per1WeekResetTime: 1_800_100_000_000,
DataV2: {
data: {
data: {
per5HourPercentage: 0.25,
per5HourResetTime: 1_800_000_000_000,
per1WeekPercentage: 0.5,
per1WeekResetTime: 1_800_100_000_000,
},
},
},
},
}),
);
@@ -48,7 +52,7 @@ describe("QwenCloud Token Plan opt-in usage", () => {
expect(new Headers(requests[0]?.init?.headers).get("Cookie")).toBe(cookie);
expect(requests[0]?.init?.redirect).toBe("manual");
expect(requests[1]?.url).toBe(
"https://home.qwencloud.com/data/api.json?product=sfm_bailian&action=IntlBroadScopeAspnGateway",
"https://cs-data.qwencloud.com/data/api.json?product=sfm_bailian&action=IntlBroadScopeAspnGateway&api=zeldaHttp.apikeyMgr.%2Ftokenplan%2Fpersonal%2Fapi%2Fv2%2Fusage",
);
const usageHeaders = new Headers(requests[1]?.init?.headers);
expect(usageHeaders.get("Cookie")).toBe(cookie);
@@ -59,8 +63,22 @@ describe("QwenCloud Token Plan opt-in usage", () => {
expect(usageHeaders.get("x-csrf-token")).toBe("csrf-token");
expect(requests[1]?.init?.redirect).toBe("manual");
const body = new URLSearchParams(String(requests[1]?.init?.body));
expect(body.get("sec_token")).toBe("sec-token");
expect(body.get("params")).toBe(
JSON.stringify({ Api: "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage", Data: {} }),
JSON.stringify({
Api: "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage",
Data: {
cornerstoneParam: {
domain: "home.qwencloud.com",
consoleSite: "QWENCLOUD",
console: "ONE_CONSOLE",
xsp_lang: "en-US",
protocol: "V2",
productCode: "p_efm",
},
},
V: "1.0",
}),
);
expect(report).toMatchObject({
provider: "alibaba-token-plan",
+22 -3
View File
@@ -23,7 +23,8 @@ describe("QwenCloud Token Plan login", () => {
expect(authRequests).toEqual([
{
url: "https://home.qwencloud.com/billing/subscription/token-plan-individual",
instructions: "Subscribe to Token Plan Individual and copy its dedicated API key",
instructions:
"Subscribe to Token Plan Individual and copy its dedicated API key. Keep this page open; the next prompt explains how to enable optional quota reporting.",
},
]);
expect(requestedUrl).toBe("https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/models");
@@ -31,12 +32,19 @@ describe("QwenCloud Token Plan login", () => {
});
test("stores an optional console Cookie while sending only the API key to inference", async () => {
const prompts = ["sk-sp-test", "session_id=test; login_aliyunid_csrf=csrf-token"];
let cookiePrompt = "";
const credential = await loginAlibabaTokenPlan({
onAuth: () => {},
onPrompt: async () => prompts.shift() ?? "",
onPrompt: async prompt => {
if (!prompt.allowEmpty) return "sk-sp-test";
cookiePrompt = prompt.message;
return "Cookie: session_id=test; login_aliyunid_csrf=csrf-token";
},
fetch: () => Promise.resolve(Response.json({ data: [{ id: "qwen3.7-plus" }] })),
});
expect(cookiePrompt).toContain("DevTools → Network");
expect(cookiePrompt).toContain("cs-data.qwencloud.com/data/api.json");
expect(cookiePrompt).toContain("Request Headers → Cookie");
expect(JSON.parse(credential)).toEqual({
token: "sk-sp-test",
cookie: "session_id=test; login_aliyunid_csrf=csrf-token",
@@ -52,6 +60,17 @@ describe("QwenCloud Token Plan login", () => {
expect(JSON.stringify(setup)).not.toContain("session_id=test");
});
test("rejects a single cookie value with actionable guidance", async () => {
const prompts = ["sk-sp-test", "5123456789012345"];
await expect(
loginAlibabaTokenPlan({
onAuth: () => {},
onPrompt: async () => prompts.shift() ?? "",
fetch: () => Promise.resolve(Response.json({ data: [{ id: "qwen3.7-plus" }] })),
}),
).rejects.toThrow("cs-data.qwencloud.com usage request");
});
test("rejects malformed compound credentials before inference setup", () => {
const model = getBundledModel<"openai-completions">("alibaba-token-plan", "qwen3.7-plus");
if (!model) throw new Error("expected bundled QwenCloud Token Plan model");
@@ -350,13 +350,17 @@ describe("AuthStorage usage cache: provider failure policy", () => {
return Promise.resolve(
usageCalls === 1
? Response.json({
code: "200",
successResponse: true,
data: {
per5HourPercentage: 0.25,
per5HourResetTime: 1_800_000_000_000,
per1WeekPercentage: 0.5,
per1WeekResetTime: 1_800_100_000_000,
DataV2: {
data: {
data: {
per5HourPercentage: 0.25,
per5HourResetTime: 1_800_000_000_000,
per1WeekPercentage: 0.5,
per1WeekResetTime: 1_800_100_000_000,
},
},
},
},
})
: Response.json({