Merge remote-tracking branch 'origin/release/v3.8.29' into HEAD

This commit is contained in:
diegosouzapw
2026-06-18 19:12:43 -03:00
14 changed files with 1148 additions and 860 deletions

View File

@@ -13,17 +13,21 @@ jobs:
runs-on: ubuntu-latest
# Mutation testing is expensive. History of the budget:
# - Full 8-module set TIMED OUT at the 180min cap (run 27705123780 = exactly 180min).
# The two god-files chatCore.ts/combo.ts dominate ~2/3 of the mutants and were
# removed from stryker.conf.json `mutate` (the Onda 3 split re-adds them).
# - The remaining 6 modules STILL timed out at 180min (run 27732877861 = exactly
# 180min): auth.ts and accountFallback.ts are large and the perTest dry-run over
# ~130 covering test files is itself costly.
# So the 6 modules are split into 3 PARALLEL batches of 2 (the two big modules in
# separate batches), each overriding the mutate set via `--mutate` (Stryker 9 CLI:
# The two god-files chatCore.ts/combo.ts dominated ~2/3 of the mutants and were
# removed from stryker.conf.json `mutate`.
# - The remaining 6 modules in 3 batches: auth.ts and accountFallback.ts are large; the
# a (auth+publicCreds) and b (accountFallback+error) batches still ran near the 180min
# cap (the perTest dry-run over ~130 covering test files is itself costly), so the two
# big modules are now ISOLATED into their own batches (a=auth, b=accountFallback).
# - Onda 3 / Fase 9 T5 re-add: combo.ts was split into 11 leaves; the 8 well-covered
# combo/* leaves are back in `mutate` (covered by the 24 combo-*.test.ts), grouped into
# 2 batches (d=heavy, e=light). chatCore/* leaves + quotaScoring/quotaStrategies (#4204)
# are follow-ups — see _mutate_godfiles_excluded_comment in stryker.conf.json.
# 5 PARALLEL batches, each overriding the mutate set via `--mutate` (Stryker 9 CLI:
# `-m, --mutate <comma-list>`; the conf's `mutate[]` remains the local-run default/union).
# Each batch is ~1/3 of the mutants and fits 180min; full coverage every night, in
# parallel (≈180min wall-clock). Re-merge into fewer batches once the Onda 3 god-file
# split shrinks per-module mutant counts.
# Each batch targets <180min; full coverage every night in parallel (~180min wall-clock).
# The `incremental` cache (per-batch key) makes runs after the first cold one much cheaper,
# so the isolated big-module batches recover even if a first cold run nears the cap.
# Runs at stryker concurrency=4 with per-process DATA_DIR isolation
# (tests/_setup/isolateDataDir.ts) — see _concurrency_comment in stryker.conf.json.
strategy:
@@ -31,11 +35,15 @@ jobs:
matrix:
batch:
- name: a
mutate: "src/sse/services/auth.ts,open-sse/utils/publicCreds.ts"
mutate: "src/sse/services/auth.ts"
- name: b
mutate: "open-sse/services/accountFallback.ts,open-sse/utils/error.ts"
mutate: "open-sse/services/accountFallback.ts"
- name: c
mutate: "src/server/authz/routeGuard.ts,src/shared/utils/circuitBreaker.ts"
mutate: "src/server/authz/routeGuard.ts,src/shared/utils/circuitBreaker.ts,open-sse/utils/error.ts,open-sse/utils/publicCreds.ts"
- name: d
mutate: "open-sse/services/combo/comboStructure.ts,open-sse/services/combo/autoStrategy.ts,open-sse/services/combo/validateQuality.ts"
- name: e
mutate: "open-sse/services/combo/shadowRouting.ts,open-sse/services/combo/targetSorters.ts,open-sse/services/combo/comboPredicates.ts,open-sse/services/combo/rrState.ts,open-sse/services/combo/comboData.ts"
timeout-minutes: 180
steps:
- uses: actions/checkout@v6

View File

