feat(alibaba): free-tier routing with live quota sync (#8893)

* feat(alibaba): add free-tier routing with console quota and builtin allowlist

Classify DashScope free vs paid models via console quota API, a hardcoded
operator allowlist fallback, and per-connection drained tracking. Wire wildcard
combo expansion, model refresh, combo exhaustion, and audit redaction for
Alibaba console credentials.

* fix(routing): reset forced connection pin and persist Alibaba free-tier drain

Drop session affinity pins when a forced connection is excluded after 429,
and record Alibaba free-tier exhaustion on upstream 403 so per-key drained
lists stay accurate without blocking sibling keys.

* fix(alibaba): prefer live quota sync over static free-tier allowlist

Stop unioning the builtin text allowlist when a console quota snapshot exists,
treat expired quotaValidityPeriod as not_capable, and add a dated JSON pack plus
sync-alibaba-allowlist script for operator refresh without code edits.

* docs(alibaba): document free-tier console path + allowlist env overrides

Adds the 4 ALIBABA_FREE_TIER_*_FE_PATH / ALIBABA_FREE_TIER_ALLOWLIST_PATH
env vars (referenced by alibabaFreeTierQuotaFetcher.ts and
alibabaFreeTierAllowlist.ts) to .env.example and
docs/reference/ENVIRONMENT.md so the env/docs contract check passes.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* refactor(open-sse): split alibabaFreeTierQuotaFetcher.ts under file-size cap

Extract pure parsing/classification/eligibility-filtering logic into
alibabaFreeTierQuotaClassify.ts and shared types/primitives into
alibabaFreeTierQuotaTypes.ts, leaving the HTTP/console-fetch flow in the
original file. Public API is unchanged (re-exported), behavior is identical.

Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com>

* fix: resolve typecheck errors in alibaba-free-tier routing

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: AndrianBalanescu <AndrianBalanescu@users.noreply.github.com>
Co-authored-by: AndrianBalanescu <andrian@balanescu.dev>
This commit is contained in:
Andrew B.
2026-08-11 02:25:23 -05:00
committed by GitHub
parent 9a99a39b33
commit 21fd0a94f8
33 changed files with 4878 additions and 31 deletions

View File

@@ -1978,6 +1978,19 @@ APP_LOG_TO_FILE=true
# ALIBABA_CODING_PLAN_HOST=
# ALIBABA_CODING_PLAN_QUOTA_URL=
# ── Alibaba Model Studio free-tier quota sync ──
# Console front-end path overrides for the free-tier quota fetcher. Used by:
# open-sse/services/alibabaFreeTierQuotaFetcher.ts. When unset, the fetcher
# uses the production Bailian console paths.
# ALIBABA_FREE_TIER_VISION_FE_PATH=
# ALIBABA_FREE_TIER_MULTIMODAL_FE_PATH=
# ALIBABA_FREE_TIER_AUDIO_FE_PATH=
# Optional path to a local JSON override for the built-in text free-tier
# allowlist. Used by: open-sse/services/alibabaFreeTierAllowlist.ts. When
# unset, the fetcher falls back to $DATA_DIR/alibaba-free-tier-allowlist.json
# then config/alibaba-free-tier-allowlist.json.
# ALIBABA_FREE_TIER_ALLOWLIST_PATH=
# ── Context window tuning ──
# Tokens reserved for completion output when computing prompt budgets.
# Used by: open-sse/services/contextManager.ts. Default: 1024.

View File

@@ -0,0 +1,82 @@
{
"asOf": "2026-07-28",
"validUntil": "2026-08-27",
"capable": [
"deepseek-v3.2",
"deepseek-v4-pro",
"glm-5.2",
"qwen-flash",
"qwen-flash-2025-07-28",
"qwen-flash-character",
"qwen-max",
"qwen-mt-flash",
"qwen-mt-lite",
"qwen-mt-plus",
"qwen-mt-turbo",
"qwen-plus-2025-04-28",
"qwen-plus-2025-07-14",
"qwen-plus-2025-07-28",
"qwen-plus-2025-09-11",
"qwen-plus-character",
"qwen-plus-latest",
"qwen3-14b",
"qwen3-235b-a22b",
"qwen3-235b-a22b-instruct-2507",
"qwen3-235b-a22b-thinking-2507",
"qwen3-30b-a3b",
"qwen3-30b-a3b-instruct-2507",
"qwen3-30b-a3b-thinking-2507",
"qwen3-32b",
"qwen3-8b",
"qwen3-coder-30b-a3b-instruct",
"qwen3-coder-480b-a35b-instruct",
"qwen3-coder-flash",
"qwen3-coder-flash-2025-07-28",
"qwen3-coder-next",
"qwen3-coder-plus",
"qwen3-coder-plus-2025-07-22",
"qwen3-coder-plus-2025-09-23",
"qwen3-max",
"qwen3-max-2025-09-23",
"qwen3-max-2026-01-23",
"qwen3-max-preview",
"qwen3-next-80b-a3b-instruct",
"qwen3-next-80b-a3b-thinking",
"qwen3.5-122b-a10b",
"qwen3.5-27b",
"qwen3.5-397b-a17b",
"qwen3.5-flash",
"qwen3.5-flash-2026-02-23",
"qwen3.5-plus",
"qwen3.5-plus-2026-02-15",
"qwen3.5-plus-2026-04-20",
"qwen3.6-27b",
"qwen3.6-35b-a3b",
"qwen3.6-flash",
"qwen3.6-flash-2026-04-16",
"qwen3.6-max-preview",
"qwen3.6-plus",
"qwen3.6-plus-2026-04-02",
"qwen3.7-flash",
"qwen3.7-flash-2026-07-15",
"qwen3.7-max-2026-05-17",
"qwen3.7-max-2026-05-20",
"qwen3.7-max-2026-06-08",
"qwen3.7-max-preview",
"qwen3.7-plus-2026-05-26",
"qwq-plus"
],
"noFreeTier": [
"deepseek-v4-flash",
"glm-5.1",
"glm-5.2-fast-preview",
"kimi-k2.7-code",
"qwen-plus",
"qwen-plus-2025-01-25",
"qwen-plus-character-ja",
"qwen-turbo",
"qwen3.5-35b-a3b",
"qwen3.7-max",
"qwen3.7-plus"
]
}

View File

@@ -1116,6 +1116,10 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
| `REDIS_URL` | `redis://localhost:6379` | `src/shared/utils/rateLimiter.ts` | Redis connection string for the rate limiter backend. |
| `ALIBABA_CODING_PLAN_HOST` | _(production host)_ | `open-sse/services/bailianQuotaFetcher.ts` | Override the host used to fetch Alibaba Bailian coding-plan quotas. |
| `ALIBABA_CODING_PLAN_QUOTA_URL` | derived from host | `open-sse/services/bailianQuotaFetcher.ts` | Full quota URL override for Alibaba Bailian. |
| `ALIBABA_FREE_TIER_VISION_FE_PATH` | `/costing-balance/free-quota-image-video` | `open-sse/services/alibabaFreeTierQuotaFetcher.ts` | Console front-end path override for fetching Alibaba Model Studio free-tier vision/media quota. |
| `ALIBABA_FREE_TIER_MULTIMODAL_FE_PATH` | `/costing-balance/free-quota-multimodal` | `open-sse/services/alibabaFreeTierQuotaFetcher.ts` | Console front-end path override for fetching Alibaba Model Studio free-tier multimodal quota. |
| `ALIBABA_FREE_TIER_AUDIO_FE_PATH` | `/costing-balance/free-quota-audio` | `open-sse/services/alibabaFreeTierQuotaFetcher.ts` | Console front-end path override for fetching Alibaba Model Studio free-tier audio quota. |
| `ALIBABA_FREE_TIER_ALLOWLIST_PATH` | _(unset)_ | `open-sse/services/alibabaFreeTierAllowlist.ts` | Optional path to a local JSON override for the built-in Alibaba free-tier text-model allowlist. Falls back to `$DATA_DIR/alibaba-free-tier-allowlist.json`, then `config/alibaba-free-tier-allowlist.json`. |
| `CONTEXT_RESERVE_TOKENS` | `1024` | `open-sse/services/contextManager.ts` | Tokens reserved for completion output when computing prompt budgets. |
| `CONTEXT_KEEP_LATEST_IMAGES` | `2` | `open-sse/services/contextManager.ts` | How many of the newest inline images to keep when pruning older ones to fit the context window (#8560). |
| `MODEL_ALIAS_COMPAT_ENABLED` | enabled | `open-sse/services/model.ts` | Toggle the legacy model-alias compatibility layer used by older clients. |

View File

@@ -0,0 +1,164 @@
/**
* @file alibabaFreeTier.ts
* @description Alibaba Model Studio free-tier drain detection, billing mode, and persisted model lockouts.
*
* @changes
* - [2026-07-25] [Composer] - Delegate free-eligible filtering to probe-based discovery module
* - [2026-07-24] [Composer] - Add free-vs-paid billing mode and permanent free-tier model drain handling
*/
import { isModelLocked, lockModel } from "./accountFallback.ts";
export type AlibabaBillingMode = "free" | "paid";
type AlibabaConnectionLike = {
id: string;
providerSpecificData?: Record<string, unknown> | null;
};
/** ~10 years — free-tier drains are permanent until the operator clears connection state. */
export const ALIBABA_FREE_DRAINED_LOCK_MS = 10 * 365 * 24 * 60 * 60 * 1000;
const ALIBABA_FREE_QUOTA_EXHAUSTED_PATTERNS = [
/\bfree quota has been exhausted\b/i,
/\bfree tier of the model has been exhausted\b/i,
/\buse free tier only\b/i,
] as const;
const ALIBABA_MODEL_STUDIO_PROVIDER_IDS = new Set(["alibaba", "alibaba-cn", "ali"]);
export {
filterAlibabaFreeEligibleModels,
isAlibabaFreeTierCapableModel,
} from "./alibabaFreeTierDiscovery.ts";
export {
filterAlibabaFreeVisionEligibleModels,
filterAlibabaFreeMultimodalEligibleModels,
filterAlibabaFreeAudioEligibleModels,
isAlibabaFreeTierVisionCapableModel,
isAlibabaFreeTierMultimodalCapableModel,
isAlibabaFreeTierAudioCapableModel,
} from "./alibabaFreeTierQuotaFetcher.ts";
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
export function isAlibabaModelStudioProvider(provider: string | null | undefined): boolean {
if (!provider) return false;
const normalized = provider.toLowerCase();
return ALIBABA_MODEL_STUDIO_PROVIDER_IDS.has(normalized);
}
export function isAlibabaFreeQuotaExhaustedError(errorText: string): boolean {
const text = String(errorText || "");
if (!text) return false;
return ALIBABA_FREE_QUOTA_EXHAUSTED_PATTERNS.some((pattern) => pattern.test(text));
}
export function getAlibabaBillingMode(
providerSpecificData: Record<string, unknown> | null | undefined
): AlibabaBillingMode {
const raw = asRecord(providerSpecificData).alibabaBillingMode;
return raw === "free" ? "free" : "paid";
}
export function getAlibabaFreeDrainedModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
const raw = asRecord(providerSpecificData).alibabaFreeDrainedModels;
if (!Array.isArray(raw)) return [];
return raw.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
}
export function isAlibabaModelFreeDrained(
provider: string | null | undefined,
providerSpecificData: Record<string, unknown> | null | undefined,
model: string | null | undefined
): boolean {
if (!isAlibabaModelStudioProvider(provider) || !model) return false;
return getAlibabaFreeDrainedModels(providerSpecificData).includes(model);
}
export function mergeAlibabaFreeDrainedModels(
providerSpecificData: Record<string, unknown> | null | undefined,
model: string
): Record<string, unknown> {
const base = asRecord(providerSpecificData);
const existing = new Set(getAlibabaFreeDrainedModels(base));
existing.add(model);
return {
...base,
alibabaFreeDrainedModels: [...existing],
};
}
export function shouldUseLiveAlibabaFreeModelDiscovery(
providerSpecificData: Record<string, unknown> | null | undefined
): boolean {
return getAlibabaBillingMode(providerSpecificData) === "free";
}
export function filterAlibabaFreeTierModels(
modelIds: readonly string[],
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
const drained = new Set(getAlibabaFreeDrainedModels(providerSpecificData));
return modelIds.filter((id) => !drained.has(id));
}
export function rehydrateAlibabaFreeDrainedModelLocks(
provider: string,
connectionId: string,
providerSpecificData: Record<string, unknown> | null | undefined
): void {
if (
!isAlibabaModelStudioProvider(provider) ||
getAlibabaBillingMode(providerSpecificData) !== "free"
) {
return;
}
for (const model of getAlibabaFreeDrainedModels(providerSpecificData)) {
if (!isModelLocked(provider, connectionId, model)) {
lockModel(
provider,
connectionId,
model,
"free_quota_exhausted",
ALIBABA_FREE_DRAINED_LOCK_MS
);
}
}
}
export async function isAlibabaFreeTierModelRoutable(
provider: string,
connectionId: string,
model: string
): Promise<boolean> {
if (!isAlibabaModelStudioProvider(provider) || !model) return true;
if (isModelLocked(provider, connectionId, model)) return false;
try {
const { getProviderConnections } = await import("../../src/lib/db/providers.ts");
const { buildAlibabaFreeTierFilterContext, isAlibabaFreeTierCapableModel } =
await import("./alibabaFreeTierDiscovery.ts");
const connections = await getProviderConnections({ provider });
const connection = connections.find((entry) => entry.id === connectionId);
if (!connection) return true;
const providerSpecificData = connection.providerSpecificData as Record<string, unknown>;
rehydrateAlibabaFreeDrainedModelLocks(provider, connectionId, providerSpecificData);
if (getAlibabaBillingMode(providerSpecificData) === "free") {
const filterContext = buildAlibabaFreeTierFilterContext(
connections as unknown as readonly AlibabaConnectionLike[],
connectionId
);
if (!isAlibabaFreeTierCapableModel(model, filterContext)) return false;
}
return !isAlibabaModelFreeDrained(provider, providerSpecificData, model);
} catch {
return !isModelLocked(provider, connectionId, model);
}
}

View File

@@ -0,0 +1,202 @@
/**
* @file alibabaFreeTierAllowlist.ts
* @description Offline fallback allowlist for Alibaba Model Studio free-tier text models.
*
* Prefer live console quota sync (`alibabaFreeTierQuotaFetcher.ts`). This pack is used
* only when no recent console snapshot exists on the connection.
*
* @changes
* - [2026-07-28] [Composer] - Load dated JSON pack from DATA_DIR/config with embedded fallback
* - [2026-07-25] [Composer] - Hardcode text free/paid model lists from operator console quota export
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
export type AlibabaFreeTierAllowlistPack = {
asOf: string;
validUntil?: string;
capable: string[];
noFreeTier: string[];
};
export const ALIBABA_FREE_TIER_TEXT_CAPABLE_MODELS = [
"deepseek-v3.2",
"deepseek-v4-pro",
"glm-5.2",
"qwen-flash",
"qwen-flash-2025-07-28",
"qwen-flash-character",
"qwen-max",
"qwen-mt-flash",
"qwen-mt-lite",
"qwen-mt-plus",
"qwen-mt-turbo",
"qwen-plus-2025-04-28",
"qwen-plus-2025-07-14",
"qwen-plus-2025-07-28",
"qwen-plus-2025-09-11",
"qwen-plus-character",
"qwen-plus-latest",
"qwen3-14b",
"qwen3-235b-a22b",
"qwen3-235b-a22b-instruct-2507",
"qwen3-235b-a22b-thinking-2507",
"qwen3-30b-a3b",
"qwen3-30b-a3b-instruct-2507",
"qwen3-30b-a3b-thinking-2507",
"qwen3-32b",
"qwen3-8b",
"qwen3-coder-30b-a3b-instruct",
"qwen3-coder-480b-a35b-instruct",
"qwen3-coder-flash",
"qwen3-coder-flash-2025-07-28",
"qwen3-coder-next",
"qwen3-coder-plus",
"qwen3-coder-plus-2025-07-22",
"qwen3-coder-plus-2025-09-23",
"qwen3-max",
"qwen3-max-2025-09-23",
"qwen3-max-2026-01-23",
"qwen3-max-preview",
"qwen3-next-80b-a3b-instruct",
"qwen3-next-80b-a3b-thinking",
"qwen3.5-122b-a10b",
"qwen3.5-27b",
"qwen3.5-397b-a17b",
"qwen3.5-flash",
"qwen3.5-flash-2026-02-23",
"qwen3.5-plus",
"qwen3.5-plus-2026-02-15",
"qwen3.5-plus-2026-04-20",
"qwen3.6-27b",
"qwen3.6-35b-a3b",
"qwen3.6-flash",
"qwen3.6-flash-2026-04-16",
"qwen3.6-max-preview",
"qwen3.6-plus",
"qwen3.6-plus-2026-04-02",
"qwen3.7-flash",
"qwen3.7-flash-2026-07-15",
"qwen3.7-max-2026-05-17",
"qwen3.7-max-2026-05-20",
"qwen3.7-max-2026-06-08",
"qwen3.7-max-preview",
"qwen3.7-plus-2026-05-26",
"qwq-plus",
] as const;
export const ALIBABA_NO_FREE_TIER_TEXT_MODELS = [
"deepseek-v4-flash",
"glm-5.1",
"glm-5.2-fast-preview",
"kimi-k2.7-code",
"qwen-plus",
"qwen-plus-2025-01-25",
"qwen-plus-character-ja",
"qwen-turbo",
"qwen3.5-35b-a3b",
"qwen3.7-max",
"qwen3.7-plus",
] as const;
const EMBEDDED_ALLOWLIST_PACK: AlibabaFreeTierAllowlistPack = {
asOf: "2026-07-25",
capable: [...ALIBABA_FREE_TIER_TEXT_CAPABLE_MODELS],
noFreeTier: [...ALIBABA_NO_FREE_TIER_TEXT_MODELS],
};
let cachedPack: AlibabaFreeTierAllowlistPack | null | undefined;
export function resetAlibabaFreeTierAllowlistCache(): void {
cachedPack = undefined;
}
function resolveAllowlistPaths(): string[] {
const paths: string[] = [];
const envPath = process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH?.trim();
if (envPath) paths.push(envPath);
const dataDir = process.env.DATA_DIR?.trim() || path.join(os.homedir(), ".omniroute");
paths.push(path.join(dataDir, "alibaba-free-tier-allowlist.json"));
paths.push(path.join(process.cwd(), "config", "alibaba-free-tier-allowlist.json"));
return paths;
}
function parseAllowlistPack(raw: unknown): AlibabaFreeTierAllowlistPack | null {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
const record = raw as Record<string, unknown>;
const capable = Array.isArray(record.capable)
? record.capable.filter(
(entry): entry is string => typeof entry === "string" && entry.length > 0
)
: [];
const noFreeTier = Array.isArray(record.noFreeTier)
? record.noFreeTier.filter(
(entry): entry is string => typeof entry === "string" && entry.length > 0
)
: [];
const asOf = typeof record.asOf === "string" && record.asOf.trim() ? record.asOf.trim() : null;
if (!asOf || capable.length === 0) return null;
return {
asOf,
validUntil:
typeof record.validUntil === "string" && record.validUntil.trim()
? record.validUntil.trim()
: undefined,
capable,
noFreeTier,
};
}
export function isAlibabaFreeTierAllowlistPackValid(
pack: AlibabaFreeTierAllowlistPack,
nowMs: number = Date.now()
): boolean {
if (!pack.validUntil) return true;
const expiresAt = Date.parse(pack.validUntil);
if (!Number.isFinite(expiresAt)) return true;
return expiresAt >= nowMs;
}
export function loadAlibabaFreeTierAllowlistPack(): AlibabaFreeTierAllowlistPack | null {
if (cachedPack !== undefined) return cachedPack;
for (const candidatePath of resolveAllowlistPaths()) {
try {
if (!fs.existsSync(candidatePath)) continue;
const parsed = parseAllowlistPack(JSON.parse(fs.readFileSync(candidatePath, "utf8")));
if (parsed && isAlibabaFreeTierAllowlistPackValid(parsed)) {
cachedPack = parsed;
return cachedPack;
}
} catch {
// Try next path.
}
}
cachedPack = null;
return cachedPack;
}
function resolveActiveAllowlistPack(): AlibabaFreeTierAllowlistPack {
return loadAlibabaFreeTierAllowlistPack() ?? EMBEDDED_ALLOWLIST_PACK;
}
export function getAlibabaBuiltinFreeTierTextCapableModels(): readonly string[] {
return resolveActiveAllowlistPack().capable;
}
export function getAlibabaBuiltinNoFreeTierTextModels(): readonly string[] {
return resolveActiveAllowlistPack().noFreeTier;
}
export function isAlibabaBuiltinFreeTierTextModel(modelId: string): boolean {
return getAlibabaBuiltinFreeTierTextCapableModels().includes(modelId);
}
export function isAlibabaBuiltinNoFreeTierTextModel(modelId: string): boolean {
return getAlibabaBuiltinNoFreeTierTextModels().includes(modelId);
}

View File

@@ -0,0 +1,359 @@
/**
* @file alibabaFreeTierDiscovery.ts
* @description Probe-based Alibaba Model Studio free-tier eligibility discovery.
*
* DashScope /models does not expose free-quota metadata. We classify models with
* minimal chat probes and persist results on the connection:
* - AllocationQuota.FreeTierOnly → model offers free tier (drained on this account)
* - 200 on native promo families (qwen/glm/deepseek/…) → free tier with remaining quota
* - 200 on third-party paid families (kimi/moonshot) → no free tier (paid billing only)
*
* @changes
* - [2026-07-25] [Composer] - Always union built-in text free-tier allowlist into capable/block checks
* - [2026-07-25] [Composer] - Delegate text filter context to canonical shared eligibility builder
* - [2026-07-25] [Composer] - Strict allowlist for alibabafree combos (no optimistic qwen/glm guessing)
* - [2026-07-25] [Composer] - Merge free-tier state across provider connections for wildcard filtering
* - [2026-07-25] [Composer] - Add probe-based free-tier model discovery for Alibaba connections
*/
import {
getAlibabaBillingMode,
getAlibabaFreeDrainedModels,
isAlibabaFreeQuotaExhaustedError,
isAlibabaModelStudioProvider,
mergeAlibabaFreeDrainedModels,
type AlibabaBillingMode,
} from "./alibabaFreeTier.ts";
import {
getAlibabaBuiltinFreeTierTextCapableModels,
getAlibabaBuiltinNoFreeTierTextModels,
} from "./alibabaFreeTierAllowlist.ts";
import {
buildAlibabaFreeTierTextFilterContext,
getAlibabaFreeTierQuotaLastSyncAt,
} from "./alibabaFreeTierQuotaFetcher.ts";
export type AlibabaFreeTierProbeVerdict =
"capable_available" | "capable_drained" | "not_capable" | "unknown";
export type AlibabaFreeTierProbeResult = {
modelId: string;
verdict: AlibabaFreeTierProbeVerdict;
status: number;
errorCode?: string;
};
/** Third-party models listed on DashScope that bill paid-only on international (no free quota toggle). */
const ALIBABA_THIRD_PARTY_PAID_PREFIXES = [/^kimi-/i, /^moonshot-/i] as const;
/** Native families that participate in DashScope new-user free-quota promos. */
const ALIBABA_NATIVE_FREE_TIER_PREFIXES = [
/^qwen/i,
/^qwq/i,
/^glm-/i,
/^deepseek-/i,
/^minimax-/i,
/^MiniMax-/i,
] as const;
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
function normalizeModelIdList(raw: unknown): string[] {
if (!Array.isArray(raw)) return [];
return raw.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
}
export function getAlibabaFreeTierCapableModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
const raw = asRecord(providerSpecificData).alibabaFreeTierCapableModels;
const drained = getAlibabaFreeDrainedModels(providerSpecificData);
return [...new Set([...normalizeModelIdList(raw), ...drained])];
}
export function getAlibabaNoFreeTierModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaNoFreeTierModels);
}
export function isAlibabaThirdPartyPaidModelFamily(modelId: string): boolean {
return ALIBABA_THIRD_PARTY_PAID_PREFIXES.some((pattern) => pattern.test(modelId));
}
export function isAlibabaNativeFreeTierModelFamily(modelId: string): boolean {
return ALIBABA_NATIVE_FREE_TIER_PREFIXES.some((pattern) => pattern.test(modelId));
}
export function parseAlibabaFreeTierProbeError(bodyText: string): {
code: string;
message: string;
} {
try {
const parsed = JSON.parse(bodyText) as {
error?: { code?: string; type?: string; message?: string };
};
const error = parsed?.error;
const code = String(error?.code || error?.type || "");
const message = String(error?.message || bodyText || "");
return { code, message };
} catch {
return { code: "", message: bodyText };
}
}
export function classifyAlibabaFreeTierProbe(
modelId: string,
status: number,
bodyText: string
): AlibabaFreeTierProbeResult {
const { code, message } = parseAlibabaFreeTierProbeError(bodyText);
const combined = `${code} ${message}`;
if (status >= 200 && status < 300) {
if (isAlibabaThirdPartyPaidModelFamily(modelId)) {
return { modelId, verdict: "not_capable", status, errorCode: code || undefined };
}
if (isAlibabaNativeFreeTierModelFamily(modelId)) {
return { modelId, verdict: "capable_available", status, errorCode: code || undefined };
}
return { modelId, verdict: "unknown", status, errorCode: code || undefined };
}
if (
status === 403 &&
(code === "AllocationQuota.FreeTierOnly" || isAlibabaFreeQuotaExhaustedError(combined))
) {
return { modelId, verdict: "capable_drained", status, errorCode: code || undefined };
}
if (status === 403 || status === 400) {
return { modelId, verdict: "not_capable", status, errorCode: code || undefined };
}
return { modelId, verdict: "unknown", status, errorCode: code || undefined };
}
export function mergeAlibabaFreeTierProbeResults(
providerSpecificData: Record<string, unknown> | null | undefined,
results: readonly AlibabaFreeTierProbeResult[],
billingMode: AlibabaBillingMode = getAlibabaBillingMode(providerSpecificData)
): Record<string, unknown> {
const base = asRecord(providerSpecificData);
const capable = new Set(getAlibabaFreeTierCapableModels(base));
const noFreeTier = new Set(getAlibabaNoFreeTierModels(base));
let next = base;
for (const result of results) {
switch (result.verdict) {
case "capable_available":
capable.add(result.modelId);
noFreeTier.delete(result.modelId);
break;
case "capable_drained":
capable.add(result.modelId);
noFreeTier.delete(result.modelId);
if (billingMode === "free") {
next = mergeAlibabaFreeDrainedModels(next, result.modelId);
}
break;
case "not_capable":
noFreeTier.add(result.modelId);
capable.delete(result.modelId);
break;
default:
break;
}
}
return {
...next,
alibabaFreeTierCapableModels: [...capable],
alibabaNoFreeTierModels: [...noFreeTier],
alibabaFreeTierProbeLastRunAt: new Date().toISOString(),
};
}
type AlibabaConnectionLike = {
id: string;
providerSpecificData?: Record<string, unknown> | null;
};
/**
* Build a merged free-tier filter context for one Alibaba connection.
*
* Free-tier eligibility (capable / no-free-tier) is account-agnostic — one synced
* console snapshot applies to every `alibabaBillingMode: free` key. Only drained
* models stay per-connection (quota exhaustion is key-specific).
*/
export function buildAlibabaFreeTierFilterContext(
connections: readonly AlibabaConnectionLike[],
connectionId: string
): Record<string, unknown> {
return buildAlibabaFreeTierTextFilterContext(connections, connectionId);
}
export function isAlibabaFreeTierCapableModel(
modelId: string,
providerSpecificData: Record<string, unknown> | null | undefined,
options: { strictAllowlist?: boolean } = {}
): boolean {
const noFreeTier = new Set([
...getAlibabaNoFreeTierModels(providerSpecificData),
...getAlibabaBuiltinNoFreeTierTextModels(),
]);
if (noFreeTier.has(modelId)) return false;
const drained = new Set(getAlibabaFreeDrainedModels(providerSpecificData));
if (drained.has(modelId)) return false;
const capable = new Set([
...normalizeModelIdList(asRecord(providerSpecificData).alibabaFreeTierCapableModels),
...getAlibabaBuiltinFreeTierTextCapableModels(),
]);
if (capable.has(modelId)) return true;
// Console quota API is authoritative when present — never guess past it.
if (getAlibabaFreeTierQuotaLastSyncAt(providerSpecificData) || options.strictAllowlist) {
return false;
}
// Optimistic inclusion for native families until a probe proves otherwise.
if (isAlibabaNativeFreeTierModelFamily(modelId) && !isAlibabaThirdPartyPaidModelFamily(modelId)) {
return true;
}
return false;
}
export function filterAlibabaFreeEligibleModels(
modelIds: readonly string[],
providerSpecificData: Record<string, unknown> | null | undefined,
options: { strictAllowlist?: boolean } = {}
): string[] {
const drained = new Set(getAlibabaFreeDrainedModels(providerSpecificData));
return modelIds.filter((id) => {
if (drained.has(id)) return false;
return isAlibabaFreeTierCapableModel(id, providerSpecificData, options);
});
}
type ProbeConnection = {
id: string;
apiKey?: string | null;
providerSpecificData?: Record<string, unknown> | null;
};
export async function probeAlibabaFreeTierModel(
connection: ProbeConnection,
modelId: string,
chatCompletionsUrl: string
): Promise<AlibabaFreeTierProbeResult> {
if (!connection.apiKey) {
return { modelId, verdict: "unknown", status: 0 };
}
try {
const response = await fetch(chatCompletionsUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${connection.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: modelId,
messages: [{ role: "user", content: "ping" }],
max_tokens: 1,
}),
});
const bodyText = await response.text();
return classifyAlibabaFreeTierProbe(modelId, response.status, bodyText);
} catch {
return { modelId, verdict: "unknown", status: 0 };
}
}
export async function probeAlibabaFreeTierModels(
connection: ProbeConnection,
modelIds: readonly string[],
chatCompletionsUrl: string,
options: { concurrency?: number } = {}
): Promise<AlibabaFreeTierProbeResult[]> {
const concurrency = Math.max(1, Math.min(options.concurrency ?? 4, 8));
const results: AlibabaFreeTierProbeResult[] = [];
let index = 0;
async function worker() {
while (index < modelIds.length) {
const current = modelIds[index];
index += 1;
results.push(await probeAlibabaFreeTierModel(connection, current, chatCompletionsUrl));
}
}
await Promise.all(Array.from({ length: Math.min(concurrency, modelIds.length) }, () => worker()));
return results;
}
export async function refreshAlibabaFreeTierModelClassification(
provider: string,
connection: ProbeConnection,
modelIds: readonly string[],
chatCompletionsUrl: string
): Promise<Record<string, unknown> | null> {
if (
!isAlibabaModelStudioProvider(provider) ||
getAlibabaBillingMode(connection.providerSpecificData) !== "free"
) {
return null;
}
if (getAlibabaFreeTierQuotaLastSyncAt(connection.providerSpecificData)) {
return null;
}
const capable = new Set(getAlibabaFreeTierCapableModels(connection.providerSpecificData));
const noFreeTier = new Set(getAlibabaNoFreeTierModels(connection.providerSpecificData));
const pending = modelIds.filter((id) => !capable.has(id) && !noFreeTier.has(id));
if (pending.length === 0) return null;
const probeResults = await probeAlibabaFreeTierModels(connection, pending, chatCompletionsUrl, {
concurrency: 4,
});
return mergeAlibabaFreeTierProbeResults(connection.providerSpecificData, probeResults);
}
export function scheduleAlibabaFreeTierProbeRefresh(
provider: string,
connection: ProbeConnection,
models: ReadonlyArray<{ id?: string | null }>,
chatCompletionsUrl: string
): void {
const modelIds = models
.map((model) => (typeof model?.id === "string" ? model.id.trim() : ""))
.filter((id) => id.length > 0);
if (modelIds.length === 0) return;
void (async () => {
try {
const merged = await refreshAlibabaFreeTierModelClassification(
provider,
connection,
modelIds,
chatCompletionsUrl
);
if (!merged) return;
const { updateProviderConnection } = await import("../../src/lib/db/providers.ts");
await updateProviderConnection(connection.id, { providerSpecificData: merged });
} catch (error) {
console.warn("[alibaba-free-tier] background probe refresh failed", {
connectionId: connection.id,
error: error instanceof Error ? error.message : String(error),
});
}
})();
}

