mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-11 01:32:22 +03:00
feat(db): models/5004 — self-correcting model context-window overrides
A persisted per-(provider, model_id) override now wins over the static catalog / models.dev sync in getModelContextLimit, so a model whose real context window diverges from the catalog resolves correctly across combo routing, pre-flight compression, and family fallback. - migration 110_model_context_overrides + src/lib/db/modelContextOverrides.ts (CRUD, cacheless, defensive: null when the table is pre-migration; rejects non-positive windows). - read-path: getModelContextLimit returns override ?? catalog; getResolvedModelCapabilities stays override-free so the reconciler can compare against it. - src/lib/contextWindowResolver.ts: pure reconcileContextWindows (deps injected) + runContextWindowReconcile (reuses getAllSyncedAvailableModels — provider /models discovery, no new fetch) + periodic job. Writes auto:discovery overrides only when the discovered window diverges from the catalog; never touches manual overrides; self-heals. - registered in instrumentation-node.ts (CONTEXT_WINDOW_RECONCILE_INTERVAL, 0 disables). TDD: DB round-trip (7), read-path precedence (3), reconcile logic (6). Consumer suites green; typecheck:core + lint + cycles + migration-numbering + env-doc-sync clean.
This commit is contained in:
@@ -1265,6 +1265,13 @@ APP_LOG_TO_FILE=true
|
||||
# Default: 86400 (24 hours)
|
||||
# MODELS_DEV_SYNC_INTERVAL=86400
|
||||
|
||||
# Self-correcting context-window reconciler interval in seconds (feature 5004).
|
||||
# Pins provider-declared windows from /models discovery as auto:discovery overrides
|
||||
# when they diverge from the catalog. Set to 0 to disable. Never overwrites manual overrides.
|
||||
# Used by: src/lib/contextWindowResolver.ts
|
||||
# Default: 86400 (24 hours)
|
||||
# CONTEXT_WINDOW_RECONCILE_INTERVAL=86400
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 20. PROVIDER-SPECIFIC SETTINGS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -744,9 +744,10 @@ Automatic model pricing data synchronization from external sources.
|
||||
|
||||
## 19. Model Sync (Dev)
|
||||
|
||||
| Variable | Default | Source File | Description |
|
||||
| -------------------------- | ------------- | -------------------------- | -------------------------------------------------------- |
|
||||
| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. |
|
||||
| Variable | Default | Source File | Description |
|
||||
| ----------------------------------- | ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. |
|
||||
| `CONTEXT_WINDOW_RECONCILE_INTERVAL` | `86400` (24h) | `src/lib/contextWindowResolver.ts` | Interval (seconds) for the self-correcting context-window reconciler (5004): pins provider-declared windows from `/models` discovery as `auto:discovery` overrides when they diverge from the catalog. Set to `0` to disable. Reuses already-synced data (no new fetch); never overwrites `manual` overrides. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -323,5 +323,16 @@ export async function registerNodejs(): Promise<void> {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn("[STARTUP] models.dev sync failed to start (non-fatal):", msg);
|
||||
}
|
||||
|
||||
// Context-window self-correction (5004): periodically reconcile provider-declared
|
||||
// windows (from /models discovery) into auto:discovery overrides. Reuses already-synced
|
||||
// data (no new fetch); disable via CONTEXT_WINDOW_RECONCILE_INTERVAL=0. Never fatal.
|
||||
try {
|
||||
const { startContextWindowReconcile } = await import("@/lib/contextWindowResolver");
|
||||
startContextWindowReconcile();
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn("[STARTUP] context-window reconcile failed to start (non-fatal):", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
147
src/lib/contextWindowResolver.ts
Normal file
147
src/lib/contextWindowResolver.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
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; 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.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, {
|
||||
getCatalogWindow: (provider, modelId) =>
|
||||
getResolvedModelCapabilities({ provider, model: modelId }).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;
|
||||
}
|
||||
}
|
||||
17
src/lib/db/migrations/110_model_context_overrides.sql
Normal file
17
src/lib/db/migrations/110_model_context_overrides.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- 110_model_context_overrides.sql
|
||||
-- Self-correcting context-window overrides per (provider, model_id). An override wins
|
||||
-- over the static catalog / models.dev sync in getModelContextLimit(). Source 'manual'
|
||||
-- is operator-set and is NEVER overwritten by the auto reconciler ('auto:discovery'),
|
||||
-- which pins the window a provider's own /models discovery declares when it diverges
|
||||
-- from the catalog. Read path: src/lib/modelCapabilities.ts::getModelContextLimit.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS model_context_overrides (
|
||||
provider TEXT NOT NULL,
|
||||
model_id TEXT NOT NULL,
|
||||
real_context INTEGER NOT NULL, -- the corrected context window (tokens), > 0
|
||||
source TEXT NOT NULL DEFAULT 'manual', -- 'manual' | 'auto:discovery'
|
||||
refreshed_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
PRIMARY KEY (provider, model_id)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mco_source ON model_context_overrides (source);
|
||||
133
src/lib/db/modelContextOverrides.ts
Normal file
133
src/lib/db/modelContextOverrides.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { getDbInstance } from "./core";
|
||||
|
||||
/**
|
||||
* Feature 5004 — self-correcting context-window overrides.
|
||||
*
|
||||
* A persisted override of a model's real context window that wins over the static
|
||||
* catalog / models.dev sync in `getModelContextLimit()`. Two sources:
|
||||
* - `manual`: operator-set; never overwritten by the auto reconciler.
|
||||
* - `auto:discovery`: written by the reconciler when a provider's own `/models`
|
||||
* discovery declares a window that diverges from the catalog.
|
||||
*
|
||||
* Cacheless on purpose: the read path already touches the DB (synced capabilities),
|
||||
* and a single indexed PK lookup is negligible — this avoids any cache-staleness
|
||||
* hazard with `resetDbInstance()` in tests.
|
||||
*/
|
||||
|
||||
export type ModelContextOverrideSource = "manual" | "auto:discovery";
|
||||
|
||||
export interface ModelContextOverride {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
realContext: number;
|
||||
source: ModelContextOverrideSource;
|
||||
refreshedAt: string;
|
||||
}
|
||||
|
||||
interface OverrideRow {
|
||||
provider: string;
|
||||
model_id: string;
|
||||
real_context: number;
|
||||
source: string;
|
||||
refreshed_at: string;
|
||||
}
|
||||
|
||||
function isPositiveInteger(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
||||
}
|
||||
|
||||
function normalizeKey(provider: unknown, modelId: unknown): { provider: string; modelId: string } | null {
|
||||
const p = typeof provider === "string" ? provider.trim() : "";
|
||||
const m = typeof modelId === "string" ? modelId.trim() : "";
|
||||
if (!p || !m) return null;
|
||||
return { provider: p, modelId: m };
|
||||
}
|
||||
|
||||
function toOverride(row: OverrideRow): ModelContextOverride {
|
||||
return {
|
||||
provider: row.provider,
|
||||
modelId: row.model_id,
|
||||
realContext: row.real_context,
|
||||
source: row.source === "auto:discovery" ? "auto:discovery" : "manual",
|
||||
refreshedAt: row.refreshed_at,
|
||||
};
|
||||
}
|
||||
|
||||
/** Full override record for (provider, modelId), or null. Never throws. */
|
||||
export function getModelContextOverrideRecord(
|
||||
provider: string | null | undefined,
|
||||
modelId: string | null | undefined
|
||||
): ModelContextOverride | null {
|
||||
const key = normalizeKey(provider, modelId);
|
||||
if (!key) return null;
|
||||
try {
|
||||
const row = getDbInstance()
|
||||
.prepare(
|
||||
"SELECT provider, model_id, real_context, source, refreshed_at " +
|
||||
"FROM model_context_overrides WHERE provider = ? AND model_id = ?"
|
||||
)
|
||||
.get(key.provider, key.modelId) as OverrideRow | undefined;
|
||||
return row ? toOverride(row) : null;
|
||||
} catch {
|
||||
// Table may not exist yet (pre-migration) — fall through to the catalog.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The overridden context window (tokens) for (provider, modelId), or null. Never throws. */
|
||||
export function getModelContextOverride(
|
||||
provider: string | null | undefined,
|
||||
modelId: string | null | undefined
|
||||
): number | null {
|
||||
const record = getModelContextOverrideRecord(provider, modelId);
|
||||
return record ? record.realContext : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert an override. `realContext` must be a positive integer (a token count);
|
||||
* anything else is rejected (no write). Returns true when a row was written.
|
||||
*/
|
||||
export function setModelContextOverride(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
realContext: number,
|
||||
source: ModelContextOverrideSource = "manual"
|
||||
): boolean {
|
||||
const key = normalizeKey(provider, modelId);
|
||||
if (!key || !isPositiveInteger(realContext)) return false;
|
||||
const normalizedSource: ModelContextOverrideSource =
|
||||
source === "auto:discovery" ? "auto:discovery" : "manual";
|
||||
getDbInstance()
|
||||
.prepare(
|
||||
"INSERT OR REPLACE INTO model_context_overrides " +
|
||||
"(provider, model_id, real_context, source, refreshed_at) " +
|
||||
"VALUES (?, ?, ?, ?, datetime('now'))"
|
||||
)
|
||||
.run(key.provider, key.modelId, realContext, normalizedSource);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Remove an override. Returns true when a row was deleted. */
|
||||
export function removeModelContextOverride(provider: string, modelId: string): boolean {
|
||||
const key = normalizeKey(provider, modelId);
|
||||
if (!key) return false;
|
||||
const info = getDbInstance()
|
||||
.prepare("DELETE FROM model_context_overrides WHERE provider = ? AND model_id = ?")
|
||||
.run(key.provider, key.modelId);
|
||||
return info.changes > 0;
|
||||
}
|
||||
|
||||
/** All overrides, newest refresh first. Never throws. */
|
||||
export function listModelContextOverrides(): ModelContextOverride[] {
|
||||
try {
|
||||
const rows = getDbInstance()
|
||||
.prepare(
|
||||
"SELECT provider, model_id, real_context, source, refreshed_at " +
|
||||
"FROM model_context_overrides ORDER BY refreshed_at DESC"
|
||||
)
|
||||
.all() as OverrideRow[];
|
||||
return rows.map(toOverride);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import { parseModel, resolveCanonicalProviderModel } from "@omniroute/open-sse/services/model.ts";
|
||||
import { MODEL_SPECS, getModelSpec, type ModelSpec } from "@/shared/constants/modelSpecs";
|
||||
import { getSyncedCapability } from "@/lib/modelsDevSync";
|
||||
import { getModelContextOverride } from "@/lib/db/modelContextOverrides";
|
||||
import { isVisionModelId } from "@/shared/constants/visionModels";
|
||||
|
||||
const TOOL_CALLING_UNSUPPORTED_PATTERNS: string[] = [];
|
||||
@@ -440,5 +441,9 @@ export function getModelContextLimit(
|
||||
typeof providerOrInput === "string" && modelId !== undefined
|
||||
? getResolvedModelCapabilities({ provider: providerOrInput, model: modelId })
|
||||
: getResolvedModelCapabilities(providerOrInput);
|
||||
return resolved.contextWindow;
|
||||
// Feature 5004: a persisted override (operator-set or auto-discovered) wins over the
|
||||
// static catalog / models.dev sync. `getResolvedModelCapabilities` stays override-free
|
||||
// so the reconciler can compare the catalog value against provider-declared windows.
|
||||
const override = getModelContextOverride(resolved.provider, resolved.model);
|
||||
return override ?? resolved.contextWindow;
|
||||
}
|
||||
|
||||
86
tests/unit/context-window-reconcile.test.ts
Normal file
86
tests/unit/context-window-reconcile.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
reconcileContextWindows,
|
||||
type DiscoveredWindow,
|
||||
type ReconcileDeps,
|
||||
} from "../../src/lib/contextWindowResolver.ts";
|
||||
|
||||
// Feature 5004 — pure reconcile logic (auto:discovery overrides), deps injected.
|
||||
|
||||
function makeDeps(
|
||||
catalog: Record<string, number | null>,
|
||||
existing: Record<string, string> = {}
|
||||
) {
|
||||
const writes: Array<[string, string, number]> = [];
|
||||
const removes: Array<[string, string]> = [];
|
||||
const deps: ReconcileDeps = {
|
||||
getCatalogWindow: (p, m) => (`${p}/${m}` in catalog ? catalog[`${p}/${m}`] : null),
|
||||
getExistingSource: (p, m) => existing[`${p}/${m}`] ?? null,
|
||||
writeAuto: (p, m, w) => writes.push([p, m, w]),
|
||||
removeOverride: (p, m) => removes.push([p, m]),
|
||||
};
|
||||
return { deps, writes, removes };
|
||||
}
|
||||
|
||||
describe("reconcileContextWindows (5004)", () => {
|
||||
it("writes an auto override when the discovered window diverges from the catalog", () => {
|
||||
const discovered: DiscoveredWindow[] = [{ provider: "openai", modelId: "gpt-x", window: 400000 }];
|
||||
const { deps, writes } = makeDeps({ "openai/gpt-x": 128000 });
|
||||
const r = reconcileContextWindows(discovered, deps);
|
||||
assert.deepEqual(writes, [["openai", "gpt-x", 400000]]);
|
||||
assert.equal(r.written, 1);
|
||||
assert.equal(r.scanned, 1);
|
||||
});
|
||||
|
||||
it("does nothing when the discovered window already matches the catalog", () => {
|
||||
const discovered: DiscoveredWindow[] = [{ provider: "openai", modelId: "gpt-x", window: 128000 }];
|
||||
const { deps, writes, removes } = makeDeps({ "openai/gpt-x": 128000 });
|
||||
const r = reconcileContextWindows(discovered, deps);
|
||||
assert.deepEqual(writes, []);
|
||||
assert.deepEqual(removes, []);
|
||||
assert.equal(r.written, 0);
|
||||
});
|
||||
|
||||
it("self-heals: removes a stale auto override once the catalog catches up", () => {
|
||||
const discovered: DiscoveredWindow[] = [{ provider: "openai", modelId: "gpt-x", window: 128000 }];
|
||||
const { deps, removes } = makeDeps({ "openai/gpt-x": 128000 }, { "openai/gpt-x": "auto:discovery" });
|
||||
const r = reconcileContextWindows(discovered, deps);
|
||||
assert.deepEqual(removes, [["openai", "gpt-x"]]);
|
||||
assert.equal(r.removed, 1);
|
||||
});
|
||||
|
||||
it("never overwrites or removes a manual override", () => {
|
||||
const discovered: DiscoveredWindow[] = [{ provider: "openai", modelId: "gpt-x", window: 999999 }];
|
||||
const { deps, writes, removes } = makeDeps({ "openai/gpt-x": 128000 }, { "openai/gpt-x": "manual" });
|
||||
const r = reconcileContextWindows(discovered, deps);
|
||||
assert.deepEqual(writes, []);
|
||||
assert.deepEqual(removes, []);
|
||||
assert.equal(r.skippedManual, 1);
|
||||
assert.equal(r.written, 0);
|
||||
});
|
||||
|
||||
it("skips invalid windows and empty keys", () => {
|
||||
const discovered: DiscoveredWindow[] = [
|
||||
{ provider: "openai", modelId: "a", window: 0 },
|
||||
{ provider: "openai", modelId: "b", window: -5 },
|
||||
{ provider: "openai", modelId: "c", window: 1.5 },
|
||||
{ provider: "openai", modelId: "d", window: null },
|
||||
{ provider: "", modelId: "e", window: 100000 },
|
||||
{ provider: "openai", modelId: "", window: 100000 },
|
||||
];
|
||||
const { deps, writes } = makeDeps({});
|
||||
const r = reconcileContextWindows(discovered, deps);
|
||||
assert.deepEqual(writes, []);
|
||||
assert.equal(r.scanned, 6);
|
||||
assert.equal(r.written, 0);
|
||||
});
|
||||
|
||||
it("writes an override when the catalog does not know the model (catalog null)", () => {
|
||||
const discovered: DiscoveredWindow[] = [{ provider: "local", modelId: "my-7b", window: 131072 }];
|
||||
const { deps, writes } = makeDeps({}); // catalog null for everything
|
||||
const r = reconcileContextWindows(discovered, deps);
|
||||
assert.deepEqual(writes, [["local", "my-7b", 131072]]);
|
||||
assert.equal(r.written, 1);
|
||||
});
|
||||
});
|
||||
90
tests/unit/db-model-context-overrides.test.ts
Normal file
90
tests/unit/db-model-context-overrides.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { describe, it, beforeEach, 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";
|
||||
|
||||
// Feature 5004 — model_context_overrides DB module round-trip.
|
||||
|
||||
const moduleDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-mco-module-"));
|
||||
process.env.DATA_DIR = moduleDataDir;
|
||||
|
||||
const coreDb = await import("../../src/lib/db/core.ts");
|
||||
const mco = await import("../../src/lib/db/modelContextOverrides.ts");
|
||||
|
||||
function resetStorage() {
|
||||
coreDb.resetDbInstance();
|
||||
fs.rmSync(moduleDataDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(moduleDataDir, { recursive: true });
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetStorage();
|
||||
// Touch the DB so migration 110 creates the table.
|
||||
coreDb.getDbInstance();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
coreDb.resetDbInstance();
|
||||
fs.rmSync(moduleDataDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("modelContextOverrides", () => {
|
||||
it("returns null when there is no override", () => {
|
||||
assert.equal(mco.getModelContextOverride("openai", "gpt-5"), null);
|
||||
assert.equal(mco.getModelContextOverrideRecord("openai", "gpt-5"), null);
|
||||
});
|
||||
|
||||
it("round-trips a manual override (set -> get -> record)", () => {
|
||||
assert.equal(mco.setModelContextOverride("openai", "gpt-5", 400000), true);
|
||||
assert.equal(mco.getModelContextOverride("openai", "gpt-5"), 400000);
|
||||
const rec = mco.getModelContextOverrideRecord("openai", "gpt-5");
|
||||
assert.equal(rec?.realContext, 400000);
|
||||
assert.equal(rec?.source, "manual");
|
||||
assert.equal(rec?.provider, "openai");
|
||||
assert.equal(rec?.modelId, "gpt-5");
|
||||
});
|
||||
|
||||
it("upserts on the same (provider, model) key and records the source", () => {
|
||||
mco.setModelContextOverride("anthropic", "claude-sonnet-4-5", 200000, "auto:discovery");
|
||||
assert.equal(mco.getModelContextOverrideRecord("anthropic", "claude-sonnet-4-5")?.source, "auto:discovery");
|
||||
// Re-set as manual overwrites the same row.
|
||||
mco.setModelContextOverride("anthropic", "claude-sonnet-4-5", 1000000, "manual");
|
||||
const rec = mco.getModelContextOverrideRecord("anthropic", "claude-sonnet-4-5");
|
||||
assert.equal(rec?.realContext, 1000000);
|
||||
assert.equal(rec?.source, "manual");
|
||||
assert.equal(mco.listModelContextOverrides().length, 1);
|
||||
});
|
||||
|
||||
it("rejects non-positive / non-integer windows and empty keys (no write)", () => {
|
||||
assert.equal(mco.setModelContextOverride("openai", "gpt-5", 0), false);
|
||||
assert.equal(mco.setModelContextOverride("openai", "gpt-5", -1), false);
|
||||
assert.equal(mco.setModelContextOverride("openai", "gpt-5", 1.5), false);
|
||||
assert.equal(mco.setModelContextOverride("", "gpt-5", 1000), false);
|
||||
assert.equal(mco.setModelContextOverride("openai", " ", 1000), false);
|
||||
assert.equal(mco.getModelContextOverride("openai", "gpt-5"), null);
|
||||
});
|
||||
|
||||
it("trims keys so lookups match writes", () => {
|
||||
mco.setModelContextOverride(" openai ", " gpt-5 ", 333000);
|
||||
assert.equal(mco.getModelContextOverride("openai", "gpt-5"), 333000);
|
||||
});
|
||||
|
||||
it("removes an override", () => {
|
||||
mco.setModelContextOverride("groq", "llama-3.3-70b", 128000);
|
||||
assert.equal(mco.removeModelContextOverride("groq", "llama-3.3-70b"), true);
|
||||
assert.equal(mco.getModelContextOverride("groq", "llama-3.3-70b"), null);
|
||||
assert.equal(mco.removeModelContextOverride("groq", "llama-3.3-70b"), false);
|
||||
});
|
||||
|
||||
it("lists all overrides", () => {
|
||||
mco.setModelContextOverride("openai", "gpt-5", 400000);
|
||||
mco.setModelContextOverride("anthropic", "claude-sonnet-4-5", 200000, "auto:discovery");
|
||||
const all = mco.listModelContextOverrides();
|
||||
assert.equal(all.length, 2);
|
||||
assert.deepEqual(
|
||||
all.map((o) => `${o.provider}/${o.modelId}`).sort(),
|
||||
["anthropic/claude-sonnet-4-5", "openai/gpt-5"]
|
||||
);
|
||||
});
|
||||
});
|
||||
59
tests/unit/model-context-override-readpath.test.ts
Normal file
59
tests/unit/model-context-override-readpath.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, it, beforeEach, 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";
|
||||
|
||||
// Feature 5004 — getModelContextLimit reads the override before the static catalog.
|
||||
|
||||
const moduleDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omni-mco-readpath-"));
|
||||
process.env.DATA_DIR = moduleDataDir;
|
||||
|
||||
const coreDb = await import("../../src/lib/db/core.ts");
|
||||
const mco = await import("../../src/lib/db/modelContextOverrides.ts");
|
||||
const caps = await import("../../src/lib/modelCapabilities.ts");
|
||||
|
||||
beforeEach(() => {
|
||||
coreDb.resetDbInstance();
|
||||
fs.rmSync(moduleDataDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(moduleDataDir, { recursive: true });
|
||||
coreDb.getDbInstance();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
coreDb.resetDbInstance();
|
||||
fs.rmSync(moduleDataDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("getModelContextLimit override precedence (5004)", () => {
|
||||
it("an override wins over the catalog, and removing it falls back to the catalog", () => {
|
||||
// Read the override-free catalog value dynamically (non-brittle for any model).
|
||||
const catalog = caps.getResolvedModelCapabilities({ provider: "openai", model: "gpt-4o" })
|
||||
.contextWindow;
|
||||
const distinct = (catalog ?? 0) + 12345;
|
||||
|
||||
mco.setModelContextOverride("openai", "gpt-4o", distinct);
|
||||
assert.equal(caps.getModelContextLimit("openai", "gpt-4o"), distinct, "override must win");
|
||||
|
||||
mco.removeModelContextOverride("openai", "gpt-4o");
|
||||
assert.equal(
|
||||
caps.getModelContextLimit("openai", "gpt-4o"),
|
||||
catalog,
|
||||
"absence must fall back to the catalog"
|
||||
);
|
||||
});
|
||||
|
||||
it("an override surfaces a window for a model the catalog does not know", () => {
|
||||
assert.equal(caps.getModelContextLimit("custom-local", "my-7b-128k"), null);
|
||||
mco.setModelContextOverride("custom-local", "my-7b-128k", 131072);
|
||||
assert.equal(caps.getModelContextLimit("custom-local", "my-7b-128k"), 131072);
|
||||
});
|
||||
|
||||
it("leaves getResolvedModelCapabilities override-free (so the reconciler sees the catalog)", () => {
|
||||
mco.setModelContextOverride("openai", "gpt-4o", 999999, "auto:discovery");
|
||||
const catalog = caps.getResolvedModelCapabilities({ provider: "openai", model: "gpt-4o" })
|
||||
.contextWindow;
|
||||
assert.notEqual(catalog, 999999, "getResolvedModelCapabilities must not reflect the override");
|
||||
assert.equal(caps.getModelContextLimit("openai", "gpt-4o"), 999999, "but getModelContextLimit does");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user