Files
OmniRoute/open-sse/services/alibabaFreeTierQuotaTypes.ts
Andrew B. 21fd0a94f8 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>
2026-08-11 04:25:23 -03:00

59 lines
2.0 KiB
TypeScript

/**
* @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));
}