View File

@@ -0,0 +1,648 @@
/**
* @file alibabaFreeTierQuotaClassify.ts
* @description Parsing + classification + eligibility-filtering logic for Alibaba Model
* Studio free-tier quota entries. Extracted from alibabaFreeTierQuotaFetcher.ts (which
* exceeded the file-size cap) to isolate the pure parsing/classification helpers from
* the HTTP/console-fetch flow. Behavior is unchanged.
*/
import { getAlibabaBillingMode } from "./alibabaFreeTier.ts";
import {
getAlibabaBuiltinFreeTierTextCapableModels,
getAlibabaBuiltinNoFreeTierTextModels,
} from "./alibabaFreeTierAllowlist.ts";
import { toNumberOrNull } from "@/shared/utils/numeric";
import {
isDashscopeAudioModelId,
isDashscopeMultimodalModelId,
isDashscopeTextModelId,
isDashscopeVisionModelId,
} from "./dashscopeTextModels.ts";
import {
asRecord,
getAlibabaFreeTierQuotaLastSyncAt,
isAlibabaLiveQuotaSyncAt,
normalizeModelIdList,
toTrimmedString,
type AlibabaFreeTierQuotaClassification,
type AlibabaFreeTierQuotaEntry,
} from "./alibabaFreeTierQuotaTypes.ts";
export function isAlibabaQuotaValidityExpired(
entry: AlibabaFreeTierQuotaEntry,
nowMs: number = Date.now()
): boolean {
if (
typeof entry.quotaValidityPeriod !== "number" ||
!Number.isFinite(entry.quotaValidityPeriod)
) {
return false;
}
return entry.quotaValidityPeriod < nowMs;
}
function parseQuotaEntry(value: unknown): AlibabaFreeTierQuotaEntry | null {
const record = asRecord(value);
const model = toTrimmedString(record.model);
if (!model) return null;
return {
model,
freeTierOnly: record.freeTierOnly === true,
quotaStatus: toTrimmedString(record.quotaStatus) || "UNKNOWN",
quotaTotal: toNumberOrNull(record.quotaTotal) ?? undefined,
quotaInitTotal: toNumberOrNull(record.quotaInitTotal) ?? undefined,
quotaTotalPercentage: toNumberOrNull(record.quotaTotalPercentage) ?? undefined,
quotaValidityPeriod: toNumberOrNull(record.quotaValidityPeriod) ?? undefined,
};
}
export function parseAlibabaFreeTierQuotaEntries(payload: unknown): AlibabaFreeTierQuotaEntry[] {
const root = asRecord(payload);
const dataV2 = asRecord(asRecord(root.data).DataV2 ?? root.DataV2);
const inner = asRecord(dataV2.data);
const payloadData = asRecord(inner.data ?? inner);
const quotas = payloadData.freeTierQuotas;
if (!Array.isArray(quotas)) return [];
return quotas
.map((entry) => parseQuotaEntry(entry))
.filter((entry): entry is AlibabaFreeTierQuotaEntry => entry !== null);
}
export function classifyAlibabaFreeTierQuotaEntry(
entry: AlibabaFreeTierQuotaEntry,
nowMs: number = Date.now()
): "available" | "capable_unknown" | "drained" | "not_capable" {
if (isAlibabaQuotaValidityExpired(entry, nowMs)) {
return "not_capable";
}
if (!entry.freeTierOnly) {
return "not_capable";
}
if (entry.quotaStatus === "VALID") {
if (typeof entry.quotaTotal === "number") {
return entry.quotaTotal > 0 ? "available" : "drained";
}
return "capable_unknown";
}
if (entry.quotaStatus === "UNKNOWN") {
return "capable_unknown";
}
return "not_capable";
}
export function classifyAlibabaVisionFreeTierQuotaEntry(
entry: AlibabaFreeTierQuotaEntry,
nowMs: number = Date.now()
): "available" | "drained" | "not_capable" {
if (isAlibabaQuotaValidityExpired(entry, nowMs)) {
return "not_capable";
}
if (!isDashscopeVisionModelId(entry.model)) {
return "not_capable";
}
if (entry.quotaStatus === "VALID") {
if (typeof entry.quotaTotal === "number") {
return entry.quotaTotal > 0 ? "available" : "drained";
}
if (typeof entry.quotaInitTotal === "number") {
return entry.quotaInitTotal > 0 ? "available" : "drained";
}
return "not_capable";
}
return "not_capable";
}
export function classifyAlibabaVisionFreeTierQuotaEntries(
entries: readonly AlibabaFreeTierQuotaEntry[]
): AlibabaFreeTierQuotaClassification {
return classifyAlibabaFreeTierQuotaEntriesByModelFilter(entries, isDashscopeVisionModelId, {
useVisionRules: true,
});
}
function classifyAlibabaFreeTierQuotaEntriesByModelFilter(
entries: readonly AlibabaFreeTierQuotaEntry[],
modelFilter: (modelId: string) => boolean,
options: { useVisionRules?: boolean } = {}
): AlibabaFreeTierQuotaClassification {
const capableModels: string[] = [];
const noFreeTierModels: string[] = [];
const drainedModels: string[] = [];
for (const entry of entries) {
if (!modelFilter(entry.model)) continue;
const verdict = options.useVisionRules
? classifyAlibabaVisionFreeTierQuotaEntry(entry)
: classifyAlibabaFreeTierQuotaEntry(entry);
switch (verdict) {
case "available":
capableModels.push(entry.model);
break;
case "capable_unknown":
capableModels.push(entry.model);
break;
case "drained":
if (options.useVisionRules) {
drainedModels.push(entry.model);
} else {
capableModels.push(entry.model);
drainedModels.push(entry.model);
}
break;
case "not_capable":
noFreeTierModels.push(entry.model);
break;
default:
break;
}
}
return {
capableModels: [...new Set(capableModels)],
noFreeTierModels: [...new Set(noFreeTierModels)],
drainedModels: [...new Set(drainedModels)],
entries: entries.filter((entry) => modelFilter(entry.model)),
};
}
export function classifyAlibabaMultimodalFreeTierQuotaEntries(
entries: readonly AlibabaFreeTierQuotaEntry[]
): AlibabaFreeTierQuotaClassification {
return classifyAlibabaFreeTierQuotaEntriesByModelFilter(entries, isDashscopeMultimodalModelId);
}
export function classifyAlibabaAudioFreeTierQuotaEntries(
entries: readonly AlibabaFreeTierQuotaEntry[]
): AlibabaFreeTierQuotaClassification {
return classifyAlibabaFreeTierQuotaEntriesByModelFilter(entries, isDashscopeAudioModelId);
}
export function classifyAlibabaFreeTierQuotaEntries(
entries: readonly AlibabaFreeTierQuotaEntry[],
options: { textOnly?: boolean } = {}
): AlibabaFreeTierQuotaClassification {
const capableModels: string[] = [];
const noFreeTierModels: string[] = [];
const drainedModels: string[] = [];
for (const entry of entries) {
if (options.textOnly && !isDashscopeTextModelId(entry.model)) continue;
const verdict = classifyAlibabaFreeTierQuotaEntry(entry);
switch (verdict) {
case "available":
case "capable_unknown":
capableModels.push(entry.model);
break;
case "drained":
capableModels.push(entry.model);
drainedModels.push(entry.model);
break;
case "not_capable":
noFreeTierModels.push(entry.model);
break;
default:
break;
}
}
return {
capableModels: [...new Set(capableModels)],
noFreeTierModels: [...new Set(noFreeTierModels)],
drainedModels: [...new Set(drainedModels)],
entries: [...entries],
};
}
function unionModelIdLists(lists: readonly (readonly string[])[]): string[] {
return [...new Set(lists.flat())];
}
/** Eligibility is account-agnostic; only drained/quota exhaustion is per-connection. */
const ALIBABA_SHARED_FREE_TIER_ELIGIBILITY_KEYS = [
"alibabaFreeTierCapableModels",
"alibabaNoFreeTierModels",
"alibabaFreeTierVisionCapableModels",
"alibabaNoFreeTierVisionModels",
"alibabaFreeTierMultimodalCapableModels",
"alibabaNoFreeTierMultimodalModels",
"alibabaFreeTierAudioCapableModels",
"alibabaNoFreeTierAudioModels",
"alibabaFreeTierQuotaEntries",
"alibabaFreeTierVisionQuotaEntries",
"alibabaFreeTierMultimodalQuotaEntries",
"alibabaFreeTierAudioQuotaEntries",
"alibabaFreeTierQuotaLastSyncAt",
"alibabaFreeTierDiscoverySource",
] as const;
export function extractAlibabaSharedFreeTierEligibility(
providerSpecificData: Record<string, unknown>
): Record<string, unknown> {
const source = asRecord(providerSpecificData);
const shared: Record<string, unknown> = {};
for (const key of ALIBABA_SHARED_FREE_TIER_ELIGIBILITY_KEYS) {
if (source[key] !== undefined) {
shared[key] = source[key];
}
}
return shared;
}
export function applyAlibabaSharedFreeTierEligibility(
targetPsd: Record<string, unknown>,
shared: Record<string, unknown>
): Record<string, unknown> {
return { ...targetPsd, ...shared };
}
export type AlibabaConnectionLike = {
id: string;
providerSpecificData?: Record<string, unknown> | null;
};
export type AlibabaFreeTierEligibilityFields = {
capableKey: string;
noFreeTierKey: string;
drainedKey: string;
};
export function pickCanonicalAlibabaFreeTierConnection(
connections: readonly AlibabaConnectionLike[],
fields: AlibabaFreeTierEligibilityFields
): AlibabaConnectionLike | undefined {
const freeConnections = connections.filter(
(connection) => getAlibabaBillingMode(connection.providerSpecificData) === "free"
);
const synced = freeConnections.filter((connection) =>
Boolean(getAlibabaFreeTierQuotaLastSyncAt(connection.providerSpecificData))
);
if (synced.length === 0) return undefined;
const withEligibility = synced.filter((connection) => {
const psd = asRecord(connection.providerSpecificData);
const capable = normalizeModelIdList(psd[fields.capableKey]);
const blocked = normalizeModelIdList(psd[fields.noFreeTierKey]);
return capable.length > 0 || blocked.length > 0;
});
const pool = withEligibility.length > 0 ? withEligibility : synced;
return pool.reduce<AlibabaConnectionLike | undefined>((best, current) => {
if (!best) return current;
const bestTime = getAlibabaFreeTierQuotaLastSyncAt(best.providerSpecificData) || "";
const currentTime = getAlibabaFreeTierQuotaLastSyncAt(current.providerSpecificData) || "";
return currentTime.localeCompare(bestTime) > 0 ? current : best;
}, undefined);
}
function resolveAlibabaFreeTierEligibilityLists(
connections: readonly AlibabaConnectionLike[],
fields: AlibabaFreeTierEligibilityFields
): { capable: string[]; noFreeTier: string[]; hasQuotaSync: boolean; quotaSyncAt?: string } {
const freeConnections = connections.filter(
(connection) => getAlibabaBillingMode(connection.providerSpecificData) === "free"
);
const hasQuotaSync = freeConnections.some((connection) =>
Boolean(getAlibabaFreeTierQuotaLastSyncAt(connection.providerSpecificData))
);
const canonical = pickCanonicalAlibabaFreeTierConnection(freeConnections, fields);
if (canonical) {
const psd = asRecord(canonical.providerSpecificData);
return {
capable: normalizeModelIdList(psd[fields.capableKey]),
noFreeTier: normalizeModelIdList(psd[fields.noFreeTierKey]),
hasQuotaSync,
quotaSyncAt: getAlibabaFreeTierQuotaLastSyncAt(psd) || "provider-canonical",
};
}
return {
capable: unionModelIdLists(
freeConnections.map((connection) =>
normalizeModelIdList(asRecord(connection.providerSpecificData)[fields.capableKey])
)
),
noFreeTier: unionModelIdLists(
freeConnections.map((connection) =>
normalizeModelIdList(asRecord(connection.providerSpecificData)[fields.noFreeTierKey])
)
),
hasQuotaSync,
};
}
function buildAlibabaCategoryFilterContext(
connections: readonly AlibabaConnectionLike[],
connectionId: string,
fields: {
capableKey: string;
noFreeTierKey: string;
drainedKey: string;
}
): Record<string, unknown> {
const freeConnections = connections.filter(
(connection) => getAlibabaBillingMode(connection.providerSpecificData) === "free"
);
const target = freeConnections.find((connection) => connection.id === connectionId);
const targetPsd = asRecord(target?.providerSpecificData);
const eligibility = resolveAlibabaFreeTierEligibilityLists(freeConnections, fields);
const merged: Record<string, unknown> = {
alibabaBillingMode: "free",
[fields.capableKey]: eligibility.capable,
[fields.noFreeTierKey]: eligibility.noFreeTier,
[fields.drainedKey]: normalizeModelIdList(targetPsd[fields.drainedKey]),
};
if (eligibility.hasQuotaSync) {
merged.alibabaFreeTierQuotaLastSyncAt =
eligibility.quotaSyncAt || getAlibabaFreeTierQuotaLastSyncAt(targetPsd) || "provider-merged";
}
return merged;
}
export function buildAlibabaFreeVisionFilterContext(
connections: readonly AlibabaConnectionLike[],
connectionId: string
): Record<string, unknown> {
return buildAlibabaCategoryFilterContext(connections, connectionId, {
capableKey: "alibabaFreeTierVisionCapableModels",
noFreeTierKey: "alibabaNoFreeTierVisionModels",
drainedKey: "alibabaFreeTierVisionDrainedModels",
});
}
export function buildAlibabaFreeMultimodalFilterContext(
connections: readonly AlibabaConnectionLike[],
connectionId: string
): Record<string, unknown> {
return buildAlibabaCategoryFilterContext(connections, connectionId, {
capableKey: "alibabaFreeTierMultimodalCapableModels",
noFreeTierKey: "alibabaNoFreeTierMultimodalModels",
drainedKey: "alibabaFreeTierMultimodalDrainedModels",
});
}
export function buildAlibabaFreeAudioFilterContext(
connections: readonly AlibabaConnectionLike[],
connectionId: string
): Record<string, unknown> {
return buildAlibabaCategoryFilterContext(connections, connectionId, {
capableKey: "alibabaFreeTierAudioCapableModels",
noFreeTierKey: "alibabaNoFreeTierAudioModels",
drainedKey: "alibabaFreeTierAudioDrainedModels",
});
}
const ALIBABA_TEXT_ELIGIBILITY_FIELDS: AlibabaFreeTierEligibilityFields = {
capableKey: "alibabaFreeTierCapableModels",
noFreeTierKey: "alibabaNoFreeTierModels",
drainedKey: "alibabaFreeDrainedModels",
};
export function buildAlibabaFreeTierTextFilterContext(
connections: readonly AlibabaConnectionLike[],
connectionId: string
): Record<string, unknown> {
const freeConnections = connections.filter(
(connection) => getAlibabaBillingMode(connection.providerSpecificData) === "free"
);
const target = freeConnections.find((connection) => connection.id === connectionId);
const targetPsd = asRecord(target?.providerSpecificData);
const eligibility = resolveAlibabaFreeTierEligibilityLists(
freeConnections,
ALIBABA_TEXT_ELIGIBILITY_FIELDS
);
const useBuiltinFallback =
!eligibility.hasQuotaSync || !isAlibabaLiveQuotaSyncAt(eligibility.quotaSyncAt ?? null);
const merged: Record<string, unknown> = {
alibabaBillingMode: "free",
alibabaFreeTierCapableModels: useBuiltinFallback
? unionModelIdLists([eligibility.capable, getAlibabaBuiltinFreeTierTextCapableModels()])
: eligibility.capable,
alibabaNoFreeTierModels: useBuiltinFallback
? unionModelIdLists([eligibility.noFreeTier, getAlibabaBuiltinNoFreeTierTextModels()])
: eligibility.noFreeTier,
alibabaFreeDrainedModels: normalizeModelIdList(targetPsd.alibabaFreeDrainedModels),
};
const syncAt =
eligibility.quotaSyncAt ||
getAlibabaFreeTierQuotaLastSyncAt(targetPsd) ||
(useBuiltinFallback ? "builtin-allowlist" : null);
if (syncAt) {
merged.alibabaFreeTierQuotaLastSyncAt = syncAt;
}
return merged;
}
export function getAlibabaFreeTierVisionCapableModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaFreeTierVisionCapableModels);
}
export function getAlibabaFreeTierVisionDrainedModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaFreeTierVisionDrainedModels);
}
export function getAlibabaNoFreeTierVisionModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaNoFreeTierVisionModels);
}
export function isAlibabaFreeTierVisionCapableModel(
modelId: string,
providerSpecificData: Record<string, unknown> | null | undefined
): boolean {
const noFreeTier = new Set(getAlibabaNoFreeTierVisionModels(providerSpecificData));
if (noFreeTier.has(modelId)) return false;
const drained = new Set(getAlibabaFreeTierVisionDrainedModels(providerSpecificData));
if (drained.has(modelId)) return false;
const capable = new Set(getAlibabaFreeTierVisionCapableModels(providerSpecificData));
if (capable.has(modelId)) return true;
if (getAlibabaFreeTierQuotaLastSyncAt(providerSpecificData)) {
return false;
}
return isDashscopeVisionModelId(modelId);
}
export function filterAlibabaFreeVisionEligibleModels(
modelIds: readonly string[],
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return filterAlibabaFreeCategoryEligibleModels(
modelIds,
providerSpecificData,
isDashscopeVisionModelId,
getAlibabaFreeTierVisionCapableModels,
getAlibabaFreeTierVisionDrainedModels,
getAlibabaNoFreeTierVisionModels
);
}
function getAlibabaFreeTierMultimodalCapableModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(
asRecord(providerSpecificData).alibabaFreeTierMultimodalCapableModels
);
}
function getAlibabaFreeTierMultimodalDrainedModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(
asRecord(providerSpecificData).alibabaFreeTierMultimodalDrainedModels
);
}
function getAlibabaNoFreeTierMultimodalModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaNoFreeTierMultimodalModels);
}
function getAlibabaFreeTierAudioCapableModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaFreeTierAudioCapableModels);
}
function getAlibabaFreeTierAudioDrainedModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaFreeTierAudioDrainedModels);
}
function getAlibabaNoFreeTierAudioModels(
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return normalizeModelIdList(asRecord(providerSpecificData).alibabaNoFreeTierAudioModels);
}
function filterAlibabaFreeCategoryEligibleModels(
modelIds: readonly string[],
providerSpecificData: Record<string, unknown> | null | undefined,
modelTypeCheck: (modelId: string) => boolean,
getCapable: (psd: Record<string, unknown> | null | undefined) => string[],
getDrained: (psd: Record<string, unknown> | null | undefined) => string[],
getNoFreeTier: (psd: Record<string, unknown> | null | undefined) => string[]
): string[] {
const drained = new Set(getDrained(providerSpecificData));
return modelIds.filter((id) => {
if (!modelTypeCheck(id)) return false;
if (drained.has(id)) return false;
return isAlibabaFreeCategoryCapableModel(
id,
providerSpecificData,
modelTypeCheck,
getCapable,
getDrained,
getNoFreeTier
);
});
}
function isAlibabaFreeCategoryCapableModel(
modelId: string,
providerSpecificData: Record<string, unknown> | null | undefined,
modelTypeCheck: (modelId: string) => boolean,
getCapable: (psd: Record<string, unknown> | null | undefined) => string[],
getDrained: (psd: Record<string, unknown> | null | undefined) => string[],
getNoFreeTier: (psd: Record<string, unknown> | null | undefined) => string[]
): boolean {
const noFreeTier = new Set(getNoFreeTier(providerSpecificData));
if (noFreeTier.has(modelId)) return false;
const drained = new Set(getDrained(providerSpecificData));
if (drained.has(modelId)) return false;
const capable = new Set(getCapable(providerSpecificData));
if (capable.has(modelId)) return true;
if (getAlibabaFreeTierQuotaLastSyncAt(providerSpecificData)) {
return false;
}
return modelTypeCheck(modelId);
}
export function isAlibabaFreeTierMultimodalCapableModel(
modelId: string,
providerSpecificData: Record<string, unknown> | null | undefined
): boolean {
return isAlibabaFreeCategoryCapableModel(
modelId,
providerSpecificData,
isDashscopeMultimodalModelId,
getAlibabaFreeTierMultimodalCapableModels,
getAlibabaFreeTierMultimodalDrainedModels,
getAlibabaNoFreeTierMultimodalModels
);
}
export function isAlibabaFreeTierAudioCapableModel(
modelId: string,
providerSpecificData: Record<string, unknown> | null | undefined
): boolean {
return isAlibabaFreeCategoryCapableModel(
modelId,
providerSpecificData,
isDashscopeAudioModelId,
getAlibabaFreeTierAudioCapableModels,
getAlibabaFreeTierAudioDrainedModels,
getAlibabaNoFreeTierAudioModels
);
}
export function filterAlibabaFreeMultimodalEligibleModels(
modelIds: readonly string[],
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return filterAlibabaFreeCategoryEligibleModels(
modelIds,
providerSpecificData,
isDashscopeMultimodalModelId,
getAlibabaFreeTierMultimodalCapableModels,
getAlibabaFreeTierMultimodalDrainedModels,
getAlibabaNoFreeTierMultimodalModels
);
}
export function filterAlibabaFreeAudioEligibleModels(
modelIds: readonly string[],
providerSpecificData: Record<string, unknown> | null | undefined
): string[] {
return filterAlibabaFreeCategoryEligibleModels(
modelIds,
providerSpecificData,
isDashscopeAudioModelId,
getAlibabaFreeTierAudioCapableModels,
getAlibabaFreeTierAudioDrainedModels,
getAlibabaNoFreeTierAudioModels
);
}

