Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(quota):** quota readings use the assigned connection egress path ([#15885](https://github.com/diegosouzapw/OmniRoute/pull/15885)) — thanks @maxmad64bis
4 changes: 3 additions & 1 deletion open-sse/services/agentrouterQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import { registerQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";

const AGENTROUTER_CONFIG = {
baseUrl: "https://agentrouter.org",
Expand Down Expand Up @@ -152,7 +153,7 @@ export async function fetchAgentrouterQuota(
try {
await throttleQuotaFetch();

const response = await fetch(url, {
const response = await fetchWithConnectionProxy(connectionId, url, {
method: "GET",
headers: {
Authorization: `Bearer ${systemAccessToken}`,
Expand All @@ -162,6 +163,7 @@ export async function fetchAgentrouterQuota(
},
signal: AbortSignal.timeout(8_000),
});
if (!response) return null;

if (response.status === 401 || response.status === 403) {
quotaCache.delete(connectionId);
Expand Down
53 changes: 35 additions & 18 deletions open-sse/services/alibabaFreeTierQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
*/

import { getAlibabaBillingMode, isAlibabaModelStudioProvider } from "./alibabaFreeTier.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";
import {
asRecord,
getAlibabaFreeTierQuotaLastSyncAt,
Expand Down Expand Up @@ -302,6 +303,7 @@ function buildRequestBody(
}

async function postConsoleFreeTierQuota(
connectionId: string | null,
region: AlibabaProviderRegion,
api: string,
cookie: string,
Expand All @@ -314,7 +316,10 @@ async function postConsoleFreeTierQuota(
body.set("sec_token", secToken);
}

const response = await fetch(buildGatewayUrl(region, api), {
const response = await fetchWithConnectionProxy(
connectionId ?? "",
buildGatewayUrl(region, api),
{
method: "POST",
headers: {
Accept: "*/*",
Expand All @@ -328,6 +333,7 @@ async function postConsoleFreeTierQuota(
body: body.toString(),
signal: AbortSignal.timeout(15_000),
});
if (!response) return null;

return response.json();
}
Expand All @@ -350,7 +356,8 @@ async function delay(ms: number): Promise<void> {

async function fetchAlibabaFreeTierQuotaEntriesForPath(
providerSpecificData: Record<string, unknown> | null | undefined,
fePath: string
fePath: string,
connectionId?: string | null
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
const cookie = getAlibabaConsoleCookie(providerSpecificData);
if (!cookie) return null;
Expand All @@ -359,6 +366,7 @@ async function fetchAlibabaFreeTierQuotaEntriesForPath(
const secToken = getAlibabaConsoleSecToken(providerSpecificData);

let payload = await postConsoleFreeTierQuota(
connectionId ?? null,
region,
FREE_TIER_QUOTA_START_API,
cookie,
Expand All @@ -369,6 +377,7 @@ async function fetchAlibabaFreeTierQuotaEntriesForPath(

if (!hasQuotaPayload(payload)) {
payload = await postConsoleFreeTierQuota(
connectionId ?? null,
region,
FREE_TIER_QUOTA_API,
cookie,
Expand All @@ -387,6 +396,7 @@ async function fetchAlibabaFreeTierQuotaEntriesForPath(
await delay(400);
}
payload = await postConsoleFreeTierQuota(
connectionId ?? null,
region,
FREE_TIER_QUOTA_API,
cookie,
Expand All @@ -403,27 +413,31 @@ async function fetchAlibabaFreeTierQuotaEntriesForPath(
}

export async function fetchAlibabaFreeTierQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
providerSpecificData: Record<string, unknown> | null | undefined,
connectionId?: string | null
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_TEXT_FE_PATH);
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_TEXT_FE_PATH, connectionId);
}

export async function fetchAlibabaFreeTierVisionQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
providerSpecificData: Record<string, unknown> | null | undefined,
connectionId?: string | null
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_VISION_FE_PATH);
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_VISION_FE_PATH, connectionId);
}

export async function fetchAlibabaFreeTierMultimodalQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
providerSpecificData: Record<string, unknown> | null | undefined,
connectionId?: string | null
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_MULTIMODAL_FE_PATH);
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_MULTIMODAL_FE_PATH, connectionId);
}

export async function fetchAlibabaFreeTierAudioQuotaEntries(
providerSpecificData: Record<string, unknown> | null | undefined
providerSpecificData: Record<string, unknown> | null | undefined,
connectionId?: string | null
): Promise<AlibabaFreeTierQuotaEntry[] | null> {
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_AUDIO_FE_PATH);
return fetchAlibabaFreeTierQuotaEntriesForPath(providerSpecificData, DEFAULT_AUDIO_FE_PATH, connectionId);
}

function mergeUniqueQuotaEntries(
Expand All @@ -442,17 +456,18 @@ function mergeUniqueQuotaEntries(
}

export async function buildAlibabaFreeTierQuotaSnapshot(
providerSpecificData: Record<string, unknown> | null | undefined
providerSpecificData: Record<string, unknown> | null | undefined,
connectionId?: string | null
): Promise<AlibabaFreeTierQuotaSnapshot | null> {
const textEntries = await fetchAlibabaFreeTierQuotaEntries(providerSpecificData);
const textEntries = await fetchAlibabaFreeTierQuotaEntries(providerSpecificData, connectionId);
if (!textEntries) return null;

const visionEntries =
(await fetchAlibabaFreeTierVisionQuotaEntries(providerSpecificData)) || textEntries;
(await fetchAlibabaFreeTierVisionQuotaEntries(providerSpecificData, connectionId)) || textEntries;
const multimodalEntries =
(await fetchAlibabaFreeTierMultimodalQuotaEntries(providerSpecificData)) || textEntries;
(await fetchAlibabaFreeTierMultimodalQuotaEntries(providerSpecificData, connectionId)) || textEntries;
const audioEntries =
(await fetchAlibabaFreeTierAudioQuotaEntries(providerSpecificData)) || textEntries;
(await fetchAlibabaFreeTierAudioQuotaEntries(providerSpecificData, connectionId)) || textEntries;

const text = classifyAlibabaFreeTierQuotaEntries(textEntries, { textOnly: true });
const vision = classifyAlibabaVisionFreeTierQuotaEntries(visionEntries);
Expand All @@ -470,7 +485,8 @@ export async function buildAlibabaFreeTierQuotaSnapshot(

export async function refreshAlibabaFreeTierQuotaClassification(
provider: string,
providerSpecificData: Record<string, unknown> | null | undefined
providerSpecificData: Record<string, unknown> | null | undefined,
connectionId?: string | null
): Promise<Record<string, unknown> | null> {
if (
!isAlibabaModelStudioProvider(provider) ||
Expand All @@ -482,7 +498,7 @@ export async function refreshAlibabaFreeTierQuotaClassification(
return null;
}

const snapshot = await buildAlibabaFreeTierQuotaSnapshot(providerSpecificData);
const snapshot = await buildAlibabaFreeTierQuotaSnapshot(providerSpecificData, connectionId);
if (!snapshot) return null;

return mergeAlibabaFreeTierQuotaClassification(providerSpecificData, snapshot);
Expand All @@ -503,7 +519,8 @@ export function scheduleAlibabaFreeTierQuotaRefresh(
try {
const merged = await refreshAlibabaFreeTierQuotaClassification(
provider,
connection.providerSpecificData
connection.providerSpecificData,
connection.id
);
if (!merged) return;
const { updateProviderConnection } = await import("../../src/lib/db/providers.ts");
Expand Down
7 changes: 5 additions & 2 deletions open-sse/services/bailianQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { registerQuotaFetcher, registerQuotaWindows, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";

// Bailian quota hosts (international / china fallback)
const BAILIAN_QUOTA_HOSTS = {
Expand Down Expand Up @@ -259,13 +260,14 @@ export async function fetchBailianQuota(
const url = getQuotaUrl();
// #6911: space concurrent upstream quota fetches (mirrors codexQuotaFetcher.ts).
await throttleQuotaFetch();
const response = await fetch(url, {
const response = await fetchWithConnectionProxy(connectionId, url, {
method: "POST",
headers,
body: JSON.stringify({}),
signal: AbortSignal.timeout(8_000),
});

if (!response) return null;
const rawData = await response.json();
const obj = toRecord(rawData);

Expand All @@ -278,12 +280,13 @@ export async function fetchBailianQuota(

// #6911: space this fallback fetch too — it is still a genuine upstream call.
await throttleQuotaFetch();
const retryResponse = await fetch(chinaUrl, {
const retryResponse = await fetchWithConnectionProxy(connectionId, chinaUrl, {
method: "POST",
headers,
body: JSON.stringify({}),
signal: AbortSignal.timeout(8_000),
});
if (!retryResponse) return null;

const retryData = await retryResponse.json();
const quota = parseBailianQuotaResponse(retryData);
Expand Down
4 changes: 3 additions & 1 deletion open-sse/services/codexQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { registerQuotaFetcher, registerQuotaWindows, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";
import { getCodexBackendIdentityHeaders } from "../config/codexClient.ts";
import { parseCodexPaidCredits } from "@/lib/providers/codexPaidCredits";

Expand Down Expand Up @@ -240,11 +241,12 @@ export async function fetchCodexQuota(
// configurable (OMNIROUTE_QUOTA_FETCH_MIN_INTERVAL_MS, 0 = disabled).
await throttleQuotaFetch();

const response = await fetch(CODEX_USAGE_URL, {
const response = await fetchWithConnectionProxy(connectionId, CODEX_USAGE_URL, {
method: "GET",
headers,
signal: AbortSignal.timeout(8_000),
});
if (!response) return null;

if (!response.ok) {
// Non-2xx: could be token expired or quota API down.
Expand Down
4 changes: 3 additions & 1 deletion open-sse/services/context7QuotaFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { registerQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";

/**
* Context7 has no dedicated usage/billing endpoint, so quota is read from the
Expand Down Expand Up @@ -139,11 +140,12 @@ export async function fetchContext7Quota(
return dedupeQuotaFetch(connectionId, async () => {
await throttleQuotaFetch();
const url = `${CONTEXT7_SEARCH_URL}?query=react`;
const response = await fetch(url, {
const response = await fetchWithConnectionProxy(connectionId, url, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (!response) return null;

const quota = parseContext7RateLimitHeaders(response.headers);
if (quota) {
Expand Down
4 changes: 3 additions & 1 deletion open-sse/services/deepseekQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import { registerQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";

// DeepSeek API config
const DEEPSEEK_CONFIG = {
Expand Down Expand Up @@ -194,7 +195,7 @@ export async function fetchDeepseekQuota(
// Cache hits above never reach here; this only paces genuine network calls.
await throttleQuotaFetch();

const response = await fetch(url, {
const response = await fetchWithConnectionProxy(connectionId, url, {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Expand All @@ -203,6 +204,7 @@ export async function fetchDeepseekQuota(
},
signal: AbortSignal.timeout(8_000),
});
if (!response) return null;

// 401/403: token invalid — remove from cache
if (response.status === 401 || response.status === 403) {
Expand Down
4 changes: 3 additions & 1 deletion open-sse/services/firecrawlQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import { registerQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";
import { isUrlHost } from "../utils/urlHost.ts";
import { toNumberOrNull } from "@/shared/utils/numeric";

Expand Down Expand Up @@ -156,14 +157,15 @@ export async function fetchFirecrawlQuota(
try {
await throttleQuotaFetch();

const response = await fetch(CREDIT_USAGE_URL, {
const response = await fetchWithConnectionProxy(connectionId, CREDIT_USAGE_URL, {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
},
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (!response) return null;

if (response.status === 401 || response.status === 403) {
quotaCache.set(connectionId, { quota: null, fetchedAt: Date.now() });
Expand Down
4 changes: 3 additions & 1 deletion open-sse/services/grokCliQuotaFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import { registerQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
import { registerMonitorFetcher } from "./quotaMonitor.ts";
import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
import { fetchWithConnectionProxy } from "./connectionProxyFetch.ts";
import { decodeGrokCreditsFrame } from "./grokCliQuotaFrame.ts";

const GROK_CLI_CONFIG = {
Expand Down Expand Up @@ -142,7 +143,7 @@ export async function fetchGrokCliQuota(
try {
await throttleQuotaFetch();

const response = await fetch(url, {
const response = await fetchWithConnectionProxy(connectionId, url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
Expand All @@ -152,6 +153,7 @@ export async function fetchGrokCliQuota(
body: GRPC_WEB_EMPTY_REQUEST_FRAME,
signal: AbortSignal.timeout(8_000),
});
if (!response) return null;

if (response.status === 401 || response.status === 403) {
// Fail-open: no proactive refreshCredentials() in this PR (see Non-Goals).
Expand Down
Loading
Loading