@@ -12,6 +12,7 @@
"_rebaseline_2026_06_18_qg9_combo_split_d6": "QG v2 Fase 9 T5 D6: combo.ts 4430->3819 — combo structure resolution extracted byte-identically to the new open-sse/services/combo/comboStructure.ts (638, <cap): runtime-step normalization, nested-combo/DAG expansion (resolveNestedComboTargets/expandRuntimeStep/getComboFromData/getComboModelsFromData/validateComboDAG/resolveNestedComboModels), weighted/direct resolution (resolveComboTargets/resolveWeightedTargets/getDirectComboTargets + composite-tier ordering), request-compatibility filtering (filterTargetsByRequestCompatibility + deriveRequestCompatibilityRequirements/getTargetCompatibilityFailures and their private helpers) and context-size sorting. Shared dedupeTargetsByExecutionKey + the toTrimmedString/toComboLike/getCombosArray normalizers also moved (dedupe to comboData.ts since both combo.ts and comboStructure use it). The 7 previously-public symbols are re-exported from combo.ts; the ~20 external consumers (chatCore.ts, /api/combos routes, embeddings, usage, catalog) are unchanged. shadowRouting.ts's resolveNestedComboTargets import redirected from the barrel to ./comboStructure.ts (removing the D4 temporary edge). Orphaned combo.ts imports dropped (getComboModelString/normalizeComboStep, getModelContextLimit, getResolvedModelCapabilities, getTargetProvider, MAX_COMBO_DEPTH, and 6 now-unused type imports). Pure move, no logic change.",
"_rebaseline_2026_06_18_qg9_combo_split_d8": "QG v2 Fase 9 T5 D8 (reduced): combo.ts 3819->3432 — auto-strategy scoring/intent/tag-routing extracted byte-identically to the new open-sse/services/combo/autoStrategy.ts (434, <cap): the quota-soft execution-candidate registry (the single _activeExecutionCandidates Map + setCandidateQuotaSoftPenalty/_registerExecutionCandidates/_unregisterExecutionCandidates, kept together for state cohesion), QUOTA_SOFT_DEPRIORITIZE_FACTOR, scoreAutoTargets, expandAutoComboCandidatePool, intent extraction (toTextContent/extractPromptForIntent/mapIntentToTaskType/toStringArray/getIntentConfig), applyRequestTagRouting and deriveComboSessionKey. buildAutoCandidates + its two private-only helpers calculateTargetContextAffinity/getBootstrapLatencyMs (and the DEFAULT_MODEL_P95_MS/MIN_HISTORY_SAMPLES/OUTPUT_TOKEN_RATIO consts they own) were DELIBERATELY KEPT in combo.ts: buildAutoCandidates is the sole user of the internal reset-window helpers (resolveResetWindowConfig/fetchResetAwareQuotaWithCache/calculateResetWindowAffinity + the ResetWindowConfig type), so keeping it there leaves those helpers private (no export) and avoids a combo <-> autoStrategy import cycle (a later task can move the reset-window block out of combo.ts without breaking autoStrategy). autoStrategy.ts never imports from the combo barrel. dedupeTargetsByExecutionKey was already in comboData.ts (D6) and is NOT re-moved. QUOTA_SOFT_DEPRIORITIZE_FACTOR + setCandidateQuotaSoftPenalty stay re-exported from combo.ts for chatCore.ts's dynamic import(\"../services/combo\"); scoreAutoTargets/expandAutoComboCandidatePool keep their public re-export too. Orphaned combo.ts imports dropped where moved-out symbols stopped being referenced; ProviderCandidate/AutoProviderCandidate/HistoricalLatencyStatsEntry types are imported into combo.ts for buildAutoCandidates. Pure move, no logic change.",
"_rebaseline_2026_06_18_qg9_combo_split_d7a": "QG v2 Fase 9 T5 D7a: combo.ts 3440->3398 (wc -l 3397 + 1, stacked on #4194 which added the passthrough-quota guards) — round-robin sticky state extracted byte-identically to the new open-sse/services/combo/rrState.ts (71, <cap). Moved exactly the 6 RR-state symbols: MAX_RR_COUNTERS, the two mutable module Maps rrCounters (new Map<string,number>) + rrStickyTargets, and the three helpers clampStickyRoundRobinTargetLimit/getStickyRoundRobinStartIndex/recordStickyRoundRobinSuccess. STATE COHESION: rrCounters and rrStickyTargets remain SINGLE instances defined once in rrState.ts; combo.ts imports the same references back and keeps mutating them directly in orderTargetsByResetAwareQuota/orderTargetsByResetWindow/handleRoundRobinCombo (no Map duplicated — sticky-3 round-robin behavior preserved, guarded by combo-routing-e2e.test.ts). The quota state left behind (MAX_RESET_AWARE_CACHE/resetAwareConnectionCache/resetAwareQuotaCache) stays in combo.ts for a later task (D7b). None of the 6 were ever public, so combo.ts imports all six back (no re-export). rrState.ts never imports from the combo barrel; it imports only ResolvedComboTarget from ./types.ts. Pure move, no logic change.",
"_rebaseline_2026_06_18_qg9_combo_split_d7b": "QG v2 Fase 9 T5 D7b: combo.ts 3398->2597 (wc -l 2596 + 1) — the reset-aware / reset-window quota block extracted byte-identically into two new leaves under open-sse/services/combo/ (both <cap). Split was size-driven (~818 source LOC > 800 cap): quotaScoring.ts (311) holds the PURE half (config consts/resolvers + window-math/scoring helpers, no state, no async); quotaStrategies.ts (568) holds the STATEFUL/async half — the two mutable caches resetAwareConnectionCache + resetAwareQuotaCache (new Map) + MAX_RESET_AWARE_CACHE kept as SINGLE instances next to their only readers/writers getQuotaAwareConnectionsForTarget + fetchResetAwareQuotaWithCache (STATE COHESION; grep 'resetAware*Cache = new Map' in combo.ts == 0), plus normalizeConnectionIds/filterAllowedConnectionIds/getTargetConnectionIds/mapWithConcurrency/preScreenTargets/orderTargetsByResetAwareQuota/orderTargetsByResetWindow. quotaStrategies imports rrCounters + MAX_RR_COUNTERS from ./rrState.ts (D7a) and the pure helpers from ./quotaScoring.ts; neither leaf imports the combo barrel. combo.ts imports back the 3 reset-window helpers buildAutoCandidates needs (resolveResetWindowConfig/fetchResetAwareQuotaWithCache/calculateResetWindowAffinity) + resolveSlaRoutingPolicy + the 3 orderers used by the orchestrator; preScreenTargets stays re-exported. Orphaned imports dropped (clamp01/PRE_SCREEN_CONCURRENCY/ProviderProfile/SlaRoutingPolicy/IsModelAvailable); hasPerModelQuota KEPT (used by the #4194 passthrough-quota guards that stay). buildAutoCandidates + calculateTargetContextAffinity/getBootstrapLatencyMs stay (D8). Pure move, no logic change.",
"_rebaseline_2026_06_18_8_2_sliding_window": "Fase 8.2 own growth: rateLimitManager.ts 1017->1022 (+5 = one import + one `await awaitProviderDefaultSlot(...)` call + a 2-line comment at the existing withRateLimit chokepoint). All sliding-window logic was extracted to the new open-sse/services/providerDefaultRateLimit.ts + open-sse/services/slidingWindowLimiter.ts (both <cap), NOT inlined. Thin wiring only; not further shrinkable.",
"_rebaseline_2026_06_18_8_1_no_thinking_alias": "Fase 8.1 own growth: catalog.ts 1435->1440 (+5 = appendNoThinkingVariants(finalModels) call + comment at the existing finalModels chokepoint) and chat.ts 1458->1471 (+13 = applyNoThinkingAlias(body) call + comment right after body.model is read, before model resolution). All real logic lives in the new open-sse/utils/noThinkingAlias.ts (<cap); both edits are thin wiring of tested helpers at the single correct integration point in each file. Not extractable.",
"_rebaseline_2026_06_18_4_4_midstream": "Fase 4.4 own growth: chatCore.ts 5980->6009 (+29 at the existing streaming-return chokepoint = capture streamRecovery.continueMidStream alongside .enabled; refactor the early-retry reopen thunk into a shared runUpstreamStream(body) helper — net DRY — and add the gated continueStream(assistantSoFar) thunk that re-runs the upstream with makeContinuationBody(bodyToSend, …), plus the onContinue log). All continuation logic (scanOpenAiSseText, makeContinuationBody, trimContinuationOverlap, the createRecoverableStream continuation path) lives in open-sse/services/streamRecovery.ts (<cap). Closes over the same per-attempt executor locals as the existing reopen; not extractable without hiding the dispatch boundary. Opt-in (default OFF).",
@@ -77,7 +78,7 @@
"open-sse/services/batchProcessor.ts": 828,
"open-sse/services/browserBackedChat.ts": 850,
"open-sse/services/claudeCodeCompatible.ts": 1202,
"open-sse/services/combo.ts": 3398,
"open-sse/services/combo.ts": 2597,
"open-sse/services/rateLimitManager.ts": 1035,
"open-sse/services/tokenRefresh.ts": 1997,
"open-sse/services/usage.ts": 3408,

View File