View File

@@ -0,0 +1,519 @@
/**
* @file alibabaFreeTierQuotaFetcher.ts
* @description Fetch Alibaba Model Studio free-tier quota from the Bailian console API.
*
* DashScope inference keys cannot list free-tier eligibility. The console exposes
* `zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuotaAsyn` with per-model
* `freeTierOnly`, `quotaStatus`, and `quotaTotal` fields.
*
* Auth: browser session cookie (`login_aliyunid_ticket` or full Cookie header) stored
* on the connection as `providerSpecificData.alibabaConsoleCookie`.
*
* Parsing/classification/eligibility-filtering logic lives in
* `alibabaFreeTierQuotaClassify.ts` and shared types/primitives in
* `alibabaFreeTierQuotaTypes.ts` (split out to stay under the file-size cap); this file
* re-exports their public API so existing imports of this module keep working
* unchanged, and owns the HTTP/console-fetch flow itself.
*
* @changes
* - [2026-07-25] [Composer] - Merge built-in text free-tier allowlist into filter context
* - [2026-07-25] [Composer] - Propagate shared free-tier eligibility across all Alibaba free connections
* - [2026-07-25] [Composer] - Add multimodal and audio free-quota classification and console fetch paths
* - [2026-07-25] [Composer] - Add vision/media free-quota classification for alibabafreevision
* - [2026-07-25] [Composer] - Add console free-tier quota fetcher for Alibaba Model Studio
* - [2026-07-25] [Composer] - Use shared toNumberOrNull instead of local coercion helper
* - [2026-08-05] - Split classification/eligibility logic into alibabaFreeTierQuotaClassify.ts
* and alibabaFreeTierQuotaTypes.ts to stay under the file-size cap
*/
import { getAlibabaBillingMode, isAlibabaModelStudioProvider } from "./alibabaFreeTier.ts";
import {
asRecord,
getAlibabaFreeTierQuotaLastSyncAt,
normalizeModelIdList,
toTrimmedString,
type AlibabaFreeTierQuotaEntry,
type AlibabaFreeTierQuotaSnapshot,
} from "./alibabaFreeTierQuotaTypes.ts";
import {
applyAlibabaSharedFreeTierEligibility,
classifyAlibabaAudioFreeTierQuotaEntries,
classifyAlibabaFreeTierQuotaEntries,
classifyAlibabaMultimodalFreeTierQuotaEntries,
classifyAlibabaVisionFreeTierQuotaEntries,
extractAlibabaSharedFreeTierEligibility,
parseAlibabaFreeTierQuotaEntries,
} from "./alibabaFreeTierQuotaClassify.ts";
// Re-export the shared types + the classification/eligibility public API so existing
// imports of this module (`from "./alibabaFreeTierQuotaFetcher.ts"`) keep working.
export type {
AlibabaFreeTierQuotaEntry,
AlibabaFreeTierQuotaClassification,
AlibabaFreeTierQuotaSnapshot,
} from "./alibabaFreeTierQuotaTypes.ts";
export { getAlibabaFreeTierQuotaLastSyncAt, isAlibabaLiveQuotaSyncAt } from "./alibabaFreeTierQuotaTypes.ts";
export {
isAlibabaQuotaValidityExpired,
parseAlibabaFreeTierQuotaEntries,
classifyAlibabaFreeTierQuotaEntry,
classifyAlibabaVisionFreeTierQuotaEntry,
classifyAlibabaVisionFreeTierQuotaEntries,
classifyAlibabaMultimodalFreeTierQuotaEntries,
classifyAlibabaAudioFreeTierQuotaEntries,
classifyAlibabaFreeTierQuotaEntries,
extractAlibabaSharedFreeTierEligibility,
applyAlibabaSharedFreeTierEligibility,
pickCanonicalAlibabaFreeTierConnection,
buildAlibabaFreeVisionFilterContext,
buildAlibabaFreeMultimodalFilterContext,
buildAlibabaFreeAudioFilterContext,
buildAlibabaFreeTierTextFilterContext,
getAlibabaFreeTierVisionCapableModels,
getAlibabaFreeTierVisionDrainedModels,
getAlibabaNoFreeTierVisionModels,
isAlibabaFreeTierVisionCapableModel,
filterAlibabaFreeVisionEligibleModels,
isAlibabaFreeTierMultimodalCapableModel,
isAlibabaFreeTierAudioCapableModel,
filterAlibabaFreeMultimodalEligibleModels,
filterAlibabaFreeAudioEligibleModels,
type AlibabaConnectionLike,
type AlibabaFreeTierEligibilityFields,
} from "./alibabaFreeTierQuotaClassify.ts";
const FREE_TIER_QUOTA_API = "zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuotaAsyn";
const FREE_TIER_QUOTA_START_API = "zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuota";
const DEFAULT_TEXT_FE_PATH = "/costing-balance/free-quota";
const DEFAULT_VISION_FE_PATH =
process.env.ALIBABA_FREE_TIER_VISION_FE_PATH?.trim() || "/costing-balance/free-quota-image-video";
const DEFAULT_MULTIMODAL_FE_PATH =
process.env.ALIBABA_FREE_TIER_MULTIMODAL_FE_PATH?.trim() ||
"/costing-balance/free-quota-multimodal";
const DEFAULT_AUDIO_FE_PATH =
process.env.ALIBABA_FREE_TIER_AUDIO_FE_PATH?.trim() || "/costing-balance/free-quota-audio";
const CONSOLE_GATEWAYS = {
"global-sg": {
host: "https://bailian-singapore-cs.alibabacloud.com",
region: "ap-southeast-1",
action: "IntlBroadScopeAspnGateway",
product: "sfm_bailian",
},
"china-beijing": {
host: "https://bailian.console.aliyun.com",
region: "cn-beijing",
action: "BroadScopeAspnGateway",
product: "sfm_bailian",
},
} as const;
type AlibabaProviderRegion = keyof typeof CONSOLE_GATEWAYS;
function resolveAlibabaConsoleRegion(
providerSpecificData: Record<string, unknown> | null | undefined
): AlibabaProviderRegion {
const region = toTrimmedString(asRecord(providerSpecificData).region);
return region === "china-beijing" ? "china-beijing" : "global-sg";
}
export function normalizeAlibabaConsoleCookie(raw: unknown): string | null {
const value = toTrimmedString(raw);
if (!value) return null;
if (/login_aliyunid_ticket=/i.test(value) || value.includes(";")) {
return value;
}
return `login_aliyunid_ticket=${value}`;
}
export function getAlibabaConsoleCookie(
providerSpecificData: Record<string, unknown> | null | undefined
): string | null {
const psd = asRecord(providerSpecificData);
return (
normalizeAlibabaConsoleCookie(psd.alibabaConsoleCookie) ||
normalizeAlibabaConsoleCookie(psd.cookie) ||
null
);
}
export function getAlibabaConsoleSecToken(
providerSpecificData: Record<string, unknown> | null | undefined
): string | null {
return toTrimmedString(asRecord(providerSpecificData).alibabaConsoleSecToken);
}
export function hasAlibabaConsoleFreeTierAuth(
providerSpecificData: Record<string, unknown> | null | undefined
): boolean {
return getAlibabaConsoleCookie(providerSpecificData) !== null;
}
export function mergeAlibabaFreeTierQuotaClassification(
providerSpecificData: Record<string, unknown> | null | undefined,
snapshot: AlibabaFreeTierQuotaSnapshot
): Record<string, unknown> {
const base = asRecord(providerSpecificData);
const coalesceList = (snapshotList: readonly string[], existingKey: string): string[] =>
snapshotList.length > 0 ? [...snapshotList] : normalizeModelIdList(base[existingKey]);
return {
...base,
alibabaFreeTierCapableModels: coalesceList(
snapshot.text.capableModels,
"alibabaFreeTierCapableModels"
),
alibabaNoFreeTierModels: coalesceList(
snapshot.text.noFreeTierModels,
"alibabaNoFreeTierModels"
),
alibabaFreeDrainedModels: coalesceList(snapshot.text.drainedModels, "alibabaFreeDrainedModels"),
alibabaFreeTierVisionCapableModels: coalesceList(
snapshot.vision.capableModels,
"alibabaFreeTierVisionCapableModels"
),
alibabaNoFreeTierVisionModels: coalesceList(
snapshot.vision.noFreeTierModels,
"alibabaNoFreeTierVisionModels"
),
alibabaFreeTierVisionDrainedModels: coalesceList(
snapshot.vision.drainedModels,
"alibabaFreeTierVisionDrainedModels"
),
alibabaFreeTierMultimodalCapableModels: coalesceList(
snapshot.multimodal.capableModels,
"alibabaFreeTierMultimodalCapableModels"
),
alibabaNoFreeTierMultimodalModels: coalesceList(
snapshot.multimodal.noFreeTierModels,
"alibabaNoFreeTierMultimodalModels"
),
alibabaFreeTierMultimodalDrainedModels: coalesceList(
snapshot.multimodal.drainedModels,
"alibabaFreeTierMultimodalDrainedModels"
),
alibabaFreeTierAudioCapableModels: coalesceList(
snapshot.audio.capableModels,
"alibabaFreeTierAudioCapableModels"
),
alibabaNoFreeTierAudioModels: coalesceList(
snapshot.audio.noFreeTierModels,
"alibabaNoFreeTierAudioModels"
),
alibabaFreeTierAudioDrainedModels: coalesceList(
snapshot.audio.drainedModels,
"alibabaFreeTierAudioDrainedModels"
),
alibabaFreeTierQuotaEntries: snapshot.entries,
alibabaFreeTierVisionQuotaEntries: snapshot.vision.entries,
alibabaFreeTierMultimodalQuotaEntries: snapshot.multimodal.entries,
alibabaFreeTierAudioQuotaEntries: snapshot.audio.entries,
alibabaFreeTierQuotaLastSyncAt: new Date().toISOString(),
alibabaFreeTierDiscoverySource: "console-quota-api",
};
}
export async function propagateAlibabaFreeTierEligibilityToSiblings(
provider: string,
sourceConnectionId: string,
mergedPsd: Record<string, unknown>
): Promise<void> {
const shared = extractAlibabaSharedFreeTierEligibility(mergedPsd);
if (!shared.alibabaFreeTierQuotaLastSyncAt) return;
const { getProviderConnections, updateProviderConnection } =
await import("../../src/lib/db/providers.ts");
const connections = await getProviderConnections({ provider });
for (const connection of connections) {
if (connection.id === sourceConnectionId) continue;
const psd = connection.providerSpecificData as Record<string, unknown> | null | undefined;
if (getAlibabaBillingMode(psd) !== "free") continue;
const updated = applyAlibabaSharedFreeTierEligibility(
asRecord(connection.providerSpecificData),
shared
);
await updateProviderConnection(connection.id as string, { providerSpecificData: updated });
}
}
function buildGatewayUrl(region: AlibabaProviderRegion, api: string): string {
const gateway = CONSOLE_GATEWAYS[region];
const params = new URLSearchParams({
action: gateway.action,
product: gateway.product,
api,
_v: "undefined",
});
return `${gateway.host}/data/api.json?${params.toString()}`;
}
function buildCornerstoneParam(
region: AlibabaProviderRegion,
fePath: string = DEFAULT_TEXT_FE_PATH
): Record<string, unknown> {
const gateway = CONSOLE_GATEWAYS[region];
const normalizedPath = fePath.startsWith("/") ? fePath : `/${fePath}`;
return {
feTraceId: crypto.randomUUID(),
feURL: `https://modelstudio.console.alibabacloud.com/${gateway.region}?tab=costing-balance#${normalizedPath}`,
protocol: "V2",
console: "ONE_CONSOLE",
productCode: "p_efm",
switchAgent: 416572,
switchUserType: 3,
domain: "modelstudio.console.alibabacloud.com",
consoleSite: "MODELSTUDIO_ALBABACLOUD",
userNickName: "",
userPrincipalName: "",
xsp_lang: "en-US",
};
}
function buildRequestBody(
region: AlibabaProviderRegion,
api: string,
taskId?: string | null,
fePath: string = DEFAULT_TEXT_FE_PATH
): URLSearchParams {
const gateway = CONSOLE_GATEWAYS[region];
const request: Record<string, unknown> = {};
if (taskId) {
request.queryFreeTierQuotaRequest = { taskId };
} else {
request.queryFreeTierQuotaRequest = {};
}
request.cornerstoneParam = buildCornerstoneParam(region, fePath);
const body = new URLSearchParams({
params: JSON.stringify({
Api: api,
V: "1.0",
Data: request,
}),
region: gateway.region,
});
return body;
}
async function postConsoleFreeTierQuota(
region: AlibabaProviderRegion,
api: string,
cookie: string,
secToken: string | null,
taskId?: string | null,
fePath: string = DEFAULT_TEXT_FE_PATH
): Promise<unknown> {
const body = buildRequestBody(region, api, taskId, fePath);
if (secToken) {
body.set("sec_token", secToken);
}
const response = await fetch(buildGatewayUrl(region, api), {
method: "POST",
headers: {
Accept: "*/*",
"Content-Type": "application/x-www-form-urlencoded",
Cookie: cookie,
Origin: "https://modelstudio.console.alibabacloud.com",
Referer: "https://modelstudio.console.alibabacloud.com/",
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
},
body: body.toString(),
signal: AbortSignal.timeout(15_000),
});
return response.json();
}
function extractTaskId(payload: unknown): string | null {
const root = asRecord(payload);
const dataV2 = asRecord(asRecord(root.data).DataV2 ?? root.DataV2);
const inner = asRecord(dataV2.data);
const payloadData = asRecord(inner.data ?? inner);
return toTrimmedString(payloadData.taskId);
}
function hasQuotaPayload(payload: unknown): boolean {
return parseAlibabaFreeTierQuotaEntries(payload).length > 0;
}
async function delay(ms: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchAlibabaFreeTierQuotaEntriesForPath(
providerSpecificData: Record<string, unknown> | null | undefined,
fePath: string
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
const cookie = getAlibabaConsoleCookie(providerSpecificData);
if (!cookie) return null;
const region = resolveAlibabaConsoleRegion(providerSpecificData);
const secToken = getAlibabaConsoleSecToken(providerSpecificData);
let payload = await postConsoleFreeTierQuota(
region,
FREE_TIER_QUOTA_START_API,
cookie,
secToken,
null,
fePath
);
if (!hasQuotaPayload(payload)) {
payload = await postConsoleFreeTierQuota(
region,
FREE_TIER_QUOTA_API,
cookie,
secToken,
null,
fePath
);
}
if (!hasQuotaPayload(payload)) {
const taskId = extractTaskId(payload);
if (!taskId) return null;
for (let attempt = 0; attempt < 8; attempt += 1) {
if (attempt > 0) {
await delay(400);
}
payload = await postConsoleFreeTierQuota(
region,
FREE_TIER_QUOTA_API,
cookie,
secToken,
taskId,
fePath
);
if (hasQuotaPayload(payload)) break;
}
}
const entries = parseAlibabaFreeTierQuotaEntries(payload);
return entries.length > 0 ? entries : null;
}
export async function fetchAlibabaFreeTierQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_TEXT_FE_PATH);
}
export async function fetchAlibabaFreeTierVisionQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_VISION_FE_PATH);
}
export async function fetchAlibabaFreeTierMultimodalQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_MULTIMODAL_FE_PATH);
}
export async function fetchAlibabaFreeTierAudioQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_AUDIO_FE_PATH);
}
function mergeUniqueQuotaEntries(
...entryGroups: Array<readonly AlibabaFreeTierQuotaEntry[]>
): AlibabaFreeTierQuotaEntry[] {
const merged: AlibabaFreeTierQuotaEntry[] = [];
const seen = new Set<string>();
for (const group of entryGroups) {
for (const entry of group) {
if (seen.has(entry.model)) continue;
seen.add(entry.model);
merged.push(entry);
}
}
return merged;
}
export async function buildAlibabaFreeTierQuotaSnapshot(
providerSpecificData: Record<string, unknown> | null | undefined
): Promise<AlibabaFreeTierQuotaSnapshot | null> {
const textEntries = await fetchAlibabaFreeTierQuotaEntries(providerSpecificData);
if (!textEntries) return null;
const visionEntries =
(await fetchAlibabaFreeTierVisionQuotaEntries(providerSpecificData)) || textEntries;
const multimodalEntries =
(await fetchAlibabaFreeTierMultimodalQuotaEntries(providerSpecificData)) || textEntries;
const audioEntries =
(await fetchAlibabaFreeTierAudioQuotaEntries(providerSpecificData)) || textEntries;
const text = classifyAlibabaFreeTierQuotaEntries(textEntries, { textOnly: true });
const vision = classifyAlibabaVisionFreeTierQuotaEntries(visionEntries);
const multimodal = classifyAlibabaMultimodalFreeTierQuotaEntries(multimodalEntries);
const audio = classifyAlibabaAudioFreeTierQuotaEntries(audioEntries);
return {
text,
vision,
multimodal,
audio,
entries: mergeUniqueQuotaEntries(textEntries, visionEntries, multimodalEntries, audioEntries),
};
}
export async function refreshAlibabaFreeTierQuotaClassification(
provider: string,
providerSpecificData: Record<string, unknown> | null | undefined
): Promise<Record<string, unknown> | null> {
if (
!isAlibabaModelStudioProvider(provider) ||
getAlibabaBillingMode(providerSpecificData) !== "free"
) {
return null;
}
if (!hasAlibabaConsoleFreeTierAuth(providerSpecificData)) {
return null;
}
const snapshot = await buildAlibabaFreeTierQuotaSnapshot(providerSpecificData);
if (!snapshot) return null;
return mergeAlibabaFreeTierQuotaClassification(providerSpecificData, snapshot);
}
type QuotaConnection = {
id: string;
providerSpecificData?: Record<string, unknown> | null;
};
export function scheduleAlibabaFreeTierQuotaRefresh(
provider: string,
connection: QuotaConnection
): void {
if (!hasAlibabaConsoleFreeTierAuth(connection.providerSpecificData)) return;
void (async () => {
try {
const merged = await refreshAlibabaFreeTierQuotaClassification(
provider,
connection.providerSpecificData
);
if (!merged) return;
const { updateProviderConnection } = await import("../../src/lib/db/providers.ts");
await updateProviderConnection(connection.id, { providerSpecificData: merged });
await propagateAlibabaFreeTierEligibilityToSiblings(provider, connection.id, merged);
} catch (error) {
console.warn("[alibaba-free-tier] console quota refresh failed", {
connectionId: connection.id,
error: error instanceof Error ? error.message : String(error),
});
}
})();
}

