mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 12:22:34 +03:00
fix(usage): console-aware Token Plan guidance + subscription hint on bailian 401 (#12288)
* fix(usage): console-aware Token Plan guidance and subscription hint on bailian 401 The personal Token Plan is sold through two consoles with different portals, gateway hosts and login tickets. Two operator-facing messages ignored the split: - The quota guidance always said 'get the cookie at home.qwencloud.com', even for connections served by the Alibaba Model Studio console — following it verbatim produces a cookie the gateway rejects (console mismatch → BailianGateway.Login.NotLogined). The guidance now derives the console from the provider via resolveConsoleSite, matching what the fetcher will do with the pasted cookie. - Key validation mapped upstream 401 to a bare 'Invalid API key'. An expired Token Plan subscription produces the exact same upstream 401 (observed live 2026-09-01: subscription ended 08-23, the working key started failing), so the message now names the subscription as a cause worth checking. * test(providers): align the remaining bailian 401/403 message pins to prefix match search-provider-validation.test.ts pinned the exact 'Invalid API key' string for the bailian validator; the message now also names an expired Token Plan subscription. Same property asserted (401/403 => invalid), prefix match.
This commit is contained in:
committed by
GitHub
parent
9d04995950
commit
accdfa9f33
@@ -11,6 +11,7 @@
|
||||
|
||||
import {
|
||||
fetchQwenTokenPlanQuota,
|
||||
resolveConsoleSite,
|
||||
QWEN_TOKEN_PLAN_WINDOW_5H,
|
||||
QWEN_TOKEN_PLAN_WINDOW_WEEKLY,
|
||||
type QwenTokenPlanQuota,
|
||||
@@ -54,13 +55,26 @@ export async function getQwenTokenPlanUsage(
|
||||
});
|
||||
|
||||
if (!quota) {
|
||||
// The same plan is sold through two consoles with different portals, gateway
|
||||
// hosts and login tickets — instructions for the wrong console produce a cookie
|
||||
// the gateway rejects (console mismatch → NotLogined). With no cookie stored the
|
||||
// console is inferred from the provider id, same rule the fetcher applies.
|
||||
const site = resolveConsoleSite("", provider);
|
||||
const guide =
|
||||
site.consoleSite === "ALIYUN"
|
||||
? "Get it at modelstudio.console.alibabacloud.com (logged in): F12 › Network, " +
|
||||
"reload, filter by api.json, click a request to " +
|
||||
"bailian-singapore-cs.alibabacloud.com and copy the whole Cookie value from " +
|
||||
"Request Headers (it contains login_aliyunid_ticket)."
|
||||
: "Get it at home.qwencloud.com › Billing › Subscription (logged in): F12 › " +
|
||||
"Network, reload, filter by api.json, click a request to " +
|
||||
"cs-data.qwencloud.com and copy the whole Cookie value from Request Headers " +
|
||||
"(it contains login_qwencloud_ticket).";
|
||||
const brand = site.consoleSite === "ALIYUN" ? "Alibaba" : "Qwen";
|
||||
return {
|
||||
message:
|
||||
"Qwen Token Plan connected. Quota needs a console session cookie — the inference " +
|
||||
"API key cannot read it. Get it at home.qwencloud.com › Billing › Subscription " +
|
||||
"(logged in): F12 › Network, reload, filter by api.json, click a request to " +
|
||||
"cs-data.qwencloud.com and copy the whole Cookie value from Request Headers " +
|
||||
"(it contains login_qwencloud_ticket). Paste it into the connection's " +
|
||||
`${brand} Token Plan connected. Quota needs a console session cookie — the ` +
|
||||
`inference API key cannot read it. ${guide} Paste it into the connection's ` +
|
||||
"'Qwen / Model Studio console cookie' field, or set QWEN_CLOUD_COOKIE. " +
|
||||
"The cookie expires with the browser session — re-paste it when this message returns.",
|
||||
};
|
||||
|
||||
@@ -302,9 +302,16 @@ export async function validateBailianCodingPlanProvider({
|
||||
}),
|
||||
});
|
||||
|
||||
// 401/403 => invalid key
|
||||
// 401/403 => invalid key. An expired/lapsed Token Plan subscription yields the
|
||||
// exact same upstream 401 invalid_api_key (observed live 2026-09-01: subscription
|
||||
// ended 08-23, the previously working key started failing), so name it as a cause.
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
"Invalid API key — or the Token Plan subscription is expired/inactive; " +
|
||||
"check it in the Model Studio console",
|
||||
};
|
||||
}
|
||||
|
||||
// Non-auth 4xx (e.g., 400 bad request) means auth passed but request was malformed
|
||||
|
||||
@@ -432,7 +432,11 @@ test("validateProviderApiKey returns invalid for 401 response (bailian-coding-pl
|
||||
});
|
||||
|
||||
assert.equal(result.valid, false, "Should return invalid for 401");
|
||||
assert.equal(result.error, "Invalid API key", "Error should be 'Invalid API key'");
|
||||
assert.match(
|
||||
String(result.error),
|
||||
/^Invalid API key/,
|
||||
"Error should start with 'Invalid API key'"
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
@@ -457,7 +461,11 @@ test("validateProviderApiKey returns invalid for 403 response (bailian-coding-pl
|
||||
});
|
||||
|
||||
assert.equal(result.valid, false, "Should return invalid for 403");
|
||||
assert.equal(result.error, "Invalid API key", "Error should be 'Invalid API key'");
|
||||
assert.match(
|
||||
String(result.error),
|
||||
/^Invalid API key/,
|
||||
"Error should start with 'Invalid API key'"
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,9 @@ test("bailian-coding-plan validation rejects 401 as invalid key", async () => {
|
||||
});
|
||||
|
||||
assert.equal(result.valid, false);
|
||||
assert.equal(result.error, "Invalid API key");
|
||||
// Prefix match: the message now also names an expired Token Plan subscription,
|
||||
// which yields the identical upstream 401/403.
|
||||
assert.match(String(result.error), /^Invalid API key/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
@@ -155,7 +157,9 @@ test("bailian-coding-plan validation rejects 403 as invalid key", async () => {
|
||||
});
|
||||
|
||||
assert.equal(result.valid, false);
|
||||
assert.equal(result.error, "Invalid API key");
|
||||
// Prefix match: the message now also names an expired Token Plan subscription,
|
||||
// which yields the identical upstream 401/403.
|
||||
assert.match(String(result.error), /^Invalid API key/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
77
tests/unit/token-plan-console-aware-messages.test.ts
Normal file
77
tests/unit/token-plan-console-aware-messages.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* The personal Token Plan is sold through two consoles (QwenCloud and Alibaba Model
|
||||
* Studio) with different hosts, gateways and login tickets. Two operator-facing
|
||||
* messages ignored that split (both bit the operator in the 2026-08/09 audits):
|
||||
*
|
||||
* 1. The quota guidance always said "get the cookie at home.qwencloud.com", even for
|
||||
* connections served by the Alibaba console — whose cookie comes from
|
||||
* modelstudio.console.alibabacloud.com and carries login_aliyunid_ticket. Following
|
||||
* the instructions verbatim produced a cookie the gateway rejects (console
|
||||
* mismatch → BailianGateway.Login.NotLogined).
|
||||
* 2. Key validation mapped upstream 401 to a bare "Invalid API key". For Token Plan
|
||||
* keys, an expired/lapsed subscription produces the exact same upstream 401
|
||||
* (observed live 2026-09-01: subscription ended 08-23, key started failing), so
|
||||
* the message must point at the subscription as a cause worth checking.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { getQwenTokenPlanUsage } from "../../open-sse/services/usage/qwen-token-plan.ts";
|
||||
import { validateProviderApiKey } from "../../src/lib/providers/validation.ts";
|
||||
|
||||
test("cookie guidance points Alibaba-console connections at the Model Studio console", async () => {
|
||||
const result = await getQwenTokenPlanUsage(
|
||||
"conn-guidance-aliyun",
|
||||
"sk-sp-any",
|
||||
{},
|
||||
"bailian-coding-plan"
|
||||
);
|
||||
|
||||
assert.ok("message" in result, "no cookie stored → guidance message expected");
|
||||
const message = (result as { message: string }).message;
|
||||
assert.match(message, /modelstudio\.console\.alibabacloud\.com/);
|
||||
assert.match(message, /bailian-singapore-cs\.alibabacloud\.com/);
|
||||
assert.match(message, /login_aliyunid_ticket/);
|
||||
assert.doesNotMatch(
|
||||
message,
|
||||
/login_qwencloud_ticket/,
|
||||
"Alibaba guidance must not tell the operator to hunt for the QwenCloud ticket"
|
||||
);
|
||||
});
|
||||
|
||||
test("cookie guidance keeps the QwenCloud instructions for the QwenCloud console", async () => {
|
||||
const result = await getQwenTokenPlanUsage(
|
||||
"conn-guidance-qwen",
|
||||
"sk-sp-any",
|
||||
{},
|
||||
"qwen-cloud-token-plan"
|
||||
);
|
||||
|
||||
assert.ok("message" in result, "no cookie stored → guidance message expected");
|
||||
const message = (result as { message: string }).message;
|
||||
assert.match(message, /home\.qwencloud\.com/);
|
||||
assert.match(message, /cs-data\.qwencloud\.com/);
|
||||
assert.match(message, /login_qwencloud_ticket/);
|
||||
});
|
||||
|
||||
test("bailian 401 mentions the subscription as a possible cause", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () =>
|
||||
new Response(JSON.stringify({ error: { code: "invalid_api_key" } }), { status: 401 });
|
||||
try {
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "bailian-coding-plan",
|
||||
apiKey: "sk-sp-expired-subscription",
|
||||
});
|
||||
assert.equal(result.valid, false);
|
||||
assert.match(String(result.error), /Invalid API key/);
|
||||
assert.match(
|
||||
String(result.error),
|
||||
/subscription/i,
|
||||
"an expired Token Plan subscription yields the same upstream 401 — say so"
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user