fix(providers): add China (Beijing) region for alibaba-token-plan

The alibaba-token-plan provider hardcoded the international Singapore
endpoint (token-plan.ap-southeast-1.maas.aliyuncs.com) in the login flow,
wire credential, openai-shared resolver, and catalog, so region-locked
China (Beijing) Token Plan sk-sp- keys were rejected with 401
invalid_api_key with no way to reach token-plan.cn-beijing.maas.aliyuncs.com.

Mirror the alibaba-coding-plan pattern: login selects a region
(International / China (Beijing) / Custom), validates against that region's
/models endpoint, and stores the chosen base URL in the credential. The
openai-shared resolver and model discovery both honor the credential's
region, while International logins keep their existing bare-token form.

Fixes #6682
This commit is contained in:
roboomp
2026-07-26 07:07:58 +00:00
parent 667111575e
commit 8f6ffb3a10
9 changed files with 204 additions and 29 deletions
+4
View File
@@ -8,6 +8,10 @@
- Added `GET /v1/credentials/disabled` to the auth broker and `AuthBrokerClient.listDisabledCredentials`: disabled-credential tombstones (`DisabledCredentialSummary` — identity, verbatim disable cause, disable timestamp; never token material) so auto-disabled accounts stay visible to clients instead of silently vanishing from the snapshot. `AuthStorage.listDisabledCredentials` serves the same data locally from SQLite; clients of brokers predating the endpoint get an empty list (404 mapped, no error).
- Added `AuthStorage.revalidateCredentials()` and the optional `AuthCredentialStore.refreshSnapshot` hook: remote broker stores re-fetch `GET /v1/snapshot` on demand so callers pairing live per-credential data with stored identities (`omp usage`) never render against the up-to-an-hour-stale disk-cached snapshot; local SQLite stores are always current and only reload.
### Fixed
- Fixed the `alibaba-token-plan` login only supporting the international Singapore endpoint, which rejected China (Beijing) Token Plan `sk-sp-` keys with `401 invalid_api_key`. Login now selects a region (International / China (Beijing) / Custom), validates the key against that region's `/models` endpoint, and stores the chosen base URL in the credential so inference and discovery both target it ([#6682](https://github.com/can1357/oh-my-pi/issues/6682)).
## [17.1.3] - 2026-07-24
### Fixed
+1 -1
View File
@@ -73,7 +73,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
- **Xiaomi MiMo** (requires `XIAOMI_API_KEY`)
- **ZenMux** (requires `ZENMUX_API_KEY`)
- **Qwen Portal** (supports `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY`)
- **QwenCloud Token Plan** (supports `/login alibaba-token-plan`, `ALIBABA_TOKEN_PLAN_API_KEY`, or `BAILIAN_TOKEN_PLAN_API_KEY`; interactive login optionally stores a `home.qwencloud.com` Cookie request header for best-effort 5-hour and 7-day quota reporting)
- **QwenCloud Token Plan** (supports `/login alibaba-token-plan`, `ALIBABA_TOKEN_PLAN_API_KEY`, or `BAILIAN_TOKEN_PLAN_API_KEY`; interactive login first selects a region — International (Singapore, default), China (Beijing) for 百炼 Token Plan keys, or a custom base URL — since region keys are non-interchangeable, then optionally stores a `home.qwencloud.com` Cookie request header for best-effort 5-hour and 7-day quota reporting)
To enable quota reporting, sign in to the Token Plan dashboard, copy the `Cookie` request-header value from a `home.qwencloud.com` request in browser developer tools, and paste it at the second login prompt. Press Enter to skip; the Cookie is sensitive and session-lived, so rerun login when it expires.
- **Cloudflare AI Gateway** (requires `CLOUDFLARE_AI_GATEWAY_API_KEY` and provider-specific gateway base URL)
- **Ollama** (local OpenAI-compatible runtime; optional `OLLAMA_API_KEY`)
@@ -272,6 +272,7 @@ export function resolveOpenAIRequestSetup(
const credential = parseAlibabaTokenPlanCredential(rawApiKey);
if (!credential) throw new AIError.ConfigurationError("Invalid QwenCloud Token Plan credential");
apiKey = credential.token;
if (credential.baseUrl) baseUrl = credential.baseUrl;
}
if (options.alibabaCodingPlanAuth && model.provider === "alibaba-coding-plan") {
+84 -18
View File
@@ -1,29 +1,90 @@
import { serializeAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
import {
ALIBABA_TOKEN_PLAN_BASE_URL,
ALIBABA_TOKEN_PLAN_CN_BASE_URL,
serializeAlibabaTokenPlanCredential,
} from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
import * as AIError from "../error";
import { createApiKeyLogin } from "./api-key-login";
import { validateApiKeyAgainstModelsEndpoint } from "./api-key-validation";
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
import type { ProviderDefinition } from "./types";
const TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
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",
promptMessage: "Paste your QwenCloud Token Plan API key",
placeholder: "sk-sp-...",
validation: {
kind: "models-endpoint",
provider: "QwenCloud Token Plan",
modelsUrl: `${TOKEN_PLAN_BASE_URL}/models`,
},
});
const INTERNATIONAL_AUTH_URL = "https://home.qwencloud.com/billing/subscription/token-plan-individual";
const CHINA_AUTH_URL = "https://www.aliyun.com/benefit/scene/tokenplan";
/**
* Log in to the QwenCloud Token Plan provider.
*
* The Token Plan ships as two regionally separate products with
* non-interchangeable keys — International (Singapore) and China (Beijing) —
* so login first selects the region (or a custom base URL) before pasting the
* key, mirroring {@link loginAlibabaCodingPlan}. The chosen region is validated
* against its own `/models` endpoint and, when it diverges from the default
* international endpoint, stored in the credential so inference and discovery
* both target it (#6682).
*/
export async function loginAlibabaTokenPlan(options: OAuthController): Promise<string> {
if (!options.onPrompt) {
throw new AIError.OnPromptRequiredError("QwenCloud Token Plan");
}
const apiKey = await loginApiKey(options);
const endpointChoice = await options.onPrompt({
message:
"Select QwenCloud Token Plan region: 1=International (default), 2=China (Beijing), 3=Custom — enter 1, 2, or 3",
placeholder: "1",
});
if (options.signal?.aborted) {
throw new AIError.LoginCancelledError();
}
const choice = endpointChoice.trim();
let baseUrl: string;
let authUrl: string;
let instructions: string;
if (choice === "2") {
baseUrl = ALIBABA_TOKEN_PLAN_CN_BASE_URL;
authUrl = CHINA_AUTH_URL;
instructions = "Subscribe to the China (Beijing) 百炼 Token Plan and copy its dedicated API key";
} else if (choice === "3") {
const customUrl = await options.onPrompt({
message: "Enter custom Token Plan base URL",
placeholder: "https://token-plan.<region>.maas.aliyuncs.com/compatible-mode/v1",
});
const trimmedUrl = customUrl.trim().replace(/\/+$/, "");
if (!trimmedUrl) {
throw new AIError.ConfigurationError("Custom URL is required for option 3");
}
baseUrl = trimmedUrl;
authUrl = INTERNATIONAL_AUTH_URL;
instructions = "Copy your Token Plan API key for the custom endpoint";
} else {
baseUrl = ALIBABA_TOKEN_PLAN_BASE_URL;
authUrl = INTERNATIONAL_AUTH_URL;
instructions = "Subscribe to Token Plan Individual and copy its dedicated API key";
}
options.onAuth?.({ url: authUrl, instructions });
const apiKeyInput = await options.onPrompt({
message: "Paste your QwenCloud Token Plan API key",
placeholder: "sk-sp-...",
});
if (options.signal?.aborted) {
throw new AIError.LoginCancelledError();
}
const apiKey = apiKeyInput.trim();
if (!apiKey) {
throw new AIError.ApiKeyRequiredError();
}
options.onProgress?.("Validating API key...");
await validateApiKeyAgainstModelsEndpoint({
provider: "QwenCloud Token Plan",
apiKey,
modelsUrl: `${baseUrl}/models`,
signal: options.signal,
fetch: options.fetch,
});
const cookie = await options.onPrompt({
message:
"Paste the Cookie request header from home.qwencloud.com for optional quota reporting, or press Enter to skip",
@@ -33,7 +94,12 @@ export async function loginAlibabaTokenPlan(options: OAuthController): Promise<s
if (options.signal?.aborted) {
throw new AIError.LoginCancelledError();
}
return serializeAlibabaTokenPlanCredential(apiKey, cookie);
// International (default) logins keep their existing bare/cookie credential
// form; only a diverging region is persisted so it can override the catalog
// base URL at inference and discovery time.
const regionUrl = baseUrl === ALIBABA_TOKEN_PLAN_BASE_URL ? undefined : baseUrl;
return serializeAlibabaTokenPlanCredential(apiKey, cookie, regionUrl);
}
export const alibabaTokenPlanProvider = {
+51 -3
View File
@@ -5,13 +5,14 @@ import { getOAuthProviders } from "@oh-my-pi/pi-ai/registry/oauth";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
describe("QwenCloud Token Plan login", () => {
test("opens the Individual subscription page and validates without inference", async () => {
test("International (default) region opens Individual page and validates without inference", async () => {
const authRequests: { url: string; instructions?: string }[] = [];
let requestedUrl = "";
let authorization = "";
const prompts = ["1", " sk-sp-test "];
const apiKey = await loginAlibabaTokenPlan({
onAuth: request => authRequests.push(request),
onPrompt: async prompt => (prompt.allowEmpty ? "" : " sk-sp-test "),
onPrompt: async prompt => (prompt.allowEmpty ? "" : (prompts.shift() ?? "")),
fetch: (input, init) => {
requestedUrl = String(input);
authorization = new Headers(init?.headers).get("Authorization") ?? "";
@@ -19,6 +20,7 @@ describe("QwenCloud Token Plan login", () => {
},
});
// International (default) keeps the bare-token credential form.
expect(apiKey).toBe("sk-sp-test");
expect(authRequests).toEqual([
{
@@ -30,8 +32,54 @@ describe("QwenCloud Token Plan login", () => {
expect(authorization).toBe("Bearer sk-sp-test");
});
test("China (Beijing) region validates against and routes inference to cn-beijing", async () => {
const authRequests: { url: string; instructions?: string }[] = [];
let requestedUrl = "";
const prompts = ["2", "sk-sp-beijing"];
const credential = await loginAlibabaTokenPlan({
onAuth: request => authRequests.push(request),
onPrompt: async prompt => (prompt.allowEmpty ? "" : (prompts.shift() ?? "")),
fetch: input => {
requestedUrl = String(input);
return Promise.resolve(Response.json({ data: [{ id: "qwen3.7-plus" }] }));
},
});
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(JSON.parse(credential)).toEqual({
token: "sk-sp-beijing",
baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
});
const model = getBundledModel<"openai-completions">("alibaba-token-plan", "qwen3.7-plus");
if (!model) throw new Error("expected bundled QwenCloud Token Plan model");
const setup = resolveOpenAIRequestSetup(model, { apiKey: credential, messages: [] });
expect(setup.baseUrl).toBe("https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1");
expect(setup.headers.Authorization).toBe("Bearer sk-sp-beijing");
});
test("custom region is validated against and stored as its own base URL", async () => {
let requestedUrl = "";
const prompts = ["3", "https://token-plan.example.com/v1/", "sk-sp-custom"];
const credential = await loginAlibabaTokenPlan({
onAuth: () => {},
onPrompt: async prompt => (prompt.allowEmpty ? "" : (prompts.shift() ?? "")),
fetch: input => {
requestedUrl = String(input);
return Promise.resolve(Response.json({ data: [] }));
},
});
expect(requestedUrl).toBe("https://token-plan.example.com/v1/models");
expect(JSON.parse(credential)).toEqual({
token: "sk-sp-custom",
baseUrl: "https://token-plan.example.com/v1",
});
});
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"];
const prompts = ["1", "sk-sp-test", "session_id=test; login_aliyunid_csrf=csrf-token"];
const credential = await loginAlibabaTokenPlan({
onAuth: () => {},
onPrompt: async () => prompts.shift() ?? "",
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed `alibaba-token-plan` locking out China (Beijing) 百炼 Token Plan subscribers: the provider hardcoded the international Singapore endpoint, so Beijing-issued `sk-sp-` keys got `401 invalid_api_key`. The wire credential now carries an optional region base URL, and model discovery targets the credential's region ([#6682](https://github.com/can1357/oh-my-pi/issues/6682)).
## [17.1.3] - 2026-07-24
### Fixed
@@ -17,7 +17,7 @@ import type { ModelManagerOptions } from "../model-manager";
import { getBundledModels } from "../models";
import type { Api, FetchImpl, Model, ModelSpec, OpenAICompat, Provider, ThinkingConfig } from "../types";
import { discoveryFetch, isAnthropicOAuthToken, isRecord, toBoolean, toNumber, toPositiveNumber } from "../utils";
import { parseAlibabaTokenPlanCredential } from "../wire/alibaba-token-plan";
import { ALIBABA_TOKEN_PLAN_BASE_URL, parseAlibabaTokenPlanCredential } from "../wire/alibaba-token-plan";
import { coreWeaveProjectHeaders } from "../wire/coreweave";
import {
COPILOT_API_HEADERS,
@@ -2434,7 +2434,7 @@ export function alibabaCodingPlanModelManagerOptions(
// Alibaba Token Plan
// ---------------------------------------------------------------------------
export const ALIBABA_TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
export { ALIBABA_TOKEN_PLAN_BASE_URL };
const ALIBABA_TOKEN_PLAN_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } as const;
const ALIBABA_TOKEN_PLAN_COMPAT: OpenAICompat = {
@@ -2556,7 +2556,10 @@ export function alibabaTokenPlanModelManagerOptions(
): ModelManagerOptions<"openai-completions"> {
const credential = config?.apiKey ? parseAlibabaTokenPlanCredential(config.apiKey) : undefined;
const apiKey = credential?.token;
const baseUrl = config?.baseUrl ?? ALIBABA_TOKEN_PLAN_BASE_URL;
// A region-locked credential (China/custom) dictates the discovery endpoint:
// its key only authenticates against its own region, so fetching /models from
// any other base URL would 401 (#6682).
const baseUrl = credential?.baseUrl ?? config?.baseUrl ?? ALIBABA_TOKEN_PLAN_BASE_URL;
return {
providerId: "alibaba-token-plan",
dynamicModelsAuthoritative: true,
@@ -1,6 +1,23 @@
/**
* International (Singapore) Token Plan endpoint. Default region; keys issued by
* the international product authenticate only here.
*/
export const ALIBABA_TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
/**
* China (Beijing) Token Plan endpoint (百炼 Token Plan). Keys are region-locked:
* a Beijing-issued key is rejected by the international endpoint with
* `invalid_api_key`, and vice versa (#6682).
*/
export const ALIBABA_TOKEN_PLAN_CN_BASE_URL = "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1";
export interface AlibabaTokenPlanCredential {
token: string;
cookie?: string;
/**
* Region base URL the key authenticates against. Absent means the default
* international endpoint ({@link ALIBABA_TOKEN_PLAN_BASE_URL}).
*/
baseUrl?: string;
}
const TOKEN_PATTERN = /^sk-[A-Za-z0-9._~+/-]+={0,2}$/;
@@ -10,18 +27,28 @@ export function parseAlibabaTokenPlanCredential(value: string): AlibabaTokenPlan
if (!trimmed) return null;
if (!trimmed.startsWith("{")) return TOKEN_PATTERN.test(trimmed) ? { token: trimmed } : null;
try {
const parsed = JSON.parse(trimmed) as { token?: unknown; cookie?: unknown };
const parsed = JSON.parse(trimmed) as { token?: unknown; cookie?: unknown; baseUrl?: unknown };
if (typeof parsed.token !== "string" || !TOKEN_PATTERN.test(parsed.token.trim())) return null;
if (parsed.cookie !== undefined && typeof parsed.cookie !== "string") return null;
if (parsed.baseUrl !== undefined && typeof parsed.baseUrl !== "string") return null;
const token = parsed.token.trim();
const cookie = parsed.cookie?.trim();
return cookie ? { token, cookie } : { token };
const baseUrl = parsed.baseUrl?.trim();
const credential: AlibabaTokenPlanCredential = { token };
if (cookie) credential.cookie = cookie;
if (baseUrl) credential.baseUrl = baseUrl;
return credential;
} catch {
return null;
}
}
export function serializeAlibabaTokenPlanCredential(token: string, cookie: string): string {
export function serializeAlibabaTokenPlanCredential(token: string, cookie: string, baseUrl?: string): string {
const trimmedCookie = cookie.trim();
return trimmedCookie ? JSON.stringify({ token, cookie: trimmedCookie }) : token;
const trimmedBaseUrl = baseUrl?.trim();
if (!trimmedCookie && !trimmedBaseUrl) return token;
const payload: { token: string; cookie?: string; baseUrl?: string } = { token };
if (trimmedCookie) payload.cookie = trimmedCookie;
if (trimmedBaseUrl) payload.baseUrl = trimmedBaseUrl;
return JSON.stringify(payload);
}
@@ -85,6 +85,28 @@ describe("QwenCloud Token Plan provider", () => {
expect(options.dynamicModelsAuthoritative).toBe(true);
});
test("routes discovery to the credential's region when it is China (Beijing)", async () => {
let requestedUrl = "";
const fetchMock: FetchImpl = input => {
requestedUrl = String(input);
return Promise.resolve(Response.json({ data: [{ id: "qwen3.7-plus", owned_by: "qwencloud" }] }));
};
const apiKey = serializeAlibabaTokenPlanCredential(
"sk-sp-beijing",
"",
"https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
);
const options = alibabaTokenPlanModelManagerOptions({ apiKey, fetch: fetchMock });
const models = await options.fetchDynamicModels?.();
expect(requestedUrl).toBe("https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/models");
expect(models?.[0]).toMatchObject({
id: "qwen3.7-plus",
baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
});
});
test("rejects malformed compound credentials before model discovery", () => {
let fetched = false;
const fetchMock: FetchImpl = () => {