View File

@@ -0,0 +1,58 @@
/**
* @file alibabaFreeTierQuotaTypes.ts
* @description Shared types + small primitive helpers for the Alibaba free-tier quota
* fetcher/classifier split (extracted from alibabaFreeTierQuotaFetcher.ts to keep that
* file under the file-size cap; behavior is unchanged).
*/
export type AlibabaFreeTierQuotaEntry = {
model: string;
freeTierOnly: boolean;
quotaStatus: string;
quotaTotal?: number;
quotaInitTotal?: number;
quotaTotalPercentage?: number;
quotaValidityPeriod?: number;
};
export type AlibabaFreeTierQuotaClassification = {
capableModels: string[];
noFreeTierModels: string[];
drainedModels: string[];
entries: AlibabaFreeTierQuotaEntry[];
};
export type AlibabaFreeTierQuotaSnapshot = {
text: AlibabaFreeTierQuotaClassification;
vision: AlibabaFreeTierQuotaClassification;
multimodal: AlibabaFreeTierQuotaClassification;
audio: AlibabaFreeTierQuotaClassification;
entries: AlibabaFreeTierQuotaEntry[];
};
export function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
: {};
}
export function toTrimmedString(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
export function normalizeModelIdList(raw: unknown): string[] {
if (!Array.isArray(raw)) return [];
return raw.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
}
export function getAlibabaFreeTierQuotaLastSyncAt(
providerSpecificData: Record<string, unknown> | null | undefined
): string | null {
return toTrimmedString(asRecord(providerSpecificData).alibabaFreeTierQuotaLastSyncAt);
}
/** True when the connection has a live console/API quota snapshot (not builtin fallback). */
export function isAlibabaLiveQuotaSyncAt(syncAt: string | null | undefined): boolean {
if (!syncAt || syncAt === "builtin-allowlist") return false;
return Number.isFinite(Date.parse(syncAt));
}

View File

@@ -32,6 +32,26 @@
import { wildcardMatch } from "../wildcardRouter.ts";
import { getProviderModels } from "../../config/providerModels.ts";
import { getActiveSyncedCatalog } from "../../../src/lib/db/models/activeSyncedCatalog.ts";
import { filterAlibabaFreeTierModels, isAlibabaModelStudioProvider } from "../alibabaFreeTier.ts";
import {
filterAlibabaFreeEligibleModels,
buildAlibabaFreeTierFilterContext,
} from "../alibabaFreeTierDiscovery.ts";
import {
buildAlibabaFreeAudioFilterContext,
buildAlibabaFreeMultimodalFilterContext,
buildAlibabaFreeVisionFilterContext,
filterAlibabaFreeAudioEligibleModels,
filterAlibabaFreeMultimodalEligibleModels,
filterAlibabaFreeVisionEligibleModels,
} from "../alibabaFreeTierQuotaFetcher.ts";
import type { AlibabaConnectionLike } from "../alibabaFreeTierQuotaFetcher.ts";
import {
isAlibabaFreeTierAudioComboName,
isAlibabaFreeTierMultimodalComboName,
isAlibabaFreeTierTextComboName,
isAlibabaFreeTierVisionComboName,
} from "../dashscopeTextModels.ts";
import type { ComboLike } from "./types.ts";
/** Sentinel pattern used for "all models of a provider". */
@@ -130,6 +150,61 @@ async function collectProviderModelIds(providerId: string): Promise<string[]> {
return getProviderModels(providerId).map((model) => model.id);
}
async function filterAlibabaFreeDrainedModelIds(
providerId: string,
modelIds: string[],
connectionId: string | null,
comboName: string
): Promise<string[]> {
if (!isAlibabaModelStudioProvider(providerId) || !connectionId) {
return modelIds;
}
try {
const { getProviderConnections } = await import("../../../src/lib/db/providers.ts");
const connections = await getProviderConnections({ provider: providerId });
const connection = connections.find((entry) => entry.id === connectionId);
if (!connection) return modelIds;
if (isAlibabaFreeTierVisionComboName(comboName)) {
return filterAlibabaFreeVisionEligibleModels(
modelIds,
buildAlibabaFreeVisionFilterContext(
connections as unknown as readonly AlibabaConnectionLike[],
connectionId
)
);
}
if (isAlibabaFreeTierMultimodalComboName(comboName)) {
return filterAlibabaFreeMultimodalEligibleModels(
modelIds,
buildAlibabaFreeMultimodalFilterContext(
connections as unknown as readonly AlibabaConnectionLike[],
connectionId
)
);
}
if (isAlibabaFreeTierAudioComboName(comboName)) {
return filterAlibabaFreeAudioEligibleModels(
modelIds,
buildAlibabaFreeAudioFilterContext(
connections as unknown as readonly AlibabaConnectionLike[],
connectionId
)
);
}
return filterAlibabaFreeEligibleModels(
modelIds,
buildAlibabaFreeTierFilterContext(
connections as unknown as readonly AlibabaConnectionLike[],
connectionId
),
{ strictAllowlist: isAlibabaFreeTierTextComboName(comboName) }
);
} catch {
return modelIds;
}
}
/**
* Expand a single provider-wildcard spec into concrete model entry objects
* that `normalizeComboStep` can process as normal model steps.
@@ -141,9 +216,16 @@ async function expandWildcardSpec(
spec: ProviderWildcardSpec,
comboName: string
): Promise<unknown[] | null> {
const modelIds = await collectProviderModelIds(spec.providerId);
let modelIds = await collectProviderModelIds(spec.providerId);
if (modelIds.length === 0) return null;
modelIds = await filterAlibabaFreeDrainedModelIds(
spec.providerId,
modelIds,
spec.connectionId,
comboName
);
const pattern = spec.modelPattern;
const matchingIds =
pattern === PROVIDER_WILDCARD_SENTINEL

View File

@@ -20,6 +20,10 @@ import {
hasPerModelQuota,
isProviderExhaustedReason,
} from "../accountFallback.ts";
import {
isAlibabaFreeQuotaExhaustedError,
isAlibabaModelStudioProvider,
} from "../alibabaFreeTier.ts";
import { RateLimitReason } from "../../config/constants.ts";
import { isProviderCircuitOpenResult, isRequestScopedUpstreamFailure } from "./comboPredicates.ts";
import { isCloudflareFingerprintRejection } from "../errorClassifier.ts";
@@ -117,6 +121,14 @@ export function applyComboTargetExhaustion(
provider &&
provider !== "unknown"
) {
// Alibaba free-tier drain is model-scoped — the connection and sibling models stay eligible.
if (
result.status === 403 &&
isAlibabaModelStudioProvider(provider) &&
isAlibabaFreeQuotaExhaustedError(opts.errorText)
) {
return false;
}
markAuthLevelExhaustion(target, { result, sets, log, tag });
return true;
}

View File

@@ -161,6 +161,12 @@ async function isTargetSelectableForWeighted(
) {
return false;
}
if (target.provider && rawModel && target.connectionId) {
const { isAlibabaFreeTierModelRoutable } = await import("../alibabaFreeTier.ts");
if (!(await isAlibabaFreeTierModelRoutable(target.provider, target.connectionId, rawModel))) {
return false;
}
}
return isModelAvailable ? await isModelAvailable(target.modelStr, target) : true;
}

View File

@@ -0,0 +1,109 @@
/**
* @file dashscopeTextModels.ts
* @description DashScope / Alibaba Model Studio text and vision model ID heuristics.
*
* @changes
* - [2026-07-25] [Composer] - Add alibabafree text combo name detection for strict allowlist routing
* - [2026-07-25] [Composer] - Add multimodal and audio model detection for free-tier combos
* - [2026-07-25] [Composer] - Add vision/media model detection for alibabafreevision
* - [2026-07-25] [Composer] - Extract DashScope text-model filter for open-sse consumers
*/
const DASHSCOPE_TEXT_MODEL_PREFIXES = [
"qwen",
"qwq-",
"deepseek-",
"glm-",
"kimi-",
"minimax-",
] as const;
const DASHSCOPE_VISION_MODEL_PREFIXES = ["wan", "qwen-image", "happyhorse", "z-image"] as const;
const DASHSCOPE_NON_TEXT_MODEL_TOKEN =
/(?:^|[-_.\/])(?:asr|audio|captioner|embedding|image|livetranslate|omni|ocr|realtime|rerank|s2s|speech|tts|video|vl)(?:$|[-_.\/])/i;
const DASHSCOPE_VISION_MODEL_TOKEN =
/(?:^|[-_.\/])(?:i2v|t2v|r2v|vace|kf2v|videoedit|animate|image-edit)(?:$|[-_.\/])/i;
export function isDashscopeTextModelId(value: unknown): boolean {
if (typeof value !== "string") return false;
const modelId = value.trim().toLowerCase();
if (!modelId || DASHSCOPE_NON_TEXT_MODEL_TOKEN.test(modelId)) return false;
return DASHSCOPE_TEXT_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
}
export function isDashscopeVisionModelId(value: unknown): value is string {
if (typeof value !== "string") return false;
const modelId = value.trim().toLowerCase();
if (!modelId || isDashscopeTextModelId(modelId)) return false;
return (
DASHSCOPE_VISION_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix)) ||
DASHSCOPE_VISION_MODEL_TOKEN.test(modelId)
);
}
const DASHSCOPE_AUDIO_PREFIXES = [
"cosyvoice",
"fun-asr",
"qwen-audio",
"qwen-voice",
"voice-enrollment",
] as const;
const DASHSCOPE_AUDIO_MODEL_TOKEN =
/(?:^|[-_.\/])(?:asr|tts|livetranslate|captioner|speech|voice-design|voice-enrollment)(?:$|[-_.\/])/i;
const DASHSCOPE_MULTIMODAL_MODEL_TOKEN = /(?:^|[-_.\/])omni(?:$|[-_.\/])/i;
export function isDashscopeAudioModelId(value: unknown): value is string {
if (typeof value !== "string") return false;
const modelId = value.trim().toLowerCase();
if (!modelId) return false;
return (
DASHSCOPE_AUDIO_PREFIXES.some((prefix) => modelId.startsWith(prefix)) ||
DASHSCOPE_AUDIO_MODEL_TOKEN.test(modelId)
);
}
export function isDashscopeMultimodalModelId(value: unknown): value is string {
if (typeof value !== "string") return false;
const modelId = value.trim().toLowerCase();
if (!modelId || isDashscopeAudioModelId(modelId) || isDashscopeVisionModelId(modelId)) {
return false;
}
return DASHSCOPE_MULTIMODAL_MODEL_TOKEN.test(modelId);
}
export function isAlibabaFreeTierTextComboName(comboName: string | null | undefined): boolean {
if (!comboName) return false;
const normalized = comboName.trim().toLowerCase();
if (
isAlibabaFreeTierVisionComboName(normalized) ||
isAlibabaFreeTierMultimodalComboName(normalized) ||
isAlibabaFreeTierAudioComboName(normalized)
) {
return false;
}
return normalized === "alibabafree" || normalized.endsWith("alibabafree");
}
export function isAlibabaFreeTierVisionComboName(comboName: string | null | undefined): boolean {
if (!comboName) return false;
const normalized = comboName.trim().toLowerCase();
return normalized === "alibabafreevision" || normalized.endsWith("freevision");
}
export function isAlibabaFreeTierMultimodalComboName(
comboName: string | null | undefined
): boolean {
if (!comboName) return false;
const normalized = comboName.trim().toLowerCase();
return normalized === "alibabafreemultimodal" || normalized.endsWith("freemultimodal");
}
export function isAlibabaFreeTierAudioComboName(comboName: string | null | undefined): boolean {
if (!comboName) return false;
const normalized = comboName.trim().toLowerCase();
return normalized === "alibabafreeaudio" || normalized.endsWith("freeaudio");
}

