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

@@ -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;
}