@@ -17,11 +17,9 @@ import {
recordProviderFailure,
isProviderExhaustedReason,
hasPerModelQuota,
type ProviderProfile,
} from "./accountFallback.ts";
import { FETCH_TIMEOUT_MS, RateLimitReason } from "../config/constants.ts";
import { errorResponse, unavailableResponse } from "../utils/error.ts";
import { clamp01 } from "../utils/number.ts";
import {
recordComboIntent,
recordComboRequest,
@@ -32,7 +30,6 @@ import {
resolveComboConfig,
getDefaultComboConfig,
resolveComboTargetTimeoutMs,
PRE_SCREEN_CONCURRENCY,
} from "./comboConfig.ts";
import {
maybeGenerateHandoff,
@@ -62,7 +59,7 @@ import { emit } from "../../src/lib/events/eventBus";
import { notifyWebhookEvent } from "../../src/lib/webhookDispatcher";
import { classifyWithConfig } from "./intentClassifier.ts";
import { selectProvider as selectAutoProvider } from "./autoCombo/engine.ts";
import { selectWithStrategy, type SlaRoutingPolicy } from "./autoCombo/routerStrategy.ts";
import { selectWithStrategy } from "./autoCombo/routerStrategy.ts";
import { parseAutoPrefix } from "./autoCombo/autoPrefix.ts";
import { handlePipelineCombo, buildPipelineResponse } from "./autoCombo/pipelineRouter.ts";
import { DEFAULT_WEIGHTS, type ProviderCandidate, type ScoringWeights } from "./autoCombo/scoring.ts";
@@ -91,7 +88,6 @@ import type {
ComboRetryAfter,
ComboErrorBody,
SingleModelTarget,
IsModelAvailable,
HandleComboChatOptions,
HandleRoundRobinOptions,
ResolvedComboTarget,
@@ -150,6 +146,19 @@ import {
expandAutoComboCandidatePool,
deriveComboSessionKey,
} from "./combo/autoStrategy.ts";
import {
resolveResetWindowConfig,
resolveSlaRoutingPolicy,
calculateResetWindowAffinity,
type ResetWindowConfig,
} from "./combo/quotaScoring.ts";
import {
fetchResetAwareQuotaWithCache,
preScreenTargets,
orderTargetsByResetAwareQuota,
orderTargetsByResetWindow,
type PreScreenResult,
} from "./combo/quotaStrategies.ts";
// Backward-compatible re-exports — these were public from combo.ts before the
// types extraction (Quality Gate v2 / Fase 9). Keep the external surface stable.
@@ -162,6 +171,9 @@ export type { SingleModelTarget, ResolvedComboTarget };
export { validateResponseQuality };
export { clampComboDepth, shouldSkipForPredictedTtft, shouldRecordProviderBreakerFailure };
export { resolveShadowTargets, scheduleShadowRouting };
// preScreenTargets was public from combo.ts before the reset-aware quota
// extraction (combo split D7b). Keep the external surface stable.
export { preScreenTargets };
export { resolveComboTargets, filterTargetsByRequestCompatibility };
export {
getComboFromData,
@@ -171,695 +183,13 @@ export {
validateComboDAG,
} from "./combo/comboStructure.ts";
const RESET_AWARE_SESSION_WINDOW_MS = 5 * 60 * 60 * 1000;
const RESET_AWARE_WEEKLY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
const RESET_AWARE_SESSION_REMAINING_WEIGHT = 0.45;
const RESET_AWARE_SESSION_RESET_PRESSURE_WEIGHT = 0.55;
const RESET_AWARE_WEEKLY_REMAINING_WEIGHT = 0.25;
const RESET_AWARE_WEEKLY_RESET_PRESSURE_WEIGHT = 0.75;
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,
};
const RESET_WINDOW_DEFAULT_TIE_BAND_MS = 60_000;
type ResetWindowName = (typeof RESET_WINDOW_NAMES)[number];
type QuotaFetchCacheConfig = {
quotaCacheTtlMs: number;
quotaCacheMaxStaleMs: number;
};
type ResetWindowConfig = ReturnType<typeof resolveResetWindowConfig>;
const MAX_RESET_AWARE_CACHE = 200;
const resetAwareConnectionCache = new Map<
string,
{ fetchedAt: number; connections: Array<Record<string, unknown>> }
>();
const resetAwareQuotaCache = new Map<
string,
{ fetchedAt: number; quota: unknown; refreshPromise: Promise<unknown> | null }
>();
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 getDurationConfig(value: unknown, fallback: number, max: number): number {
const numericValue = finiteNumberOrNull(value);
if (numericValue === null || numericValue < 0) return fallback;
return Math.min(max, Math.floor(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,
quotaCacheTtlMs: getDurationConfig(config?.resetAwareQuotaCacheTtlMs, 0, 300_000),
quotaCacheMaxStaleMs: getDurationConfig(config?.resetAwareQuotaCacheMaxStaleMs, 0, 3_600_000),
};
}
function resolveResetWindowConfig(config: Record<string, unknown> | null | undefined) {
const rawWindows = Array.isArray(config?.resetWindowWindows) ? config.resetWindowWindows : null;
const windows = rawWindows
?.filter((windowName): windowName is ResetWindowName =>
(RESET_WINDOW_NAMES as readonly string[]).includes(String(windowName))
)
.filter((windowName, index, array) => array.indexOf(windowName) === index);
const effectiveWindows =
windows && windows.length > 0
? windows
: config?.resetWindowIncludeSession === true
? (["weekly", "session"] as ResetWindowName[])
: (["weekly"] as ResetWindowName[]);
return {
windows: effectiveWindows,
tieBandMs: Math.max(
0,
finiteNumberOrNull(config?.resetWindowTieBandMs) ?? RESET_WINDOW_DEFAULT_TIE_BAND_MS
),
quotaCacheTtlMs: getDurationConfig(config?.resetWindowQuotaCacheTtlMs, 0, 300_000),
quotaCacheMaxStaleMs: getDurationConfig(config?.resetWindowQuotaCacheMaxStaleMs, 0, 3_600_000),
};
}
function resolveSlaRoutingPolicy(
config: Record<string, unknown> | null | undefined
): SlaRoutingPolicy | undefined {
if (!config) return undefined;
const nestedSla = isRecord(config.sla) ? config.sla : {};
const targetP95Ms = finiteNumberOrNull(config.slaTargetP95Ms ?? nestedSla.targetP95Ms);
const maxErrorRate = finiteNumberOrNull(config.slaMaxErrorRate ?? nestedSla.maxErrorRate);
const maxCostPer1MTokens = finiteNumberOrNull(
config.slaMaxCostPer1MTokens ?? nestedSla.maxCostPer1MTokens
);
const hardConstraints = config.slaHardConstraints ?? nestedSla.hardConstraints;
const policy: SlaRoutingPolicy = {};
if (targetP95Ms !== null && targetP95Ms > 0) policy.targetP95Ms = targetP95Ms;
if (maxErrorRate !== null && maxErrorRate >= 0) policy.maxErrorRate = clamp01(maxErrorRate);
if (maxCostPer1MTokens !== null && maxCostPer1MTokens > 0) {
policy.maxCostPer1MTokens = maxCostPer1MTokens;
}
if (typeof hardConstraints === "boolean") policy.hardConstraints = hardConstraints;
return Object.keys(policy).length > 0 ? policy : undefined;
}
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 normalizeWindowPercentUsed(value: unknown): number | null {
const numericValue = finiteNumberOrNull(value);
if (numericValue === null) return null;
if (numericValue > 1) return clamp01(numericValue / 100);
return clamp01(numericValue);
}
function getNamedQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
if (!quota || !isRecord(quota)) return null;
if (windowName === "session") return getQuotaWindow(quota, "window5h");
if (windowName === "weekly") {
return getQuotaWindow(quota, "window7d") || getQuotaWindow(quota, "windowWeekly");
}
if (windowName === "monthly") return getQuotaWindow(quota, "windowMonthly");
return null;
}
function getWindowsMapQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
if (!quota || !isRecord(quota) || !isRecord(quota.windows)) return null;
const candidates = Object.entries(quota.windows)
.map(([key, value]) => ({ key: key.toLowerCase(), value }))
.filter(({ key }) => key === windowName || key.startsWith(`${windowName} `));
if (candidates.length === 0) return null;
candidates.sort((a, b) => a.key.localeCompare(b.key));
const window = candidates[0].value;
if (!isRecord(window)) return null;
return {
percentUsed: normalizeWindowPercentUsed(window.percentUsed),
resetAt: normalizeResetAt(window.resetAt),
};
}
function resolveQuotaWindowByName(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
return getNamedQuotaWindow(quota, windowName) || getWindowsMapQuotaWindow(quota, windowName);
}
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,
remainingWeight: number,
resetPressureWeight: number
): number {
const normalizedRemaining = clamp01(remaining);
const resetUrgency = getResetUrgency(resetAt, windowMs);
const resetPressure = resetUrgency * (1 - normalizedRemaining);
return remainingWeight * normalizedRemaining + resetPressureWeight * resetPressure;
}
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,
RESET_AWARE_SESSION_REMAINING_WEIGHT,
RESET_AWARE_SESSION_RESET_PRESSURE_WEIGHT
);
const weeklyScore = scoreQuotaWindow(
weeklyRemaining,
weeklyWindow?.resetAt ?? normalizeResetAt(quota.resetAt),
RESET_AWARE_WEEKLY_WINDOW_MS,
RESET_AWARE_WEEKLY_REMAINING_WEIGHT,
RESET_AWARE_WEEKLY_RESET_PRESSURE_WEIGHT
);
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>>)
: [];
if (
!resetAwareConnectionCache.has(provider) &&
resetAwareConnectionCache.size >= MAX_RESET_AWARE_CACHE
) {
const oldest = resetAwareConnectionCache.keys().next().value;
if (oldest !== undefined) resetAwareConnectionCache.delete(oldest);
}
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 fetchResetAwareQuotaWithCache({
provider,
connectionId,
connection,
fetcher,
config,
log,
comboName,
}: {
provider: string;
connectionId: string;
connection?: Record<string, unknown>;
fetcher: (connectionId: string, connection?: Record<string, unknown>) => Promise<unknown>;
config: QuotaFetchCacheConfig;
log: { debug?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void };
comboName: string;
}): Promise<unknown> {
const cacheKey = `${provider}:${connectionId}`;
const ttlMs = config.quotaCacheTtlMs;
const maxStaleMs = config.quotaCacheMaxStaleMs;
const now = Date.now();
const cached = resetAwareQuotaCache.get(cacheKey);
if (ttlMs <= 0 && maxStaleMs <= 0) {
try {
return await fetcher(connectionId, connection);
} catch (error) {
log.warn?.("COMBO", "Reset-aware quota fetch failed.", {
comboName,
connectionId,
err: error,
operation: "quotaFetch",
provider,
});
return null;
}
}
const refresh = () => {
const existing = resetAwareQuotaCache.get(cacheKey);
if (existing?.refreshPromise != null) return existing.refreshPromise;
const refreshPromise = fetcher(connectionId, connection)
.then((quota) => {
if (quota) {
if (
!resetAwareQuotaCache.has(cacheKey) &&
resetAwareQuotaCache.size >= MAX_RESET_AWARE_CACHE
) {
const oldest = resetAwareQuotaCache.keys().next().value;
if (oldest !== undefined) resetAwareQuotaCache.delete(oldest);
}
resetAwareQuotaCache.set(cacheKey, {
quota,
fetchedAt: Date.now(),
refreshPromise: null,
});
} else {
resetAwareQuotaCache.delete(cacheKey);
}
return quota;
})
.catch((error) => {
const previous = resetAwareQuotaCache.get(cacheKey);
if (previous) {
if (
!resetAwareQuotaCache.has(cacheKey) &&
resetAwareQuotaCache.size >= MAX_RESET_AWARE_CACHE
) {
const oldest = resetAwareQuotaCache.keys().next().value;
if (oldest !== undefined) resetAwareQuotaCache.delete(oldest);
}
resetAwareQuotaCache.set(cacheKey, { ...previous, refreshPromise: null });
}
log.warn?.("COMBO", "Reset-aware quota fetch failed.", {
comboName,
connectionId,
err: error,
operation: "quotaFetch",
provider,
});
return null;
});
if (!resetAwareQuotaCache.has(cacheKey) && resetAwareQuotaCache.size >= MAX_RESET_AWARE_CACHE) {
const oldest = resetAwareQuotaCache.keys().next().value;
if (oldest !== undefined) resetAwareQuotaCache.delete(oldest);
}
resetAwareQuotaCache.set(cacheKey, {
quota: existing?.quota ?? cached?.quota ?? null,
fetchedAt: existing?.fetchedAt ?? cached?.fetchedAt ?? 0,
refreshPromise,
});
return refreshPromise;
};
if (ttlMs > 0 && cached) {
const age = now - cached.fetchedAt;
if (age <= ttlMs) return cached.quota;
if (maxStaleMs > 0 && age <= ttlMs + maxStaleMs) {
void refresh();
return cached.quota;
}
}
return refresh();
}
type PreScreenResult = { profile: ProviderProfile | null; available: boolean };
export async function preScreenTargets(
targets: ResolvedComboTarget[],
isModelAvailable?: IsModelAvailable | null
): Promise<Map<string, PreScreenResult>> {
if (targets.length === 0) {
return new Map();
}
const results = await mapWithConcurrency(
targets,
PRE_SCREEN_CONCURRENCY,
async (target): Promise<{ key: string; result: PreScreenResult }> => {
const profile = await getRuntimeProviderProfile(target.provider).catch(() => null);
const breaker = getCircuitBreaker(target.provider);
if (breaker.getStatus().state === "OPEN") {
return { key: target.executionKey, result: { profile, available: false } };
}
let available = true;
if (isModelAvailable) {
// IsModelAvailable may return a sync boolean or a Promise; Promise.resolve
// normalizes both so the .catch() never runs against a bare boolean.
available = await Promise.resolve(isModelAvailable(target.modelStr, target)).catch(
() => true
);
}
return { key: target.executionKey, result: { profile, available } };
}
);
const map = new Map<string, PreScreenResult>();
for (const { key, result } of results) {
map.set(key, result);
}
return map;
}
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,
fetchResetAwareQuotaWithCache({
provider,
connectionId: target.connectionId,
connection: connectionById.get(target.connectionId),
fetcher,
config,
log,
comboName,
})
);
}
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;
if (!rrCounters.has(key) && rrCounters.size >= MAX_RR_COUNTERS) {
const oldest = rrCounters.keys().next().value;
if (oldest !== undefined) rrCounters.delete(oldest);
}
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 getResetWindowTimestampMs(quota: unknown, windows: ResetWindowName[]): number {
if (!quota || !isRecord(quota) || quota.limitReached === true) return Infinity;
let selectedResetMs = Infinity;
for (const windowName of windows) {
const window = resolveQuotaWindowByName(quota, windowName);
const resetMs = parseResetTimeMs(window?.resetAt ?? null);
if (Number.isFinite(resetMs)) {
selectedResetMs = Math.min(selectedResetMs, resetMs);
}
}
if (!Number.isFinite(selectedResetMs)) {
selectedResetMs = parseResetTimeMs(normalizeResetAt(quota.resetAt));
}
return Number.isFinite(selectedResetMs) ? selectedResetMs : Infinity;
}
function getResetWindowHorizonMs(windows: ResetWindowName[]): number {
if (windows.includes("monthly")) return 30 * 24 * 60 * 60 * 1000;
if (windows.includes("weekly")) return RESET_AWARE_WEEKLY_WINDOW_MS;
return RESET_AWARE_SESSION_WINDOW_MS;
}
function calculateResetWindowAffinity(quota: unknown, config: ResetWindowConfig): number {
const resetMs = getResetWindowTimestampMs(quota, config.windows);
if (!Number.isFinite(resetMs)) return 0.5;
const msUntilReset = resetMs - Date.now();
if (msUntilReset <= 0) return 1;
return clamp01(1 - msUntilReset / getResetWindowHorizonMs(config.windows));
}
// Reset-aware / reset-window quota config, scoring, and window-math helpers were
// extracted to combo/quotaScoring.ts (pure) and the stateful cache + strategy
// orderers to combo/quotaStrategies.ts (combo split D7b). The two cache Maps
// (resetAwareConnectionCache, resetAwareQuotaCache) live as single instances in
// quotaStrategies.ts alongside their only readers/writers (state cohesion).
// combo.ts imports back the three reset-window helpers buildAutoCandidates +
// orchestration need, plus the strategy orderers and preScreenTargets (above).
// Bootstrap defaults from ClawRouter benchmark (used when no local latency history exists yet)
const DEFAULT_MODEL_P95_MS: Record<string, number> = {
@@ -1069,137 +399,6 @@ export async function buildAutoCandidates(
return candidates;
}
async function orderTargetsByResetWindow(
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 = resolveResetWindowConfig(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,
fetchResetAwareQuotaWithCache({
provider,
connectionId: target.connectionId,
connection: connectionById.get(target.connectionId),
fetcher,
config,
log,
comboName,
})
);
}
quota = await quotaPromises.get(quotaKey)!;
}
return {
target,
resetMs: getResetWindowTimestampMs(quota, config.windows),
index,
};
}
);
scoredTargets.sort((a, b) => {
if (a.resetMs !== b.resetMs) return a.resetMs - b.resetMs;
return a.index - b.index;
});
const bestResetMs = scoredTargets[0]?.resetMs ?? Infinity;
if (!Number.isFinite(bestResetMs) || config.tieBandMs <= 0) {
return scoredTargets.map((entry) => entry.target);
}
const tiedTargets = scoredTargets.filter(
(entry) => entry.resetMs - bestResetMs <= config.tieBandMs
);
if (tiedTargets.length <= 1) return scoredTargets.map((entry) => entry.target);
const key = `reset-window:${comboName}`;
const counter = rrCounters.get(key) || 0;
if (!rrCounters.has(key) && rrCounters.size >= MAX_RR_COUNTERS) {
const oldest = rrCounters.keys().next().value;
if (oldest !== undefined) rrCounters.delete(oldest);
}
rrCounters.set(key, counter + 1);
const startIndex = counter % tiedTargets.length;
const 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);
}
/**
* Handle combo chat with fallback.
* @param {Object} options

View File

@@ -0,0 +1,311 @@
/**
* Pure scoring + window-math helpers for the reset-aware / reset-window combo
* strategies. No module state, no async, no I/O — extracted byte-identically
* from combo.ts (QG v2 Fase 9 T5 D7b) as the pure half of the reset-aware quota
* block. The stateful/async half (cache Maps + connection/quota fetchers +
* orderTargets*) lives in ./quotaStrategies.ts, which imports the scoring
* helpers from here.
*
* Pure leaf: this module never imports from the combo barrel.
*/
import { clamp01 } from "../../utils/number.ts";
import { isRecord } from "./comboData.ts";
import type { SlaRoutingPolicy } from "../autoCombo/routerStrategy.ts";
import { RESET_WINDOW_NAMES } from "./types.ts";
import type { ResolvedComboTarget } from "./types.ts";
const RESET_AWARE_SESSION_WINDOW_MS = 5 * 60 * 60 * 1000;
const RESET_AWARE_WEEKLY_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
const RESET_AWARE_SESSION_REMAINING_WEIGHT = 0.45;
const RESET_AWARE_SESSION_RESET_PRESSURE_WEIGHT = 0.55;
const RESET_AWARE_WEEKLY_REMAINING_WEIGHT = 0.25;
const RESET_AWARE_WEEKLY_RESET_PRESSURE_WEIGHT = 0.75;
const RESET_AWARE_DEFAULTS = {
sessionWeight: 0.35,
weeklyWeight: 0.65,
tieBandPercent: 5,
exhaustionGuardPercent: 10,
};
const RESET_WINDOW_DEFAULT_TIE_BAND_MS = 60_000;
type ResetWindowName = (typeof RESET_WINDOW_NAMES)[number];
export type QuotaFetchCacheConfig = {
quotaCacheTtlMs: number;
quotaCacheMaxStaleMs: number;
};
export type ResetWindowConfig = ReturnType<typeof resolveResetWindowConfig>;
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 getDurationConfig(value: unknown, fallback: number, max: number): number {
const numericValue = finiteNumberOrNull(value);
if (numericValue === null || numericValue < 0) return fallback;
return Math.min(max, Math.floor(numericValue));
}
export 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,
quotaCacheTtlMs: getDurationConfig(config?.resetAwareQuotaCacheTtlMs, 0, 300_000),
quotaCacheMaxStaleMs: getDurationConfig(config?.resetAwareQuotaCacheMaxStaleMs, 0, 3_600_000),
};
}
export function resolveResetWindowConfig(config: Record<string, unknown> | null | undefined) {
const rawWindows = Array.isArray(config?.resetWindowWindows) ? config.resetWindowWindows : null;
const windows = rawWindows
?.filter((windowName): windowName is ResetWindowName =>
(RESET_WINDOW_NAMES as readonly string[]).includes(String(windowName))
)
.filter((windowName, index, array) => array.indexOf(windowName) === index);
const effectiveWindows =
windows && windows.length > 0
? windows
: config?.resetWindowIncludeSession === true
? (["weekly", "session"] as ResetWindowName[])
: (["weekly"] as ResetWindowName[]);
return {
windows: effectiveWindows,
tieBandMs: Math.max(
0,
finiteNumberOrNull(config?.resetWindowTieBandMs) ?? RESET_WINDOW_DEFAULT_TIE_BAND_MS
),
quotaCacheTtlMs: getDurationConfig(config?.resetWindowQuotaCacheTtlMs, 0, 300_000),
quotaCacheMaxStaleMs: getDurationConfig(config?.resetWindowQuotaCacheMaxStaleMs, 0, 3_600_000),
};
}
export function resolveSlaRoutingPolicy(
config: Record<string, unknown> | null | undefined
): SlaRoutingPolicy | undefined {
if (!config) return undefined;
const nestedSla = isRecord(config.sla) ? config.sla : {};
const targetP95Ms = finiteNumberOrNull(config.slaTargetP95Ms ?? nestedSla.targetP95Ms);
const maxErrorRate = finiteNumberOrNull(config.slaMaxErrorRate ?? nestedSla.maxErrorRate);
const maxCostPer1MTokens = finiteNumberOrNull(
config.slaMaxCostPer1MTokens ?? nestedSla.maxCostPer1MTokens
);
const hardConstraints = config.slaHardConstraints ?? nestedSla.hardConstraints;
const policy: SlaRoutingPolicy = {};
if (targetP95Ms !== null && targetP95Ms > 0) policy.targetP95Ms = targetP95Ms;
if (maxErrorRate !== null && maxErrorRate >= 0) policy.maxErrorRate = clamp01(maxErrorRate);
if (maxCostPer1MTokens !== null && maxCostPer1MTokens > 0) {
policy.maxCostPer1MTokens = maxCostPer1MTokens;
}
if (typeof hardConstraints === "boolean") policy.hardConstraints = hardConstraints;
return Object.keys(policy).length > 0 ? policy : undefined;
}
export 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 normalizeWindowPercentUsed(value: unknown): number | null {
const numericValue = finiteNumberOrNull(value);
if (numericValue === null) return null;
if (numericValue > 1) return clamp01(numericValue / 100);
return clamp01(numericValue);
}
function getNamedQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
if (!quota || !isRecord(quota)) return null;
if (windowName === "session") return getQuotaWindow(quota, "window5h");
if (windowName === "weekly") {
return getQuotaWindow(quota, "window7d") || getQuotaWindow(quota, "windowWeekly");
}
if (windowName === "monthly") return getQuotaWindow(quota, "windowMonthly");
return null;
}
function getWindowsMapQuotaWindow(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
if (!quota || !isRecord(quota) || !isRecord(quota.windows)) return null;
const candidates = Object.entries(quota.windows)
.map(([key, value]) => ({ key: key.toLowerCase(), value }))
.filter(({ key }) => key === windowName || key.startsWith(`${windowName} `));
if (candidates.length === 0) return null;
candidates.sort((a, b) => a.key.localeCompare(b.key));
const window = candidates[0].value;
if (!isRecord(window)) return null;
return {
percentUsed: normalizeWindowPercentUsed(window.percentUsed),
resetAt: normalizeResetAt(window.resetAt),
};
}
function resolveQuotaWindowByName(
quota: unknown,
windowName: ResetWindowName
): { percentUsed: number | null; resetAt: string | null } | null {
return getNamedQuotaWindow(quota, windowName) || getWindowsMapQuotaWindow(quota, windowName);
}
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,
remainingWeight: number,
resetPressureWeight: number
): number {
const normalizedRemaining = clamp01(remaining);
const resetUrgency = getResetUrgency(resetAt, windowMs);
const resetPressure = resetUrgency * (1 - normalizedRemaining);
return remainingWeight * normalizedRemaining + resetPressureWeight * resetPressure;
}
export 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,
RESET_AWARE_SESSION_REMAINING_WEIGHT,
RESET_AWARE_SESSION_RESET_PRESSURE_WEIGHT
);
const weeklyScore = scoreQuotaWindow(
weeklyRemaining,
weeklyWindow?.resetAt ?? normalizeResetAt(quota.resetAt),
RESET_AWARE_WEEKLY_WINDOW_MS,
RESET_AWARE_WEEKLY_REMAINING_WEIGHT,
RESET_AWARE_WEEKLY_RESET_PRESSURE_WEIGHT
);
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 };
}
export function getResetWindowTimestampMs(quota: unknown, windows: ResetWindowName[]): number {
if (!quota || !isRecord(quota) || quota.limitReached === true) return Infinity;
let selectedResetMs = Infinity;
for (const windowName of windows) {
const window = resolveQuotaWindowByName(quota, windowName);
const resetMs = parseResetTimeMs(window?.resetAt ?? null);
if (Number.isFinite(resetMs)) {
selectedResetMs = Math.min(selectedResetMs, resetMs);
}
}
if (!Number.isFinite(selectedResetMs)) {
selectedResetMs = parseResetTimeMs(normalizeResetAt(quota.resetAt));
}
return Number.isFinite(selectedResetMs) ? selectedResetMs : Infinity;
}
function getResetWindowHorizonMs(windows: ResetWindowName[]): number {
if (windows.includes("monthly")) return 30 * 24 * 60 * 60 * 1000;
if (windows.includes("weekly")) return RESET_AWARE_WEEKLY_WINDOW_MS;
return RESET_AWARE_SESSION_WINDOW_MS;
}
export function calculateResetWindowAffinity(quota: unknown, config: ResetWindowConfig): number {
const resetMs = getResetWindowTimestampMs(quota, config.windows);
if (!Number.isFinite(resetMs)) return 0.5;
const msUntilReset = resetMs - Date.now();
if (msUntilReset <= 0) return 1;
return clamp01(1 - msUntilReset / getResetWindowHorizonMs(config.windows));
}

