mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-19 13:23:50 +03:00
Merged. Root cause first: `publishers.models.list` was called with `pageSize=1000` against Google's hard maximum of 300, so every publisher answered 400 and discovery silently fell back to the stale static catalog. On top of that the PR separates API-key from Service-Account capabilities correctly (keys cannot list Model Garden — project-scoped curated catalog; SA tokens can — live catalog), stops treating the expected generativelanguage rejection of a Service Account as a discovery failure, replaces the speculative partner IDs with documented MaaS IDs, and rejects OAuth client-config JSON with a clear message instead of a misleading one. The 5 ESLint errors flagged during the earlier fix sweep were fixed in your own follow-up commits; the branch was reconciled with the release tip and the 42 locale files were checked for lost keys before this merge. Validated as a combined board first (this PR merged with the 4 siblings of the JxnLexn wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 77 passing / 0 failing focused node:test cases across the test files the wave touches. The wave's i18n fill (new keys carried to all 66 locales), free-tier doc counts and file-size rebaseline land in one follow-up PR right after the wave, as with #13904. Thank you — the credential-capability distinction and the retired/non-chat filtering are what make the Vertex listing trustworthy.
159 lines
5.5 KiB
TypeScript
159 lines
5.5 KiB
TypeScript
import { getAllSyncedAvailableModels } from "./db/models";
|
|
import { getResolvedModelCapabilities } from "./modelCapabilities";
|
|
import {
|
|
getModelContextOverrideRecord,
|
|
setModelContextOverride,
|
|
removeModelContextOverride,
|
|
} from "./db/modelContextOverrides";
|
|
|
|
/**
|
|
* Feature 5004 — self-correcting context-window reconciler.
|
|
*
|
|
* Compares each model's provider-declared window (captured by `/models` discovery in
|
|
* `syncedAvailableModels`) against the override-free catalog and, when they diverge,
|
|
* pins the discovered value as an `auto:discovery` override so the real window wins in
|
|
* `getModelContextLimit`. It never touches `manual` overrides, and it self-heals by
|
|
* removing a now-redundant auto override when the catalog has caught up.
|
|
*
|
|
* No new network fetch: it reconciles data the managed-model import already persisted,
|
|
* so it does not duplicate `modelsDevSync` / `modelDiscovery`.
|
|
*/
|
|
|
|
export interface DiscoveredWindow {
|
|
provider: string;
|
|
modelId: string;
|
|
window: number | null;
|
|
}
|
|
|
|
export interface ReconcileDeps {
|
|
getCatalogWindow: (provider: string, modelId: string) => number | null;
|
|
getExistingSource: (provider: string, modelId: string) => string | null;
|
|
writeAuto: (provider: string, modelId: string, window: number) => void;
|
|
removeOverride: (provider: string, modelId: string) => void;
|
|
}
|
|
|
|
export interface ReconcileResult {
|
|
scanned: number;
|
|
written: number;
|
|
removed: number;
|
|
skippedManual: number;
|
|
}
|
|
|
|
/**
|
|
* Pure reconcile: given the discovered windows and a set of injectable deps, decide
|
|
* which auto overrides to write/remove. Deterministic and side-effect-free except
|
|
* through the injected `writeAuto`/`removeOverride`.
|
|
*/
|
|
export function reconcileContextWindows(
|
|
discovered: DiscoveredWindow[],
|
|
deps: ReconcileDeps
|
|
): ReconcileResult {
|
|
const result: ReconcileResult = { scanned: 0, written: 0, removed: 0, skippedManual: 0 };
|
|
for (const { provider, modelId, window } of discovered) {
|
|
result.scanned++;
|
|
if (!provider || !modelId) continue;
|
|
if (typeof window !== "number" || !Number.isInteger(window) || window <= 0) continue;
|
|
|
|
const existingSource = deps.getExistingSource(provider, modelId);
|
|
if (existingSource === "manual") {
|
|
result.skippedManual++;
|
|
continue;
|
|
}
|
|
|
|
const catalog = deps.getCatalogWindow(provider, modelId);
|
|
if (window !== catalog) {
|
|
deps.writeAuto(provider, modelId, window);
|
|
result.written++;
|
|
} else if (existingSource) {
|
|
// Discovered window now matches the catalog and a stale auto override exists → drop it.
|
|
deps.removeOverride(provider, modelId);
|
|
result.removed++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/** Flatten the per-provider discovery map into the reconcile input. */
|
|
function toDiscoveredWindows(
|
|
byProvider: Record<
|
|
string,
|
|
Array<{ id: string; contextWindow?: number; inputTokenLimit?: number }>
|
|
>
|
|
): DiscoveredWindow[] {
|
|
const out: DiscoveredWindow[] = [];
|
|
for (const [provider, models] of Object.entries(byProvider)) {
|
|
for (const m of models) {
|
|
out.push({
|
|
provider,
|
|
modelId: m.id,
|
|
window: m.contextWindow ?? m.inputTokenLimit ?? null,
|
|
});
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Run the reconcile against the live DB (discovery → overrides). */
|
|
export async function runContextWindowReconcile(): Promise<ReconcileResult> {
|
|
const byProvider = await getAllSyncedAvailableModels();
|
|
const discovered = toDiscoveredWindows(byProvider);
|
|
return reconcileContextWindows(discovered, {
|
|
// Compare against the override-free catalog view. A persisted override must
|
|
// never feed back into the comparison that (re)writes it, or the reconciler
|
|
// oscillates (write → equal → remove → differ → write ...).
|
|
getCatalogWindow: (provider, modelId) =>
|
|
getResolvedModelCapabilities({ provider, model: modelId }, { persistedOverrides: false })
|
|
.contextWindow,
|
|
getExistingSource: (provider, modelId) =>
|
|
getModelContextOverrideRecord(provider, modelId)?.source ?? null,
|
|
writeAuto: (provider, modelId, window) => {
|
|
setModelContextOverride(provider, modelId, window, "auto:discovery");
|
|
},
|
|
removeOverride: (provider, modelId) => {
|
|
removeModelContextOverride(provider, modelId);
|
|
},
|
|
});
|
|
}
|
|
|
|
// --- Periodic job (mirrors modelsDevSync.startPeriodicSync) ---
|
|
|
|
const DEFAULT_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
let reconcileTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
function resolveIntervalMs(): number {
|
|
const raw = process.env.CONTEXT_WINDOW_RECONCILE_INTERVAL;
|
|
if (raw === undefined) return DEFAULT_INTERVAL_MS;
|
|
const seconds = Number(raw);
|
|
if (!Number.isFinite(seconds) || seconds <= 0) return 0; // 0/invalid → disabled
|
|
return Math.floor(seconds * 1000);
|
|
}
|
|
|
|
/**
|
|
* Start the periodic reconcile. Idempotent. Disabled when
|
|
* `CONTEXT_WINDOW_RECONCILE_INTERVAL=0`. Best-effort: failures are swallowed (the
|
|
* static catalog remains the source of truth).
|
|
*/
|
|
export function startContextWindowReconcile(intervalMs?: number): void {
|
|
if (reconcileTimer) return;
|
|
const interval = intervalMs ?? resolveIntervalMs();
|
|
if (!interval || interval <= 0) return;
|
|
|
|
const tick = () => {
|
|
void runContextWindowReconcile().catch(() => {
|
|
// Swallow — reconcile is advisory; the catalog still resolves windows.
|
|
});
|
|
};
|
|
|
|
// Initial non-blocking pass, then on the interval.
|
|
setTimeout(tick, 0);
|
|
reconcileTimer = setInterval(tick, interval);
|
|
reconcileTimer.unref?.();
|
|
}
|
|
|
|
export function stopContextWindowReconcile(): void {
|
|
if (reconcileTimer) {
|
|
clearInterval(reconcileTimer);
|
|
reconcileTimer = null;
|
|
}
|
|
}
|