View File

@@ -251,7 +251,8 @@
"release:contributors": "node scripts/release/gen-contributors.mjs",
"release:uncovered": "node scripts/release/list-uncovered-commits.mjs",
"test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:serial": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/unit/serial/**/*.test.ts\""
"test:unit:serial": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/unit/serial/**/*.test.ts\"",
"alibaba:sync-allowlist": "node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs"
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.1073.0",

View File

@@ -0,0 +1,427 @@
{
"code": "200",
"data": {
"DataV2": {
"data": {
"data": {
"freeTierQuotas": [
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-2025-09-08",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-30b-a3b-captioner",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-vd-2026-01-26",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-vc-realtime-2026-01-15",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "cosyvoice-v3-flash",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-flash-realtime",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-voice-enrollment",
"quotaTotal": 1000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-2025-08-25",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-mtl",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-realtime-2025-11-07",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-flash-2025-09-18",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-flash",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-flash-realtime-2025-09-18",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-realtime-2026-02-10",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-livetranslate-flash-realtime-2025-09-22",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-vc-2026-01-22",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1791820800000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-audio-3.0-tts-flash",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-instruct-flash-2026-01-26",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-2025-11-07",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-flash-2025-11-27",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-vc-realtime-2025-11-27",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-realtime-2025-10-27",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-realtime",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-vd-realtime-2025-12-16",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "cosyvoice-v3-plus",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-instruct-flash-realtime-2026-01-22",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-2026-02-10",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-instruct-flash",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-instruct-flash-realtime",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-mtl-2025-08-25",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1791820800000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-audio-3.0-tts-plus",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-vd-realtime-2026-01-15",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-livetranslate-flash",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-realtime",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-filetrans",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash-filetrans-2025-11-17",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-livetranslate-flash-realtime",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-tts-flash-realtime-2025-11-27",
"quotaTotal": 10000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-livetranslate-flash-realtime-2026-05-19",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-livetranslate-flash-realtime",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-livetranslate-flash-2025-12-01",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-asr-flash",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 36000,
"quotaValidityPeriod": 1789488000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "fun-asr-flash-2026-06-15",
"quotaTotal": 36000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 40,
"model": "qwen-voice-design",
"quotaTotal": 4,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 0,
"freeTierOnly": false,
"quotaTotalPercentage": 0,
"model": "voice-enrollment",
"quotaTotal": 0,
"quotaStatus": "VALID"
}
]
},
"success": true
}
}
}
}

View File

@@ -0,0 +1,201 @@
{
"code": "200",
"data": {
"DataV2": {
"data": {
"data": {
"freeTierQuotas": [
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-plus-2026-03-15",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-flash-realtime-2025-09-15",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-flash-realtime",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-omni-turbo-realtime-2025-05-08",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-flash-realtime-2026-03-15",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-plus",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-flash",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-flash-2025-12-01",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-omni-turbo-realtime",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-omni-turbo",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen2.5-omni-7b",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-plus-realtime",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen-omni-turbo-2025-03-26",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-flash",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-flash-realtime-2025-12-01",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-plus-realtime-2026-03-15",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-flash-realtime",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3.5-omni-flash-2026-03-15",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 1000000,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": true,
"quotaTotalPercentage": 100,
"model": "qwen3-omni-flash-2025-09-15",
"quotaTotal": 1000000,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 0,
"freeTierOnly": false,
"quotaTotalPercentage": 0,
"model": "qwen-omni-turbo-realtime-latest",
"quotaTotal": 0,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 0,
"freeTierOnly": false,
"quotaTotalPercentage": 0,
"model": "qwen-omni-turbo-latest",
"quotaTotal": 0,
"quotaStatus": "VALID"
}
]
},
"success": true
}
}
}
}

View File