View File

@@ -0,0 +1,568 @@
/**
* Stateful + async reset-aware / reset-window quota strategies for combo routing.
*
* Holds the two mutable module-level caches that back reset-aware routing
* (`resetAwareConnectionCache` for per-provider active connections and
* `resetAwareQuotaCache` for per-connection quota snapshots), plus the helpers
* that read/write them and the strategy orderers. Extracted byte-identically
* from combo.ts (QG v2 Fase 9 T5 D7b) — the larger, stateful half of the
* reset-aware quota block. The pure scoring/window-math half lives in
* ./quotaScoring.ts and is imported here.
*
* State cohesion: `resetAwareConnectionCache`, `resetAwareQuotaCache`, and
* `MAX_RESET_AWARE_CACHE` MUST remain single instances defined once here,
* alongside their only readers/writers (getQuotaAwareConnectionsForTarget,
* fetchResetAwareQuotaWithCache) — never duplicate a Map.
*
* Cross-module state: the tie-band round-robin in orderTargetsByResetAwareQuota
* and orderTargetsByResetWindow shares the same rrCounters Map from ./rrState.ts
* (D7a) so reset-aware tie rotation stays consistent with round-robin routing.
*
* Pure leaf: this module never imports from the combo barrel.
*/
import { getRuntimeProviderProfile, type ProviderProfile } from "../accountFallback.ts";
import { PRE_SCREEN_CONCURRENCY } from "../comboConfig.ts";
import { getQuotaFetcher } from "../quotaPreflight.ts";
import { getCircuitBreaker } from "../../../src/shared/utils/circuitBreaker";
import { getProviderConnections } from "../../../src/lib/db/providers";
import { MAX_RR_COUNTERS, rrCounters } from "./rrState.ts";
import type { ResolvedComboTarget, IsModelAvailable } from "./types.ts";
import {
resolveResetAwareConfig,
resolveResetWindowConfig,
getResetAwareProvider,
scoreResetAwareQuota,
getResetWindowTimestampMs,
type QuotaFetchCacheConfig,
} from "./quotaScoring.ts";
const RESET_AWARE_CONNECTION_CACHE_TTL_MS = 30_000;
const RESET_AWARE_QUOTA_FETCH_CONCURRENCY = 5;
const MAX_RESET_AWARE_CACHE = 200;
const resetAwareConnectionCache = new Map<
string,
{ fetchedAt: number; connections: Array<Record<string, unknown>> }
>();
const resetAwareQuotaCache = new Map<
string,
{ fetchedAt: number; quota: unknown; refreshPromise: Promise<unknown> | null }
>();
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>>)
: [];
if (
!resetAwareConnectionCache.has(provider) &&
resetAwareConnectionCache.size >= MAX_RESET_AWARE_CACHE
) {
const oldest = resetAwareConnectionCache.keys().next().value;
if (oldest !== undefined) resetAwareConnectionCache.delete(oldest);
}
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;
}
export async function fetchResetAwareQuotaWithCache({
provider,
connectionId,
connection,
fetcher,
config,
log,
comboName,
}: {
provider: string;
connectionId: string;
connection?: Record<string, unknown>;
fetcher: (connectionId: string, connection?: Record<string, unknown>) => Promise<unknown>;
config: QuotaFetchCacheConfig;
log: { debug?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void };
comboName: string;
}): Promise<unknown> {
const cacheKey = `${provider}:${connectionId}`;
const ttlMs = config.quotaCacheTtlMs;
const maxStaleMs = config.quotaCacheMaxStaleMs;
const now = Date.now();
const cached = resetAwareQuotaCache.get(cacheKey);
if (ttlMs <= 0 && maxStaleMs <= 0) {
try {
return await fetcher(connectionId, connection);
} catch (error) {
log.warn?.("COMBO", "Reset-aware quota fetch failed.", {
comboName,
connectionId,
err: error,
operation: "quotaFetch",
provider,
});
return null;
}
}
const refresh = () => {
const existing = resetAwareQuotaCache.get(cacheKey);
if (existing?.refreshPromise != null) return existing.refreshPromise;
const refreshPromise = fetcher(connectionId, connection)
.then((quota) => {
if (quota) {
if (
!resetAwareQuotaCache.has(cacheKey) &&
resetAwareQuotaCache.size >= MAX_RESET_AWARE_CACHE
) {
const oldest = resetAwareQuotaCache.keys().next().value;
if (oldest !== undefined) resetAwareQuotaCache.delete(oldest);
}
resetAwareQuotaCache.set(cacheKey, {
quota,
fetchedAt: Date.now(),
refreshPromise: null,
});
} else {
resetAwareQuotaCache.delete(cacheKey);
}
return quota;
})
.catch((error) => {
const previous = resetAwareQuotaCache.get(cacheKey);
if (previous) {
if (
!resetAwareQuotaCache.has(cacheKey) &&
resetAwareQuotaCache.size >= MAX_RESET_AWARE_CACHE
) {
const oldest = resetAwareQuotaCache.keys().next().value;
if (oldest !== undefined) resetAwareQuotaCache.delete(oldest);
}
resetAwareQuotaCache.set(cacheKey, { ...previous, refreshPromise: null });
}
log.warn?.("COMBO", "Reset-aware quota fetch failed.", {
comboName,
connectionId,
err: error,
operation: "quotaFetch",
provider,
});
return null;
});
if (!resetAwareQuotaCache.has(cacheKey) && resetAwareQuotaCache.size >= MAX_RESET_AWARE_CACHE) {
const oldest = resetAwareQuotaCache.keys().next().value;
if (oldest !== undefined) resetAwareQuotaCache.delete(oldest);
}
resetAwareQuotaCache.set(cacheKey, {
quota: existing?.quota ?? cached?.quota ?? null,
fetchedAt: existing?.fetchedAt ?? cached?.fetchedAt ?? 0,
refreshPromise,
});
return refreshPromise;
};
if (ttlMs > 0 && cached) {
const age = now - cached.fetchedAt;
if (age <= ttlMs) return cached.quota;
if (maxStaleMs > 0 && age <= ttlMs + maxStaleMs) {
void refresh();
return cached.quota;
}
}
return refresh();
}
export type PreScreenResult = { profile: ProviderProfile | null; available: boolean };
export async function preScreenTargets(
targets: ResolvedComboTarget[],
isModelAvailable?: IsModelAvailable | null
): Promise<Map<string, PreScreenResult>> {
if (targets.length === 0) {
return new Map();
}
const results = await mapWithConcurrency(
targets,
PRE_SCREEN_CONCURRENCY,
async (target): Promise<{ key: string; result: PreScreenResult }> => {
const profile = await getRuntimeProviderProfile(target.provider).catch(() => null);
const breaker = getCircuitBreaker(target.provider);
if (breaker.getStatus().state === "OPEN") {
return { key: target.executionKey, result: { profile, available: false } };
}
let available = true;
if (isModelAvailable) {
// IsModelAvailable may return a sync boolean or a Promise; Promise.resolve
// normalizes both so the .catch() never runs against a bare boolean.
available = await Promise.resolve(isModelAvailable(target.modelStr, target)).catch(
() => true
);
}
return { key: target.executionKey, result: { profile, available } };
}
);
const map = new Map<string, PreScreenResult>();
for (const { key, result } of results) {
map.set(key, result);
}
return map;
}
export 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,
fetchResetAwareQuotaWithCache({
provider,
connectionId: target.connectionId,
connection: connectionById.get(target.connectionId),
fetcher,
config,
log,
comboName,
})
);
}
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;
if (!rrCounters.has(key) && rrCounters.size >= MAX_RR_COUNTERS) {
const oldest = rrCounters.keys().next().value;
if (oldest !== undefined) rrCounters.delete(oldest);
}
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);
}
export async function orderTargetsByResetWindow(
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 = resolveResetWindowConfig(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,
fetchResetAwareQuotaWithCache({
provider,
connectionId: target.connectionId,
connection: connectionById.get(target.connectionId),
fetcher,
config,
log,
comboName,
})
);
}
quota = await quotaPromises.get(quotaKey)!;
}
return {
target,
resetMs: getResetWindowTimestampMs(quota, config.windows),
index,
};
}
);
scoredTargets.sort((a, b) => {
if (a.resetMs !== b.resetMs) return a.resetMs - b.resetMs;
return a.index - b.index;
});
const bestResetMs = scoredTargets[0]?.resetMs ?? Infinity;
if (!Number.isFinite(bestResetMs) || config.tieBandMs <= 0) {
return scoredTargets.map((entry) => entry.target);
}
const tiedTargets = scoredTargets.filter(
(entry) => entry.resetMs - bestResetMs <= config.tieBandMs
);
if (tiedTargets.length <= 1) return scoredTargets.map((entry) => entry.target);
const key = `reset-window:${comboName}`;
const counter = rrCounters.get(key) || 0;
if (!rrCounters.has(key) && rrCounters.size >= MAX_RR_COUNTERS) {
const oldest = rrCounters.keys().next().value;
if (oldest !== undefined) rrCounters.delete(oldest);
}
rrCounters.set(key, counter + 1);
const startIndex = counter % tiedTargets.length;
const 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);
}

