diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index cd0a73665b..8ab2b2c98d 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -11,6 +11,7 @@ "_rebaseline_2026_06_18_qg9_combo_split_d5": "QG v2 Fase 9 T5 D5: combo.ts 4589->4430 — target sorters extracted byte-identically to the new open-sse/services/combo/targetSorters.ts (3819 — combo structure resolution extracted byte-identically to the new open-sse/services/combo/comboStructure.ts (638, 3432 — auto-strategy scoring/intent/tag-routing extracted byte-identically to the new open-sse/services/combo/autoStrategy.ts (434, 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, ) + 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_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 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 (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 (; -// In-memory atomic counter per combo for round-robin distribution -// Resets on server restart (by design — no stale state) -// Eviction limits to prevent unbounded memory growth -const MAX_RR_COUNTERS = 500; const MAX_RESET_AWARE_CACHE = 200; -const rrCounters = new Map(); -const rrStickyTargets = new Map(); - const resetAwareConnectionCache = new Map< string, { fetchedAt: number; connections: Array> } @@ -204,50 +205,6 @@ const resetAwareQuotaCache = new Map< { fetchedAt: number; quota: unknown; refreshPromise: Promise | null } >(); -function clampStickyRoundRobinTargetLimit(value: unknown): number { - const numericValue = Number(value); - if (!Number.isFinite(numericValue)) return 1; - return Math.min(Math.max(Math.floor(numericValue), 1), 1000); -} - -function getStickyRoundRobinStartIndex( - comboName: string, - targets: ResolvedComboTarget[], - stickyLimit: number -): { startIndex: number; counter: number } { - const sticky = rrStickyTargets.get(comboName); - const stickyIndex = sticky - ? targets.findIndex((target) => target.executionKey === sticky.executionKey) - : -1; - if (stickyLimit > 1 && sticky && stickyIndex >= 0 && sticky.successCount < stickyLimit) { - return { startIndex: stickyIndex, counter: rrCounters.get(comboName) || 0 }; - } - - const counter = rrCounters.get(comboName) || 0; - return { startIndex: counter % targets.length, counter }; -} - -function recordStickyRoundRobinSuccess( - comboName: string, - target: ResolvedComboTarget, - stickyLimit: number, - targets: ResolvedComboTarget[] -): void { - const sticky = rrStickyTargets.get(comboName); - const successCount = sticky?.executionKey === target.executionKey ? sticky.successCount + 1 : 1; - if (successCount >= stickyLimit) { - const servedIndex = targets.findIndex((entry) => entry.executionKey === target.executionKey); - rrCounters.set( - comboName, - servedIndex >= 0 ? servedIndex + 1 : (rrCounters.get(comboName) || 0) + 1 - ); - rrStickyTargets.delete(comboName); - return; - } - - rrStickyTargets.set(comboName, { executionKey: target.executionKey, successCount }); -} - function finiteNumberOrNull(value: unknown): number | null { const numericValue = Number(value); return Number.isFinite(numericValue) ? numericValue : null; diff --git a/open-sse/services/combo/rrState.ts b/open-sse/services/combo/rrState.ts new file mode 100644 index 0000000000..119961258c --- /dev/null +++ b/open-sse/services/combo/rrState.ts @@ -0,0 +1,71 @@ +/** + * Round-robin sticky state for combo routing. + * + * Holds the two mutable module-level Maps that back round-robin distribution + * (`rrCounters`) and sticky round-robin target affinity (`rrStickyTargets`), + * plus the helpers that read/write them. Extracted byte-identically from + * combo.ts (QG v2 Fase 9 T5 D7a). + * + * State cohesion: these two Maps MUST remain single instances. combo.ts imports + * the same references back and mutates them directly (orderTargetsByResetAwareQuota, + * orderTargetsByResetWindow, handleRoundRobinCombo) — never duplicate a Map. + * + * Pure leaf: this module never imports from the combo barrel. + */ + +import type { ResolvedComboTarget } from "./types.ts"; + +// In-memory atomic counter per combo for round-robin distribution +// Resets on server restart (by design — no stale state) +// Eviction limits to prevent unbounded memory growth +export const MAX_RR_COUNTERS = 500; + +export const rrCounters = new Map(); +export const rrStickyTargets = new Map< + string, + { executionKey: string; successCount: number } +>(); + +export function clampStickyRoundRobinTargetLimit(value: unknown): number { + const numericValue = Number(value); + if (!Number.isFinite(numericValue)) return 1; + return Math.min(Math.max(Math.floor(numericValue), 1), 1000); +} + +export function getStickyRoundRobinStartIndex( + comboName: string, + targets: ResolvedComboTarget[], + stickyLimit: number +): { startIndex: number; counter: number } { + const sticky = rrStickyTargets.get(comboName); + const stickyIndex = sticky + ? targets.findIndex((target) => target.executionKey === sticky.executionKey) + : -1; + if (stickyLimit > 1 && sticky && stickyIndex >= 0 && sticky.successCount < stickyLimit) { + return { startIndex: stickyIndex, counter: rrCounters.get(comboName) || 0 }; + } + + const counter = rrCounters.get(comboName) || 0; + return { startIndex: counter % targets.length, counter }; +} + +export function recordStickyRoundRobinSuccess( + comboName: string, + target: ResolvedComboTarget, + stickyLimit: number, + targets: ResolvedComboTarget[] +): void { + const sticky = rrStickyTargets.get(comboName); + const successCount = sticky?.executionKey === target.executionKey ? sticky.successCount + 1 : 1; + if (successCount >= stickyLimit) { + const servedIndex = targets.findIndex((entry) => entry.executionKey === target.executionKey); + rrCounters.set( + comboName, + servedIndex >= 0 ? servedIndex + 1 : (rrCounters.get(comboName) || 0) + 1 + ); + rrStickyTargets.delete(comboName); + return; + } + + rrStickyTargets.set(comboName, { executionKey: target.executionKey, successCount }); +}