@@ -0,0 +1,573 @@
{
"code": "200",
"data": {
"DataV2": {
"ret": ["SUCCESS::接口调用成功"],
"data": {
"data": {
"freeTierQuotas": [
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-vace-plus",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-videoedit",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-kf2v-plus",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-edit-plus",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1789920000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.1-i2v",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-edit-plus-2025-10-30",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-i2v",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-image",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-t2v",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-edit",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-t2v-plus",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-2.0",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-t2v",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "z-image-turbo",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-max-2025-12-30",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-r2v",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-animate-move",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-edit-max-2026-01-16",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-i2v",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.5-t2v-preview",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-t2v-plus",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.0-t2v",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.0-r2v",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.5-t2i-preview",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1790092800000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-2.0-pro-2026-06-22",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-i2v-turbo",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-t2v-turbo",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-t2i-turbo",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.0-video-edit",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1789920000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.1-r2v",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-i2v-2026-04-25",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1789920000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.1-t2v",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.5-i2v-preview",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-t2i-flash",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-i2v-plus",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-t2i-plus",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-i2v-flash",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1790697600000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-t2v-2026-06-12",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1790697600000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-r2v-2026-06-12",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-2.0-2026-03-03",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-edit-max",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-max",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-2.0-pro-2026-03-03",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.5-i2i-preview",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-plus",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-2.0-pro",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-plus-2026-01-09",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-i2v-plus",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-image-pro",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-r2v",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 200,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.1-t2i-plus",
"quotaTotal": 200,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.2-animate-mix",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-t2i",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-2.0-pro-2026-04-22",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-i2v-flash",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-t2v-2026-04-25",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 100,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "qwen-image-edit-plus-2025-12-15",
"quotaTotal": 100,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.6-r2v-flash",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 10,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "happyhorse-1.0-i2v",
"quotaTotal": 10,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 50,
"quotaValidityPeriod": 1786896000000,
"freeTierOnly": false,
"quotaTotalPercentage": 100,
"model": "wan2.7-image",
"quotaTotal": 50,
"quotaStatus": "VALID"
},
{
"quotaInitTotal": 0,
"freeTierOnly": false,
"quotaTotalPercentage": 0,
"model": "qwen-image-3.0-pro",
"quotaTotal": 0,
"quotaStatus": "VALID"
}
]
},
"success": true
}
},
"success": true
}
}

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env node
/**
* @file sync-alibaba-allowlist.mjs
* @description Build config/alibaba-free-tier-allowlist.json from Bailian console quota JSON exports.
*
* Usage:
* node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs path/to/quota.json [...]
* node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs --from-samples
*
* Writes:
* - config/alibaba-free-tier-allowlist.json (repo baseline)
* - ~/.omniroute/alibaba-free-tier-allowlist.json when DATA_DIR unset
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
classifyAlibabaFreeTierQuotaEntries,
parseAlibabaFreeTierQuotaEntries,
} from "../../open-sse/services/alibabaFreeTierQuotaFetcher.ts";
import { isDashscopeTextModelId } from "../../open-sse/services/dashscopeTextModels.ts";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, "../..");
const SAMPLE_FILES = [
"scripts/ops/alibabafreeaudio-quota.sample.json",
"scripts/ops/alibabafreemultimodal-quota.sample.json",
"scripts/ops/alibabafreevision-quota.sample.json",
].map((relativePath) => path.join(repoRoot, relativePath));
function readJsonFile(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function collectInputs(argv) {
if (argv.includes("--from-samples")) {
return SAMPLE_FILES.filter((filePath) => fs.existsSync(filePath));
}
return argv.filter((arg) => !arg.startsWith("-"));
}
function classifyTextEntries(allEntries) {
const capable = new Set();
const noFreeTier = new Set();
for (const entry of allEntries) {
if (!isDashscopeTextModelId(entry.model)) continue;
const classified = classifyAlibabaFreeTierQuotaEntries([entry], { textOnly: true });
for (const modelId of classified.capableModels) capable.add(modelId);
for (const modelId of classified.noFreeTierModels) noFreeTier.add(modelId);
}
return {
capable: [...capable].sort(),
noFreeTier: [...noFreeTier].sort(),
};
}
function main() {
const inputs = collectInputs(process.argv.slice(2));
if (inputs.length === 0) {
console.error("Usage: sync-alibaba-allowlist.mjs <quota.json> [...] | --from-samples");
process.exit(1);
}
const allEntries = [];
for (const inputPath of inputs) {
const payload = readJsonFile(inputPath);
allEntries.push(...parseAlibabaFreeTierQuotaEntries(payload));
}
const { capable, noFreeTier } = classifyTextEntries(allEntries);
if (capable.length === 0) {
console.error("No text free-tier models found in input payloads.");
process.exit(1);
}
const asOf = new Date().toISOString().slice(0, 10);
const validUntil = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
const pack = { asOf, validUntil, capable, noFreeTier };
const serialized = `${JSON.stringify(pack, null, 2)}\n`;
const configPath = path.join(repoRoot, "config", "alibaba-free-tier-allowlist.json");
fs.mkdirSync(path.dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, serialized);
const dataDir = process.env.DATA_DIR?.trim() || path.join(os.homedir(), ".omniroute");
const runtimePath = path.join(dataDir, "alibaba-free-tier-allowlist.json");
fs.mkdirSync(dataDir, { recursive: true });
fs.writeFileSync(runtimePath, serialized);
console.log(`Wrote ${capable.length} capable + ${noFreeTier.length} blocked models`);
console.log(` config: ${configPath}`);
console.log(` runtime: ${runtimePath}`);
console.log(` validUntil: ${validUntil}`);
}
main();

View File

@@ -7,12 +7,16 @@ export type QuotaScrapingFieldValues = {
opencodeGoWorkspaceId: string;
opencodeGoAuthCookie: string;
ollamaCloudUsageCookie: string;
alibabaConsoleCookie: string;
alibabaConsoleSecToken: string;
};
export const EMPTY_QUOTA_SCRAPING_FIELDS: QuotaScrapingFieldValues = {
opencodeGoWorkspaceId: "",
opencodeGoAuthCookie: "",
ollamaCloudUsageCookie: "",
alibabaConsoleCookie: "",
alibabaConsoleSecToken: "",
};
export function assignQuotaScrapingProviderData(
@@ -27,6 +31,14 @@ export function assignQuotaScrapingProviderData(
}
} else if (provider === "ollama-cloud" && values.ollamaCloudUsageCookie.trim()) {
target.ollamaCloudUsageCookie = values.ollamaCloudUsageCookie.trim();
} else if (
(provider === "alibaba" || provider === "alibaba-cn") &&
values.alibabaConsoleCookie.trim()
) {
target.alibabaConsoleCookie = values.alibabaConsoleCookie.trim();
if (values.alibabaConsoleSecToken.trim()) {
target.alibabaConsoleSecToken = values.alibabaConsoleSecToken.trim();
}
}
}
@@ -109,5 +121,54 @@ export default function QuotaScrapingFields({
);
}
if (provider === "alibaba" || provider === "alibaba-cn") {
return (
<div className="flex flex-col gap-3 rounded-lg border border-border/50 bg-surface/20 p-4">
<Input
label={providerText(
t,
"alibabaConsoleCookieLabel",
"Alibaba console cookie (free-tier sync)"
)}
name="alibabaConsoleCookie"
type="password"
value={values.alibabaConsoleCookie}
onChange={(e) => onChange({ alibabaConsoleCookie: e.target.value })}
placeholder="login_aliyunid_ticket=..."
hint={providerText(
t,
"alibabaConsoleCookieHint",
editMode
? "Leave blank to keep the stored cookie. Paste login_aliyunid_ticket or the full Cookie header from modelstudio.console.alibabacloud.com."
: "Paste login_aliyunid_ticket or the full Cookie header from the Model Studio console Free Quota page."
)}
autoComplete="off"
spellCheck={false}
autoCapitalize="off"
/>
<Input
label={providerText(
t,
"alibabaConsoleSecTokenLabel",
"Alibaba console sec_token (optional)"
)}
name="alibabaConsoleSecToken"
type="password"
value={values.alibabaConsoleSecToken}
onChange={(e) => onChange({ alibabaConsoleSecToken: e.target.value })}
placeholder="KmdQ..."
hint={providerText(
t,
"alibabaConsoleSecTokenHint",
"Optional. Copy sec_token from the Free Quota network request if cookie-only sync fails."
)}
autoComplete="off"
spellCheck={false}
autoCapitalize="off"
/>
</div>
);
}
return null;
}

View File

@@ -20,35 +20,18 @@ import {
} from "@omniroute/open-sse/config/providers/registry/kimi/coding/runtime.ts";
import { ALIBABA_MODEL_STUDIO_MODELS } from "@omniroute/open-sse/config/providers/registry/alibaba/index.ts";
import { QWEN_CLOUD_TEXT_MODELS } from "@omniroute/open-sse/config/providers/registry/qwen-cloud/index.ts";
import { filterAlibabaFreeEligibleModels } from "@omniroute/open-sse/services/alibabaFreeTierDiscovery.ts";
import { shouldUseLiveAlibabaFreeModelDiscovery } from "@omniroute/open-sse/services/alibabaFreeTier.ts";
import { isDashscopeTextModelId } from "@omniroute/open-sse/services/dashscopeTextModels.ts";
import { extractZaiToken } from "@omniroute/open-sse/executors/zai-web.ts";
import { normalizeOpenAiLikeModelsResponse } from "./normalizers";
const DASHSCOPE_TEXT_MODEL_PREFIXES = [
"qwen",
"qwq-",
"deepseek-",
"glm-",
"kimi-",
"minimax-",
] as const;
// DashScope's OpenAI-compatible /models response contains only the standard
// id/object/owned_by fields for Alibaba and Qwen Cloud, so there is no upstream
// modality field to filter on. Keep known text-generation families and reject IDs
// whose tokenized names identify media, speech, embedding, reranking, or vision-only lines.
const DASHSCOPE_NON_TEXT_MODEL_TOKEN =
/(?:^|[-_.\/])(?:asr|audio|captioner|embedding|image|livetranslate|omni|ocr|realtime|rerank|s2s|speech|tts|video|vl)(?:$|[-_.\/])/i;
const QWEN_CLOUD_TEXT_MODEL_IDS = new Set(QWEN_CLOUD_TEXT_MODELS.map((model) => model.id));
const ALIBABA_MODEL_STUDIO_MODEL_IDS = new Set(
ALIBABA_MODEL_STUDIO_MODELS.map((model) => model.id)
);
export function isDashscopeTextModelId(value: unknown): value is string {
if (typeof value !== "string") return false;
const modelId = value.trim().toLowerCase();
if (!modelId || DASHSCOPE_NON_TEXT_MODEL_TOKEN.test(modelId)) return false;
return DASHSCOPE_TEXT_MODEL_PREFIXES.some((prefix) => modelId.startsWith(prefix));
}
export { isDashscopeTextModelId };
export function parseDashscopeTextModels(data: any): any[] {
const models = Array.isArray(data?.data)
@@ -83,6 +66,23 @@ export function parseAlibabaModelStudioModels(data: any): any[] {
);
}
export function parseAlibabaModelStudioModelsForConnection(
data: any,
providerSpecificData?: Record<string, unknown> | null
): any[] {
if (shouldUseLiveAlibabaFreeModelDiscovery(providerSpecificData)) {
const models = parseDashscopeTextModels(data);
const eligibleIds = new Set(
filterAlibabaFreeEligibleModels(
models.map((model: { id?: string }) => model.id).filter(Boolean) as string[],
providerSpecificData
)
);
return models.filter((model: { id?: string }) => model.id && eligibleIds.has(model.id));
}
return parseAlibabaModelStudioModels(data);
}
export function parseQwenCloudTextModels(data: any): any[] {
return parseCuratedDashscopeModels(data, QWEN_CLOUD_TEXT_MODELS, QWEN_CLOUD_TEXT_MODEL_IDS);
}

View File

@@ -2322,7 +2322,15 @@ export async function GET(
}
const data = await response.json();
const pageModels = config.parseResponse(data);
let pageModels = config.parseResponse(data);
if (provider === "alibaba" || provider === "alibaba-cn") {
const { parseAlibabaModelStudioModelsForConnection } =
await import("./discovery/providerModelsConfig.ts");
pageModels = parseAlibabaModelStudioModelsForConnection(
data,
connection.providerSpecificData as Record<string, unknown> | null | undefined
);
}
allModels = allModels.concat(pageModels);
const nextPageToken = data.nextPageToken;
@@ -2344,6 +2352,45 @@ export async function GET(
);
}
if (provider === "alibaba" || provider === "alibaba-cn") {
const { shouldUseLiveAlibabaFreeModelDiscovery } =
await import("@omniroute/open-sse/services/alibabaFreeTier.ts");
const { scheduleAlibabaFreeTierProbeRefresh } =
await import("@omniroute/open-sse/services/alibabaFreeTierDiscovery.ts");
const { scheduleAlibabaFreeTierQuotaRefresh, hasAlibabaConsoleFreeTierAuth } =
await import("@omniroute/open-sse/services/alibabaFreeTierQuotaFetcher.ts");
const { resolveAlibabaProviderBaseUrl } =
await import("@/shared/constants/alibabaProviderRegions.ts");
const providerSpecificData = connection.providerSpecificData as Record<
string,
unknown
> | null;
if (shouldUseLiveAlibabaFreeModelDiscovery(providerSpecificData)) {
if (hasAlibabaConsoleFreeTierAuth(providerSpecificData)) {
scheduleAlibabaFreeTierQuotaRefresh(provider, {
id: connectionId,
providerSpecificData,
});
} else {
const baseUrl = resolveAlibabaProviderBaseUrl(
provider,
providerSpecificData,
paginationBaseUrl.replace(/\/models$/, "")
);
scheduleAlibabaFreeTierProbeRefresh(
provider,
{
id: connectionId,
apiKey: token,
providerSpecificData,
},
allModels,
`${baseUrl.replace(/\/$/, "")}/chat/completions`
);
}
}
}
return buildApiDiscoveryResponse(allModels);
} catch (error) {
if (error instanceof SafeOutboundFetchError && error.code === "URL_GUARD_BLOCKED") {

View File

@@ -76,6 +76,8 @@ export function summarizeProviderConnectionForAudit(connection: unknown) {
if (Object.keys(providerSpecificData).length > 0) {
const sanitizedProviderSpecificData = { ...providerSpecificData };
delete sanitizedProviderSpecificData.consoleApiKey;
delete sanitizedProviderSpecificData.alibabaConsoleCookie;
delete sanitizedProviderSpecificData.alibabaConsoleSecToken;
sanitized.providerSpecificData = sanitizedProviderSpecificData;
}

View File

@@ -311,6 +311,8 @@ export function validateProviderSpecificData(
"ollamaCloudUsageCookie",
"ollamaCloudCookie",
"usageCookie",
"alibabaConsoleCookie",
"alibabaConsoleSecToken",
] as const) {
const value = data[key];
if (value !== undefined && value !== null && typeof value !== "string") {

View File

@@ -107,6 +107,7 @@ import { isSubscriptionQuotaText } from "@omniroute/open-sse/services/quotaTextC
import { resolveUseUpstream429BreakerHints } from "@/shared/utils/providerHints";
import { getCircuitBreaker, isLocalStreamLifecycleError } from "../../shared/utils/circuitBreaker";
import { markAccountExhaustedFrom429 } from "../../domain/quotaCache";
import { resolveForcedConnectionForCredentialPool } from "../services/sessionAffinityPin.ts";
import { RequestTelemetry, recordTelemetry } from "../../shared/utils/requestTelemetry";
import { generateRequestId } from "../../shared/utils/requestId";
import { logAuditEvent } from "../../lib/compliance/index";
@@ -1310,9 +1311,19 @@ async function handleSingleModelChat(
...(!forceLiveComboTest && bypassProviderQuotaPolicy
? { bypassQuotaPolicy: true }
: {}),
...(runtimeOptions.forcedConnectionId
? { forcedConnectionId: runtimeOptions.forcedConnectionId }
: {}),
...(() => {
const effectiveForcedId = resolveForcedConnectionForCredentialPool({
forcedConnectionId: runtimeOptions.forcedConnectionId ?? null,
excludedConnectionIds,
connections: [],
allowRateLimitedConnections:
runtimeOptions.allowRateLimitedConnection === true || forceLiveComboTest,
bypassQuotaPolicy: forceLiveComboTest || bypassProviderQuotaPolicy,
isQuotaExhausted: () => false,
isQuotaPolicyBlocked: () => false,
});
return effectiveForcedId ? { forcedConnectionId: effectiveForcedId } : {};
})(),
}
);
preselectedCredentials = null;

View File

@@ -56,6 +56,15 @@ import {
classifyProviderError,
PROVIDER_ERROR_TYPES,
} from "@omniroute/open-sse/services/errorClassifier.ts";
import {
ALIBABA_FREE_DRAINED_LOCK_MS,
getAlibabaBillingMode,
isAlibabaFreeQuotaExhaustedError,
isAlibabaModelFreeDrained,
isAlibabaModelStudioProvider,
mergeAlibabaFreeDrainedModels,
rehydrateAlibabaFreeDrainedModelLocks,
} from "@omniroute/open-sse/services/alibabaFreeTier.ts";
import {
getCodexModelScope,
@@ -74,6 +83,7 @@ import { isModelExcludedByConnection } from "@/domain/connectionModelRules";
import {
applySessionAffinityPin,
formatSessionKeyForLog,
resolveForcedConnectionForCredentialPool,
resolveSessionAffinityTtlMs,
selectSessionAffinityConnection,
} from "./sessionAffinityPin";
@@ -1038,6 +1048,15 @@ export async function getProviderCredentials(
let connections = (Array.isArray(connectionsRaw) ? connectionsRaw : [])
.map(createLazyConnectionView)
.filter((conn) => conn.id.length > 0);
if (isAlibabaModelStudioProvider(provider)) {
for (const conn of connections) {
rehydrateAlibabaFreeDrainedModelLocks(
provider,
conn.id,
conn.providerSpecificData as Record<string, unknown>
);
}
}
// allowedConnections: restrict to specific connection IDs (from API key policy, #363)
if (allowedConnections && allowedConnections.length > 0) {
connections = connections.filter((conn) => allowedConnections.includes(conn.id));
@@ -1060,6 +1079,19 @@ export async function getProviderCredentials(
evaluateQuotaLimitPolicy(provider, c as ProviderConnectionView, requestedModel).blocked,
}) ?? forcedConnectionId;
forcedConnectionId = resolveForcedConnectionForCredentialPool({
forcedConnectionId,
excludedConnectionIds,
connections,
allowRateLimitedConnections,
bypassQuotaPolicy,
isQuotaExhausted: (connectionId) =>
isQuotaExhaustedForRequest(connectionId, provider, requestedModel),
isQuotaPolicyBlocked: (connection) =>
evaluateQuotaLimitPolicy(provider, connection as ProviderConnectionView, requestedModel)
.blocked,
});
if (forcedConnectionId) {
connections = connections.filter((conn) => conn.id === forcedConnectionId);
}
@@ -1205,7 +1237,15 @@ export async function getProviderCredentials(
return false;
}
// Per-model lockout: if this specific model/family is locked on this connection, skip it
if (requestedModel && isModelLocked(provider, c.id, requestedModel)) {
if (
requestedModel &&
(isModelLocked(provider, c.id, requestedModel) ||
isAlibabaModelFreeDrained(
provider,
c.providerSpecificData as Record<string, unknown>,
requestedModel
))
) {
connectionFilterStatus.set(c.id, "modelLocked");
if (
provider === "antigravity" &&
@@ -1987,6 +2027,9 @@ export async function markAccountUnavailable(
// Read passthroughModels from connection config (user-configured per-model quota)
const connProviderSpecificData = (conn?.providerSpecificData as Record<string, unknown>) || {};
if (provider && conn) {
rehydrateAlibabaFreeDrainedModelLocks(provider, connectionId, connProviderSpecificData);
}
const connectionPassthroughModels = connProviderSpecificData.passthroughModels as
boolean | undefined;
// #2997: per-connection opt-out of the TRANSIENT connection cooldown. When set,
@@ -2096,6 +2139,51 @@ export async function markAccountUnavailable(
if (!shouldFallback) return { shouldFallback: false, cooldownMs: 0 };
const providerErrorType = classifyProviderError(status, errorText, provider);
if (
isAlibabaModelStudioProvider(provider) &&
status === 403 &&
model &&
isAlibabaFreeQuotaExhaustedError(errorText)
) {
const billingMode = getAlibabaBillingMode(connProviderSpecificData);
if (billingMode === "free") {
const persistedProviderSpecificData = mergeAlibabaFreeDrainedModels(
connProviderSpecificData,
model
);
await updateProviderConnection(connectionId, {
providerSpecificData: persistedProviderSpecificData,
lastErrorType: "free_quota_exhausted",
lastError: `Model ${model} free quota exhausted`,
lastErrorAt: new Date().toISOString(),
errorCode: status,
});
rehydrateAlibabaFreeDrainedModelLocks(
provider!,
connectionId,
persistedProviderSpecificData
);
recordModelLockoutFailure(
provider!,
connectionId,
model!,
"free_quota_exhausted",
status,
0,
effectiveProviderProfile,
{
exactCooldownMs: ALIBABA_FREE_DRAINED_LOCK_MS,
maxCooldownMs: ALIBABA_FREE_DRAINED_LOCK_MS,
}
);
log.info(
"AUTH",
`Alibaba free-tier drain for ${provider}:${model} — model permanently removed from routing (billingMode=free)`
);
return { shouldFallback: true, cooldownMs: 0 };
}
}
if (provider && resolveProviderId(provider) === "grok-web" && status === 403 && model) {
const lockout = recordModelLockoutFailure(
provider,

View File

@@ -17,6 +17,9 @@
* connection, so the existing 429-driven `deleteSessionAccountAffinity`
* failover still owns rotating away from a pin that stops working.
*
* @changes
* - [2026-07-24] [Composer] - Drop forcedConnectionId when excluded or ineligible (429 loop fix)
*
* This module stays decoupled from auth.ts internals: the three predicates that
* live in (or would cause a cycle back into) auth.ts —
* `isTerminalConnectionStatus`, `isCodexScopeUnavailable`, and the quota-policy
@@ -163,9 +166,7 @@ export function resolveSessionAffinityTtlMs(
): number {
const override = Number(options.sessionAffinityTtlMs);
if (Number.isFinite(override) && override > 0) return override;
const configured = Number(
settings.sessionAffinityTtlMs ?? settings.codexSessionAffinityTtlMs
);
const configured = Number(settings.sessionAffinityTtlMs ?? settings.codexSessionAffinityTtlMs);
if (Number.isFinite(configured) && configured > 0) return configured;
return 0;
}
@@ -255,3 +256,41 @@ export function applySessionAffinityPin(params: ApplySessionAffinityPinParams):
);
return pinned.connectionId;
}
export interface ResolveForcedConnectionForPoolParams {
forcedConnectionId: string | null;
excludedConnectionIds: ReadonlySet<string>;
connections: AffinityPinConnection[];
allowRateLimitedConnections: boolean;
bypassQuotaPolicy: boolean;
isQuotaExhausted: (connectionId: string) => boolean;
isQuotaPolicyBlocked: (connection: AffinityPinConnection) => boolean;
}
/**
* Reset-aware combo routing pins a single `forcedConnectionId` per target. When
* that account 429s (quota exhausted / cooldown), the chat retry loop excludes
* it — but keeping the force would narrow the pool back to the same dead
* account. Drop the pin whenever the forced id is excluded or no longer eligible.
*/
export function resolveForcedConnectionForCredentialPool(
params: ResolveForcedConnectionForPoolParams
): string | null {
const forced = params.forcedConnectionId?.trim() || null;
if (!forced || params.excludedConnectionIds.has(forced)) return null;
if (params.connections.length === 0) {
return forced;
}
const forcedConn = params.connections.find((conn) => conn.id === forced);
if (!forcedConn) return null;
if (!params.allowRateLimitedConnections && isAccountUnavailable(forcedConn.rateLimitedUntil)) {
return null;
}
if (params.isQuotaExhausted(forced)) return null;
if (!params.bypassQuotaPolicy && params.isQuotaPolicyBlocked(forcedConn)) return null;
return forced;
}