View File

@@ -142,14 +142,37 @@ function isCanonicalFilter(value: z.infer<typeof rtkFilterSchema>): value is Rtk
return "label" in value && "match" in value && "rules" in value;
}
/**
* Conservative, dependency-free ReDoS guard. Custom RTK filters (DATA_DIR/rtk/filters.json)
* carry user-supplied regex strings that are compiled and run against untrusted tool output;
* a nested unbounded quantifier ((a+)+, (a*)*, ([a-z]+)+, (a+|b)+ …) causes catastrophic
* backtracking. This flags the common single-group nested-quantifier shapes so the loader
* never compiles them. Heuristic by design — a full analysis would use `safe-regex` (not
* installable in this symlinked worktree); it is itself linear (no nested quantifier).
*/
export function isReDoSProne(pattern: string): boolean {
return /\([^()]*(?:[+*]|\{\d+,\})[^()]*\)\s*(?:[+*]|\{\d+,\})/.test(pattern);
}
function dropReDoSProne(patterns: string[]): string[] {
return patterns.filter((p) => !isReDoSProne(p));
}
export function validateRtkFilter(value: unknown): RtkFilterDefinition {
const parsed = rtkFilterSchema.parse(value);
if (!isCanonicalFilter(parsed)) {
const collapse = dropReDoSProne(parsed.collapsePatterns);
return {
...parsed,
stripPatterns: dropReDoSProne(parsed.stripPatterns),
keepPatterns: dropReDoSProne(parsed.keepPatterns),
priorityPatterns: dropReDoSProne(parsed.priorityPatterns),
collapsePatterns: collapse,
replace: parsed.replace.filter((r) => !isReDoSProne(r.pattern)),
matchOutput: parsed.matchOutput.filter((r) => !isReDoSProne(r.pattern)),
commandPatterns: [],
matchPatterns: [],
deduplicate: parsed.collapsePatterns.length > 0,
deduplicate: collapse.length > 0,
};
}
@@ -159,17 +182,17 @@ export function validateRtkFilter(value: unknown): RtkFilterDefinition {
name: parsed.label,
description: parsed.description,
commandTypes: parsed.match.outputTypes,
commandPatterns: parsed.match.commands,
matchPatterns: parsed.match.patterns,
commandPatterns: dropReDoSProne(parsed.match.commands),
matchPatterns: dropReDoSProne(parsed.match.patterns),
category: parsed.category,
priority: parsed.priority,
stripPatterns: parsed.rules.dropPatterns,
keepPatterns: parsed.rules.includePatterns,
priorityPatterns: preservePatterns,
collapsePatterns: parsed.rules.collapsePatterns,
stripPatterns: dropReDoSProne(parsed.rules.dropPatterns),
keepPatterns: dropReDoSProne(parsed.rules.includePatterns),
priorityPatterns: dropReDoSProne(preservePatterns),
collapsePatterns: dropReDoSProne(parsed.rules.collapsePatterns),
stripAnsi: parsed.rules.stripAnsi,
replace: parsed.rules.replace,
matchOutput: parsed.rules.matchOutput,
replace: parsed.rules.replace.filter((r) => !isReDoSProne(r.pattern)),
matchOutput: parsed.rules.matchOutput.filter((r) => !isReDoSProne(r.pattern)),
truncateLineAt: parsed.rules.truncateLineAt,
onEmpty: parsed.rules.onEmpty,
filterStderr: parsed.rules.filterStderr,

View File

@@ -19,8 +19,14 @@ const SECRET_PATTERNS: Array<[RegExp, string]> = [
[/\b(sk-[A-Za-z0-9_-]{16,})\b/g, "[REDACTED_OPENAI_KEY]"],
[/\b(xox[baprs]-[A-Za-z0-9-]{16,})\b/g, "[REDACTED_SLACK_TOKEN]"],
[/\b(AKIA[0-9A-Z]{16})\b/g, "[REDACTED_AWS_KEY]"],
[/((?:api[_-]?key|token|secret|password)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s]+)/gi, "$1[REDACTED]"],
[/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/-]+=*/gi, "$1[REDACTED]"],
// key=value / key: value for common credential field names (flat alternation — no nesting,
// so no ReDoS). Covers names the bare token/secret/password set misses (private_key, etc).
[
/((?:api[_-]?key|api[_-]?token|access[_-]?key|access[_-]?token|client[_-]?secret|auth[_-]?token|private[_-]?key|secret[_-]?key|credentials?|token|secret|password)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s]+)/gi,
"$1[REDACTED]",
],
// Authorization / Proxy-Authorization with Bearer OR Basic (curl -v emits Basic <base64>).
[/((?:Proxy-)?Authorization:\s*(?:Bearer|Basic)\s+)[A-Za-z0-9._~+/-]+=*/gi, "$1[REDACTED]"],
];
function dataDir(): string {

View File

@@ -230,6 +230,8 @@ export const DEFAULT_RTK_CONFIG: RtkConfig = {
trustProjectFilters: false,
rawOutputRetention: "never",
rawOutputMaxBytes: 1_048_576,
enableGrouping: false,
groupingThreshold: 3,
};
export const DEFAULT_COMPRESSION_LANGUAGE_CONFIG: CompressionLanguageConfig = {

View File

@@ -172,6 +172,16 @@ function normalizeRtkConfig(value: unknown): RtkConfig {
1024,
10_000_000
),
enableGrouping:
typeof record.enableGrouping === "boolean"
? record.enableGrouping
: (DEFAULT_RTK_CONFIG.enableGrouping ?? false),
groupingThreshold: boundedInt(
record.groupingThreshold,
DEFAULT_RTK_CONFIG.groupingThreshold ?? 3,
2,
100
),
};
}

