Merge PR #2019 and resolve conflicts

This commit is contained in:
diegosouzapw
2026-05-08 17:50:18 -03:00
parent a52c9ceb45
commit 276e5da137
55 changed files with 1948 additions and 197 deletions

View File

@@ -17,6 +17,11 @@ export const FETCH_TIMEOUT_MS = upstreamTimeouts.fetchTimeoutMs;
// idle for this duration. Override with STREAM_IDLE_TIMEOUT_MS env var.
export const STREAM_IDLE_TIMEOUT_MS = upstreamTimeouts.streamIdleTimeoutMs;
// Timeout for the first useful SSE event. Keep this much shorter than the
// post-start idle timeout so slow-thinking models can keep streaming after the
// first token, while dead 200 OK streams fail fast enough for combo fallback.
export const STREAM_READINESS_TIMEOUT_MS = upstreamTimeouts.streamReadinessTimeoutMs;
// Timeout for reading the full response body after headers arrive (ms).
// Prevents indefinite hangs when the upstream sends headers but stalls on the body.
// Defaults to FETCH_TIMEOUT_MS. Override with FETCH_BODY_TIMEOUT_MS env var.

View File

@@ -10,6 +10,7 @@ import {
} from "../utils/stream.ts";
import { ensureStreamReadiness } from "../utils/streamReadiness.ts";
import { createStreamController, pipeWithDisconnect } from "../utils/streamHandler.ts";
import { createSseHeartbeatTransform } from "../utils/sseHeartbeat.ts";
import { addBufferToUsage, filterUsageForFormat, estimateUsage } from "../utils/usageTracking.ts";
import { refreshWithRetry } from "../services/tokenRefresh.ts";
import { createRequestLogger } from "../utils/requestLogger.ts";
@@ -33,6 +34,7 @@ import {
MAX_TOOLS_LIMIT,
PROVIDER_MAX_TOKENS,
STREAM_IDLE_TIMEOUT_MS,
STREAM_READINESS_TIMEOUT_MS,
} from "../config/constants.ts";
import {
classifyProviderError,
@@ -78,6 +80,11 @@ import {
providerSupportsCaching,
} from "../utils/cacheControlPolicy.ts";
import { getCachedSettings } from "@/lib/db/readCache";
import { applyCodexGlobalFastServiceTier } from "@/lib/providers/codexFastTier";
import {
getCodexRequestDefaults,
normalizeCodexServiceTier,
} from "@/lib/providers/requestDefaults";
import { cacheReasoningFromAssistantMessage } from "../services/reasoningCache.ts";
import { sanitizeOpenAITool } from "../services/toolSchemaSanitizer.ts";
import {
@@ -976,6 +983,7 @@ export async function handleChatCore({
comboStepId = null,
comboExecutionKey = null,
disableEmergencyFallback = false,
cachedSettings = null,
}) {
let { provider, model, extendedContext } = modelInfo;
const requestedModel =
@@ -990,6 +998,21 @@ export async function handleChatCore({
log?.info?.("STAGE_TRACE", `${traceId} ${label} t=${elapsed}ms${suffix}`);
};
let tokensCompressed: number | null = null;
let effectiveServiceTier: "standard" | "priority" = "standard";
const resolveEffectiveServiceTier = (requestBody?: unknown): "standard" | "priority" => {
if (provider !== "codex") return "standard";
const requestRecord =
requestBody && typeof requestBody === "object" && !Array.isArray(requestBody)
? (requestBody as Record<string, unknown>)
: {};
const rawServiceTier = requestRecord.service_tier;
if (typeof rawServiceTier === "string" && rawServiceTier.trim().length > 0) {
return normalizeCodexServiceTier(rawServiceTier) ? "priority" : "standard";
}
return getCodexRequestDefaults(credentials?.providerSpecificData).serviceTier === "priority"
? "priority"
: "standard";
};
const persistFailureUsage = (statusCode: number, errorCode?: string | null) => {
saveRequestUsage({
provider: provider || "unknown",
@@ -1004,6 +1027,7 @@ export async function handleChatCore({
connectionId: connectionId || undefined,
apiKeyId: apiKeyInfo?.id || undefined,
apiKeyName: apiKeyInfo?.name || undefined,
serviceTier: effectiveServiceTier,
}).catch(() => {});
};
@@ -1093,7 +1117,9 @@ export async function handleChatCore({
| undefined)
: undefined;
const idempotentCost = idempotentUsage
? await calculateCost(provider, model, idempotentUsage as Record<string, number>)
? await calculateCost(provider, model, idempotentUsage as Record<string, number>, {
serviceTier: effectiveServiceTier,
})
: 0;
return {
success: true,
@@ -1411,7 +1437,9 @@ export async function handleChatCore({
nativeCodexPassthrough && isCompactResponsesEndpoint(endpointPath)
? false
: resolveStreamFlag(body?.stream, acceptHeader);
const settings = await getCachedSettings();
const settings = cachedSettings ?? (await getCachedSettings());
credentials = applyCodexGlobalFastServiceTier(provider, credentials, settings);
effectiveServiceTier = resolveEffectiveServiceTier(body);
setGeminiThoughtSignatureMode(settings.antigravitySignatureCacheMode);
const semanticCacheEnabled = settings.semanticCacheEnabled !== false;
@@ -1449,7 +1477,9 @@ export async function handleChatCore({
extractUsageFromResponse(cached as Record<string, unknown>, provider) ||
((cached as Record<string, unknown>)?.usage as Record<string, unknown> | undefined);
const cachedCost = cachedUsage
? await calculateCost(provider, model, cachedUsage as Record<string, number>)
? await calculateCost(provider, model, cachedUsage as Record<string, number>, {
serviceTier: effectiveServiceTier,
})
: 0;
persistAttemptLogs({
status: 200,
@@ -1907,7 +1937,8 @@ export async function handleChatCore({
effectiveModel ?? "",
{
input: tokensSaved,
}
},
{ serviceTier: effectiveServiceTier }
);
insertCompressionAnalyticsRow({
timestamp: new Date().toISOString(),
@@ -2993,6 +3024,7 @@ export async function handleChatCore({
providerUrl = result.url;
providerHeaders = result.headers;
finalBody = result.transformedBody;
effectiveServiceTier = resolveEffectiveServiceTier(finalBody);
claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta(
targetFormat,
finalBody,
@@ -3726,6 +3758,7 @@ export async function handleChatCore({
connectionId: connectionId || undefined,
apiKeyId: apiKeyInfo?.id || undefined,
apiKeyName: apiKeyInfo?.name || undefined,
serviceTier: effectiveServiceTier,
}).catch((err) => {
console.error("Failed to save usage stats:", err.message);
});
@@ -3858,7 +3891,9 @@ export async function handleChatCore({
(translatedResponse?.usage && typeof translatedResponse.usage === "object"
? translatedResponse.usage
: null);
const estimatedCost = responseUsage ? await calculateCost(provider, model, responseUsage) : 0;
const estimatedCost = responseUsage
? await calculateCost(provider, model, responseUsage, { serviceTier: effectiveServiceTier })
: 0;
if (postCallGuardrails.blocked) {
const guardrailMessage = postCallGuardrails.message || "Response blocked by guardrail";
@@ -3952,7 +3987,7 @@ export async function handleChatCore({
// Streaming response
const streamReadiness = await ensureStreamReadiness(providerResponse, {
timeoutMs: STREAM_IDLE_TIMEOUT_MS,
timeoutMs: STREAM_READINESS_TIMEOUT_MS,
provider,
model,
log,
@@ -4000,8 +4035,9 @@ export async function handleChatCore({
)
),
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
[OMNIROUTE_RESPONSE_HEADERS.cache]: "MISS",
...buildOmniRouteResponseMetaHeaders({
provider,
@@ -4070,6 +4106,7 @@ export async function handleChatCore({
connectionId: connectionId || undefined,
apiKeyId: apiKeyInfo?.id || undefined,
apiKeyName: apiKeyInfo?.name || undefined,
serviceTier: effectiveServiceTier,
}).catch((err) => {
console.error("Failed to save usage stats:", err.message);
});
@@ -4088,7 +4125,7 @@ export async function handleChatCore({
});
if (apiKeyInfo?.id && streamUsage) {
calculateCost(provider, model, streamUsage)
calculateCost(provider, model, streamUsage, { serviceTier: effectiveServiceTier })
.then((estimatedCost) => {
if (estimatedCost > 0) recordCost(apiKeyInfo.id, estimatedCost);
})
@@ -4227,6 +4264,9 @@ export async function handleChatCore({
} else {
finalStream = pipeWithDisconnect(providerResponse, transformStream, streamController);
}
finalStream = finalStream.pipeThrough(
createSseHeartbeatTransform({ signal: streamController.signal })
);
return {
success: true,

View File

@@ -1,7 +1,8 @@
/**
* Shared combo (model combo) handling with fallback support
* Supports: priority, weighted, round-robin, random, least-used, cost-optimized,
* strict-random, auto, fill-first, p2c, lkgp, context-optimized, and context-relay strategies
* reset-aware, strict-random, auto, fill-first, p2c, lkgp, context-optimized,
* and context-relay strategies
*/
import {
@@ -16,6 +17,7 @@ import { recordComboIntent, recordComboRequest, getComboMetrics } from "./comboM
import { resolveComboConfig, getDefaultComboConfig } from "./comboConfig.ts";
import { maybeGenerateHandoff, resolveContextRelayConfig } from "./contextHandoff.ts";
import { fetchCodexQuota } from "./codexQuotaFetcher.ts";
import { getQuotaFetcher } from "./quotaPreflight.ts";
import * as semaphore from "./rateLimitSemaphore.ts";
import { getCircuitBreaker } from "../../src/shared/utils/circuitBreaker";
import { fisherYatesShuffle, getNextFromDeck } from "../../src/shared/utils/shuffleDeck";
@@ -72,6 +74,12 @@ const MAX_COMBO_DEPTH = 3;
const MAX_FALLBACK_WAIT_MS = 5000;
const MAX_GLOBAL_ATTEMPTS = 30;
function resolveDelayMs(value: unknown, fallback: number): number {
const numericValue = Number(value);
if (!Number.isFinite(numericValue) || numericValue < 0) return fallback;
return numericValue;
}
function comboModelNotFoundResponse(message: string) {
return errorResponse(404, message);
}
@@ -88,6 +96,18 @@ const DEFAULT_MODEL_P95_MS = {
"deepseek-chat": 2000,
};
const MIN_HISTORY_SAMPLES = 10;
const RESET_AWARE_SESSION_WINDOW_MS = 5 * 60 * 60 * 1000;
const RESET_AWARE_WEEKLY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
const RESET_AWARE_REMAINING_WEIGHT = 0.55;
const RESET_AWARE_RESET_WEIGHT = 0.45;
const RESET_AWARE_CONNECTION_CACHE_TTL_MS = 30_000;
const RESET_AWARE_QUOTA_FETCH_CONCURRENCY = 5;
const RESET_AWARE_DEFAULTS = {
sessionWeight: 0.35,
weeklyWeight: 0.65,
tieBandPercent: 5,
exhaustionGuardPercent: 10,
};
export type ResolvedComboTarget = {
kind: "model";
@@ -212,6 +232,11 @@ export async function validateResponseQuality(
// Resets on server restart (by design — no stale state)
const rrCounters = new Map();
const resetAwareConnectionCache = new Map<
string,
{ fetchedAt: number; connections: Array<Record<string, unknown>> }
>();
/**
* Normalize a model entry to { model, weight }
* Supports both legacy string format and new object format
@@ -699,6 +724,363 @@ function orderTargetsByPowerOfTwoChoices(targets: ResolvedComboTarget[], comboNa
return [targets[selectedIndex], ...targets.filter((_, index) => index !== selectedIndex)];
}
function clamp01(value: number): number {
if (!Number.isFinite(value)) return 0;
return Math.max(0, Math.min(1, value));
}
function finiteNumberOrNull(value: unknown): number | null {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : null;
}
function getPercentConfig(value: unknown, fallback: number): number {
const numericValue = finiteNumberOrNull(value);
if (numericValue === null) return fallback;
return Math.max(0, Math.min(100, numericValue));
}
function getWeightConfig(value: unknown, fallback: number): number {
const numericValue = finiteNumberOrNull(value);
if (numericValue === null || numericValue < 0) return fallback;
return numericValue;
}
function resolveResetAwareConfig(config: Record<string, unknown> | null | undefined) {
const sessionWeight = getWeightConfig(
config?.resetAwareSessionWeight,
RESET_AWARE_DEFAULTS.sessionWeight
);
const weeklyWeight = getWeightConfig(
config?.resetAwareWeeklyWeight,
RESET_AWARE_DEFAULTS.weeklyWeight
);
const totalWeight = sessionWeight + weeklyWeight;
const normalizedSessionWeight =
totalWeight > 0 ? sessionWeight / totalWeight : RESET_AWARE_DEFAULTS.sessionWeight;
return {
sessionWeight: normalizedSessionWeight,
weeklyWeight: 1 - normalizedSessionWeight,
tieBand:
getPercentConfig(config?.resetAwareTieBandPercent, RESET_AWARE_DEFAULTS.tieBandPercent) / 100,
exhaustionGuard:
getPercentConfig(
config?.resetAwareExhaustionGuardPercent,
RESET_AWARE_DEFAULTS.exhaustionGuardPercent
) / 100,
};
}
function getResetAwareProvider(target: ResolvedComboTarget): string | null {
const provider = (target.providerId || target.provider || "").toLowerCase();
return provider || null;
}
function normalizeResetAt(value: unknown): string | null {
if (typeof value === "string" && value.trim().length > 0) return value.trim();
if (typeof value === "number" && Number.isFinite(value)) return String(value);
return null;
}
function parseResetTimeMs(resetAt: string | null | undefined): number {
if (!resetAt) return NaN;
const resetTime = Date.parse(resetAt);
if (Number.isFinite(resetTime)) return resetTime;
if (!/^\d+(?:\.\d+)?$/.test(resetAt)) return NaN;
const numericResetAt = Number(resetAt);
if (!Number.isFinite(numericResetAt)) return NaN;
return numericResetAt < 10_000_000_000 ? numericResetAt * 1000 : numericResetAt;
}
function getQuotaWindow(
quota: unknown,
key: "window5h" | "window7d" | "windowWeekly" | "windowMonthly"
): { percentUsed: number | null; resetAt: string | null } | null {
if (!isRecord(quota)) return null;
const window = quota[key];
if (!isRecord(window)) return null;
const percentUsed = finiteNumberOrNull(window.percentUsed);
const resetAt = normalizeResetAt(window.resetAt);
return { percentUsed, resetAt };
}
function getResetUrgency(resetAt: string | null | undefined, windowMs: number): number {
if (!resetAt) return 0.5;
const resetTime = parseResetTimeMs(resetAt);
if (!Number.isFinite(resetTime)) return 0.5;
const msUntilReset = resetTime - Date.now();
if (msUntilReset <= 0) return 1;
return clamp01(1 - msUntilReset / windowMs);
}
function scoreQuotaWindow(
remaining: number,
resetAt: string | null | undefined,
windowMs: number
): number {
return (
RESET_AWARE_REMAINING_WEIGHT * clamp01(remaining) +
RESET_AWARE_RESET_WEIGHT * getResetUrgency(resetAt, windowMs)
);
}
function scoreResetAwareQuota(quota: unknown, config: ReturnType<typeof resolveResetAwareConfig>) {
if (!quota || !isRecord(quota)) return { score: 0.5 };
if (quota.limitReached === true) return { score: -Infinity };
const overallPercentUsed = clamp01(finiteNumberOrNull(quota.percentUsed) ?? 0.5);
const sessionWindow = getQuotaWindow(quota, "window5h");
const weeklyWindow = getQuotaWindow(quota, "window7d") || getQuotaWindow(quota, "windowWeekly");
const sessionRemaining = clamp01(1 - (sessionWindow?.percentUsed ?? overallPercentUsed));
const weeklyRemaining = clamp01(1 - (weeklyWindow?.percentUsed ?? overallPercentUsed));
const sessionScore = scoreQuotaWindow(
sessionRemaining,
sessionWindow?.resetAt,
RESET_AWARE_SESSION_WINDOW_MS
);
const weeklyScore = scoreQuotaWindow(
weeklyRemaining,
weeklyWindow?.resetAt ?? normalizeResetAt(quota.resetAt),
RESET_AWARE_WEEKLY_WINDOW_MS
);
let score = config.sessionWeight * sessionScore + config.weeklyWeight * weeklyScore;
if (config.exhaustionGuard > 0 && sessionRemaining < config.exhaustionGuard) {
score *= Math.max(0.05, sessionRemaining / config.exhaustionGuard);
}
return { score };
}
async function getQuotaAwareConnectionsForTarget(
target: ResolvedComboTarget,
connectionCache: Map<string, Array<Record<string, unknown>>>,
connectionLoadPromises: Map<string, Promise<Array<Record<string, unknown>>>>,
comboName: string,
log: { warn?: (...args: unknown[]) => void }
) {
const provider = getResetAwareProvider(target);
if (!provider || !getQuotaFetcher(provider)) return [];
if (!connectionCache.has(provider)) {
const cached = resetAwareConnectionCache.get(provider);
if (cached && Date.now() - cached.fetchedAt < RESET_AWARE_CONNECTION_CACHE_TTL_MS) {
connectionCache.set(provider, cached.connections);
return cached.connections;
}
if (!connectionLoadPromises.has(provider)) {
connectionLoadPromises.set(
provider,
(async () => {
try {
const connections = await getProviderConnections({ provider, isActive: true });
const activeConnections = Array.isArray(connections)
? (connections as Array<Record<string, unknown>>)
: [];
resetAwareConnectionCache.set(provider, {
connections: activeConnections,
fetchedAt: Date.now(),
});
return activeConnections;
} catch (error) {
log.warn?.("COMBO", "Reset-aware failed to load quota-aware connections.", {
comboName,
err: error,
operation: "getProviderConnections",
provider,
});
return [];
}
})()
);
}
const connections = await connectionLoadPromises.get(provider)!;
connectionCache.set(provider, connections);
}
return connectionCache.get(provider) || [];
}
function normalizeConnectionIds(value: unknown): string[] | null {
if (!Array.isArray(value)) return null;
const ids = value.filter(
(connectionId): connectionId is string =>
typeof connectionId === "string" && connectionId.trim().length > 0
);
return ids.length > 0 ? ids : null;
}
function filterAllowedConnectionIds(
connectionIds: string[],
apiKeyAllowedConnectionIds: string[] | null | undefined
): string[] {
const allowedIds = normalizeConnectionIds(apiKeyAllowedConnectionIds);
if (!allowedIds) return connectionIds;
const allowedSet = new Set(allowedIds);
return connectionIds.filter((connectionId) => allowedSet.has(connectionId));
}
function getTargetConnectionIds(
target: ResolvedComboTarget,
connections: Array<Record<string, unknown>>
): string[] {
let connectionIds: string[];
if (target.connectionId) {
return [target.connectionId];
}
if (Array.isArray(target.allowedConnectionIds) && target.allowedConnectionIds.length > 0) {
return target.allowedConnectionIds.filter(
(connectionId): connectionId is string =>
typeof connectionId === "string" && connectionId.trim().length > 0
);
}
connectionIds = connections
.map((connection) => (typeof connection.id === "string" ? connection.id : null))
.filter((connectionId): connectionId is string => !!connectionId);
return connectionIds;
}
async function mapWithConcurrency<T, R>(
items: T[],
concurrency: number,
mapper: (item: T, index: number) => Promise<R>
): Promise<R[]> {
const results = new Array<R>(items.length);
let nextIndex = 0;
const workerCount = Math.max(1, Math.min(concurrency, items.length));
await Promise.all(
Array.from({ length: workerCount }, async () => {
while (nextIndex < items.length) {
const currentIndex = nextIndex++;
results[currentIndex] = await mapper(items[currentIndex], currentIndex);
}
})
);
return results;
}
async function orderTargetsByResetAwareQuota(
targets: ResolvedComboTarget[],
comboName: string,
configSource: Record<string, unknown> | null | undefined,
log: { warn?: (...args: unknown[]) => void },
apiKeyAllowedConnectionIds?: string[] | null
) {
if (targets.length === 0) return targets;
const config = resolveResetAwareConfig(configSource);
const connectionCache = new Map<string, Array<Record<string, unknown>>>();
const connectionLoadPromises = new Map<string, Promise<Array<Record<string, unknown>>>>();
const quotaPromises = new Map<string, Promise<unknown>>();
const connectionById = new Map<string, Record<string, unknown>>();
const expandedTargets: ResolvedComboTarget[] = [];
const targetsWithConnections = await Promise.all(
targets.map(async (target) => ({
connections: await getQuotaAwareConnectionsForTarget(
target,
connectionCache,
connectionLoadPromises,
comboName,
log
),
target,
}))
);
for (const { target, connections } of targetsWithConnections) {
for (const connection of connections) {
if (typeof connection.id === "string") connectionById.set(connection.id, connection);
}
const unrestrictedConnectionIds = getTargetConnectionIds(target, connections);
const connectionIds = filterAllowedConnectionIds(
unrestrictedConnectionIds,
apiKeyAllowedConnectionIds
);
if (connectionIds.length === 0) {
if (
unrestrictedConnectionIds.length > 0 &&
normalizeConnectionIds(apiKeyAllowedConnectionIds)
) {
continue;
}
expandedTargets.push(target);
continue;
}
for (const connectionId of connectionIds) {
expandedTargets.push({
...target,
connectionId,
executionKey:
target.connectionId === connectionId
? target.executionKey
: `${target.executionKey}@${connectionId}`,
});
}
}
const scoredTargets = await mapWithConcurrency(
expandedTargets,
RESET_AWARE_QUOTA_FETCH_CONCURRENCY,
async (target, index) => {
let quota: unknown = null;
const provider = getResetAwareProvider(target);
const fetcher = provider ? getQuotaFetcher(provider) : null;
if (fetcher && provider && target.connectionId) {
const quotaKey = `${provider}:${target.connectionId}`;
if (!quotaPromises.has(quotaKey)) {
quotaPromises.set(
quotaKey,
fetcher(target.connectionId, connectionById.get(target.connectionId)).catch((error) => {
log.warn?.("COMBO", "Reset-aware quota fetch failed.", {
comboName,
connectionId: target.connectionId,
err: error,
operation: "quotaFetch",
provider,
});
return null;
})
);
}
quota = await quotaPromises.get(quotaKey)!;
}
const { score } = scoreResetAwareQuota(quota, config);
return { target, score, index };
}
);
scoredTargets.sort((a, b) => {
if (b.score !== a.score) return b.score - a.score;
return a.index - b.index;
});
const bestScore = scoredTargets[0]?.score ?? 0;
const tiedTargets = scoredTargets.filter((entry) => bestScore - entry.score <= config.tieBand);
let orderedTiedTargets = tiedTargets;
if (tiedTargets.length > 1) {
const key = `reset-aware:${comboName}`;
const counter = rrCounters.get(key) || 0;
rrCounters.set(key, counter + 1);
const startIndex = counter % tiedTargets.length;
orderedTiedTargets = [...tiedTargets.slice(startIndex), ...tiedTargets.slice(0, startIndex)];
}
const tiedExecutionKeys = new Set(orderedTiedTargets.map((entry) => entry.target.executionKey));
return [
...orderedTiedTargets,
...scoredTargets.filter((entry) => !tiedExecutionKeys.has(entry.target.executionKey)),
].map((entry) => entry.target);
}
function toTextContent(content) {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
@@ -1076,6 +1458,7 @@ export async function handleComboChat({
allCombos,
relayOptions,
signal,
apiKeyAllowedConnections = null,
}) {
const strategy = normalizeRoutingStrategy(combo.strategy || "priority");
const relayConfig =
@@ -1284,7 +1667,8 @@ export async function handleComboChat({
? resolveComboConfig(combo, settings)
: { ...getDefaultComboConfig(), ...(combo.config || {}) };
const maxRetries = config.maxRetries ?? 1;
const retryDelayMs = config.retryDelayMs ?? 2000;
const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000);
const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0);
let orderedTargets =
strategy === "weighted"
@@ -1516,6 +1900,18 @@ export async function handleComboChat({
}
}
log.info("COMBO", `Cost-optimized ordering: cheapest first (${orderedTargets[0]?.modelStr})`);
} else if (strategy === "reset-aware") {
orderedTargets = await orderTargetsByResetAwareQuota(
orderedTargets,
combo.name,
config,
log,
apiKeyAllowedConnections
);
log.info(
"COMBO",
`Reset-aware ordering: ${orderedTargets[0]?.modelStr}${orderedTargets[0]?.connectionId ? ` (${orderedTargets[0].connectionId})` : ""} first`
);
} else if (strategy === "context-optimized") {
orderedTargets = sortTargetsByContextSize(orderedTargets);
log.info("COMBO", `Context-optimized ordering: largest first (${orderedTargets[0]?.modelStr})`);
@@ -1671,17 +2067,19 @@ export async function handleComboChat({
// Record last known good provider (LKGP) for this combo/model (#919)
if (provider) {
try {
const { setLKGP } = await import("../../src/lib/localDb");
await Promise.all([
setLKGP(combo.name, target.executionKey, provider),
setLKGP(combo.name, combo.id || combo.name, provider),
]);
} catch (err) {
log.warn("COMBO", "Failed to record Last Known Good Provider. This is non-fatal.", {
err,
});
}
void (async () => {
try {
const { setLKGP } = await import("../../src/lib/localDb");
await Promise.all([
setLKGP(combo.name, target.executionKey, provider),
setLKGP(combo.name, combo.id || combo.name, provider),
]);
} catch (err) {
log.warn("COMBO", "Failed to record Last Known Good Provider. This is non-fatal.", {
err,
});
}
})();
}
return quality.clonedResponse ?? result;
@@ -1785,8 +2183,8 @@ export async function handleComboChat({
log.warn("COMBO", `Model ${modelStr} failed, trying next`, { status: result.status });
const fallbackWaitMs =
retryDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS
? Math.min(cooldownMs, retryDelayMs)
fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS
? Math.min(cooldownMs, fallbackDelayMs)
: 0;
if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) {
log.info("COMBO", `Waiting ${fallbackWaitMs}ms before fallback to next model`);
@@ -1873,7 +2271,8 @@ async function handleRoundRobinCombo({
const concurrency = config.concurrencyPerModel ?? 3;
const queueTimeout = config.queueTimeoutMs ?? 30000;
const maxRetries = config.maxRetries ?? 1;
const retryDelayMs = config.retryDelayMs ?? 2000;
const retryDelayMs = resolveDelayMs(config.retryDelayMs, 2000);
const fallbackDelayMs = resolveDelayMs(config.fallbackDelayMs, 0);
const orderedTargets = resolveComboTargets(combo, allCombos);
const filteredTargets = await applyRequestTagRouting(orderedTargets, body, log);
@@ -1997,21 +2396,23 @@ async function handleRoundRobinCombo({
});
recordedAttempts++;
if (provider) {
try {
const { setLKGP } = await import("../../src/lib/localDb");
await Promise.all([
setLKGP(combo.name, target.executionKey, provider),
setLKGP(combo.name, combo.id || combo.name, provider),
]);
} catch (err) {
log.warn(
"COMBO-RR",
"Failed to record Last Known Good Provider. This is non-fatal.",
{
err,
}
);
}
void (async () => {
try {
const { setLKGP } = await import("../../src/lib/localDb");
await Promise.all([
setLKGP(combo.name, target.executionKey, provider),
setLKGP(combo.name, combo.id || combo.name, provider),
]);
} catch (err) {
log.warn(
"COMBO-RR",
"Failed to record Last Known Good Provider. This is non-fatal.",
{
err,
}
);
}
})();
}
return result;
}
@@ -2131,8 +2532,8 @@ async function handleRoundRobinCombo({
log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { status: result.status });
const fallbackWaitMs =
retryDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS
? Math.min(cooldownMs, retryDelayMs)
fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS
? Math.min(cooldownMs, fallbackDelayMs)
: 0;
if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) {
log.info("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`);

View File

@@ -9,8 +9,9 @@ const DEFAULT_COMBO_CONFIG = {
strategy: "priority",
maxRetries: 1,
retryDelayMs: 2000,
concurrencyPerModel: 3,
queueTimeoutMs: 30000,
fallbackDelayMs: 0,
concurrencyPerModel: 3, // max simultaneous requests per model (round-robin)
queueTimeoutMs: 30000, // max wait time in semaphore queue (round-robin)
handoffThreshold: 0.85,
handoffModel: "",
handoffProviders: ["codex"],
@@ -18,6 +19,10 @@ const DEFAULT_COMBO_CONFIG = {
maxComboDepth: 3,
trackMetrics: true,
manifestRouting: false,
resetAwareSessionWeight: 0.35,
resetAwareWeeklyWeight: 0.65,
resetAwareTieBandPercent: 5,
resetAwareExhaustionGuardPercent: 10,
};
const LEGACY_COMBO_RESILIENCE_KEYS = new Set([

View File

@@ -35,6 +35,10 @@ export function registerQuotaFetcher(provider: string, fetcher: QuotaFetcher): v
quotaFetcherRegistry.set(provider, fetcher);
}
export function getQuotaFetcher(provider: string): QuotaFetcher | undefined {
return quotaFetcherRegistry.get(provider) || quotaFetcherRegistry.get(provider.toLowerCase());
}
export function isQuotaPreflightEnabled(connection: Record<string, unknown>): boolean {
const psd = connection?.providerSpecificData as Record<string, unknown> | undefined;
return psd?.quotaPreflightEnabled === true;
@@ -49,7 +53,7 @@ export async function preflightQuota(
return { proceed: true };
}
const fetcher = quotaFetcherRegistry.get(provider);
const fetcher = getQuotaFetcher(provider);
if (!fetcher) {
return { proceed: true };
}

View File

@@ -521,7 +521,46 @@ async function getCrofUsage(apiKey: string) {
return { quotas };
}
const GLM_QUOTA_ORDER = ["5 Hours Quota", "Weekly Quota", "Monthly Tools", "Tokens", "Time Limit"];
function getGlmQuotaLabel(type: unknown, unit: unknown): string | null {
const normalized = typeof type === "string" ? type.trim().toUpperCase() : "";
const unitValue = toNumber(unit, -1);
switch (normalized) {
case "TOKENS_LIMIT":
case "TOKEN_LIMIT":
if (unitValue === 3) return "5 Hours Quota";
if (unitValue === 6) return "Weekly Quota";
return "Tokens";
case "TIME_LIMIT":
case "TIME_USAGE_LIMIT":
if (unitValue === 5) return "Monthly Tools";
return "Time Limit";
default:
return null;
}
}
function orderGlmQuotas(quotas: Record<string, UsageQuota>): Record<string, UsageQuota> {
const ordered: Record<string, UsageQuota> = {};
for (const key of GLM_QUOTA_ORDER) {
if (quotas[key]) ordered[key] = quotas[key];
}
for (const [key, quota] of Object.entries(quotas)) {
if (!ordered[key]) ordered[key] = quota;
}
return ordered;
}
async function getGlmUsage(apiKey: string, providerSpecificData?: Record<string, unknown>) {
if (!apiKey) {
return { message: "API key not available. Add a coding plan API key to view usage." };
}
const quotaUrl = getGlmQuotaUrl(providerSpecificData);
const res = await fetch(quotaUrl, {
@@ -543,13 +582,14 @@ async function getGlmUsage(apiKey: string, providerSpecificData?: Record<string,
for (const limit of limits) {
const src = toRecord(limit);
if (src.type !== "TOKENS_LIMIT") continue;
const label = getGlmQuotaLabel(src.type, src.unit);
if (!label) continue;
const usedPercent = toNumber(src.percentage, 0);
const resetMs = toNumber(src.nextResetTime, 0);
const remaining = Math.max(0, 100 - usedPercent);
quotas["session"] = {
quotas[label] = {
used: usedPercent,
total: 100,
remaining,
@@ -564,7 +604,7 @@ async function getGlmUsage(apiKey: string, providerSpecificData?: Record<string,
? levelRaw.charAt(0).toUpperCase() + levelRaw.slice(1).toLowerCase()
: "Unknown";
return { plan, quotas };
return { plan, quotas: orderGlmQuotas(quotas) };
}
/**
@@ -714,6 +754,7 @@ export async function getUsageForProvider(connection, options: { forceRefresh?:
case "qoder":
return await getQoderUsage(accessToken);
case "glm":
case "zai":
case "glmt":
return await getGlmUsage(apiKey, providerSpecificData);
case "minimax":

View File

@@ -1,4 +1,4 @@
const DEFAULT_INTERVAL_MS = 15_000;
export const DEFAULT_SSE_HEARTBEAT_INTERVAL_MS = 15_000;
type SseHeartbeatTransformOptions = {
intervalMs?: number;
@@ -6,7 +6,7 @@ type SseHeartbeatTransformOptions = {
};
export function createSseHeartbeatTransform({
intervalMs = DEFAULT_INTERVAL_MS,
intervalMs = DEFAULT_SSE_HEARTBEAT_INTERVAL_MS,
signal,
}: SseHeartbeatTransformOptions = {}) {
let intervalId: ReturnType<typeof setInterval> | undefined;

View File

@@ -3,6 +3,7 @@ import { trackPendingRequest } from "@/lib/usageDb";
// Stream handler with disconnect detection - shared for all providers
const PENDING_REQUEST_CLEARED_MARKER = "__omniroutePendingRequestCleared";
const DISCONNECT_ABORT_DELAY_MS = 2_000;
type StreamDisconnectEvent = {
reason: string;
@@ -97,7 +98,7 @@ export function createStreamController({
// Delay abort to allow cleanup
abortTimeout = setTimeout(() => {
abortController.abort();
}, 500);
}, DISCONNECT_ABORT_DELAY_MS);
onDisconnect?.({ reason, duration: Date.now() - startTime });
},
@@ -201,7 +202,9 @@ export function createDisconnectAwareStream(transformStream, streamController) {
cancel(reason) {
streamController.handleDisconnect(reason || "cancelled");
reader.cancel();
writer.abort();
setTimeout(() => {
writer.abort();
}, DISCONNECT_ABORT_DELAY_MS).unref?.();
},
});
}

View File

@@ -116,6 +116,52 @@ function extractContentPreview(content) {
// ---------- Main ----------
if (!fs.existsSync(DOCS_DIR)) {
if (fs.existsSync(OUT_FILE)) {
console.warn(
`[generate-docs-index] ${DOCS_DIR} not found; keeping existing generated docs index.`
);
process.exit(0);
}
fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
fs.writeFileSync(
OUT_FILE,
`// AUTO-GENERATED by scripts/generate-docs-index.mjs — DO NOT EDIT MANUALLY
// Regenerate with: node scripts/generate-docs-index.mjs
export interface AutoGenDocItem {
slug: string;
title: string;
fileName: string;
}
export interface AutoGenNavSection {
title: string;
items: AutoGenDocItem[];
}
export interface AutoGenSearchItem {
slug: string;
title: string;
fileName: string;
section: string;
content: string;
headings: string[];
}
export const autoNavSections: AutoGenNavSection[] = [];
export const autoSearchIndex: AutoGenSearchItem[] = [];
export const autoAllSlugs: string[] = [];
`,
"utf8"
);
console.warn(`[generate-docs-index] ${DOCS_DIR} not found; generated empty docs index.`);
process.exit(0);
}
const files = fs.readdirSync(DOCS_DIR).filter((f) => f.endsWith(".md") || f.endsWith(".mdx"));
const docs = [];

View File

@@ -64,11 +64,14 @@ const STRATEGY_OPTIONS = ROUTING_STRATEGIES.map((strategy) => ({
const STRATEGY_LABEL_FALLBACK = {
"context-relay": "Context Relay",
"reset-aware": "Reset-Aware RR",
};
const STRATEGY_DESC_FALLBACK = {
"context-relay":
"Priority-style routing with automatic context handoffs when account rotation happens.",
"reset-aware":
"Quota remaining and reset windows decide the order; similar scores rotate round-robin.",
};
const STRATEGY_GUIDANCE_FALLBACK = {
@@ -108,6 +111,11 @@ const STRATEGY_GUIDANCE_FALLBACK = {
avoid: "Avoid when pricing data is missing or outdated.",
example: "Example: Batch or background jobs where lower cost matters most.",
},
"reset-aware": {
when: "Use when multiple accounts with quota telemetry have different reset windows.",
avoid: "Avoid when quota telemetry is unavailable for most accounts.",
example: "Example: Prefer a 60% weekly account resetting tomorrow over 80% that resets later.",
},
"fill-first": {
when: "Use when you want to drain one provider's quota fully before moving to the next.",
avoid: "Avoid when you need request-level load balancing across providers.",
@@ -230,6 +238,15 @@ const STRATEGY_RECOMMENDATIONS_FALLBACK = {
"Use for batch/background jobs where cost is the main KPI.",
],
},
"reset-aware": {
title: "Reset-aware account rotation",
description: "Balances remaining provider quota against reset timing.",
tips: [
"Use explicit account steps or account-tag routing for providers with quota telemetry.",
"Tune session vs weekly weights when short-term exhaustion is more risky.",
"Keep the tie band small so equivalent accounts still rotate fairly.",
],
},
"fill-first": {
title: "Quota drain strategy",
description: "Exhausts one provider's quota before moving to the next in chain.",
@@ -439,6 +456,7 @@ function getStrategyBadgeClass(strategy) {
if (strategy === "random") return "bg-purple-500/15 text-purple-600 dark:text-purple-400";
if (strategy === "least-used") return "bg-cyan-500/15 text-cyan-600 dark:text-cyan-400";
if (strategy === "cost-optimized") return "bg-teal-500/15 text-teal-600 dark:text-teal-400";
if (strategy === "reset-aware") return "bg-lime-500/15 text-lime-700 dark:text-lime-300";
if (strategy === "fill-first") return "bg-orange-500/15 text-orange-600 dark:text-orange-400";
if (strategy === "p2c") return "bg-indigo-500/15 text-indigo-600 dark:text-indigo-400";
return "bg-blue-500/15 text-blue-600 dark:text-blue-400";

View File

@@ -53,6 +53,10 @@ import {
getClaudeCodeCompatibleRequestDefaults as _getClaudeCodeCompatibleRequestDefaults,
getCodexRequestDefaults as _getCodexRequestDefaults,
} from "@/lib/providers/requestDefaults";
import {
getCodexEffectiveFastServiceTier,
isCodexGlobalFastServiceTierEnabled,
} from "@/lib/providers/codexFastTier";
import { isClaudeExtraUsageBlockEnabled } from "@/lib/providers/claudeExtraUsage";
import { parseExtraApiKeys } from "@/shared/utils/parseApiKeys";
import { resolveDashboardProviderInfo } from "../providerPageUtils";
@@ -512,6 +516,7 @@ interface ConnectionRowProps {
isOAuth: boolean;
isClaude?: boolean;
isCodex?: boolean;
codexFastGlobalEnabled?: boolean;
isFirst: boolean;
isLast: boolean;
onMoveUp: () => void;
@@ -1015,6 +1020,8 @@ export default function ProviderDetailPage() {
);
const [applyingCodexAuthId, setApplyingCodexAuthId] = useState<string | null>(null);
const [exportingCodexAuthId, setExportingCodexAuthId] = useState<string | null>(null);
const [codexGlobalFastServiceTier, setCodexGlobalFastServiceTier] = useState(false);
const [savingCodexGlobalFastServiceTier, setSavingCodexGlobalFastServiceTier] = useState(false);
const isOpenAICompatible = isOpenAICompatibleProvider(providerId);
const isCcCompatible = isClaudeCodeCompatibleProvider(providerId);
const isAnthropicCompatible =
@@ -1239,6 +1246,16 @@ export default function ProviderDetailPage() {
.catch(() => {});
}, [fetchConnections, fetchAliases]);
useEffect(() => {
if (providerId !== "codex") return;
fetch("/api/settings", { cache: "no-store" })
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
setCodexGlobalFastServiceTier(isCodexGlobalFastServiceTierEnabled(data));
})
.catch(() => {});
}, [providerId]);
const loadConnProxies = useCallback(async (conns: { id?: string }[]) => {
if (!conns.length) return;
try {
@@ -1686,6 +1703,32 @@ export default function ProviderDetailPage() {
}
};
const handleToggleCodexGlobalFastServiceTier = async (enabled: boolean) => {
if (savingCodexGlobalFastServiceTier) return;
setSavingCodexGlobalFastServiceTier(true);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ codexServiceTier: { enabled } }),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
notify.error(data.error || "Failed to update Codex Fast setting");
return;
}
setCodexGlobalFastServiceTier(enabled);
notify.success(enabled ? "Codex Fast enabled globally" : "Codex Fast disabled globally");
} catch (error) {
console.error("Error toggling Codex Fast setting:", error);
notify.error("Failed to update Codex Fast setting");
} finally {
setSavingCodexGlobalFastServiceTier(false);
}
};
const handleRetestConnection = async (connectionId) => {
if (!connectionId || retestingId) return;
setRetestingId(connectionId);
@@ -2795,9 +2838,22 @@ export default function ProviderDetailPage() {
{/* Connections */}
{!isUpstreamProxyProvider && (
<Card>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 flex-wrap items-center gap-2">
<h2 className="text-lg font-semibold">{t("connections")}</h2>
{providerId === "codex" && (
<div title="Apply Codex Fast tier to all Codex connections by default">
<Toggle
size="sm"
checked={codexGlobalFastServiceTier}
onChange={handleToggleCodexGlobalFastServiceTier}
disabled={savingCodexGlobalFastServiceTier}
label="Fast default"
ariaLabel="Toggle Codex Fast default"
className="rounded-lg border border-sky-500/20 bg-sky-500/5 px-2 py-1"
/>
</div>
)}
{/* Provider-level proxy indicator/button */}
<button
onClick={() =>
@@ -2826,42 +2882,44 @@ export default function ProviderDetailPage() {
: t("providerProxy")}
</button>
</div>
{connections.length > 1 && (
<button
onClick={handleBatchTestAll}
disabled={batchTesting || !!retestingId}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
batchTesting
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title={t("testAll")}
aria-label={t("testAll")}
>
<span className="material-symbols-outlined text-[14px]">
{batchTesting ? "sync" : "play_arrow"}
</span>
{batchTesting ? t("testing") : t("testAll")}
</button>
)}
{!isCompatible ? (
<div className="flex items-center gap-2">
<Button size="sm" icon="add" onClick={openPrimaryAddFlow}>
{providerSupportsPat ? "Add PAT" : t("add")}
</Button>
{providerId === "qoder" && (
<Button size="sm" variant="secondary" onClick={() => setShowOAuthModal(true)}>
Experimental OAuth
<div className="flex shrink-0 flex-wrap items-center justify-end gap-2">
{connections.length > 1 && (
<button
onClick={handleBatchTestAll}
disabled={batchTesting || !!retestingId}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
batchTesting
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
: "bg-bg-subtle border-border text-text-muted hover:text-text-primary hover:border-primary/40"
}`}
title={t("testAll")}
aria-label={t("testAll")}
>
<span className="material-symbols-outlined text-[14px]">
{batchTesting ? "sync" : "play_arrow"}
</span>
{batchTesting ? t("testing") : t("testAll")}
</button>
)}
{!isCompatible ? (
<>
<Button size="sm" icon="add" onClick={openPrimaryAddFlow}>
{providerSupportsPat ? "Add PAT" : t("add")}
</Button>
)}
</div>
) : (
connections.length === 0 && (
<Button size="sm" icon="add" onClick={() => setShowAddApiKeyModal(true)}>
{t("add")}
</Button>
)
)}
{providerId === "qoder" && (
<Button size="sm" variant="secondary" onClick={() => setShowOAuthModal(true)}>
Experimental OAuth
</Button>
)}
</>
) : (
connections.length === 0 && (
<Button size="sm" icon="add" onClick={() => setShowAddApiKeyModal(true)}>
{t("add")}
</Button>
)
)}
</div>
</div>
{connections.length === 0 ? (
@@ -2904,6 +2962,7 @@ export default function ProviderDetailPage() {
connection={conn}
isOAuth={conn.authType === "oauth"}
isClaude={providerId === "claude"}
codexFastGlobalEnabled={codexGlobalFastServiceTier}
isFirst={index === 0}
isLast={index === sorted.length - 1}
onMoveUp={() => handleSwapPriority(conn, sorted[index - 1])}
@@ -3022,6 +3081,7 @@ export default function ProviderDetailPage() {
connection={conn}
isOAuth={conn.authType === "oauth"}
isClaude={providerId === "claude"}
codexFastGlobalEnabled={codexGlobalFastServiceTier}
isFirst={gi === 0 && index === 0}
isLast={
gi === groupKeys.length - 1 && index === groupConns.length - 1
@@ -4966,6 +5026,7 @@ function ConnectionRow({
isOAuth,
isClaude,
isCodex,
codexFastGlobalEnabled,
isCcCompatible,
cliproxyapiEnabled,
isFirst,
@@ -5069,6 +5130,12 @@ function ConnectionRow({
const normalizedCodexPolicy = normalizeCodexLimitPolicy(codexPolicy);
const codex5hEnabled = normalizedCodexPolicy.use5h;
const codexWeeklyEnabled = normalizedCodexPolicy.useWeekly;
const codexFastEnabled = isCodex
? getCodexEffectiveFastServiceTier(
connection.providerSpecificData,
codexFastGlobalEnabled === true
)
: false;
const claudeBlockExtraUsageEnabled = isClaude
? isClaudeExtraUsageBlockEnabled("claude", connection.providerSpecificData)
: false;
@@ -5209,6 +5276,19 @@ function ConnectionRow({
{isCodex && (
<>
<span className="text-text-muted/30 select-none">|</span>
{codexFastEnabled && (
<span
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium bg-sky-500/15 text-sky-500"
title={
codexFastGlobalEnabled
? "Global Codex fast tier is active"
: "Codex fast tier is active for this connection"
}
>
<span className="material-symbols-outlined text-[13px]">bolt</span>
Fast
</span>
)}
<button
onClick={() => onToggleCodex5h?.(!codex5hEnabled)}
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium transition-all cursor-pointer ${

View File

@@ -21,6 +21,7 @@ interface ProviderStats {
errorTime?: string | null;
allDisabled?: boolean;
expiryStatus?: "expired" | "expiring_soon" | string | null;
codexFastActive?: boolean;
}
interface ProviderCardProps {
@@ -56,7 +57,8 @@ function getStatusDisplay(
connected: number,
error: number,
errorCode: string | null | undefined,
t: ReturnType<typeof useTranslations>
t: ReturnType<typeof useTranslations>,
afterConnected?: ReactNode
) {
const parts: ReactNode[] = [];
if (connected > 0) {
@@ -65,6 +67,7 @@ function getStatusDisplay(
{t("connected", { count: connected })}
</Badge>
);
if (afterConnected) parts.push(afterConnected);
}
if (error > 0) {
const errText = errorCode
@@ -97,6 +100,17 @@ export default function ProviderCard({
const isCompatible = isOpenAICompatibleProvider(providerId);
const isCcCompatible = isClaudeCodeCompatibleProvider(providerId);
const isAnthropicCompatible = isAnthropicCompatibleProvider(providerId) && !isCcCompatible;
const codexFastChip =
providerId === "codex" && stats.codexFastActive ? (
<span
key="fast"
className="inline-flex items-center gap-0.5 rounded-full bg-sky-500/10 px-1.5 py-0 text-[9px] font-semibold uppercase tracking-wide text-sky-600 dark:text-sky-400"
title="Codex Fast tier is active"
>
<span className="material-symbols-outlined text-[10px] leading-none">bolt</span>
Fast
</span>
) : null;
const dotLabels: Record<string, string> = {
free: tc("free"),
@@ -183,7 +197,7 @@ export default function ProviderCard({
</Badge>
) : (
<>
{getStatusDisplay(connected, error, stats.errorCode, t)}
{getStatusDisplay(connected, error, stats.errorCode, t, codexFastChip)}
{(authType === "free" || provider.hasFree === true) && (
<Badge
variant="success"

View File

@@ -25,6 +25,10 @@ import {
} from "./providerPageUtils";
import type { ProviderEntry } from "./providerPageUtils";
import { readConfiguredOnlyPreference, writeConfiguredOnlyPreference } from "./providerPageStorage";
import {
getCodexEffectiveFastServiceTier,
isCodexGlobalFastServiceTierEnabled,
} from "@/lib/providers/codexFastTier";
import AddCompatibleProviderModal from "./components/AddCompatibleProviderModal";
import ProviderCard from "./components/ProviderCard";
import ProviderCountBadge from "./components/ProviderCountBadge";
@@ -102,6 +106,7 @@ export default function ProvidersPage() {
const [providerNodes, setProviderNodes] = useState<any[]>([]);
const [ccCompatibleProviderEnabled, setCcCompatibleProviderEnabled] = useState(false);
const [expirations, setExpirations] = useState<any>(null);
const [codexGlobalFastServiceTier, setCodexGlobalFastServiceTier] = useState(false);
const [loading, setLoading] = useState(true);
const [showAddCompatibleModal, setShowAddCompatibleModal] = useState(false);
const [showAddAnthropicCompatibleModal, setShowAddAnthropicCompatibleModal] = useState(false);
@@ -131,20 +136,23 @@ export default function ProvidersPage() {
useEffect(() => {
const fetchData = async () => {
try {
const [connectionsRes, nodesRes, expirationsRes] = await Promise.all([
const [connectionsRes, nodesRes, expirationsRes, settingsRes] = await Promise.all([
fetch("/api/providers"),
fetch("/api/provider-nodes"),
fetch("/api/providers/expiration"),
fetch("/api/settings", { cache: "no-store" }),
]);
const connectionsData = await connectionsRes.json();
const nodesData = await nodesRes.json();
const expirationsData = await expirationsRes.json();
const settingsData = settingsRes.ok ? await settingsRes.json() : null;
if (connectionsRes.ok) setConnections(connectionsData.connections || []);
if (nodesRes.ok) {
setProviderNodes(nodesData.nodes || []);
setCcCompatibleProviderEnabled(nodesData.ccCompatibleProviderEnabled === true);
}
if (expirationsRes.ok && expirationsData) setExpirations(expirationsData);
setCodexGlobalFastServiceTier(isCodexGlobalFastServiceTierEnabled(settingsData));
} catch (error) {
console.log("Error fetching data:", error);
} finally {
@@ -279,7 +287,23 @@ export default function ProvidersPage() {
if (hasExpired) expiryStatus = "expired";
else if (hasExpiringSoon) expiryStatus = "expiring_soon";
return { connected, error, total, errorCode, errorTime, allDisabled, expiryStatus };
const codexFastActive =
providerId === "codex" &&
(codexGlobalFastServiceTier ||
providerConnections.some((connection) =>
getCodexEffectiveFastServiceTier(connection.providerSpecificData, false)
));
return {
connected,
error,
total,
errorCode,
errorTime,
allDisabled,
expiryStatus,
codexFastActive,
};
};
// Toggle all connections for a provider on/off

View File

@@ -36,6 +36,7 @@ const PROVIDER_CONFIG = {
codex: { label: "OpenAI Codex", color: "#10A37F" },
claude: { label: "Claude Code", color: "#D97757" },
glm: { label: "GLM (Z.AI)", color: "#4A90D9" },
zai: { label: "Z.AI", color: "#2563EB" },
glmt: { label: "GLM Thinking", color: "#2563EB" },
"kimi-coding": { label: "Kimi Coding", color: "#1E3A8A" },
minimax: { label: "MiniMax", color: "#7C3AED" },
@@ -299,11 +300,12 @@ export default function ProviderLimits() {
claude: 5,
kiro: 6,
glm: 7,
glmt: 8,
"kimi-coding": 9,
minimax: 10,
"minimax-cn": 11,
nanogpt: 12,
zai: 8,
glmt: 9,
"kimi-coding": 10,
minimax: 11,
"minimax-cn": 12,
nanogpt: 13,
};
return [...filteredConnections].sort(
(a, b) => (priority[a.provider] || 9) - (priority[b.provider] || 9)
@@ -552,7 +554,12 @@ export default function ProviderLimits() {
{/* Account Info */}
<div className="flex items-center gap-2.5 min-w-0">
<div className="w-8 h-8 rounded-lg flex items-center justify-center overflow-hidden shrink-0">
<ProviderIcon providerId={conn.provider} size={32} type="color" />
<ProviderIcon
providerId={conn.provider}
size={32}
type="color"
className="object-contain"
/>
</div>
<div className="min-w-0">
<div className="text-[13px] font-semibold text-text-main truncate">

View File

@@ -22,6 +22,11 @@ const QUOTA_LABEL_MAP: Record<string, string> = {
agentic_request_freetrial: "Agentic (Trial)",
credits: "AI Credits",
models: "Models",
"5 Hours Quota": "5 Hours",
"Weekly Quota": "Weekly",
"Monthly Tools": "Monthly Tools",
tokens: "Tokens",
time_limit: "Time Limit",
};
function toRecord(value: unknown): Record<string, unknown> {

View File

@@ -49,6 +49,7 @@ export async function GET(request: Request) {
strategy: "priority",
maxRetries: 1,
retryDelayMs: 2000,
fallbackDelayMs: 0,
handoffThreshold: 0.85,
handoffModel: "",
maxMessagesForSummary: 30,

View File

@@ -36,7 +36,8 @@ const WEEKDAY_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
type PricingByProvider = Record<string, Record<string, Record<string, unknown>>>;
type ComputeCostFromPricing = (
pricing: Record<string, unknown> | null | undefined,
tokens: Record<string, number | undefined> | null | undefined
tokens: Record<string, number | undefined> | null | undefined,
options?: Record<string, unknown>
) => number;
function toNumber(value: unknown): number {
@@ -56,6 +57,15 @@ function roundCost(value: number): number {
return Math.round(value * 1_000_000) / 1_000_000;
}
function normalizeServiceTier(value: unknown): "standard" | "priority" {
const tier = typeof value === "string" ? value.trim().toLowerCase() : "";
return tier === "priority" || tier === "fast" ? "priority" : "standard";
}
function getServiceTierLabel(serviceTier: string): string {
return normalizeServiceTier(serviceTier) === "priority" ? "Fast" : "Standard";
}
function appendWhereCondition(whereClause: string, condition: string): string {
return whereClause ? `${whereClause} AND (${condition})` : `WHERE (${condition})`;
}
@@ -206,6 +216,7 @@ function computeUsageRowCost(
const provider = toStringValue(row.provider);
const model = toStringValue(row.model);
if (!provider || !model) return 0;
const serviceTier = normalizeServiceTier(row.serviceTier ?? row.service_tier);
const pricing = resolveModelPricing(
pricingByProvider,
@@ -216,13 +227,21 @@ function computeUsageRowCost(
);
if (!pricing) return 0;
return computeCostFromPricing(pricing, {
input: toNumber(row.promptTokens),
output: toNumber(row.completionTokens),
cacheRead: toNumber(row.cacheReadTokens),
cacheCreation: toNumber(row.cacheCreationTokens),
reasoning: toNumber(row.reasoningTokens),
});
return computeCostFromPricing(
pricing,
{
input: toNumber(row.promptTokens),
output: toNumber(row.completionTokens),
cacheRead: toNumber(row.cacheReadTokens),
cacheCreation: toNumber(row.cacheCreationTokens),
reasoning: toNumber(row.reasoningTokens),
},
{
provider,
model,
serviceTier,
}
);
}
function formatUtcDate(date: Date): string {
@@ -349,6 +368,7 @@ export async function GET(request: Request) {
DATE(timestamp) as date,
LOWER(provider) as provider,
LOWER(model) as model,
COALESCE(NULLIF(service_tier, ''), 'standard') as serviceTier,
COALESCE(SUM(tokens_input), 0) as promptTokens,
COALESCE(SUM(tokens_output), 0) as completionTokens,
COALESCE(SUM(tokens_cache_read), 0) as cacheReadTokens,
@@ -356,7 +376,7 @@ export async function GET(request: Request) {
COALESCE(SUM(tokens_reasoning), 0) as reasoningTokens
FROM usage_history
${whereClause}
GROUP BY DATE(timestamp), LOWER(provider), LOWER(model)
GROUP BY DATE(timestamp), LOWER(provider), LOWER(model), serviceTier
ORDER BY date ASC
`
)
@@ -402,6 +422,7 @@ export async function GET(request: Request) {
SELECT
LOWER(model) as model,
LOWER(provider) as provider,
COALESCE(NULLIF(service_tier, ''), 'standard') as serviceTier,
COUNT(*) as requests,
COALESCE(SUM(tokens_input), 0) as promptTokens,
COALESCE(SUM(tokens_output), 0) as completionTokens,
@@ -414,9 +435,8 @@ export async function GET(request: Request) {
COALESCE(MAX(timestamp), '') as lastUsed
FROM usage_history
${whereClause}
GROUP BY LOWER(model), LOWER(provider)
GROUP BY LOWER(model), LOWER(provider), serviceTier
ORDER BY requests DESC
LIMIT 50
`
)
.all(params) as Array<Record<string, unknown>>;
@@ -427,6 +447,7 @@ export async function GET(request: Request) {
SELECT
LOWER(provider) as provider,
LOWER(model) as model,
COALESCE(NULLIF(service_tier, ''), 'standard') as serviceTier,
COALESCE(SUM(tokens_input), 0) as promptTokens,
COALESCE(SUM(tokens_output), 0) as completionTokens,
COALESCE(SUM(tokens_cache_read), 0) as cacheReadTokens,
@@ -434,7 +455,7 @@ export async function GET(request: Request) {
COALESCE(SUM(tokens_reasoning), 0) as reasoningTokens
FROM usage_history
${whereClause}
GROUP BY LOWER(provider), LOWER(model)
GROUP BY LOWER(provider), LOWER(model), serviceTier
`
)
.all(params) as Array<Record<string, unknown>>;
@@ -465,6 +486,7 @@ export async function GET(request: Request) {
COALESCE(NULLIF(c.display_name, ''), NULLIF(c.email, ''), NULLIF(c.name, ''), usage_history.connection_id, 'unknown') as account,
LOWER(usage_history.provider) as provider,
LOWER(usage_history.model) as model,
COALESCE(NULLIF(usage_history.service_tier, ''), 'standard') as serviceTier,
COALESCE(SUM(usage_history.tokens_input), 0) as promptTokens,
COALESCE(SUM(usage_history.tokens_output), 0) as completionTokens,
COALESCE(SUM(usage_history.tokens_cache_read), 0) as cacheReadTokens,
@@ -473,7 +495,7 @@ export async function GET(request: Request) {
FROM usage_history
LEFT JOIN provider_connections c ON c.id = usage_history.connection_id
${whereClause.replace(/timestamp/g, "usage_history.timestamp").replace(/api_key_/g, "usage_history.api_key_")}
GROUP BY account, LOWER(usage_history.provider), LOWER(usage_history.model)
GROUP BY account, LOWER(usage_history.provider), LOWER(usage_history.model), serviceTier
`
)
.all(params) as Array<Record<string, unknown>>;
@@ -511,6 +533,7 @@ export async function GET(request: Request) {
COALESCE(NULLIF(api_key_id, ''), NULLIF(api_key_name, ''), 'unknown') as apiKeyGroupKey,
LOWER(provider) as provider,
LOWER(model) as model,
COALESCE(NULLIF(service_tier, ''), 'standard') as serviceTier,
COUNT(*) as requests,
COALESCE(SUM(tokens_input), 0) as promptTokens,
COALESCE(SUM(tokens_output), 0) as completionTokens,
@@ -520,7 +543,28 @@ export async function GET(request: Request) {
COALESCE(SUM(tokens_input + tokens_output), 0) as totalTokens
FROM usage_history
${apiKeyWhereClause}
GROUP BY COALESCE(NULLIF(api_key_id, ''), NULLIF(api_key_name, ''), 'unknown'), NULLIF(api_key_id, ''), LOWER(provider), LOWER(model)
GROUP BY COALESCE(NULLIF(api_key_id, ''), NULLIF(api_key_name, ''), 'unknown'), NULLIF(api_key_id, ''), LOWER(provider), LOWER(model), serviceTier
`
)
.all(params) as Array<Record<string, unknown>>;
const serviceTierRows = db
.prepare(
`
SELECT
COALESCE(NULLIF(service_tier, ''), 'standard') as serviceTier,
LOWER(provider) as provider,
LOWER(model) as model,
COUNT(*) as requests,
COALESCE(SUM(tokens_input), 0) as promptTokens,
COALESCE(SUM(tokens_output), 0) as completionTokens,
COALESCE(SUM(tokens_cache_read), 0) as cacheReadTokens,
COALESCE(SUM(tokens_cache_creation), 0) as cacheCreationTokens,
COALESCE(SUM(tokens_reasoning), 0) as reasoningTokens,
COALESCE(SUM(tokens_input + tokens_output), 0) as totalTokens
FROM usage_history
${whereClause}
GROUP BY serviceTier, LOWER(provider), LOWER(model)
`
)
.all(params) as Array<Record<string, unknown>>;
@@ -633,6 +677,11 @@ export async function GET(request: Request) {
firstRequest: summaryRow?.firstRequest || "",
lastRequest: summaryRow?.lastRequest || "",
fallbackCount: Number(fallbackRow?.fallbacks || 0),
fastRequests: 0,
standardRequests: 0,
fastCost: 0,
standardCost: 0,
fastRequestSharePct: 0,
fallbackRatePct:
Number(fallbackRow?.fallback_eligible || 0) > 0
? Number(
@@ -697,14 +746,11 @@ export async function GET(request: Request) {
}
summary.streak = computeActivityStreak(activityMap);
const byModel = modelRows.map((row) => {
const modelMap = new Map<string, Record<string, unknown>>();
for (const row of modelRows) {
const model = row.model as string;
const provider = row.provider as string;
const short = normalizeModelName(model);
const tokens = {
input: Number(row.promptTokens) || 0,
output: Number(row.completionTokens) || 0,
};
const cost = computeUsageRowCost(
row,
pricingByProvider,
@@ -712,23 +758,59 @@ export async function GET(request: Request) {
normalizeModelName,
computeCostFromPricing
);
return {
const key = `${provider}::${model}`;
const existing = modelMap.get(key) || {
model: short,
provider,
rawModel: model,
requests: 0,
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
latencyWeightedTotal: 0,
successfulRequests: 0,
lastUsed: "",
cost: 0,
};
const requests = Number(row.requests) || 0;
existing.requests = Number(existing.requests || 0) + requests;
existing.promptTokens = Number(existing.promptTokens || 0) + Number(row.promptTokens || 0);
existing.completionTokens =
Number(existing.completionTokens || 0) + Number(row.completionTokens || 0);
existing.totalTokens = Number(existing.totalTokens || 0) + Number(row.totalTokens || 0);
existing.latencyWeightedTotal =
Number(existing.latencyWeightedTotal || 0) + Number(row.avgLatencyMs || 0) * requests;
existing.successfulRequests =
Number(existing.successfulRequests || 0) + Number(row.successfulRequests || 0);
if (!existing.lastUsed || String(row.lastUsed || "") > String(existing.lastUsed || "")) {
existing.lastUsed = row.lastUsed;
}
existing.cost = Number(existing.cost || 0) + cost;
modelMap.set(key, existing);
}
const byModel = Array.from(modelMap.values())
.map((row) => ({
model: row.model,
provider: row.provider,
rawModel: row.rawModel,
requests: Number(row.requests),
promptTokens: tokens.input,
completionTokens: tokens.output,
promptTokens: Number(row.promptTokens),
completionTokens: Number(row.completionTokens),
totalTokens: Number(row.totalTokens),
avgLatencyMs: Math.round(Number(row.avgLatencyMs)),
avgLatencyMs:
Number(row.requests) > 0
? Math.round(Number(row.latencyWeightedTotal || 0) / Number(row.requests))
: 0,
successRatePct:
Number(row.requests) > 0
? Number((Number(row.successfulRequests) / Number(row.requests)) * 100).toFixed(2)
? Number((Number(row.successfulRequests || 0) / Number(row.requests)) * 100).toFixed(2)
: 0,
lastUsed: row.lastUsed,
cost: roundCost(cost),
};
});
cost: roundCost(Number(row.cost || 0)),
}))
.sort((left, right) => Number(right.requests) - Number(left.requests))
.slice(0, 50);
const totalCost = Array.from(dailyCostByDate.values()).reduce((sum, cost) => sum + cost, 0);
summary.totalCost = roundCost(totalCost);
@@ -839,6 +921,56 @@ export async function GET(request: Request) {
.map((row) => ({ ...row, cost: roundCost(row.cost) }))
.sort((left, right) => right.cost - left.cost);
const serviceTierMap = new Map<
string,
{
serviceTier: "standard" | "priority";
label: string;
requests: number;
promptTokens: number;
completionTokens: number;
totalTokens: number;
cost: number;
}
>();
for (const row of serviceTierRows) {
const serviceTier = normalizeServiceTier(row.serviceTier);
const existing = serviceTierMap.get(serviceTier) || {
serviceTier,
label: getServiceTierLabel(serviceTier),
requests: 0,
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
cost: 0,
};
existing.requests += Number(row.requests || 0);
existing.promptTokens += Number(row.promptTokens || 0);
existing.completionTokens += Number(row.completionTokens || 0);
existing.totalTokens += Number(row.totalTokens || 0);
existing.cost += computeUsageRowCost(
row,
pricingByProvider,
PROVIDER_ID_TO_ALIAS,
normalizeModelName,
computeCostFromPricing
);
serviceTierMap.set(serviceTier, existing);
}
const byServiceTier = Array.from(serviceTierMap.values())
.map((row) => ({ ...row, cost: roundCost(row.cost) }))
.sort((left, right) => (left.serviceTier === "priority" ? -1 : 1));
const fastTier = serviceTierMap.get("priority");
const standardTier = serviceTierMap.get("standard");
summary.fastRequests = fastTier?.requests || 0;
summary.fastCost = roundCost(fastTier?.cost || 0);
summary.standardRequests = standardTier?.requests || 0;
summary.standardCost = roundCost(standardTier?.cost || 0);
summary.fastRequestSharePct =
summary.totalRequests > 0
? Number(((Number(summary.fastRequests) / Number(summary.totalRequests)) * 100).toFixed(2))
: 0;
const weeklyTokens = [0, 0, 0, 0, 0, 0, 0];
const weeklyCounts = [0, 0, 0, 0, 0, 0, 0];
const weeklyPattern = WEEKDAY_LABELS.map((day) => ({
@@ -874,6 +1006,7 @@ export async function GET(request: Request) {
byProvider,
byApiKey,
byAccount,
byServiceTier,
weeklyPattern,
weeklyTokens,
weeklyCounts,

View File

@@ -1396,6 +1396,8 @@
"randomDesc": "Einheitliche Zufallsauswahl, dann Rückgriff auf verbleibende Modelle",
"leastUsedDesc": "Wählt das Modell mit den wenigsten Anfragen aus und gleicht die Last über die Zeit aus",
"costOptimizedDesc": "Leitet basierend auf dem Preis zuerst zum günstigsten Modell weiter",
"resetAware": "Reset-Aware RR",
"resetAwareDesc": "Gewichtet Restquote gegen 5h- und Wochen-Resets und rotiert ähnliche Scores per Round Robin",
"strictRandom": "Strict Random",
"strictRandomDesc": "Shuffle deck — uses each model once before reshuffling",
"models": "Modelle",
@@ -1447,6 +1449,11 @@
"avoid": "Preisdaten fehlen oder sind veraltet.",
"example": "Hintergrund- oder Batch-Jobs, bei denen geringere Kosten bevorzugt werden."
},
"reset-aware": {
"when": "Du routest über mehrere Konten mit Quota-Telemetrie und unterschiedlichen Reset-Fenstern.",
"avoid": "Für die meisten Konten fehlen Quota-Telemetriedaten.",
"example": "Bevorzuge ein Konto mit 60 % Wochen-Restquote und Reset morgen vor 80 % mit späterem Reset."
},
"strict-random": {
"when": "Use when you want perfectly even spread — each model used once before repeating.",
"avoid": "Avoid when models have different quality or latency and order matters.",
@@ -1555,6 +1562,13 @@
"tip2": "Behalte einen Qualitäts-Fallback für schwierige Prompts.",
"tip3": "Ideal für Batch/Hintergrundjobs, bei denen Kosten das Haupt-KPI sind."
},
"reset-aware": {
"title": "Reset-bewusste Kontorotation",
"description": "Gewichtet verbleibende Provider-Quote gegen Reset-Zeitpunkte.",
"tip1": "Nutze explizite Kontoschritte oder Tag-Routing für Provider mit Quota-Telemetrie.",
"tip2": "Passe 5h- und Wochengewichtung an, wenn kurzfristige Erschöpfung riskant ist.",
"tip3": "Halte das Tie-Band klein, damit gleichwertige Konten fair rotieren."
},
"strict-random": {
"title": "Shuffle deck distribution",
"description": "Each model is used exactly once per cycle before reshuffling.",
@@ -2908,6 +2922,8 @@
"leastUsedDesc": "Wählen Sie das zuletzt verwendete Konto aus",
"costOpt": "Kosten Opt",
"costOptDesc": "Bevorzugen Sie das günstigste verfügbare Konto",
"resetAware": "Reset-Aware RR",
"resetAwareDesc": "Bevorzugt Konten mit gesunder Restquote und näherem Reset",
"strictRandom": "Strict Random",
"strictRandomDesc": "Shuffle deck — uses each account once before reshuffling",
"stickyLimit": "Sticky-Limit",

View File

@@ -1505,6 +1505,8 @@
"randomDesc": "Uniform random selection, then fallback to remaining models",
"leastUsedDesc": "Picks the model with fewest requests, balancing load over time",
"costOptimizedDesc": "Routes to the cheapest model first based on pricing",
"resetAware": "Reset-Aware RR",
"resetAwareDesc": "Balances remaining quota against 5h and weekly resets, then round-robins similar scores",
"strictRandom": "Strict Random",
"strictRandomDesc": "Shuffle deck — uses each model once before reshuffling",
"models": "Models",
@@ -1563,6 +1565,11 @@
"avoid": "Pricing data is missing or outdated.",
"example": "Background or batch jobs where lower cost is preferred."
},
"reset-aware": {
"when": "You route across multiple accounts with quota telemetry and different reset windows.",
"avoid": "Quota telemetry is unavailable for most accounts.",
"example": "Prefer a 60% weekly account resetting tomorrow over an 80% account resetting later."
},
"strict-random": {
"when": "Use when you want perfectly even spread — each model used once before repeating.",
"avoid": "Avoid when models have different quality or latency and order matters.",
@@ -1735,6 +1742,13 @@
"tip2": "Keep a quality fallback for hard prompts.",
"tip3": "Use for batch/background jobs where cost is the main KPI."
},
"reset-aware": {
"title": "Reset-aware account rotation",
"description": "Balances remaining provider quota against reset timing.",
"tip1": "Use explicit account steps or account-tag routing for providers with quota telemetry.",
"tip2": "Tune session vs weekly weights when short-term exhaustion is more risky.",
"tip3": "Keep the tie band small so equivalent accounts still rotate fairly."
},
"strict-random": {
"title": "Shuffle deck distribution",
"description": "Each model is used exactly once per cycle before reshuffling.",
@@ -3373,6 +3387,8 @@
"leastUsedDesc": "Pick least recently used account",
"costOpt": "Cost Opt",
"costOptDesc": "Prefer cheapest available account",
"resetAware": "Reset-Aware RR",
"resetAwareDesc": "Prefer accounts with healthy remaining quota and nearer resets",
"strictRandom": "Strict Random",
"strictRandomDesc": "Shuffle deck — uses each account once before reshuffling",
"stickyLimit": "Sticky Limit",

View File

@@ -250,6 +250,7 @@ const SCHEMA_SQL = `
tokens_cache_read INTEGER DEFAULT 0,
tokens_cache_creation INTEGER DEFAULT 0,
tokens_reasoning INTEGER DEFAULT 0,
service_tier TEXT DEFAULT 'standard',
status TEXT,
success INTEGER DEFAULT 1,
latency_ms INTEGER DEFAULT 0,
@@ -541,6 +542,11 @@ function ensureUsageHistoryColumns(db: SqliteDatabase) {
db.exec("ALTER TABLE usage_history ADD COLUMN error_code TEXT");
console.log("[DB] Added usage_history.error_code column");
}
if (!columnNames.has("service_tier")) {
db.exec("ALTER TABLE usage_history ADD COLUMN service_tier TEXT DEFAULT 'standard'");
console.log("[DB] Added usage_history.service_tier column");
}
db.exec("CREATE INDEX IF NOT EXISTS idx_uh_service_tier ON usage_history(service_tier)");
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
console.warn("[DB] Failed to verify usage_history schema:", message);

View File

@@ -303,8 +303,10 @@ function isSchemaAlreadyApplied(
);
case "045":
return hasColumn(db, "call_logs", "tokens_compressed");
case "051":
case "053":
return !hasColumn(db, "files", "status");
case "054":
return hasColumn(db, "usage_history", "service_tier");
default:
return false;
}

View File

@@ -97,6 +97,7 @@ CREATE TABLE IF NOT EXISTS usage_history (
tokens_cache_read INTEGER DEFAULT 0,
tokens_cache_creation INTEGER DEFAULT 0,
tokens_reasoning INTEGER DEFAULT 0,
service_tier TEXT DEFAULT 'standard',
status TEXT,
success INTEGER DEFAULT 1,
latency_ms INTEGER DEFAULT 0,
@@ -107,6 +108,7 @@ CREATE TABLE IF NOT EXISTS usage_history (
CREATE INDEX IF NOT EXISTS idx_uh_timestamp ON usage_history(timestamp);
CREATE INDEX IF NOT EXISTS idx_uh_provider ON usage_history(provider);
CREATE INDEX IF NOT EXISTS idx_uh_model ON usage_history(model);
CREATE INDEX IF NOT EXISTS idx_uh_service_tier ON usage_history(service_tier);
CREATE TABLE IF NOT EXISTS call_logs (
id TEXT PRIMARY KEY,

View File

@@ -1,21 +0,0 @@
CREATE TABLE IF NOT EXISTS tier_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS tier_assignments (
provider TEXT NOT NULL,
model TEXT NOT NULL,
tier TEXT NOT NULL CHECK (tier IN ('free', 'cheap', 'premium')),
cost_per_1m_input REAL DEFAULT 0,
cost_per_1m_output REAL DEFAULT 0,
has_free_tier INTEGER DEFAULT 0,
free_quota_limit INTEGER,
reason TEXT,
updated_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (provider, model)
);
CREATE INDEX IF NOT EXISTS idx_tier_assignments_provider ON tier_assignments(provider);
CREATE INDEX IF NOT EXISTS idx_tier_assignments_tier ON tier_assignments(tier);

View File

@@ -1,2 +0,0 @@
-- 051: Remove status column from files table
ALTER TABLE files DROP COLUMN status;

View File

@@ -0,0 +1,4 @@
-- Migration 051: Track effective service tier for usage analytics
-- Used to distinguish Codex Fast (priority) requests from standard requests.
ALTER TABLE usage_history ADD COLUMN service_tier TEXT DEFAULT 'standard';
CREATE INDEX IF NOT EXISTS idx_uh_service_tier ON usage_history(service_tier);

View File

@@ -97,6 +97,7 @@ export async function getSettings() {
hideEndpointTailscaleFunnel: false,
hideEndpointNgrokTunnel: false,
comboConfigMode: "guided",
codexServiceTier: { enabled: false },
alwaysPreserveClientCache: "auto",
idempotencyWindowMs: 5000,
wsAuth: false,

View File

@@ -0,0 +1,65 @@
import { getCodexRequestDefaults, normalizeCodexServiceTier } from "./requestDefaults";
type JsonRecord = Record<string, unknown>;
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
export function isCodexGlobalFastServiceTierEnabled(settings: unknown): boolean {
const record = asRecord(settings);
const codexServiceTier = record.codexServiceTier;
if (typeof codexServiceTier === "boolean") {
return codexServiceTier;
}
const codexServiceTierRecord = asRecord(codexServiceTier);
if (codexServiceTierRecord.enabled === true) {
return true;
}
return record.codexFastServiceTier === true;
}
export function getCodexEffectiveFastServiceTier(
providerSpecificData: unknown,
globalFastServiceTierEnabled: boolean
): boolean {
return (
globalFastServiceTierEnabled ||
getCodexRequestDefaults(providerSpecificData).serviceTier === "priority"
);
}
export function applyCodexGlobalFastServiceTier<T extends JsonRecord | null | undefined>(
provider: string | null | undefined,
credentials: T,
settings: unknown
): T {
if (provider !== "codex" || !isCodexGlobalFastServiceTierEnabled(settings)) {
return credentials;
}
if (!credentials || typeof credentials !== "object" || Array.isArray(credentials)) {
return credentials;
}
const providerSpecificData = asRecord(credentials.providerSpecificData);
const requestDefaults = asRecord(providerSpecificData.requestDefaults);
if (normalizeCodexServiceTier(requestDefaults.serviceTier)) {
return credentials;
}
return {
...credentials,
providerSpecificData: {
...providerSpecificData,
requestDefaults: {
...requestDefaults,
serviceTier: "priority",
},
},
} as T;
}

View File

@@ -22,6 +22,12 @@ export function normalizeModelName(model: string): string {
return parts[parts.length - 1];
}
export type CostCalculationOptions = {
provider?: string | null;
model?: string | null;
serviceTier?: string | null;
};
function toNumber(value: unknown, fallback = 0): number {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim().length > 0) {
@@ -31,6 +37,35 @@ function toNumber(value: unknown, fallback = 0): number {
return fallback;
}
function normalizeServiceTier(value: unknown): string {
return typeof value === "string" ? value.trim().toLowerCase() : "";
}
function stripCodexEffortSuffix(model: string): string {
return model.replace(/-(?:xhigh|high|medium|low|none)$/i, "");
}
export function getCodexFastCostMultiplier(
provider: string | null | undefined,
model: string | null | undefined,
serviceTier: string | null | undefined
): number {
const providerKey = normalizeServiceTier(provider);
const tier = normalizeServiceTier(serviceTier);
if (
(providerKey !== "codex" && providerKey !== "cx") ||
(tier !== "priority" && tier !== "fast")
) {
return 1;
}
const modelKey = stripCodexEffortSuffix(normalizeModelName(String(model || "")).toLowerCase());
const compactModelKey = modelKey.replace(/-/g, "");
if (modelKey === "gpt-5.5" || compactModelKey === "gpt5.5") return 2.5;
if (modelKey === "gpt-5.4" || compactModelKey === "gpt5.4") return 2;
return 1;
}
/**
* Calculate cost for a usage entry.
*
@@ -45,7 +80,8 @@ function toNumber(value: unknown, fallback = 0): number {
*/
export function computeCostFromPricing(
pricing: Record<string, unknown> | null | undefined,
tokens: Record<string, number | undefined> | null | undefined
tokens: Record<string, number | undefined> | null | undefined,
options: CostCalculationOptions = {}
): number {
if (!pricing || !tokens) return 0;
const inputPrice = toNumber(pricing.input, 0);
@@ -71,13 +107,14 @@ export function computeCostFromPricing(
const cacheCreationTokens = tokens.cacheCreation ?? tokens.cache_creation_input_tokens ?? 0;
if (cacheCreationTokens > 0) cost += cacheCreationTokens * (cacheCreationPrice / 1_000_000);
return cost;
return cost * getCodexFastCostMultiplier(options.provider, options.model, options.serviceTier);
}
export async function calculateCost(
provider: string,
model: string,
tokens: Record<string, number | undefined> | null | undefined
tokens: Record<string, number | undefined> | null | undefined,
options: CostCalculationOptions = {}
): Promise<number> {
if (!tokens || !provider || !model) return 0;
@@ -98,7 +135,11 @@ export async function calculateCost(
pricing && typeof pricing === "object" && !Array.isArray(pricing)
? (pricing as Record<string, unknown>)
: {};
return computeCostFromPricing(pricingRecord, tokens);
return computeCostFromPricing(pricingRecord, tokens, {
provider,
model,
...options,
});
} catch (error) {
console.error("Error calculating cost:", error);
return 0;

View File

@@ -46,6 +46,7 @@ interface ProviderConnectionLike {
const PROVIDER_LIMITS_APIKEY_PROVIDERS = new Set([
"glm",
"zai",
"glmt",
"minimax",
"minimax-cn",

View File

@@ -44,6 +44,11 @@ function toStringOrNull(value: unknown): string | null {
return typeof value === "string" && value.trim().length > 0 ? value : null;
}
function normalizeServiceTier(value: unknown): string {
const tier = typeof value === "string" ? value.trim().toLowerCase() : "";
return tier === "priority" || tier === "fast" ? "priority" : "standard";
}
function toNumber(value: unknown): number {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim().length > 0) {
@@ -299,6 +304,7 @@ export async function getUsageDb(sinceIso?: string | null, limit?: number, curso
connectionId: toStringOrNull(r.connection_id),
apiKeyId: toStringOrNull(r.api_key_id),
apiKeyName: toStringOrNull(r.api_key_name),
serviceTier: normalizeServiceTier(r.service_tier),
tokens: {
input: toNumber(r.tokens_input),
output: toNumber(r.tokens_output),
@@ -332,13 +338,14 @@ export async function saveRequestUsage(entry: any) {
try {
const db = getDbInstance();
const timestamp = entry.timestamp || new Date().toISOString();
const serviceTier = normalizeServiceTier(entry.serviceTier ?? entry.service_tier);
db.prepare(
`
INSERT INTO usage_history (provider, model, connection_id, api_key_id, api_key_name,
tokens_input, tokens_output, tokens_cache_read, tokens_cache_creation, tokens_reasoning,
status, success, latency_ms, ttft_ms, error_code, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
service_tier, status, success, latency_ms, ttft_ms, error_code, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
).run(
entry.provider || null,
@@ -351,6 +358,7 @@ export async function saveRequestUsage(entry: any) {
getPromptCacheReadTokens(entry.tokens),
getPromptCacheCreationTokens(entry.tokens),
getReasoningTokens(entry.tokens),
serviceTier,
entry.status || null,
entry.success === false ? 0 : 1,
Number.isFinite(Number(entry.latencyMs)) ? Number(entry.latencyMs) : 0,
@@ -409,6 +417,7 @@ export async function getUsageHistory(filter: any = {}) {
connectionId: toStringOrNull(r.connection_id),
apiKeyId: toStringOrNull(r.api_key_id),
apiKeyName: toStringOrNull(r.api_key_name),
serviceTier: normalizeServiceTier(r.service_tier),
tokens: {
input: toNumber(r.tokens_input),
output: toNumber(r.tokens_output),

View File

@@ -87,7 +87,8 @@ export async function getUsageStats() {
tokens_output,
tokens_cache_read,
tokens_cache_creation,
tokens_reasoning
tokens_reasoning,
service_tier
FROM usage_history
WHERE DATE(timestamp) >= ?
@@ -104,7 +105,8 @@ export async function getUsageStats() {
total_output_tokens as tokens_output,
0 as tokens_cache_read,
0 as tokens_cache_creation,
0 as tokens_reasoning
0 as tokens_reasoning,
'standard' as service_tier
FROM daily_usage_summary
WHERE date < ?
@@ -211,6 +213,7 @@ export async function getUsageStats() {
const connectionId = toStringOrEmpty(row.connection_id) || null;
const apiKeyId = toStringOrEmpty(row.api_key_id) || null;
const apiKeyName = toStringOrEmpty(row.api_key_name) || null;
const serviceTier = toStringOrEmpty(row.service_tier) || "standard";
const promptTokens = toNumber(row.tokens_input);
const completionTokens = toNumber(row.tokens_output);
@@ -223,7 +226,7 @@ export async function getUsageStats() {
cacheCreation: toNumber(row.tokens_cache_creation),
reasoning: toNumber(row.tokens_reasoning),
};
const entryCost = await calculateCost(provider, model, entryTokens);
const entryCost = await calculateCost(provider, model, entryTokens, { serviceTier });
stats.totalPromptTokens += promptTokens;
stats.totalCompletionTokens += completionTokens;

View File

@@ -11,6 +11,7 @@ interface ToggleProps {
size?: "sm" | "md" | "lg";
className?: string;
title?: string;
ariaLabel?: string;
}
export default function Toggle({
@@ -21,6 +22,8 @@ export default function Toggle({
disabled = false,
size = "md",
className,
title,
ariaLabel,
}: ToggleProps) {
const sizes = {
sm: {
@@ -58,7 +61,8 @@ export default function Toggle({
type="button"
role="switch"
aria-checked={checked}
aria-label={!label ? description || "Toggle" : undefined}
aria-label={ariaLabel || label || description || title || "Toggle"}
title={title}
disabled={disabled}
onClick={handleClick}
className={cn(

View File

@@ -18,6 +18,7 @@ import {
ProviderCostDonut,
ModelOverTimeChart,
ProviderTable,
ServiceTierBreakdown,
ApiKeyFilterDropdown,
CustomRangePicker,
} from "./analytics";
@@ -310,10 +311,10 @@ export default function UsageAnalytics() {
color: "text-violet-500",
},
{
icon: "swap_horiz",
label: "Fallback Rate",
value: `${Number(s.fallbackRatePct || 0).toFixed(1)}%`,
color: "text-amber-500",
icon: "bolt",
label: "Fast Requests",
value: fmt(s.fastRequests || 0),
color: "text-sky-500",
},
],
},
@@ -330,6 +331,12 @@ export default function UsageAnalytics() {
value: `${providerDiversity.toFixed(1)}%`,
color: "text-sky-500",
},
{
icon: "swap_horiz",
label: "Fallback Rate",
value: `${Number(s.fallbackRatePct || 0).toFixed(1)}%`,
color: "text-amber-500",
},
],
},
]}
@@ -350,6 +357,9 @@ export default function UsageAnalytics() {
<ProviderCostDonut byProvider={analytics?.byProvider} />
</div>
{/* Fast / Standard service tier split */}
<ServiceTierBreakdown byServiceTier={analytics?.byServiceTier} summary={s} />
{/* Model Usage Over Time (stacked area) */}
<ModelOverTimeChart
dailyByModel={analytics?.dailyByModel}

View File

@@ -1081,6 +1081,71 @@ export function ModelTable({ byModel, summary }) {
);
}
export function ServiceTierBreakdown({ byServiceTier, summary }) {
const data = useMemo(() => byServiceTier || [], [byServiceTier]);
const totalRequests = Number(summary?.totalRequests || 0);
const totalCost = Number(summary?.totalCost || 0);
if (!data.length) {
return null;
}
return (
<Card className="overflow-hidden">
<div className="p-4 border-b border-border flex items-center justify-between gap-3">
<h3 className="text-sm font-semibold text-text-muted uppercase tracking-wider">
Service Tier
</h3>
<span className="text-[11px] text-text-muted">Fast / Standard cost split</span>
</div>
<div className="divide-y divide-border">
{data.map((tier) => {
const isFast = tier.serviceTier === "priority";
const requestPct =
totalRequests > 0
? ((Number(tier.requests || 0) / totalRequests) * 100).toFixed(1)
: "0";
const costPct =
totalCost > 0 ? ((Number(tier.cost || 0) / totalCost) * 100).toFixed(1) : "0";
return (
<div key={tier.serviceTier} className="p-4 flex flex-col gap-3">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<span
className={`material-symbols-outlined text-[18px] ${
isFast ? "text-sky-500" : "text-text-muted"
}`}
>
{isFast ? "bolt" : "speed"}
</span>
<div>
<div className="text-sm font-semibold text-text-main">{tier.label}</div>
<div className="text-xs text-text-muted">
{fmtFull(tier.requests)} requests · {fmt(tier.totalTokens)} tokens
</div>
</div>
</div>
<div className="text-right">
<div className="font-mono text-sm font-semibold text-amber-500">
{fmtCost(tier.cost)}
</div>
<div className="text-xs text-text-muted">{costPct}% of cost</div>
</div>
</div>
<div className="h-1.5 rounded-full bg-black/5 dark:bg-white/10 overflow-hidden">
<div
className={`h-full rounded-full ${isFast ? "bg-sky-500" : "bg-text-muted/50"}`}
style={{ width: `${requestPct}%` }}
/>
</div>
</div>
);
})}
</div>
</Card>
);
}
// ── UsageDetail ────────────────────────────────────────────────────────────
export function UsageDetail({ summary }) {

View File

@@ -25,6 +25,7 @@ export {
ProviderCostDonut,
ModelOverTimeChart,
ProviderTable,
ServiceTierBreakdown,
} from "./charts";
export { default as ApiKeyFilterDropdown } from "./ApiKeyFilterDropdown";

View File

@@ -1849,6 +1849,7 @@ export const USAGE_SUPPORTED_PROVIDERS = [
"claude",
"kimi-coding",
"glm",
"zai",
"glmt",
"minimax",
"minimax-cn",

View File

@@ -8,6 +8,7 @@ export const ROUTING_STRATEGY_VALUES = [
"random",
"least-used",
"cost-optimized",
"reset-aware",
"strict-random",
"auto",
"lkgp",
@@ -123,6 +124,13 @@ export const ROUTING_STRATEGIES: RoutingStrategyOption[] = [
settingsDescKey: "costOptDesc",
icon: "savings",
},
{
value: "reset-aware",
labelKey: "resetAware",
combosDescKey: "resetAwareDesc",
settingsDescKey: "resetAwareDesc",
icon: "event_repeat",
},
{
value: "strict-random",
labelKey: "strictRandom",

View File

@@ -8,6 +8,7 @@ type ReadTimeoutOptions = {
export const DEFAULT_FETCH_TIMEOUT_MS = 600_000;
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 600_000;
export const DEFAULT_STREAM_READINESS_TIMEOUT_MS = 30_000;
export const DEFAULT_FETCH_CONNECT_TIMEOUT_MS = 30_000;
export const DEFAULT_FETCH_KEEPALIVE_TIMEOUT_MS = 4_000;
export const DEFAULT_API_BRIDGE_PROXY_TIMEOUT_MS = 30_000;
@@ -24,6 +25,7 @@ function hasEnvValue(env: EnvSource, name: string): boolean {
export type UpstreamTimeoutConfig = {
fetchTimeoutMs: number;
streamIdleTimeoutMs: number;
streamReadinessTimeoutMs: number;
fetchHeadersTimeoutMs: number;
fetchBodyTimeoutMs: number;
fetchConnectTimeoutMs: number;
@@ -89,10 +91,20 @@ export function getUpstreamTimeoutConfig(
logger,
}
);
const streamReadinessTimeoutMs = readTimeoutMs(
env,
"STREAM_READINESS_TIMEOUT_MS",
DEFAULT_STREAM_READINESS_TIMEOUT_MS,
{
allowZero: true,
logger,
}
);
return {
fetchTimeoutMs,
streamIdleTimeoutMs,
streamReadinessTimeoutMs,
fetchHeadersTimeoutMs: readTimeoutMs(env, "FETCH_HEADERS_TIMEOUT_MS", fetchTimeoutMs, {
allowZero: true,
logger,

View File

@@ -375,6 +375,7 @@ const comboRuntimeConfigSchema = z
strategy: comboStrategySchema.optional(),
maxRetries: z.coerce.number().int().min(0).max(10).optional(),
retryDelayMs: z.coerce.number().int().min(0).max(60000).optional(),
fallbackDelayMs: z.coerce.number().int().min(0).max(60000).optional(),
timeoutMs: z.coerce.number().int().min(1000).optional(),
concurrencyPerModel: z.coerce.number().int().min(1).max(20).optional(),
queueTimeoutMs: z.coerce.number().int().min(1000).max(120000).optional(),
@@ -395,6 +396,10 @@ const comboRuntimeConfigSchema = z
explorationRate: z.number().min(0).max(1).optional(),
routerStrategy: z.string().optional(),
compositeTiers: compositeTiersSchema.optional(),
resetAwareSessionWeight: z.coerce.number().min(0).max(100).optional(),
resetAwareWeeklyWeight: z.coerce.number().min(0).max(100).optional(),
resetAwareTieBandPercent: z.coerce.number().min(0).max(100).optional(),
resetAwareExhaustionGuardPercent: z.coerce.number().min(0).max(100).optional(),
})
.strict();
@@ -439,6 +444,7 @@ export const updateSettingsSchema = z.object({
bruteForceProtection: z.boolean().optional(),
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(),
codexServiceTier: z.object({ enabled: z.boolean() }).optional(),
// Routing settings (#134)
fallbackStrategy: settingsFallbackStrategySchema.optional(),
wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),

View File

@@ -37,6 +37,7 @@ export const updateSettingsSchema = z.object({
debugMode: z.boolean().optional(),
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(),
codexServiceTier: z.object({ enabled: z.boolean() }).optional(),
// Routing settings (#134)
fallbackStrategy: z.enum(ACCOUNT_FALLBACK_STRATEGY_VALUES).optional(),
wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),

View File

@@ -26,7 +26,7 @@ import {
import * as log from "../utils/logger";
import { checkAndRefreshToken } from "../services/tokenRefresh";
import { deleteHandoff, getHandoff } from "@/lib/db/contextHandoffs";
import { getCachedSettings, getSettings, getCombos } from "@/lib/localDb";
import { getCachedSettings, getCombos } from "@/lib/localDb";
import {
ensureOpenAIStoreSessionFallback,
isOpenAIResponsesStoreEnabled,
@@ -91,6 +91,21 @@ registerBailianCodingPlanQuotaFetcher();
// opt-in) when the active bucket reaches zero.
registerCrofUsageFetcher();
let combosCachePromise: Promise<unknown[]> | null = null;
let combosCacheTs = 0;
const COMBOS_CACHE_TTL_MS = 10_000;
async function getCombosCachedForChat(): Promise<unknown[]> {
const now = Date.now();
if (combosCachePromise && now - combosCacheTs < COMBOS_CACHE_TTL_MS) {
return combosCachePromise;
}
combosCacheTs = now;
combosCachePromise = getCombos().catch(() => []);
return combosCachePromise;
}
function normalizeAllowedConnectionIds(value: unknown): string[] | null {
if (!Array.isArray(value)) return null;
const ids = value.filter(
@@ -301,9 +316,18 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
// Pre-check function used by combo routing. For explicit combo live tests,
// avoid pre-skipping so each model gets a real execution attempt.
const comboPreselectedCredentials = new Map<string, any>();
const getComboCredentialCacheKey = (
modelString: string,
target?: { connectionId?: string | null; executionKey?: string | null }
) => `${target?.executionKey || target?.connectionId || ""}:${modelString}`;
const checkModelAvailable = async (
modelString: string,
target?: { connectionId?: string | null; allowedConnectionIds?: string[] | null }
target?: {
connectionId?: string | null;
allowedConnectionIds?: string[] | null;
executionKey?: string | null;
}
) => {
if (isComboLiveTest) return true;
@@ -335,13 +359,14 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
);
if (!creds || creds.allRateLimited) return false;
comboPreselectedCredentials.set(getComboCredentialCacheKey(modelString, target), creds);
return true;
};
// Fetch settings and all combos for config cascade and nested resolution
const [settings, allCombos] = await Promise.all([
getSettings().catch(() => ({})),
getCombos().catch(() => []),
getCachedSettings().catch(() => ({})),
getCombosCachedForChat(),
]);
const relayConfig =
combo.strategy === "context-relay" ? resolveComboConfig(combo, settings) : null;
@@ -376,6 +401,10 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
allowedConnectionIds: target?.allowedConnectionIds ?? null,
comboStepId: target?.stepId || null,
comboExecutionKey: target?.executionKey || target?.stepId || null,
preselectedCredentials: comboPreselectedCredentials.get(
getComboCredentialCacheKey(m, target)
),
cachedSettings: settings,
},
combo.strategy,
true
@@ -384,6 +413,7 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
log,
settings,
allCombos,
apiKeyAllowedConnections: apiKeyInfo?.allowedConnections ?? null,
relayOptions:
combo.strategy === "context-relay"
? {
@@ -494,6 +524,8 @@ async function handleSingleModelChat(
allowedConnectionIds?: string[] | null;
comboStepId?: string | null;
comboExecutionKey?: string | null;
preselectedCredentials?: any;
cachedSettings?: any;
} = {},
comboStrategy: string | null = null,
isCombo: boolean = false
@@ -588,7 +620,7 @@ async function handleSingleModelChat(
const userAgent = request?.headers?.get("user-agent") || "";
const baseRetrySettings = resolveCooldownAwareRetrySettings(
await getCachedSettings().catch(() => ({}))
runtimeOptions.cachedSettings ?? (await getCachedSettings().catch(() => ({})))
);
const disableCooldownAwareRetry =
isCombo || forceLiveComboTest || runtimeOptions.emergencyFallbackTried === true;
@@ -622,26 +654,31 @@ async function handleSingleModelChat(
let lastError = requestRetryLastError;
let lastStatus = requestRetryLastStatus;
let lastCooldownMs = requestRetryLastCooldownMs;
let preselectedCredentials = runtimeOptions.preselectedCredentials;
while (true) {
const credentials = await getProviderCredentialsWithQuotaPreflight(
provider,
null,
effectiveAllowedConnections,
model,
{
excludeConnectionIds: Array.from(excludedConnectionIds),
...(forceLiveComboTest
? {
allowSuppressedConnections: true,
bypassQuotaPolicy: true,
const credentials =
preselectedCredentials && excludedConnectionIds.size === 0
? preselectedCredentials
: await getProviderCredentialsWithQuotaPreflight(
provider,
null,
effectiveAllowedConnections,
model,
{
excludeConnectionIds: Array.from(excludedConnectionIds),
...(forceLiveComboTest
? {
allowSuppressedConnections: true,
bypassQuotaPolicy: true,
}
: {}),
...(runtimeOptions.forcedConnectionId
? { forcedConnectionId: runtimeOptions.forcedConnectionId }
: {}),
}
: {}),
...(runtimeOptions.forcedConnectionId
? { forcedConnectionId: runtimeOptions.forcedConnectionId }
: {}),
}
);
);
preselectedCredentials = null;
if (!credentials || "allRateLimited" in credentials) {
if (credentials?.allRateLimited) {
@@ -775,6 +812,7 @@ async function handleSingleModelChat(
comboExecutionKey: runtimeOptions.comboExecutionKey ?? runtimeOptions.comboStepId ?? null,
extendedContext,
providerProfile,
cachedSettings: runtimeOptions.cachedSettings,
});
if (telemetry) telemetry.endPhase();

View File

@@ -276,6 +276,7 @@ export async function executeChatWithBreaker({
comboExecutionKey,
extendedContext,
providerProfile,
cachedSettings,
}: any): Promise<{ result: any; tlsFingerprintUsed: boolean }> {
let tlsFingerprintUsed = false;
@@ -296,6 +297,7 @@ export async function executeChatWithBreaker({
isCombo,
comboStepId,
comboExecutionKey,
cachedSettings,
onCredentialsRefreshed: async (newCreds: any) => {
await updateProviderCredentials(credentials.connectionId, {
accessToken: newCreds.accessToken,

View File

@@ -1,6 +1,6 @@
// Re-export from open-sse with localDb integration
import { getModelAliases, getComboByName, getProviderNodes, getCustomModels } from "@/lib/localDb";
import { getSettings } from "@/lib/localDb";
import { getCachedSettings } from "@/lib/localDb";
import { getComboStepTarget } from "@/lib/combos/steps";
import {
parseModel,
@@ -83,7 +83,7 @@ export async function getModelInfo(modelStr) {
// stripModelPrefix: if enabled, strip provider prefix and re-resolve
// the bare model name using existing heuristics (claude-* → anthropic, etc.)
try {
const settings = await getSettings();
const settings = await getCachedSettings();
if (settings.stripModelPrefix === true) {
const strippedResult = await getModelInfoCore(parsed.model, getModelAliases);
return { ...strippedResult, extendedContext };

View File

@@ -10,6 +10,7 @@ export interface Combo {
nodes: ComboNode[];
maxRetries: number;
retryDelayMs: number;
fallbackDelayMs?: number;
timeoutMs: number;
healthCheckEnabled: boolean;
createdAt: string;

View File

@@ -32,6 +32,7 @@ export interface ComboDefaults {
strategy: RoutingStrategyValue;
maxRetries: number;
retryDelayMs: number;
fallbackDelayMs?: number;
maxComboDepth: number;
trackMetrics: boolean;
concurrencyPerModel?: number;

View File

@@ -0,0 +1,55 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
applyCodexGlobalFastServiceTier,
getCodexEffectiveFastServiceTier,
isCodexGlobalFastServiceTierEnabled,
} from "../../src/lib/providers/codexFastTier.ts";
test("Codex global fast tier recognizes legacy and current setting shapes", () => {
assert.equal(isCodexGlobalFastServiceTierEnabled({ codexServiceTier: { enabled: true } }), true);
assert.equal(isCodexGlobalFastServiceTierEnabled({ codexServiceTier: true }), true);
assert.equal(isCodexGlobalFastServiceTierEnabled({ codexFastServiceTier: true }), true);
assert.equal(
isCodexGlobalFastServiceTierEnabled({ codexServiceTier: { enabled: false } }),
false
);
assert.equal(isCodexGlobalFastServiceTierEnabled({}), false);
});
test("Codex effective fast tier combines global and per-connection defaults", () => {
assert.equal(getCodexEffectiveFastServiceTier({}, false), false);
assert.equal(getCodexEffectiveFastServiceTier({}, true), true);
assert.equal(
getCodexEffectiveFastServiceTier({ requestDefaults: { serviceTier: "priority" } }, false),
true
);
assert.equal(
getCodexEffectiveFastServiceTier({ requestDefaults: { serviceTier: "fast" } }, false),
true
);
});
test("Codex global fast tier injects priority default without overwriting connection defaults", () => {
const injected = applyCodexGlobalFastServiceTier(
"codex",
{ providerSpecificData: { workspaceId: "ws-1" } },
{ codexServiceTier: { enabled: true } }
);
assert.deepEqual(injected.providerSpecificData, {
workspaceId: "ws-1",
requestDefaults: { serviceTier: "priority" },
});
const existing = { providerSpecificData: { requestDefaults: { serviceTier: "fast" } } };
assert.equal(
applyCodexGlobalFastServiceTier("codex", existing, { codexServiceTier: { enabled: true } }),
existing
);
assert.equal(
applyCodexGlobalFastServiceTier("openai", existing, { codexServiceTier: { enabled: true } }),
existing
);
});

View File

@@ -133,7 +133,7 @@ test("signal abort during fallback wait interrupts immediately", async () => {
combo: makeCombo("priority", ["a/m1", "b/m2"]),
handleSingleModel,
log,
settings: { retryDelayMs: 5000 }, // 5s delay would normally be slow
settings: { fallbackDelayMs: 5000 }, // 5s delay would normally be slow
allCombos: [],
signal: ac.signal,
});

View File

@@ -14,6 +14,7 @@ test("getDefaultComboConfig returns a fresh copy of the defaults", () => {
assert.equal(first.strategy, "priority");
assert.equal(first.maxRetries, 1);
assert.equal(first.retryDelayMs, 2000);
assert.equal(first.fallbackDelayMs, 0);
assert.ok(!("timeoutMs" in first));
assert.ok(!("healthCheckEnabled" in first));
assert.equal(first.handoffThreshold, 0.85);
@@ -40,6 +41,7 @@ test("resolveComboConfig applies the full cascade from defaults to combo overrid
openai: {
timeoutMs: 60000,
retryDelayMs: 500,
fallbackDelayMs: 100,
},
},
},
@@ -48,6 +50,7 @@ test("resolveComboConfig applies the full cascade from defaults to combo overrid
assert.equal(result.strategy, "round-robin");
assert.equal(result.retryDelayMs, 500);
assert.equal(result.fallbackDelayMs, 100);
assert.equal(result.maxRetries, 4);
assert.ok(!("timeoutMs" in result));
assert.ok(!("healthCheckEnabled" in result));

View File

@@ -1,16 +1,22 @@
import test, { after } from "node:test";
import assert from "node:assert/strict";
import { randomUUID } from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-strategies-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
const ORIGINAL_FETCH = globalThis.fetch;
process.env.DATA_DIR = TEST_DATA_DIR;
const dbCore = await import("../../src/lib/db/core.ts");
const { handleComboChat } = await import("../../open-sse/services/combo.ts");
const { invalidateCodexQuotaCache, registerCodexConnection, registerCodexQuotaFetcher } =
await import("../../open-sse/services/codexQuotaFetcher.ts");
const { registerQuotaFetcher } = await import("../../open-sse/services/quotaPreflight.ts");
const combosDb = await import("../../src/lib/db/combos.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const { recordComboRequest } = await import("../../open-sse/services/comboMetrics.ts");
const { saveModelsDevCapabilities } = await import("../../src/lib/modelsDevSync.ts");
@@ -22,6 +28,7 @@ after(() => {
} else {
process.env.DATA_DIR = ORIGINAL_DATA_DIR;
}
globalThis.fetch = ORIGINAL_FETCH;
});
const reqBodyNullContext = {
@@ -93,8 +100,105 @@ async function selectedModelFor(combo: Record<string, unknown>, body: Record<str
return calls[0];
}
function codexQuota({
used5h,
reset5hSeconds,
used7d,
reset7dSeconds,
}: {
used5h: number;
reset5hSeconds: number;
used7d: number;
reset7dSeconds: number;
}) {
return {
rate_limit: {
primary_window: {
used_percent: used5h,
reset_after_seconds: reset5hSeconds,
},
secondary_window: {
used_percent: used7d,
reset_after_seconds: reset7dSeconds,
},
},
};
}
function installCodexQuotaMock(quotasByToken: Record<string, unknown>) {
const previousFetch = globalThis.fetch;
globalThis.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
const headers = init?.headers as Record<string, string> | undefined;
const authorization = headers?.Authorization || headers?.authorization || "";
const token = authorization.replace(/^Bearer\s+/i, "");
const quota = quotasByToken[token];
if (!quota) return Response.json({ error: "missing quota" }, { status: 404 });
return Response.json(quota);
};
return () => {
globalThis.fetch = previousFetch;
};
}
function resetAwareCombo(
name: string,
connections: Array<{ id: string; token: string }>,
config: Record<string, unknown> = {}
) {
registerCodexQuotaFetcher();
for (const connection of connections) {
invalidateCodexQuotaCache(connection.id);
registerCodexConnection(connection.id, { accessToken: connection.token });
}
return {
name,
strategy: "reset-aware",
config,
models: connections.map((connection, index) => ({
kind: "model",
provider: "codex",
providerId: "codex",
model: "gpt-5",
connectionId: connection.id,
id: `${name}-${index}`,
})),
};
}
async function selectedConnectionFor(
combo: Record<string, unknown>,
options: { apiKeyAllowedConnections?: string[] | null } = {}
) {
const calls: Array<string | null> = [];
const response = await handleComboChat({
body: reqBodyTextArray,
combo,
allCombos: [combo],
isModelAvailable: undefined,
relayOptions: undefined,
signal: undefined,
settings: {},
log: makeLog(),
apiKeyAllowedConnections: options.apiKeyAllowedConnections,
handleSingleModel: async (
_body: unknown,
modelStr: string,
target?: { connectionId?: string | null; allowedConnectionIds?: string[] | null }
) => {
calls.push(target?.connectionId ?? null);
return okResponse(modelStr);
},
});
assert.equal(response.status, 200);
assert.equal(calls.length > 0, true);
return calls[0];
}
test("least-used strategy prefers the model with fewer recorded combo requests", async () => {
const name = `least-used-${crypto.randomUUID()}`;
const name = `least-used-${randomUUID()}`;
const busyModel = "openai/gpt-4";
const idleModel = "openai/gpt-3.5-turbo";
const combo = await combosDb.createCombo({
@@ -121,7 +225,7 @@ test("context-optimized strategy prefers the largest context window", async () =
});
const combo = await combosDb.createCombo({
name: `context-optimized-${crypto.randomUUID()}`,
name: `context-optimized-${randomUUID()}`,
strategy: "context-optimized",
models: ["test-context/small", "test-context/large", "unknown/unknown"],
});
@@ -131,7 +235,7 @@ test("context-optimized strategy prefers the largest context window", async () =
test("auto strategy handles null and empty prompt edge cases without throwing", async () => {
const combo = await combosDb.createCombo({
name: `auto-${crypto.randomUUID()}`,
name: `auto-${randomUUID()}`,
strategy: "auto",
config: { auto: { explorationRate: 0 } },
models: ["openai/gpt-4"],
@@ -146,3 +250,288 @@ test("auto strategy handles null and empty prompt edge cases without throwing",
);
assert.equal(await selectedModelFor(combo, { model: combo.name, messages: [] }), "openai/gpt-4");
});
test("reset-aware strategy prefers lower weekly remaining quota when reset is much sooner", async (t) => {
const soon = { id: `soon-${randomUUID()}`, token: `token-soon-${randomUUID()}` };
const later = { id: `later-${randomUUID()}`, token: `token-later-${randomUUID()}` };
t.after(
installCodexQuotaMock({
[soon.token]: codexQuota({
used5h: 10,
reset5hSeconds: 3600,
used7d: 40,
reset7dSeconds: 24 * 3600,
}),
[later.token]: codexQuota({
used5h: 10,
reset5hSeconds: 3600,
used7d: 20,
reset7dSeconds: 5 * 24 * 3600,
}),
})
);
const combo = resetAwareCombo(`reset-aware-soon-${randomUUID()}`, [soon, later]);
assert.equal(await selectedConnectionFor(combo), soon.id);
});
test("reset-aware strategy avoids accounts near 5h exhaustion", async (t) => {
const exhausted5h = {
id: `exhausted-${randomUUID()}`,
token: `token-exhausted-${randomUUID()}`,
};
const healthy5h = {
id: `healthy-${randomUUID()}`,
token: `token-healthy-${randomUUID()}`,
};
t.after(
installCodexQuotaMock({
[exhausted5h.token]: codexQuota({
used5h: 98,
reset5hSeconds: 20 * 60,
used7d: 5,
reset7dSeconds: 24 * 3600,
}),
[healthy5h.token]: codexQuota({
used5h: 20,
reset5hSeconds: 4 * 3600,
used7d: 50,
reset7dSeconds: 4 * 24 * 3600,
}),
})
);
const combo = resetAwareCombo(`reset-aware-guard-${randomUUID()}`, [exhausted5h, healthy5h]);
assert.equal(await selectedConnectionFor(combo), healthy5h.id);
});
test("reset-aware strategy rotates similar scores with round-robin tie breaking", async (t) => {
const first = { id: `first-${randomUUID()}`, token: `token-first-${randomUUID()}` };
const second = {
id: `second-${randomUUID()}`,
token: `token-second-${randomUUID()}`,
};
const reset5hAt = Math.floor((Date.now() + 2 * 3600 * 1000) / 1000);
const reset7dAt = Math.floor((Date.now() + 3 * 24 * 3600 * 1000) / 1000);
const quota = {
rate_limit: {
primary_window: { used_percent: 50, reset_at: reset5hAt },
secondary_window: { used_percent: 50, reset_at: reset7dAt },
},
};
t.after(
installCodexQuotaMock({
[first.token]: quota,
[second.token]: quota,
})
);
const combo = resetAwareCombo(`reset-aware-rr-${randomUUID()}`, [first, second], {
resetAwareTieBandPercent: 100,
});
const selections = [
await selectedConnectionFor(combo),
await selectedConnectionFor(combo),
await selectedConnectionFor(combo),
];
assert.equal(selections.includes(first.id), true);
assert.equal(selections.includes(second.id), true);
});
test("reset-aware strategy uses registered quota fetchers for non-Codex providers", async () => {
const provider = `quota-provider-${randomUUID()}`;
const soon = `soon-${randomUUID()}`;
const later = `later-${randomUUID()}`;
const resetAtSoon = new Date(Date.now() + 24 * 3600 * 1000).toISOString();
const resetAtLater = new Date(Date.now() + 5 * 24 * 3600 * 1000).toISOString();
registerQuotaFetcher(provider, async (connectionId) => {
if (connectionId === soon) {
return { used: 40, total: 100, percentUsed: 0.4, resetAt: resetAtSoon };
}
if (connectionId === later) {
return { used: 20, total: 100, percentUsed: 0.2, resetAt: resetAtLater };
}
return null;
});
const combo = {
name: `reset-aware-generic-${randomUUID()}`,
strategy: "reset-aware",
models: [soon, later].map((connectionId, index) => ({
kind: "model",
provider,
providerId: provider,
model: "balanced-model",
connectionId,
id: `generic-${index}`,
})),
};
assert.equal(await selectedConnectionFor(combo), soon);
});
test("reset-aware strategy deduplicates quota fetches for repeated connection targets", async () => {
const provider = `dedupe-provider-${randomUUID()}`;
const connectionId = `shared-${randomUUID()}`;
let fetchCount = 0;
registerQuotaFetcher(provider, async (id) => {
fetchCount++;
assert.equal(id, connectionId);
return {
used: 20,
total: 100,
percentUsed: 0.2,
resetAt: new Date(Date.now() + 24 * 3600 * 1000).toISOString(),
};
});
const combo = {
name: `reset-aware-dedupe-${randomUUID()}`,
strategy: "reset-aware",
models: ["model-a", "model-b"].map((model, index) => ({
kind: "model",
provider,
providerId: provider,
model,
connectionId,
id: `dedupe-${index}`,
})),
};
assert.equal(await selectedConnectionFor(combo), connectionId);
assert.equal(fetchCount, 1);
});
test("reset-aware strategy respects API-key allowed connections during expansion", async () => {
const provider = `limited-provider-${randomUUID()}`;
const disallowed = await providersDb.createProviderConnection({
provider,
authType: "apikey",
name: `disallowed-${randomUUID()}`,
apiKey: "sk-disallowed",
isActive: true,
});
const allowed = await providersDb.createProviderConnection({
provider,
authType: "apikey",
name: `allowed-${randomUUID()}`,
apiKey: "sk-allowed",
isActive: true,
});
const allowedId = String(allowed.id);
const disallowedId = String(disallowed.id);
const fetchedConnectionIds: string[] = [];
registerQuotaFetcher(provider, async (connectionId) => {
fetchedConnectionIds.push(connectionId);
return {
used: connectionId === disallowedId ? 60 : 20,
total: 100,
percentUsed: connectionId === disallowedId ? 0.6 : 0.2,
resetAt: new Date(Date.now() + 24 * 3600 * 1000).toISOString(),
};
});
const combo = {
name: `reset-aware-api-key-${randomUUID()}`,
strategy: "reset-aware",
models: [
{
kind: "model",
provider,
providerId: provider,
model: "balanced-model",
id: "limited-provider-step",
},
],
};
assert.equal(
await selectedConnectionFor(combo, { apiKeyAllowedConnections: [allowedId] }),
allowedId
);
assert.deepEqual(fetchedConnectionIds, [allowedId]);
});
test("reset-aware strategy parses numeric reset timestamps from quota telemetry", async () => {
const provider = `timestamp-provider-${randomUUID()}`;
const soon = `timestamp-soon-${randomUUID()}`;
const later = `timestamp-later-${randomUUID()}`;
const soonResetSeconds = Math.floor((Date.now() + 24 * 3600 * 1000) / 1000);
const laterResetMs = Date.now() + 5 * 24 * 3600 * 1000;
registerQuotaFetcher(provider, async (connectionId) => ({
used: connectionId === soon ? 40 : 20,
total: 100,
percentUsed: connectionId === soon ? 0.4 : 0.2,
resetAt: connectionId === soon ? soonResetSeconds : laterResetMs,
}));
const combo = {
name: `reset-aware-timestamps-${randomUUID()}`,
strategy: "reset-aware",
models: [soon, later].map((connectionId, index) => ({
kind: "model",
provider,
providerId: provider,
model: "balanced-model",
connectionId,
id: `timestamp-${index}`,
})),
};
assert.equal(await selectedConnectionFor(combo), soon);
});
test("reset-aware strategy scores provider-specific weekly windows when available", async () => {
const provider = `weekly-provider-${randomUUID()}`;
const soon = `weekly-soon-${randomUUID()}`;
const later = `weekly-later-${randomUUID()}`;
const resetAtSoon = new Date(Date.now() + 24 * 3600 * 1000).toISOString();
const resetAtLater = new Date(Date.now() + 5 * 24 * 3600 * 1000).toISOString();
registerQuotaFetcher(provider, async (connectionId) => {
if (connectionId === soon) {
return {
used: 40,
total: 100,
percentUsed: 0.4,
resetAt: resetAtSoon,
window5h: { percentUsed: 0.1, resetAt: resetAtSoon },
windowWeekly: { percentUsed: 0.4, resetAt: resetAtSoon },
};
}
if (connectionId === later) {
return {
used: 20,
total: 100,
percentUsed: 0.2,
resetAt: resetAtLater,
window5h: { percentUsed: 0.1, resetAt: resetAtSoon },
windowWeekly: { percentUsed: 0.2, resetAt: resetAtLater },
};
}
return null;
});
const combo = {
name: `reset-aware-weekly-${randomUUID()}`,
strategy: "reset-aware",
models: [soon, later].map((connectionId, index) => ({
kind: "model",
provider,
providerId: provider,
model: "balanced-model",
connectionId,
id: `weekly-${index}`,
})),
};
assert.equal(await selectedConnectionFor(combo), soon);
});

View File

@@ -67,6 +67,7 @@ test("quota labels normalize session and weekly windows while preserving readabl
});
test("MiniMax providers are exposed to the limits dashboard support list", () => {
assert.ok(providerConstants.USAGE_SUPPORTED_PROVIDERS.includes("zai"));
assert.ok(providerConstants.USAGE_SUPPORTED_PROVIDERS.includes("minimax"));
assert.ok(providerConstants.USAGE_SUPPORTED_PROVIDERS.includes("minimax-cn"));
});
@@ -104,3 +105,44 @@ test("MiniMax quota payloads use generic provider parsing and stale resets still
assert.equal(providerLimitUtils.formatQuotaLabel(parsed[0].name), "Session");
assert.equal(providerLimitUtils.formatQuotaLabel(parsed[1].name), "Weekly");
});
test("Z.AI quota labels render 5h, weekly and monthly tool usage", () => {
assert.equal(providerLimitUtils.formatQuotaLabel("5 Hours Quota"), "5 Hours");
assert.equal(providerLimitUtils.formatQuotaLabel("Weekly Quota"), "Weekly");
assert.equal(providerLimitUtils.formatQuotaLabel("Monthly Tools"), "Monthly Tools");
const future = new Date(Date.now() + 5 * 60_000).toISOString();
const parsed = providerLimitUtils.parseQuotaData("zai", {
quotas: {
"5 Hours Quota": {
used: 15,
total: 100,
remaining: 85,
remainingPercentage: 85,
resetAt: future,
},
"Weekly Quota": {
used: 4,
total: 100,
remaining: 96,
remainingPercentage: 96,
resetAt: future,
},
"Monthly Tools": {
used: 0,
total: 100,
remaining: 100,
remainingPercentage: 100,
resetAt: future,
},
},
});
assert.equal(parsed.length, 3);
assert.equal(parsed[0].name, "5 Hours Quota");
assert.equal(parsed[0].remainingPercentage, 85);
assert.equal(parsed[1].name, "Weekly Quota");
assert.equal(parsed[1].remainingPercentage, 96);
assert.equal(parsed[2].name, "Monthly Tools");
assert.equal(parsed[2].remainingPercentage, 100);
});

View File

@@ -68,6 +68,23 @@ test("settings schemas accept combo configuration modes", () => {
assert.equal(sharedSettingsSchema.safeParse({ comboConfigMode: "compact" }).success, false);
});
test("settings schemas accept global Codex fast tier setting", () => {
const payload = { codexServiceTier: { enabled: true } };
const routeParsed = settingsRouteSchema.parse(payload);
const sharedParsed = sharedSettingsSchema.parse(payload);
assert.deepEqual(routeParsed.codexServiceTier, { enabled: true });
assert.deepEqual(sharedParsed.codexServiceTier, { enabled: true });
assert.equal(
settingsRouteSchema.safeParse({ codexServiceTier: { enabled: "yes" } }).success,
false
);
assert.equal(
sharedSettingsSchema.safeParse({ codexServiceTier: { enabled: "yes" } }).success,
false
);
});
test("settings schemas accept endpoint tunnel visibility toggles", () => {
const payload = {
hideEndpointCloudflaredTunnel: true,

View File

@@ -150,6 +150,50 @@ test("GET /api/usage/analytics resolves Codex GPT-5.5 pricing through provider a
assertClose(body.byModel[0].cost, 0.02);
});
test("GET /api/usage/analytics applies Codex Fast tier multipliers and exposes tier split", async () => {
const db = core.getDbInstance();
const timestamp = new Date().toISOString();
db.prepare(
`INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, success, latency_ms, service_tier, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run("codex", "gpt-5.5", "codex-fast", 1000, 500, 1, 250, "priority", timestamp);
db.prepare(
`INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, success, latency_ms, service_tier, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run("codex", "gpt-5.5", "codex-standard", 1000, 500, 1, 250, "standard", timestamp);
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assertClose(body.summary.totalCost, 0.07);
assert.equal(body.summary.fastRequests, 1);
assert.equal(body.summary.standardRequests, 1);
assertClose(body.summary.fastCost, 0.05);
assertClose(body.summary.standardCost, 0.02);
assert.equal(body.byServiceTier.length, 2);
assertClose(body.byProvider[0].cost, 0.07);
assertClose(body.byModel[0].cost, 0.07);
});
test("GET /api/usage/analytics applies Codex GPT-5.4 Fast multiplier", async () => {
await localDb.updatePricing({
codex: { "gpt-5.4": { input: 5, output: 30 } },
});
const db = core.getDbInstance();
db.prepare(
`INSERT INTO usage_history (provider, model, connection_id, tokens_input, tokens_output, success, latency_ms, service_tier, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run("codex", "gpt-5.4", "codex-fast", 1000, 500, 1, 250, "priority", new Date().toISOString());
const response = await analyticsRoute.GET(makeRequest("http://localhost/api/usage/analytics"));
const body = await response.json();
assert.equal(response.status, 200);
assertClose(body.summary.totalCost, 0.04);
assertClose(body.summary.fastCost, 0.04);
});
test("GET /api/usage/analytics maps Codex auto-review usage to GPT-5.5 pricing", async () => {
const db = core.getDbInstance();
db.prepare(

View File

@@ -363,6 +363,22 @@ test("computeAnalytics groups renamed API key usage by stable ID", async () => {
assert.equal(analytics.byApiKey[0].completionTokens, 15);
});
test("Codex Fast service tier applies documented GPT-5.5 and GPT-5.4 cost multipliers", async () => {
await localDb.updatePricing({
codex: {
"gpt-5.5": { input: 5, output: 30 },
"gpt-5.4": { input: 5, output: 30 },
},
});
const tokens = { input: 1000, output: 500 };
assert.equal(await calculateCost("codex", "gpt-5.5", tokens), 0.02);
assert.equal(await calculateCost("codex", "gpt-5.5", tokens, { serviceTier: "priority" }), 0.05);
assert.equal(await calculateCost("codex", "gpt-5.4-high", tokens, { serviceTier: "fast" }), 0.04);
assert.equal(await calculateCost("openai", "gpt-5.5", tokens, { serviceTier: "priority" }), 0);
});
test("recent request summaries are generated from SQLite call logs", async () => {
const connection = await providersDb.createProviderConnection({
provider: "log-provider",

View File

@@ -28,6 +28,7 @@ async function seedUsageEntries(
success?: boolean;
latencyMs?: number;
minutesAgo?: number;
serviceTier?: string;
}>
) {
for (const [i, e] of entries.entries()) {
@@ -41,6 +42,7 @@ async function seedUsageEntries(
success: e.success !== false,
latencyMs: e.latencyMs || 100,
timestamp: new Date(Date.now() - (e.minutesAgo || i) * 60 * 1000).toISOString(),
serviceTier: e.serviceTier,
});
}
}
@@ -89,6 +91,32 @@ test("getUsageDb filters by sinceIso date", async () => {
assert.equal(result.data.history[0].provider, "new");
});
test("usage history persists service tier and defaults to standard", async () => {
await usageHistory.saveRequestUsage({
provider: "codex",
model: "gpt-5.5",
tokens: { input: 100, output: 50 },
success: true,
serviceTier: "priority",
timestamp: new Date().toISOString(),
});
await usageHistory.saveRequestUsage({
provider: "openai",
model: "gpt-4o",
tokens: { input: 10, output: 5 },
success: true,
timestamp: new Date(Date.now() + 1).toISOString(),
});
const history = await usageHistory.getUsageHistory({ provider: "codex" });
const usageDb = await usageHistory.getUsageDb();
assert.equal(history.length, 1);
assert.equal(history[0].serviceTier, "priority");
assert.equal(usageDb.data.history[0].serviceTier, "priority");
assert.equal(usageDb.data.history[1].serviceTier, "standard");
});
test("getUsageDb provides nextCursor when rows exceed MAX_ROWS", async () => {
const db = core.getDbInstance();
// Insert exactly MAX_ROWS + 1 = 10001 rows so getUsageDb returns a cursor

View File

@@ -856,7 +856,7 @@ test("usage service covers Codex auth failures, Kiro hard failures, Kimi no-quot
assert.equal(qwenCatch.message, "Unable to fetch Qwen usage.");
});
test("usage service covers Qwen, Qoder, GLM and GLMT branches", async () => {
test("usage service covers Qwen, Qoder, GLM, Z.AI and GLMT branches", async () => {
const qwenMissingUrl: any = await usageService.getUsageForProvider({
provider: "qwen",
accessToken: "qwen-token",
@@ -877,6 +877,15 @@ test("usage service covers Qwen, Qoder, GLM and GLMT branches", async () => {
});
assert.match(qoder.message, /Usage tracked per request/i);
const glmMissingKey: any = await usageService.getUsageForProvider({
provider: "glm",
apiKey: "",
});
assert.equal(
glmMissingKey.message,
"API key not available. Add a coding plan API key to view usage."
);
globalThis.fetch = async (url, init = {}) => {
if (String(url).includes("/api/monitor/usage/quota/limit")) {
assert.equal((init as any).headers.Authorization, "Bearer glm-key");
@@ -887,9 +896,24 @@ test("usage service covers Qwen, Qoder, GLM and GLMT branches", async () => {
limits: [
{
type: "TOKENS_LIMIT",
percentage: "64",
unit: 3,
usage: 15,
currentValue: 85,
percentage: "15",
nextResetTime: Date.now() + 120_000,
},
{
type: "TOKENS_LIMIT",
unit: 6,
percentage: "64",
nextResetTime: Date.now() + 604_800_000,
},
{
type: "TIME_LIMIT",
unit: 5,
percentage: "7",
nextResetTime: Date.now() + 300_000,
},
{
type: "OTHER_LIMIT",
percentage: "10",
@@ -910,8 +934,21 @@ test("usage service covers Qwen, Qoder, GLM and GLMT branches", async () => {
providerSpecificData: { apiRegion: "invalid-region" },
});
assert.equal(glm.plan, "Pro");
assert.equal(glm.quotas.session.used, 64);
assert.equal(glm.quotas.session.remaining, 36);
assert.equal(glm.quotas["5 Hours Quota"].used, 15);
assert.equal(glm.quotas["5 Hours Quota"].remaining, 85);
assert.equal(glm.quotas["Weekly Quota"].used, 64);
assert.equal(glm.quotas["Weekly Quota"].remaining, 36);
assert.equal(glm.quotas["Monthly Tools"].used, 7);
assert.equal(glm.quotas["Monthly Tools"].remaining, 93);
const zai: any = await usageService.getUsageForProvider({
provider: "zai",
apiKey: "glm-key",
});
assert.equal(zai.plan, "Pro");
assert.equal(zai.quotas["5 Hours Quota"].used, 15);
assert.equal(zai.quotas["Weekly Quota"].remaining, 36);
assert.equal(zai.quotas["Monthly Tools"].remaining, 93);
const glmt: any = await usageService.getUsageForProvider({
provider: "glmt",
@@ -919,8 +956,8 @@ test("usage service covers Qwen, Qoder, GLM and GLMT branches", async () => {
providerSpecificData: { apiRegion: "international" },
});
assert.equal(glmt.plan, "Pro");
assert.equal(glmt.quotas.session.used, 64);
assert.equal(glmt.quotas.session.remaining, 36);
assert.equal(glmt.quotas["5 Hours Quota"].used, 15);
assert.equal(glmt.quotas["Weekly Quota"].remaining, 36);
globalThis.fetch = async () => new Response("nope", { status: 401 });
await assert.rejects(