View File

@@ -0,0 +1,44 @@
/**
* @file alibaba-free-tier-allowlist.test.ts
* @description Tests for built-in Alibaba free-tier text model allowlists.
*
* @changes
* - [2026-07-25] [Composer] - Add built-in allowlist regression tests
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
ALIBABA_FREE_TIER_TEXT_CAPABLE_MODELS,
ALIBABA_NO_FREE_TIER_TEXT_MODELS,
isAlibabaBuiltinFreeTierTextModel,
isAlibabaBuiltinNoFreeTierTextModel,
isAlibabaFreeTierAllowlistPackValid,
loadAlibabaFreeTierAllowlistPack,
resetAlibabaFreeTierAllowlistCache,
} from "../../open-sse/services/alibabaFreeTierAllowlist.ts";
test("built-in allowlist includes operator free models and excludes paid blocklist", () => {
assert.ok(ALIBABA_FREE_TIER_TEXT_CAPABLE_MODELS.includes("qwen3.6-plus"));
assert.ok(ALIBABA_FREE_TIER_TEXT_CAPABLE_MODELS.includes("glm-5.2"));
assert.ok(ALIBABA_NO_FREE_TIER_TEXT_MODELS.includes("qwen3.7-max"));
assert.ok(ALIBABA_NO_FREE_TIER_TEXT_MODELS.includes("glm-5.2-fast-preview"));
assert.equal(isAlibabaBuiltinFreeTierTextModel("qwen3.6-plus"), true);
assert.equal(isAlibabaBuiltinNoFreeTierTextModel("kimi-k2.7-code"), true);
assert.equal(isAlibabaBuiltinFreeTierTextModel("qwen3.7-max"), false);
});
test("allowlist JSON pack overrides embedded lists when valid", () => {
const previousPath = process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH;
const packPath = `${process.cwd()}/config/alibaba-free-tier-allowlist.json`;
process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH = packPath;
resetAlibabaFreeTierAllowlistCache();
const pack = loadAlibabaFreeTierAllowlistPack();
assert.ok(pack);
assert.ok(isAlibabaFreeTierAllowlistPackValid(pack!));
assert.ok(pack!.capable.includes("qwen3.6-plus"));
if (previousPath) process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH = previousPath;
else delete process.env.ALIBABA_FREE_TIER_ALLOWLIST_PATH;
resetAlibabaFreeTierAllowlistCache();
});

View File

@@ -0,0 +1,180 @@
/**
* @file alibaba-free-tier-discovery.test.ts
* @description Tests for probe-based Alibaba free-tier model classification.
*
* @changes
* - [2026-07-25] [Composer] - Add Alibaba free-tier probe classification tests
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
buildAlibabaFreeTierFilterContext,
classifyAlibabaFreeTierProbe,
filterAlibabaFreeEligibleModels,
isAlibabaFreeTierCapableModel,
isAlibabaNativeFreeTierModelFamily,
isAlibabaThirdPartyPaidModelFamily,
mergeAlibabaFreeTierProbeResults,
} from "../../open-sse/services/alibabaFreeTierDiscovery.ts";
const FREE_TIER_ONLY_BODY = JSON.stringify({
error: {
code: "AllocationQuota.FreeTierOnly",
message:
'The free quota has been exhausted. To continue accessing the model on a paid basis, please disable the "use free tier only" mode in the management console.',
},
});
test("classifyAlibabaFreeTierProbe: FreeTierOnly means capable but drained", () => {
const result = classifyAlibabaFreeTierProbe("qwen3.6-plus", 403, FREE_TIER_ONLY_BODY);
assert.equal(result.verdict, "capable_drained");
});
test("classifyAlibabaFreeTierProbe: native 200 means capable with remaining quota", () => {
const result = classifyAlibabaFreeTierProbe("qwen3-coder-plus", 200, "{}");
assert.equal(result.verdict, "capable_available");
});
test("classifyAlibabaFreeTierProbe: kimi 200 is paid-only third-party family", () => {
const result = classifyAlibabaFreeTierProbe("kimi-k2.7-code", 200, "{}");
assert.equal(result.verdict, "not_capable");
assert.equal(isAlibabaThirdPartyPaidModelFamily("kimi-k2.7-code"), true);
assert.equal(isAlibabaNativeFreeTierModelFamily("kimi-k2.7-code"), false);
});
test("mergeAlibabaFreeTierProbeResults persists capable and no-free-tier sets", () => {
const merged = mergeAlibabaFreeTierProbeResults(
{ alibabaBillingMode: "free" },
[
{ modelId: "qwen3.6-plus", verdict: "capable_drained", status: 403 },
{ modelId: "kimi-k2.7-code", verdict: "not_capable", status: 200 },
{ modelId: "qwen3-coder-plus", verdict: "capable_available", status: 200 },
],
"free"
);
assert.ok(merged.alibabaFreeTierCapableModels?.includes("qwen3.6-plus"));
assert.ok(merged.alibabaFreeTierCapableModels?.includes("qwen3-coder-plus"));
assert.ok(merged.alibabaNoFreeTierModels?.includes("kimi-k2.7-code"));
assert.ok(merged.alibabaFreeDrainedModels?.includes("qwen3.6-plus"));
});
test("filterAlibabaFreeEligibleModels excludes drained and paid-only families", () => {
const filtered = filterAlibabaFreeEligibleModels(
["qwen3.6-plus", "qwen3-coder-plus", "kimi-k2.7-code", "glm-5.2"],
{
alibabaFreeDrainedModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["kimi-k2.7-code"],
}
);
assert.deepEqual(filtered, ["qwen3-coder-plus", "glm-5.2"]);
});
test("isAlibabaFreeTierCapableModel defaults native families to capable before probe", () => {
assert.equal(isAlibabaFreeTierCapableModel("qwen3-coder-next", {}), true);
assert.equal(isAlibabaFreeTierCapableModel("kimi-k2.7-code", {}), false);
assert.equal(
isAlibabaFreeTierCapableModel("kimi-k2.7-code", {
alibabaNoFreeTierModels: ["kimi-k2.7-code"],
}),
false
);
});
test("isAlibabaFreeTierCapableModel does not guess when console quota sync exists", () => {
const psd = {
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["qwen3.7-plus"],
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T00:00:00.000Z",
};
assert.equal(isAlibabaFreeTierCapableModel("qwen3.6-plus", psd), true);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.7-plus", psd), false);
assert.equal(isAlibabaFreeTierCapableModel("qwen3-coder-next", psd), true);
assert.equal(isAlibabaFreeTierCapableModel("totally-unknown-model", psd), false);
});
test("buildAlibabaFreeTierFilterContext blocks paid models on unsynced sibling keys", () => {
const merged = buildAlibabaFreeTierFilterContext(
[
{
id: "main",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T00:00:00.000Z",
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["glm-5.2-fast-preview"],
},
},
{
id: "main-2",
providerSpecificData: {
alibabaBillingMode: "free",
},
},
],
"main-2"
);
assert.equal(isAlibabaFreeTierCapableModel("glm-5.2-fast-preview", merged), false);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.6-plus", merged), true);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.7-max", merged), false);
assert.equal(isAlibabaFreeTierCapableModel("totally-unknown-model", merged), false);
});
test("buildAlibabaFreeTierFilterContext prefers sibling with populated free-model lists", () => {
const merged = buildAlibabaFreeTierFilterContext(
[
{
id: "main",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T12:00:00.000Z",
alibabaFreeTierCapableModels: [],
alibabaNoFreeTierModels: [],
},
},
{
id: "main-2",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T06:00:00.000Z",
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["glm-5.2-fast-preview", "qwen3.7-max"],
},
},
],
"main-2"
);
assert.equal(isAlibabaFreeTierCapableModel("glm-5.2-fast-preview", merged), false);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.6-plus", merged), true);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.7-max", merged), false);
assert.equal(isAlibabaFreeTierCapableModel("qwen3-coder-next", merged), true);
});
test("strictAllowlist blocks optimistic qwen/glm guessing for alibabafree combos", () => {
const psd = {
alibabaBillingMode: "free",
alibabaFreeDrainedModels: [],
alibabaNoFreeTierModels: [],
alibabaFreeTierCapableModels: [],
};
assert.equal(isAlibabaFreeTierCapableModel("qwen-not-in-builtin-list", psd), true);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.7-max", psd), false);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.7-max", psd, { strictAllowlist: true }), false);
assert.equal(
isAlibabaFreeTierCapableModel(
"qwen3.6-plus",
{ ...psd, alibabaFreeTierCapableModels: ["qwen3.6-plus"] },
{ strictAllowlist: true }
),
true
);
assert.deepEqual(
filterAlibabaFreeEligibleModels(["qwen3.7-max", "qwen3.6-plus", "glm-5.2-fast-preview"], psd, {
strictAllowlist: true,
}),
["qwen3.6-plus"]
);
});

View File

@@ -0,0 +1,113 @@
/**
* @file alibaba-free-tier-exhaustion.test.ts
* @description Regression tests for Alibaba Model Studio free-tier drain handling.
*
* @changes
* - [2026-07-24] [Composer] - Add free-tier exhaustion detection and combo exhaustion tests
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
ALIBABA_FREE_DRAINED_LOCK_MS,
filterAlibabaFreeTierModels,
getAlibabaBillingMode,
getAlibabaFreeDrainedModels,
isAlibabaFreeQuotaExhaustedError,
isAlibabaModelFreeDrained,
isAlibabaModelStudioProvider,
mergeAlibabaFreeDrainedModels,
} from "../../open-sse/services/alibabaFreeTier.ts";
import {
applyComboTargetExhaustion,
type ComboExhaustionSets,
} from "../../open-sse/services/combo/targetExhaustion.ts";
import {
clearAllModelLockouts,
isModelLocked,
lockModel,
} from "../../open-sse/services/accountFallback.ts";
const ALIBABA_FREE_QUOTA_ERROR =
"The free quota has been exhausted. To continue accessing the model on a paid basis, please complete your payment information (or disable the use free tier only mode).";
test("detects Alibaba free-quota exhaustion phrasing", () => {
assert.equal(isAlibabaFreeQuotaExhaustedError(ALIBABA_FREE_QUOTA_ERROR), true);
assert.equal(isAlibabaFreeQuotaExhaustedError("free tier of the model has been exhausted"), true);
assert.equal(isAlibabaFreeQuotaExhaustedError("Forbidden"), false);
});
test("defaults Alibaba billing mode to paid and supports free override", () => {
assert.equal(getAlibabaBillingMode({}), "paid");
assert.equal(getAlibabaBillingMode({ alibabaBillingMode: "free" }), "free");
});
test("mergeAlibabaFreeDrainedModels persists unique model ids", () => {
const first = mergeAlibabaFreeDrainedModels({}, "qwen3.7-max-preview");
const second = mergeAlibabaFreeDrainedModels(first, "qwen3.6-plus");
const third = mergeAlibabaFreeDrainedModels(second, "qwen3.7-max-preview");
assert.deepEqual(getAlibabaFreeDrainedModels(third), ["qwen3.7-max-preview", "qwen3.6-plus"]);
assert.equal(isAlibabaModelFreeDrained("alibaba", third, "qwen3.7-max-preview"), true);
assert.equal(isAlibabaModelFreeDrained("openai", third, "qwen3.7-max-preview"), false);
});
test("isAlibabaModelStudioProvider recognizes alibaba family ids", () => {
assert.equal(isAlibabaModelStudioProvider("alibaba"), true);
assert.equal(isAlibabaModelStudioProvider("alibaba-cn"), true);
assert.equal(isAlibabaModelStudioProvider("ali"), true);
assert.equal(isAlibabaModelStudioProvider("bailian-coding-plan"), false);
});
test("alibaba free-quota 403 does not exhaust the whole connection in combo routing", () => {
const s: ComboExhaustionSets = {
exhaustedProviders: new Set<string>(),
exhaustedConnections: new Set<string>(),
transientRateLimitedProviders: new Set<string>(),
};
const log = { info() {}, warn() {}, error() {}, debug() {} };
const target = {
kind: "model",
executionKey: "ek",
modelStr: "alibaba/qwen3.7-max-preview",
provider: "alibaba",
providerId: null,
connectionId: "conn-1",
} as Parameters<typeof applyComboTargetExhaustion>[0];
const exhausted = applyComboTargetExhaustion(target, {
result: { status: 403 },
fallbackResult: {},
errorText: ALIBABA_FREE_QUOTA_ERROR,
rawModel: "qwen3.7-max-preview",
isTokenLimitBreach: false,
allAccountsRateLimited: false,
sets: s,
log,
tag: "COMBO",
exhaustedLogLevel: "info",
});
assert.equal(exhausted, false);
assert.equal(s.exhaustedConnections.size, 0);
assert.equal(s.exhaustedProviders.size, 0);
});
test("filterAlibabaFreeTierModels removes only persisted drained models", () => {
const filtered = filterAlibabaFreeTierModels(["qwen3.7-max-preview", "qwen3.6-plus"], {
alibabaFreeDrainedModels: ["qwen3.7-max-preview"],
});
assert.deepEqual(filtered, ["qwen3.6-plus"]);
});
test("permanent free-tier lockout uses a multi-year cooldown window", () => {
clearAllModelLockouts();
lockModel(
"alibaba",
"conn-1",
"qwen3.7-max-preview",
"free_quota_exhausted",
ALIBABA_FREE_DRAINED_LOCK_MS
);
assert.equal(isModelLocked("alibaba", "conn-1", "qwen3.7-max-preview"), true);
clearAllModelLockouts();
});

View File

@@ -0,0 +1,468 @@
/**
* @file alibaba-free-tier-quota-fetcher.test.ts
* @description Tests for Alibaba console free-tier quota API parsing and classification.
*
* @changes
* - [2026-07-25] [Composer] - Add multimodal and audio quota classification tests
* - [2026-07-25] [Composer] - Add console free-tier quota fetcher tests
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
classifyAlibabaAudioFreeTierQuotaEntries,
classifyAlibabaFreeTierQuotaEntries,
classifyAlibabaFreeTierQuotaEntry,
classifyAlibabaMultimodalFreeTierQuotaEntries,
classifyAlibabaVisionFreeTierQuotaEntries,
isAlibabaQuotaValidityExpired,
mergeAlibabaFreeTierQuotaClassification,
normalizeAlibabaConsoleCookie,
parseAlibabaFreeTierQuotaEntries,
applyAlibabaSharedFreeTierEligibility,
buildAlibabaFreeTierTextFilterContext,
extractAlibabaSharedFreeTierEligibility,
isAlibabaFreeTierAudioCapableModel,
isAlibabaFreeTierMultimodalCapableModel,
isAlibabaFreeTierVisionCapableModel,
} from "../../open-sse/services/alibabaFreeTierQuotaFetcher.ts";
import { isAlibabaFreeTierCapableModel } from "../../open-sse/services/alibabaFreeTierDiscovery.ts";
import {
isDashscopeAudioModelId,
isDashscopeMultimodalModelId,
isDashscopeVisionModelId,
} from "../../open-sse/services/dashscopeTextModels.ts";
const SAMPLE_CONSOLE_RESPONSE = {
code: "200",
data: {
DataV2: {
data: {
data: {
freeTierQuotas: [
{
quotaInitTotal: 1000000,
quotaValidityPeriod: 1786896000000,
freeTierOnly: true,
quotaTotalPercentage: 99.98,
model: "qwen3.6-plus",
quotaTotal: 999795,
quotaStatus: "VALID",
},
{
freeTierOnly: false,
model: "qwen3.7-plus",
quotaStatus: "UNKNOWN",
},
{
freeTierOnly: false,
model: "kimi-k2.7-code",
quotaStatus: "UNKNOWN",
},
{
freeTierOnly: true,
model: "glm-5.2",
quotaStatus: "UNKNOWN",
},
{
quotaInitTotal: 0,
freeTierOnly: false,
quotaTotalPercentage: 0,
model: "glm-5.2-fast-preview",
quotaTotal: 0,
quotaStatus: "VALID",
},
{
quotaInitTotal: 1000000,
freeTierOnly: true,
quotaTotalPercentage: 100,
model: "qwen3-vl-plus",
quotaTotal: 1000000,
quotaStatus: "VALID",
},
],
},
success: true,
},
},
},
};
const SAMPLE_VISION_CONSOLE_RESPONSE = {
code: "200",
data: {
DataV2: {
data: {
data: {
freeTierQuotas: [
{
quotaInitTotal: 50,
freeTierOnly: false,
quotaTotalPercentage: 100,
model: "wan2.7-t2v",
quotaTotal: 50,
quotaStatus: "VALID",
},
{
quotaInitTotal: 100,
freeTierOnly: false,
quotaTotalPercentage: 100,
model: "qwen-image-2.0-pro",
quotaTotal: 100,
quotaStatus: "VALID",
},
{
quotaInitTotal: 0,
freeTierOnly: false,
quotaTotalPercentage: 0,
model: "qwen-image-3.0-pro",
quotaTotal: 0,
quotaStatus: "VALID",
},
{
quotaInitTotal: 1000000,
freeTierOnly: true,
quotaTotalPercentage: 100,
model: "qwen3.6-plus",
quotaTotal: 999795,
quotaStatus: "VALID",
},
],
},
success: true,
},
},
},
};
const SAMPLE_MULTIMODAL_CONSOLE_RESPONSE = {
code: "200",
data: {
DataV2: {
data: {
data: {
freeTierQuotas: [
{
quotaInitTotal: 1000000,
freeTierOnly: true,
model: "qwen3.5-omni-plus",
quotaTotal: 1000000,
quotaStatus: "VALID",
},
{
quotaInitTotal: 0,
freeTierOnly: false,
model: "qwen-omni-turbo-latest",
quotaTotal: 0,
quotaStatus: "VALID",
},
{
quotaInitTotal: 36000,
freeTierOnly: true,
model: "qwen3-asr-flash",
quotaTotal: 36000,
quotaStatus: "VALID",
},
],
},
success: true,
},
},
},
};
const SAMPLE_AUDIO_CONSOLE_RESPONSE = {
code: "200",
data: {
DataV2: {
data: {
data: {
freeTierQuotas: [
{
quotaInitTotal: 36000,
freeTierOnly: true,
model: "qwen3-asr-flash",
quotaTotal: 36000,
quotaStatus: "VALID",
},
{
quotaInitTotal: 10000,
freeTierOnly: true,
model: "qwen3-tts-flash",
quotaTotal: 10000,
quotaStatus: "VALID",
},
{
quotaInitTotal: 0,
freeTierOnly: false,
model: "voice-enrollment",
quotaTotal: 0,
quotaStatus: "VALID",
},
{
quotaInitTotal: 1000000,
freeTierOnly: true,
model: "qwen3.5-omni-plus",
quotaTotal: 1000000,
quotaStatus: "VALID",
},
],
},
success: true,
},
},
},
};
test("parseAlibabaFreeTierQuotaEntries reads console payload", () => {
const entries = parseAlibabaFreeTierQuotaEntries(SAMPLE_CONSOLE_RESPONSE);
assert.equal(entries.length, 6);
assert.equal(entries[0].model, "qwen3.6-plus");
assert.equal(entries[0].freeTierOnly, true);
});
test("classifyAlibabaFreeTierQuotaEntry maps console flags", () => {
assert.equal(
classifyAlibabaFreeTierQuotaEntry({
model: "qwen3.6-plus",
freeTierOnly: true,
quotaStatus: "VALID",
quotaTotal: 100,
}),
"available"
);
assert.equal(
classifyAlibabaFreeTierQuotaEntry({
model: "glm-5.2",
freeTierOnly: true,
quotaStatus: "UNKNOWN",
}),
"capable_unknown"
);
assert.equal(
classifyAlibabaFreeTierQuotaEntry({
model: "qwen3.7-plus",
freeTierOnly: false,
quotaStatus: "UNKNOWN",
}),
"not_capable"
);
assert.equal(
classifyAlibabaFreeTierQuotaEntry({
model: "glm-5.2-fast-preview",
freeTierOnly: false,
quotaStatus: "VALID",
quotaTotal: 0,
quotaInitTotal: 0,
}),
"not_capable"
);
});
test("classifyAlibabaFreeTierQuotaEntry treats expired quotaValidityPeriod as not_capable", () => {
assert.equal(
classifyAlibabaFreeTierQuotaEntry(
{
model: "qwen3.6-plus",
freeTierOnly: true,
quotaStatus: "VALID",
quotaTotal: 100,
quotaValidityPeriod: Date.now() - 60_000,
},
Date.now()
),
"not_capable"
);
assert.equal(
isAlibabaQuotaValidityExpired({
model: "qwen3.6-plus",
freeTierOnly: true,
quotaStatus: "VALID",
quotaValidityPeriod: Date.now() + 60_000,
}),
false
);
});
test("classifyAlibabaFreeTierQuotaEntries filters text-only models for alibabafree", () => {
const classification = classifyAlibabaFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_CONSOLE_RESPONSE),
{ textOnly: true }
);
assert.ok(classification.capableModels.includes("qwen3.6-plus"));
assert.ok(classification.capableModels.includes("glm-5.2"));
assert.ok(classification.noFreeTierModels.includes("qwen3.7-plus"));
assert.ok(classification.noFreeTierModels.includes("kimi-k2.7-code"));
assert.ok(classification.noFreeTierModels.includes("glm-5.2-fast-preview"));
assert.equal(classification.capableModels.includes("qwen3-vl-plus"), false);
});
test("classifyAlibabaVisionFreeTierQuotaEntries includes promo vision quota models", () => {
const classification = classifyAlibabaVisionFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_VISION_CONSOLE_RESPONSE)
);
assert.ok(classification.capableModels.includes("wan2.7-t2v"));
assert.ok(classification.capableModels.includes("qwen-image-2.0-pro"));
assert.ok(classification.drainedModels.includes("qwen-image-3.0-pro"));
assert.equal(classification.capableModels.includes("qwen-image-3.0-pro"), false);
assert.equal(classification.capableModels.includes("qwen3.6-plus"), false);
});
test("isDashscopeVisionModelId recognizes wan and qwen-image families", () => {
assert.equal(isDashscopeVisionModelId("wan2.7-t2v"), true);
assert.equal(isDashscopeVisionModelId("qwen-image-plus"), true);
assert.equal(isDashscopeVisionModelId("qwen3.6-plus"), false);
});
test("classifyAlibabaMultimodalFreeTierQuotaEntries requires freeTierOnly omni models", () => {
const classification = classifyAlibabaMultimodalFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_MULTIMODAL_CONSOLE_RESPONSE)
);
assert.ok(classification.capableModels.includes("qwen3.5-omni-plus"));
assert.ok(classification.noFreeTierModels.includes("qwen-omni-turbo-latest"));
assert.equal(classification.capableModels.includes("qwen3-asr-flash"), false);
});
test("classifyAlibabaAudioFreeTierQuotaEntries includes asr and tts free-tier models", () => {
const classification = classifyAlibabaAudioFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_AUDIO_CONSOLE_RESPONSE)
);
assert.ok(classification.capableModels.includes("qwen3-asr-flash"));
assert.ok(classification.capableModels.includes("qwen3-tts-flash"));
assert.ok(classification.noFreeTierModels.includes("voice-enrollment"));
assert.equal(classification.capableModels.includes("qwen3.5-omni-plus"), false);
});
test("isDashscopeMultimodalModelId and isDashscopeAudioModelId classify model families", () => {
assert.equal(isDashscopeMultimodalModelId("qwen3.5-omni-plus"), true);
assert.equal(isDashscopeMultimodalModelId("qwen3-asr-flash"), false);
assert.equal(isDashscopeAudioModelId("qwen3-tts-flash"), true);
assert.equal(isDashscopeAudioModelId("cosyvoice-v3-flash"), true);
assert.equal(isDashscopeAudioModelId("qwen3.5-omni-plus"), false);
});
test("mergeAlibabaFreeTierQuotaClassification makes discovery authoritative", () => {
const text = classifyAlibabaFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_CONSOLE_RESPONSE),
{ textOnly: true }
);
const vision = classifyAlibabaVisionFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_VISION_CONSOLE_RESPONSE)
);
const multimodal = classifyAlibabaMultimodalFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_MULTIMODAL_CONSOLE_RESPONSE)
);
const audio = classifyAlibabaAudioFreeTierQuotaEntries(
parseAlibabaFreeTierQuotaEntries(SAMPLE_AUDIO_CONSOLE_RESPONSE)
);
const merged = mergeAlibabaFreeTierQuotaClassification(
{ alibabaBillingMode: "free" },
{
text,
vision,
multimodal,
audio,
entries: [...text.entries, ...vision.entries, ...multimodal.entries, ...audio.entries],
}
);
assert.equal(merged.alibabaFreeTierDiscoverySource, "console-quota-api");
assert.equal(typeof merged.alibabaFreeTierQuotaLastSyncAt, "string");
assert.ok(merged.alibabaFreeTierVisionCapableModels?.includes("wan2.7-t2v"));
assert.equal(merged.alibabaFreeTierVisionCapableModels?.includes("qwen-image-3.0-pro"), false);
assert.ok(merged.alibabaFreeTierMultimodalCapableModels?.includes("qwen3.5-omni-plus"));
assert.ok(merged.alibabaFreeTierAudioCapableModels?.includes("qwen3-tts-flash"));
assert.equal(isAlibabaFreeTierCapableModel("qwen3.6-plus", merged), true);
assert.equal(isAlibabaFreeTierCapableModel("qwen3.7-plus", merged), false);
assert.equal(isAlibabaFreeTierVisionCapableModel("wan2.7-t2v", merged), true);
assert.equal(isAlibabaFreeTierVisionCapableModel("qwen-image-3.0-pro", merged), false);
assert.equal(isAlibabaFreeTierMultimodalCapableModel("qwen3.5-omni-plus", merged), true);
assert.equal(isAlibabaFreeTierAudioCapableModel("qwen3-tts-flash", merged), true);
});
test("mergeAlibabaFreeTierQuotaClassification preserves text lists on partial category snapshot", () => {
const merged = mergeAlibabaFreeTierQuotaClassification(
{
alibabaBillingMode: "free",
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["qwen3.7-max"],
},
{
text: { capableModels: [], noFreeTierModels: [], drainedModels: [], entries: [] },
vision: {
capableModels: ["wan2.7-t2v"],
noFreeTierModels: [],
drainedModels: [],
entries: [],
},
multimodal: { capableModels: [], noFreeTierModels: [], drainedModels: [], entries: [] },
audio: { capableModels: [], noFreeTierModels: [], drainedModels: [], entries: [] },
entries: [],
}
);
assert.deepEqual(merged.alibabaFreeTierCapableModels, ["qwen3.6-plus"]);
assert.deepEqual(merged.alibabaNoFreeTierModels, ["qwen3.7-max"]);
assert.deepEqual(merged.alibabaFreeTierVisionCapableModels, ["wan2.7-t2v"]);
});
test("buildAlibabaFreeTierTextFilterContext uses canonical sibling free-model lists", () => {
const context = buildAlibabaFreeTierTextFilterContext(
[
{
id: "main",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T12:00:00.000Z",
alibabaFreeTierCapableModels: [],
alibabaNoFreeTierModels: [],
},
},
{
id: "main-2",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T06:00:00.000Z",
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["qwen3.7-max", "glm-5.2-fast-preview"],
},
},
],
"main"
);
assert.ok((context.alibabaFreeTierCapableModels as string[]).includes("qwen3.6-plus"));
assert.equal((context.alibabaFreeTierCapableModels as string[]).includes("glm-5.2"), false);
assert.ok((context.alibabaNoFreeTierModels as string[]).includes("qwen3.7-max"));
assert.ok((context.alibabaNoFreeTierModels as string[]).includes("glm-5.2-fast-preview"));
});
test("shared free-tier eligibility copies lists but preserves per-connection drained models", () => {
const shared = extractAlibabaSharedFreeTierEligibility({
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["qwen3.7-max"],
alibabaFreeTierQuotaLastSyncAt: "2026-07-25T00:00:00.000Z",
});
const applied = applyAlibabaSharedFreeTierEligibility(
{
alibabaBillingMode: "free",
alibabaFreeDrainedModels: ["qwen3.6-plus"],
},
shared
);
assert.deepEqual(applied.alibabaFreeTierCapableModels, ["qwen3.6-plus"]);
assert.deepEqual(applied.alibabaFreeDrainedModels, ["qwen3.6-plus"]);
});
test("normalizeAlibabaConsoleCookie accepts ticket-only paste", () => {
assert.equal(normalizeAlibabaConsoleCookie("abc123ticket"), "login_aliyunid_ticket=abc123ticket");
assert.equal(
normalizeAlibabaConsoleCookie("login_aliyunid_ticket=abc123ticket; cna=xyz"),
"login_aliyunid_ticket=abc123ticket; cna=xyz"
);
});

View File

@@ -32,10 +32,13 @@ const {
const { replaceSyncedAvailableModelsForConnection, getSyncedAvailableModels } =
await import("../../src/lib/db/models.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const core = await import("../../src/lib/db/core.ts");
core.getDbInstance(); // initialise DB + run migrations
const { normalizeComboRecord } = await import("../../src/lib/combos/steps.ts");
// ── Helpers ──────────────────────────────────────────────────────────────────
function makeCombo(models: unknown[], name = "test-combo") {
@@ -406,3 +409,98 @@ test("#8926: inactive synced catalog does not override static wildcard fallback"
"an inactive connection must not make its synced catalog authoritative"
);
});
test("expandProviderWildcardsInCombo: skips Alibaba free-tier drained models for pinned connections", async () => {
const connection = await providersDb.createProviderConnection({
provider: "alibaba",
authType: "apikey",
name: "alibaba-free-wildcard",
apiKey: "test-dashscope-key",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeDrainedModels: ["qwen3.7-max-preview"],
},
});
await seedSyncedModels("alibaba", connection.id, [
"qwen3.7-max-preview",
"qwen3.6-plus",
"glm-5.2",
]);
const combo = makeCombo([
{
kind: "provider-wildcard",
providerId: "alibaba",
modelPattern: "*",
connectionId: connection.id,
},
]);
const result = await expandProviderWildcardsInCombo(combo);
const models = result.models.map((entry) => (entry as { model: string }).model);
assert.equal(models.includes("alibaba/qwen3.7-max-preview"), false);
assert.ok(models.includes("alibaba/qwen3.6-plus"));
assert.ok(models.includes("alibaba/glm-5.2"));
assert.equal(models.includes("alibaba/kimi-k2.7-code"), false);
});
test("expandProviderWildcardsInCombo: alibabafree combo uses strict console allowlist only", async () => {
const connection = await providersDb.createProviderConnection({
provider: "alibaba",
authType: "apikey",
name: "alibaba-free-strict",
apiKey: "test-dashscope-key-strict-allowlist",
providerSpecificData: {
alibabaBillingMode: "free",
alibabaFreeTierCapableModels: ["qwen3.6-plus"],
alibabaNoFreeTierModels: ["glm-5.2-fast-preview", "qwen3.7-max"],
},
});
await seedSyncedModels("alibaba", connection.id, [
"qwen3.6-plus",
"qwen3.7-max",
"glm-5.2-fast-preview",
"glm-5.2",
]);
const combo = makeCombo(
[
{
kind: "provider-wildcard",
providerId: "alibaba",
modelPattern: "*",
connectionId: connection.id,
},
],
"alibabafree"
);
const result = await expandProviderWildcardsInCombo(combo);
const models = result.models.map((entry) => (entry as { model: string }).model);
assert.ok(models.includes("alibaba/qwen3.6-plus"));
assert.ok(models.includes("alibaba/glm-5.2"));
assert.equal(models.includes("alibaba/qwen3.7-max"), false);
assert.equal(models.includes("alibaba/glm-5.2-fast-preview"), false);
});
test("normalizeComboRecord: preserves provider-wildcard steps for DB round-trip", () => {
const normalized = normalizeComboRecord({
name: "alibabafree",
models: [
{
kind: "provider-wildcard",
providerId: "alibaba",
modelPattern: "*",
connectionId: "conn-main",
label: "main",
},
"alibaba/qwen*",
],
});
assert.equal(normalized.models.length, 2);
assert.equal(normalized.models[0].kind, "provider-wildcard");
assert.equal((normalized.models[0] as { providerId: string }).providerId, "alibaba");
assert.equal((normalized.models[0] as { connectionId?: string }).connectionId, "conn-main");
assert.equal(normalized.models[1].kind, "provider-wildcard");
assert.equal((normalized.models[1] as { modelPattern: string }).modelPattern, "qwen*");
});

View File

@@ -176,3 +176,50 @@ test("legacy Alibaba China connections also sync only Beijing text models", asyn
expectedModelIds: ALIBABA_MODEL_STUDIO_MODEL_IDS,
});
});
test("Alibaba free billing mode syncs all live text models, not only the curated catalog", async () => {
await resetStorage();
const connection = await providersDb.createProviderConnection({
provider: "alibaba",
authType: "apikey",
name: "alibaba-free-live",
apiKey: "test-dashscope-key",
providerSpecificData: { region: "global-sg", alibabaBillingMode: "free" },
});
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () =>
new Response(JSON.stringify({ object: "list", data: MIXED_DASHSCOPE_MODELS }), {
status: 200,
headers: { "Content-Type": "application/json" },
})) as typeof globalThis.fetch;
try {
const response = await modelsRoute.GET(
new Request(`http://localhost/api/providers/${connection.id}/models?refresh=true`),
{ params: { id: connection.id } }
);
assert.equal(response.status, 200);
const body = await response.json();
assert.ok(
body.models.length > ALIBABA_MODEL_STUDIO_MODEL_IDS.length,
"free billing mode should expose more than the curated catalog"
);
assert.ok(
body.models.some((model: { id: string }) => model.id === "qwen3-coder-next"),
"expected upstream-only free-tier model to be included"
);
assert.equal(
body.models.some((model: { id: string }) => model.id === "qwen-image-2.0-pro"),
false,
"non-text models must still be filtered out"
);
assert.equal(
body.models.some((model: { id: string }) => model.id === "kimi-k2.7-code"),
false,
"third-party paid-only models must be excluded from free billing discovery"
);
} finally {
globalThis.fetch = originalFetch;
}
});

View File

@@ -0,0 +1,86 @@
import test from "node:test";
import assert from "node:assert/strict";
import { resolveForcedConnectionForCredentialPool } from "../../src/sse/services/sessionAffinityPin.ts";
const conn = (id: string, rateLimitedUntil: string | null = null) => ({
id,
rateLimitedUntil,
});
test("resolveForcedConnectionForCredentialPool drops forced id when excluded after 429 fallback", () => {
const excluded = new Set(["dead-account"]);
assert.equal(
resolveForcedConnectionForCredentialPool({
forcedConnectionId: "dead-account",
excludedConnectionIds: excluded,
connections: [conn("dead-account"), conn("healthy-account")],
allowRateLimitedConnections: false,
bypassQuotaPolicy: false,
isQuotaExhausted: () => false,
isQuotaPolicyBlocked: () => false,
}),
null
);
});
test("resolveForcedConnectionForCredentialPool keeps forced id when eligible", () => {
assert.equal(
resolveForcedConnectionForCredentialPool({
forcedConnectionId: "healthy-account",
excludedConnectionIds: new Set(),
connections: [conn("healthy-account"), conn("other-account")],
allowRateLimitedConnections: false,
bypassQuotaPolicy: false,
isQuotaExhausted: () => false,
isQuotaPolicyBlocked: () => false,
}),
"healthy-account"
);
});
test("resolveForcedConnectionForCredentialPool drops forced id on cooldown", () => {
const future = new Date(Date.now() + 60_000).toISOString();
assert.equal(
resolveForcedConnectionForCredentialPool({
forcedConnectionId: "cooling-account",
excludedConnectionIds: new Set(),
connections: [conn("cooling-account", future)],
allowRateLimitedConnections: false,
bypassQuotaPolicy: false,
isQuotaExhausted: () => false,
isQuotaPolicyBlocked: () => false,
}),
null
);
});
test("resolveForcedConnectionForCredentialPool drops forced id when quota exhausted", () => {
assert.equal(
resolveForcedConnectionForCredentialPool({
forcedConnectionId: "exhausted-account",
excludedConnectionIds: new Set(),
connections: [conn("exhausted-account")],
allowRateLimitedConnections: false,
bypassQuotaPolicy: false,
isQuotaExhausted: (id) => id === "exhausted-account",
isQuotaPolicyBlocked: () => false,
}),
null
);
});
test("resolveForcedConnectionForCredentialPool with empty connections only checks exclusion", () => {
assert.equal(
resolveForcedConnectionForCredentialPool({
forcedConnectionId: "pinned-account",
excludedConnectionIds: new Set(),
connections: [],
allowRateLimitedConnections: false,
bypassQuotaPolicy: false,
isQuotaExhausted: () => true,
isQuotaPolicyBlocked: () => true,
}),
"pinned-account"
);
});