View File

@@ -49,6 +49,8 @@ export const rtkConfigSchema = z
trustProjectFilters: z.boolean().optional(),
rawOutputRetention: rtkRawOutputRetentionSchema.optional(),
rawOutputMaxBytes: z.number().int().min(1024).max(10_000_000).optional(),
enableGrouping: z.boolean().optional(),
groupingThreshold: z.number().int().min(2).max(100).optional(),
})
.strict();

View File

@@ -186,15 +186,30 @@
]
},
"_mutate_godfiles_excluded_comment": [
"2026-06-18 (Onda 2 budget): chatCore.ts (5874 LOC) + combo.ts (5282 LOC) — the two",
"god-files — were REMOVED from `mutate`. They dominate ~2/3 of the ~15k mutants, and the",
"full 8-module run TIMED OUT at the 180min nightly cap (run 27705123780, concurrency=4 +",
"DATA_DIR isolation: started 16:47:33 -> killed 19:47:48 = exactly 180min; the prior 120min",
"scheduled run also timed out). #4078 made concurrency safe but the tap-runner re-spawns a",
"node process per test file PER MUTANT, so spawn cost dominates and 15k mutants does not fit.",
"The 6 smaller modules below fit the budget and produce REAL mutation scores now; the two",
"god-files regain mutation coverage after the Onda 3 split into smaller units (re-add them",
"here — ideally as the split sub-modules). See project memory: Quality Gate v2 / Fase 9."
"2026-06-18 (Onda 2 budget): chatCore.ts + combo.ts — the two god-files — were REMOVED",
"from `mutate`. They dominated ~2/3 of the ~15k mutants; the full 8-module run TIMED OUT",
"at the 180min nightly cap (run 27705123780: 16:47:33 -> killed 19:47:48 = exactly 180min;",
"the prior 120min scheduled run also timed out). #4078 made concurrency safe but the",
"tap-runner re-spawns a node process per test file PER MUTANT, so spawn cost dominates and",
"15k mutants does not fit.",
"",
"2026-06-18 (Onda 3 / Fase 9 T5 re-add): the combo.ts god-file was split into 11 small",
"leaf modules under open-sse/services/combo/ (PRs #4162/#4175/#4186/#4196/#4204). The",
"routing LOGIC that justified combo.ts being in `mutate` now lives in those leaves, so the",
"8 well-covered combo/* leaves are ADDED back to `mutate` here (comboStructure/autoStrategy/",
"validateQuality/shadowRouting/targetSorters/comboPredicates/rrState/comboData). They are",
"covered by the 24 combo-*.test.ts files already in tap.testFiles. types.ts is omitted",
"(pure type declarations produce 0 mutants).",
"",
"STILL EXCLUDED (follow-ups, NOT in `mutate` yet):",
" - combo.ts + chatCore.ts barrels: their handleComboChat/handleChatCore CORES were not",
" split (out of scope — Fase 3 ChatCoreContext refactor). The barrels are now thin-ish",
" but still large; keep excluded until the cores are split.",
" - combo/quotaScoring.ts + combo/quotaStrategies.ts: in PR #4204 (D7b), not yet merged.",
" Add them here once #4204 lands (they are covered by combo-prescreen/combo-config tests).",
" - chatCore/* leaves (15 modules): thinner test coverage (only ~7 chatcore-*.test.ts in",
" tap.testFiles); add after a covering-test audit so they don't all 'survive (no coverage)'.",
"See project memory: Quality Gate v2 / Fase 9 (project-combo-split)."
],
"mutate": [
"open-sse/services/accountFallback.ts",
@@ -202,7 +217,15 @@
"src/server/authz/routeGuard.ts",
"open-sse/utils/error.ts",
"open-sse/utils/publicCreds.ts",
"src/shared/utils/circuitBreaker.ts"
"src/shared/utils/circuitBreaker.ts",
"open-sse/services/combo/comboStructure.ts",
"open-sse/services/combo/autoStrategy.ts",
"open-sse/services/combo/validateQuality.ts",
"open-sse/services/combo/shadowRouting.ts",
"open-sse/services/combo/targetSorters.ts",
"open-sse/services/combo/comboPredicates.ts",
"open-sse/services/combo/rrState.ts",
"open-sse/services/combo/comboData.ts"
],
"_ignorePatterns_comment": [
"ignorePatterns = files NOT copied into the Stryker sandbox. It does NOT scope",

View File

@@ -0,0 +1,54 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
validateRtkFilter,
isReDoSProne,
} from "../../../open-sse/services/compression/engines/rtk/filterSchema.ts";
// Custom RTK filters (DATA_DIR/rtk/filters.json) carry user-supplied regex strings that are
// compiled and run against untrusted tool output. A nested unbounded quantifier ((a+)+, (a*)*)
// can trigger catastrophic backtracking (ReDoS). validateRtkFilter must drop such patterns so
// they are never compiled. Dependency-free heuristic (safe-regex not installable here).
describe("RTK filter ReDoS guard", () => {
it("flags nested unbounded quantifiers and accepts safe patterns", () => {
assert.equal(isReDoSProne("(a+)+"), true);
assert.equal(isReDoSProne("(a*)*"), true);
assert.equal(isReDoSProne("([a-z]+)+"), true);
assert.equal(isReDoSProne("(a+|b)+"), true);
assert.equal(isReDoSProne("(?:\\d+)+"), true);
assert.equal(isReDoSProne("(ab)+"), false);
assert.equal(isReDoSProne("\\d{1,8}"), false);
assert.equal(isReDoSProne("error|fail|FAIL"), false);
assert.equal(isReDoSProne("^\\s*$"), false);
});
it("strips ReDoS-prone patterns from a canonical filter at validation", () => {
const def = validateRtkFilter({
id: "x",
label: "X",
category: "generic",
match: { commands: [], patterns: ["(a+)+", "ERROR\\b"], outputTypes: [] },
rules: { dropPatterns: ["(.*)*", "^\\s*$"] },
// preserve provided explicitly: a pack filter omitting it crashes validateRtkFilter
// (pre-existing: rtkFilterPreserveSchema.default({}) leaves errorPatterns undefined).
preserve: { errorPatterns: [], summaryPatterns: [] },
});
assert.deepEqual(def.matchPatterns, ["ERROR\\b"], "catastrophic matchPattern dropped");
assert.deepEqual(def.stripPatterns, ["^\\s*$"], "catastrophic dropPattern removed");
});
it("strips ReDoS-prone patterns from a legacy filter at validation", () => {
const def = validateRtkFilter({
id: "y",
name: "Y",
category: "generic",
commandTypes: ["shell"],
stripPatterns: ["([a-z]+)*", "keepme"],
});
assert.deepEqual(def.stripPatterns, ["keepme"], "catastrophic legacy stripPattern removed");
});
});

View File

@@ -0,0 +1,63 @@
import { describe, it, beforeEach, afterEach, after } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { rtkConfigSchema } from "../../../src/shared/validation/compressionConfigSchemas.ts";
import { DEFAULT_RTK_CONFIG } from "../../../open-sse/services/compression/types.ts";
// The RTK R5 grouping feature is read by the engine (config.enableGrouping / groupingThreshold)
// but was unreachable in production: the Zod schema (.strict()) rejected the two fields on write
// and normalizeRtkConfig dropped them on read. This proves both gates now let them through.
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-rtk-grouping-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../../src/lib/db/core.ts");
const { getCompressionSettings, updateCompressionSettings } = await import(
"../../../src/lib/db/compression.ts"
);
describe("RTK grouping config persistence (R5)", () => {
beforeEach(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
});
afterEach(() => {
core.resetDbInstance();
});
after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
});
it("accepts enableGrouping / groupingThreshold on the write schema", () => {
assert.equal(
rtkConfigSchema.safeParse({ enableGrouping: true, groupingThreshold: 5 }).success,
true
);
// groupingThreshold below the minimum run length (2) is rejected.
assert.equal(rtkConfigSchema.safeParse({ groupingThreshold: 1 }).success, false);
});
it("preserves enableGrouping / groupingThreshold through a DB round-trip", async () => {
const settings = await updateCompressionSettings({
rtkConfig: { ...DEFAULT_RTK_CONFIG, enableGrouping: true, groupingThreshold: 7 },
});
assert.equal(settings.rtkConfig.enableGrouping, true);
assert.equal(settings.rtkConfig.groupingThreshold, 7);
// Survives a fresh read (not just the write-path return value).
core.resetDbInstance();
const reread = await getCompressionSettings();
assert.equal(reread.rtkConfig.enableGrouping, true);
assert.equal(reread.rtkConfig.groupingThreshold, 7);
});
});

View File

@@ -146,4 +146,22 @@ describe("RTK raw output retention", () => {
assert.equal(pointer, null);
fs.rmSync(blocker, { force: true });
});
it("redacts Basic/Proxy auth headers and key fields the base patterns miss", () => {
const basic = redactRtkRawOutput("> Authorization: Basic dXNlcjpwYXNzd29yZA==");
assert.equal(basic.redacted, true);
assert.ok(!basic.text.includes("dXNlcjpwYXNzd29yZA=="), "Authorization: Basic base64 redacted");
const proxy = redactRtkRawOutput("Proxy-Authorization: Basic c2VjcmV0OnBhc3M=");
assert.equal(proxy.redacted, true);
assert.ok(!proxy.text.includes("c2VjcmV0OnBhc3M="), "Proxy-Authorization redacted");
const pkey = redactRtkRawOutput("private_key=abc123def456ghi");
assert.equal(pkey.redacted, true);
assert.ok(!pkey.text.includes("abc123def456ghi"), "private_key value redacted");
const cred = redactRtkRawOutput("credential: my-credential-value-x");
assert.equal(cred.redacted, true);
assert.ok(!cred.text.includes("my-credential-value-x"), "credential value redacted");
});
});