Files
OmniRoute/src/instrumentation-node.ts
Paijo 3238df3204 perf: lazy provider init, P2C quota cache, structuredClone elimination, getSettings→getCachedSettings (batch 2) (#7893)
* perf: startup parallelization, stream TextEncoder lift, auth middleware bottlenecks

Startup (~100-300ms faster cold start):
- Parallelize 4 early imports via Promise.all() in registerNodejs()
- Parallelize 10 independent background services via Promise.allSettled()
- Each service has independent try/catch — no failure domino effect

Streaming pipeline (8 fewer TextEncoder GC allocations per SSE event):
- Lift new TextEncoder() from per-chunk inside buildClaudeStreamingResponse
  to function scope alongside existing decoder singleton

Auth middleware bottlenecks (from PerfBottleneckAnalysis):
- Backoff decay loop: replace updateProviderConnection (full CRUD:
  SELECT+encrypt+cache-invalidate+backup) with resetConnectionBackoff
  (targeted UPDATE of backoff/error columns only)
- Dual .filter() for quota: replace two passes calling
  isQuotaExhaustedForRequest per connection with a single for loop
  partitioning into withQuota/exhaustedQuota
- Debug-log filter recomputation: capture connectionFilterStatus Map
  during the filter pass; debug loop reads 6 string comparisons instead
  of 6 function calls per connection

Supporting:
- Add resetConnectionBackoff to src/lib/db/providers.ts (patterned after
  clearConnectionErrorIfUnchanged, no CAS check)
- Re-export resetConnectionBackoff from src/lib/localDb.ts
- Update integration-wiring.test.ts regex for parallelized dynamic import

* perf: P2C quota cache, lazy provider init, structuredClone elimination, getSettings→getCachedSettings

- **auth.ts: P2C quota re-evaluation cache** — quotaResults Map threaded
  through selectPoolSubset → compareP2CConnections → getP2CConnectionScore.
  Populated during filter + partition passes, eliminating redundant
  evaluateQuotaLimitPolicy / isQuotaExhaustedForRequest calls when the
  P2C comparator re-evaluates previously-scored connections.
- **constants.ts: lazy PROVIDERS via Proxy** — replaces eager
  generateLegacyProviders() + loadProviderCredentials() at module load
  with Proxy delegating to deferred init on first property access.
- **providerModels.ts: lazy PROVIDER_MODELS + PROVIDER_ID_TO_ALIAS** —
  same Proxy pattern for both exports; generateModels()/generateAliasMap()
  deferred until first read.
- **stream.ts: structuredClone → minimal object spread** — replaces
  O(n) deep clone of SSE response chunks with targeted reconstruction
  of only mutated fields (usage, delta.content, finish_reason).
- **progressTracker.ts: TextDecoder lift** — module-level decoder
  instead of per-chunk new TextDecoder().
- **Route files: getSettings() → getCachedSettings()** — 13 API route
  files converted from uncached per-request DB reads to TTL-cached
  wrapper (5s default), eliminating redundant queries on every request.
- **settings.ts: re-export getCachedSettings** from readCache for
  non-localDb consumers.
- **Remove settingsCache.ts** — dead file, no imports reference it.

TS compile: 0 errors. Auth tests: 225/225 pass. Services: 269/269 pass.

* perf: Phase 1 tangible wins — egressCache eviction, mmap_size PRAGMA, composite indexes, proxyFallback lazy import

- egressCache: lazy TTL eviction on getCachedEgressIp access (bounds memory
  leak to distinct proxy URLs, typically <100)
- mmap_size: apply stored PRAGMA from key_value table (256MiB default) after
  applyStoredDatabaseOptimizationSettings — setting was stored but never applied
- schemaColumns: add idx_uh_provider_model_timestamp (covers getModelLatencyStats)
  and idx_pc_provider_auth_type (covers 6+ provider_connections queries)
- proxyFallback: convert static import to dynamic import() inside error handler
  (defers 210ms module load from startup to first proxy-retry scenario)

* perf: add dedup expression index, unref() sweep timers

- Add COALESCE expression index idx_uh_dedup on usage_history
  matching the exact dedup query pattern. Eliminates FULL TABLE
  SCAN on every saveRequestUsage insert.
- Add composite idx_uh_provider_model_timestamp on usage_history.
- Add composite idx_pc_provider_auth_type on provider_connections.
- Add .unref() to setInterval in batchProcessor.ts (polling loop).
- Add .unref() to setInterval in runtimeHeartbeat.ts (heartbeat).

* perf: bump SQLite cache_size default from 16MB to 64MB

New installs now start with 64MB page cache (was 16MB). Existing
users' stored settings are unchanged. Reduces disk reads for the
typical ~250MB database by keeping ~25% of pages in memory.

Also resolved pre-existing merge conflict in webhooks.ts.

* docs: add Redis production config guide and proxy port clash investigation report

- docs/redis-production-config.md: comprehensive Redis tuning guide
  covering client options, server config, Docker settings, scaling,
  and monitoring for all three Redis workloads (rate limiting,
  auth cache, quota store)
- docs/proxy-port-clash-report.md: investigation confirming proxy
  subsystem has no port binding issues; real EADDRINUSE history
  traced to process supervisor crash-loop restart race (#4425) and
  live-dashboard port clash (#6324), both already fixed

* fix: address PR #7893 review — add Proxy traps, extract migrations to reduce providers.ts size

- Add set trap to PROVIDER_ID_TO_ALIAS Proxy (providerModels.ts)
- Add deleteProperty traps to all three lazy Proxies (PROVIDERS,
  PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS)
- Extract autoMigrateLegacyEncryptedConnections and getGheCopilotHosts
  from providers.ts (1129→1036 lines, -93) into providers/migrations.ts
- Both functions re-exported via providers.ts for backward compat

File-size ratchet resolved: src/lib/db/providers.ts now 1036 lines.

* fix: resolve merge conflict markers in 3 route/test files

- model-combo-mappings/route.ts: kept upstream version (Zod pagination
  via validateBody + isValidationFailure), restored missing return
  statement for GET handler
- playground/presets/route.ts: kept stashed version details (satisfies
  type-narrowing + inlined Response) — functionally identical
- error-sanitization.test.ts: matches upstream exactly (no diff)

Test verification: same 7 pre-existing failures confirmed on upstream
baseline (c1bdd91e7). Zero regressions from conflict resolution.

Closes remaining uncommitted work from PR #7046 rebase.

* test(db): add resetConnectionBackoff coverage + fix file-size ratchet regression

- Add tests/unit/reset-connection-backoff.test.ts: covers the new
  resetConnectionBackoff lightweight-UPDATE helper (clears backoff/error
  columns and re-activates a connection, unconditional-write behavior on
  terminal statuses, no-op for empty/unknown ids). Zero prior coverage
  per pre-merge review of PR #7893 (Hard Rule #8).
- open-sse/services/batchProcessor.ts: fold the new unref() call into the
  existing setInterval(...).unref() chain instead of a separate statement,
  keeping the file at the frozen 915-line ratchet (Timeout.unref() already
  returns `this`, so no type cast is needed).
- src/lib/localDb.ts: drop one blank separator line so the new
  resetConnectionBackoff re-export stays within the frozen 808-line ratchet.

Pre-merge fix for PR #7893 (perf/startup-stream-auth-optimizations) per
/green-prs plan-file _tasks/pipeline/prs/1-analyzed/7893-*.plan.md.

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
2026-07-21 11:50:14 -03:00

544 lines
24 KiB
TypeScript
Executable File

/**
* Node.js-only instrumentation logic.
*
* Separated from instrumentation.ts so that Turbopack's Edge bundler
* does not trace into Node.js-only modules (fs, path, os, better-sqlite3, etc.)
* and emit spurious "not supported in Edge Runtime" warnings.
*/
import { markServerReady, markServerStarting } from "@/lib/serverLifecycle";
function getRandomBytes(byteLength: number): Uint8Array {
const bytes = new Uint8Array(byteLength);
globalThis.crypto.getRandomValues(bytes);
return bytes;
}
function toBase64(bytes: Uint8Array): string {
return btoa(String.fromCodePoint(...bytes));
}
function toHex(bytes: Uint8Array): string {
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
/**
* Rename a Node process title so OmniRoute is identifiable in `ps`/`htop`
* instead of the generic Next.js standalone server name.
*
* Only rewrites titles that start with "next-server", preserving any
* trailing suffix (e.g. " (v16.2.9)"). Every other title — including one
* that has already been renamed, or one that merely contains
* "next-server" elsewhere — passes through unchanged. Empty/undefined-safe.
*/
export function renameProcessTitle(currentTitle: string): string {
if (!currentTitle) return currentTitle;
if (!currentTitle.startsWith("next-server")) return currentTitle;
return `omniroute${currentTitle.slice("next-server".length)}`;
}
/**
* Normalize any thrown/rejected value into a real `Error` instance.
*
* Next.js's own `registerInstrumentation()` wrapper (see
* `node_modules/next/dist/server/lib/router-utils/instrumentation-globals.external.js`)
* unconditionally does `err.message = \`...${err.message}\`` on whatever our
* `register()` export rejects with, assuming it is always an `Error`. If a raw
* non-Error primitive bubbles up instead (e.g. sql.js's WASM adapter throws the
* bare string `"Database closed"` — see `./lib/db/adapters/sqljsAdapter.ts`),
* that assignment throws `TypeError: Cannot create property 'message' on
* string '...'` in strict mode, masking the original error and crashing the
* whole server on every boot (#6560). Normalizing before it leaves our code
* guarantees Next always receives something `.message`-assignable.
*/
export function normalizeBootError(err: unknown): Error {
return err instanceof Error ? err : new Error(String(err));
}
// Matches sql.js's raw `throw "Database closed"` (and similarly-worded
// variants) thrown when a query runs against an already-closed WASM handle —
// typically a stale globalThis-cached adapter left over by a prior
// close/reload racing with this boot (#6560).
const TRANSIENT_DB_CLOSED_RE = /database\s*(connection\s*)?(is\s*)?closed/i;
/**
* Initialize the SQLite singleton for boot, tolerating one transient
* "database closed" failure (#6560) by retrying once — the driverFactory
* cache-eviction fix (`preInitSqlJs`) makes the retry create a fresh adapter
* instead of reusing the dead one. Any other failure (or a second consecutive
* "database closed") is re-thrown as a real `Error` via `normalizeBootError`
* so it can never crash instrumentation with a masking TypeError — the caller
* (`registerNodejs`) still surfaces it as a real boot failure.
*
* `ensureDbInitializedFn` is only for tests to inject a fake without
* module-mocking (`node:test` does not support `mock.module` reliably here).
*/
export async function ensureDbReadyForBoot(
ensureDbInitializedFn?: () => Promise<void>
): Promise<void> {
const ensureDbInitialized =
ensureDbInitializedFn ?? (await import("@/lib/db/core")).ensureDbInitialized;
try {
await ensureDbInitialized();
} catch (err: unknown) {
const normalized = normalizeBootError(err);
if (!TRANSIENT_DB_CLOSED_RE.test(normalized.message)) {
// Fatal, non-transient boot-time DB init failure (e.g. the entire
// better-sqlite3 -> node:sqlite -> sql.js driver cascade failed, as on
// Termux/Android when no SQLite driver is usable). This runs BEFORE
// initConsoleInterceptor() is wired up, so this is the only chance to
// get the real root cause into stdout/app.log — without it, the
// process keeps its HTTP listener up while every DB-touching route
// 500s forever with a permanently empty log (#7773).
console.error(
"[STARTUP] Fatal: Database driver initialization failed:",
normalized.message
);
throw normalized;
}
console.warn(
"[STARTUP] Database was closed by a prior reload/shutdown — retrying with a fresh connection (#6560):",
normalized.message
);
try {
await ensureDbInitialized();
} catch (retryErr: unknown) {
const normalizedRetryErr = normalizeBootError(retryErr);
console.error(
"[STARTUP] Fatal: Database driver initialization failed after retry:",
normalizedRetryErr.message
);
throw normalizedRetryErr;
}
}
}
function isBackgroundServicesDisabled(): boolean {
const raw = process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES;
if (!raw) return false;
return new Set(["1", "true", "yes", "on"]).has(raw.trim().toLowerCase());
}
async function ensureSecrets(): Promise<void> {
let getPersistedSecret = (_key: string): string | null => null;
let persistSecret = (_key: string, _value: string): void => {};
try {
({ getPersistedSecret, persistSecret } = await import("@/lib/db/secrets"));
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn(
"[STARTUP] Secret persistence unavailable; falling back to process-local secrets:",
msg
);
}
if (!process.env.JWT_SECRET || process.env.JWT_SECRET.trim() === "") {
const persisted = getPersistedSecret("jwtSecret");
if (persisted) {
process.env.JWT_SECRET = persisted;
console.log("[STARTUP] JWT_SECRET restored from persistent store");
} else {
const generated = toBase64(getRandomBytes(48));
process.env.JWT_SECRET = generated;
persistSecret("jwtSecret", generated);
console.log("[STARTUP] JWT_SECRET auto-generated and persisted (random 64-char secret)");
}
}
if (!process.env.API_KEY_SECRET || process.env.API_KEY_SECRET.trim() === "") {
const persisted = getPersistedSecret("apiKeySecret");
if (persisted) {
process.env.API_KEY_SECRET = persisted;
} else {
const generated = toHex(getRandomBytes(32));
process.env.API_KEY_SECRET = generated;
persistSecret("apiKeySecret", generated);
console.log(
"[STARTUP] API_KEY_SECRET auto-generated and persisted (random 64-char hex secret)"
);
}
}
}
/**
* Warm the model catalog's durable, apiKey-independent sub-caches at startup
* so real /v1/models traffic (any client, any API key) avoids paying their
* cold-build cost on first use. Fire-and-forget from the caller, non-fatal.
*
* getUnifiedModelsResponse()'s own top-level Response cache (`catalogCache`
* in catalog.ts) is keyed by prefix/isCodex/apiKey AND has only a 1.5s TTL
* (CATALOG_CACHE_TTL_MS — a burst-dedup window added for #6408 to coalesce
* concurrent SDK/dashboard requests, not a startup-warm cache). Warming that
* cache with an unauthenticated dummy request has essentially no lasting
* effect: real traffic almost never arrives within 1.5s of this warmup
* completing, regardless of whether its cache key happens to match a real
* client's apiKey. The one genuinely durable, apiKey-independent cost in the
* catalog build is getOpenRouterCatalog()'s 24h disk-cached network fetch
* (src/lib/catalog/openrouterCatalog.ts) — buildUnifiedModelsResponseCore()
* calls it unconditionally whenever an OpenRouter connection is configured,
* decoupled from the per-key Response cache entirely, so warming it directly
* here benefits every subsequent /v1/models request regardless of that
* request's own apiKey. Only fetched when an OpenRouter connection actually
* exists, so deployments that never use OpenRouter don't pay an unconditional
* third-party network call at every boot.
*
* Exported (rather than left inline in registerNodejs()) so it can be unit
* tested directly without exercising the rest of the startup sequence.
*/
export async function warmModelCatalogCache(): Promise<void> {
try {
const { getUnifiedModelsResponse } = await import("@/app/api/v1/models/catalog");
await getUnifiedModelsResponse(new Request("http://127.0.0.1/v1/models"));
console.log("[STARTUP] Model catalog cache warmed");
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Model catalog warmup failed (non-fatal):", msg);
}
try {
const [{ getProviderConnections }, { getOpenRouterCatalog }] = await Promise.all([
import("@/lib/db/providers"),
import("@/lib/catalog/openrouterCatalog"),
]);
const openrouterConnections = await getProviderConnections({
provider: "openrouter",
isActive: true,
});
if (openrouterConnections.length > 0) {
await getOpenRouterCatalog();
console.log("[STARTUP] OpenRouter model catalog cache warmed");
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] OpenRouter catalog warmup failed (non-fatal):", msg);
}
}
export async function registerNodejs(): Promise<void> {
markServerStarting();
// Rename the process title so OmniRoute is identifiable in ps/htop instead
// of the generic "next-server" standalone server name.
process.title = renameProcessTitle(process.title);
// Initialize proxy fetch patch FIRST (before any HTTP requests)
await import("@omniroute/open-sse/index.ts");
console.log("[STARTUP] Global fetch proxy patch initialized");
// Guarantee the SQLite singleton — including a sql.js WASM pre-init when
// both synchronous drivers (better-sqlite3, node:sqlite) are unavailable —
// is ready before ANY other startup step reaches getDbInstance(). This
// MUST run before ensureSecrets, clearStaleCrashCooldowns,
// getSettings, initAuditLog below: those all reach getDbInstance()
// transitively, and used to run ahead of this call (previously at the end
// of this function), throwing the misleading "sql.js WASM ainda não foi
// pré-inicializado" error for an existing DB file when both sync drivers
// failed (#7288 / #7494). ensureDbInitialized() itself is idempotent and
// caches the singleton, so every later getDbInstance() call below is a
// free no-op re-read of the same connection — no double-init cost.
await ensureDbReadyForBoot();
await ensureSecrets();
await Promise.all([
import("@/lib/env/runtimeEnv").then(({ enforceWebRuntimeEnv }) => enforceWebRuntimeEnv()),
import("@/lib/usage/migrations"),
import("@/lib/consoleInterceptor").then(({ initConsoleInterceptor }) => initConsoleInterceptor()),
]);
// Clear stale transient connection cooldowns persisted from an unclean crash.
// A crash mid-burst can leave far-future `rate_limited_until` values in the DB
// that cause every connection to be skipped by getProviderCredentials(), making
// all subsequent requests time out at Bottleneck's maxWaitMs (120 s default).
// Terminal states (banned / expired / credits_exhausted) are intentionally kept.
// See: https://github.com/diegosouzapw/OmniRoute/issues/3625 (Part A)
try {
const { clearStaleCrashCooldowns } = await import("@/lib/db/providers");
const { cleared } = clearStaleCrashCooldowns();
if (cleared > 0) {
console.log(
`[STARTUP] Cleared ${cleared} stale transient connection cooldown(s) from prior crash (#3625)`
);
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not clear stale crash cooldowns (non-fatal):", msg);
}
const [
{ initGracefulShutdown },
{ initApiBridgeServer },
{ startBackgroundRefresh },
{ ensureCloudSyncInitialized },
{ startProviderLimitsSyncScheduler },
{ getSettings },
{ applyRuntimeSettings },
{ startRuntimeConfigHotReload },
{ startSpendBatchWriter },
{ registerDefaultGuardrails },
{ ensurePersistentManagementPasswordHash },
{ skillExecutor },
{ registerBuiltinSkills },
] = await Promise.all([
import("@/lib/gracefulShutdown"),
import("@/lib/apiBridgeServer"),
import("@/domain/quotaCache"),
import("@/lib/initCloudSync"),
import("@/shared/services/providerLimitsSyncScheduler"),
import("@/lib/db/settings"),
import("@/lib/config/runtimeSettings"),
import("@/lib/config/hotReload"),
import("@/lib/spend/batchWriter"),
import("@/lib/guardrails"),
import("@/lib/auth/managementPassword"),
import("@/lib/skills/executor"),
import("@/lib/skills/builtins"),
]);
// Proxy health scheduler (auto-removes dead proxies on interval)
await import("@/lib/proxyHealth/scheduler");
// Free-proxy auto-sync scheduler (re-fetches free-proxy sources on interval, #7079)
await import("@/lib/freeProxyProviders/scheduler");
initGracefulShutdown();
initApiBridgeServer();
startSpendBatchWriter();
registerDefaultGuardrails();
registerBuiltinSkills(skillExecutor);
console.log("[STARTUP] Spend batch writer started");
console.log("[STARTUP] Guardrail registry initialized");
console.log("[STARTUP] Builtin skill handlers registered");
if (!isBackgroundServicesDisabled()) {
startBackgroundRefresh();
console.log("[STARTUP] Quota cache background refresh started");
startProviderLimitsSyncScheduler();
console.log("[STARTUP] Provider limits sync scheduler started");
const { startQuotaAutoPing } = await import("@/lib/services/quotaAutoPing");
startQuotaAutoPing();
console.log("[STARTUP] Quota auto-ping scheduler started (opt-in, no-op until enabled)");
const cloudSyncInitialized = await ensureCloudSyncInitialized();
console.log(
`[STARTUP] Cloud/model sync background bootstrap ${cloudSyncInitialized ? "initialized" : "skipped"}`
);
const { initBatchProcessor } = await import("@omniroute/open-sse/services/batchProcessor");
initBatchProcessor();
console.log("[STARTUP] Batch processor started");
}
try {
const [
{ migrateCodexConnectionDefaultsFromLegacySettings },
{ startSessionAccountAffinityCleanup },
{ seedDefaultModelAliases },
] = await Promise.all([
import("@/lib/providers/codexConnectionDefaults"),
import("@/lib/db/sessionAccountAffinity"),
import("@/lib/modelAliasSeed"),
]);
let settings = await getSettings();
const passwordState = await ensurePersistentManagementPasswordHash({
logger: console,
settings,
source: "startup",
});
settings = passwordState.settings;
const runtimeChanges = await applyRuntimeSettings(settings, { force: true, source: "startup" });
if (runtimeChanges.length > 0) {
console.log(
`[STARTUP] Runtime settings hydrated: ${runtimeChanges
.map((entry) => entry.section)
.join(", ")}`
);
}
// Restore Global System Prompt into in-memory config (#2468/#2470)
if (settings.systemPrompt) {
const { setSystemPromptConfig } =
await import("@omniroute/open-sse/services/systemPrompt.ts");
setSystemPromptConfig(settings.systemPrompt);
console.log("[STARTUP] Global System Prompt restored from settings");
}
// Restore the proxy-level Thinking-Budget config (#5312 RC-A). It lives in
// `settings.thinkingBudget` and is NOT covered by applyRuntimeSettings, so
// without this the dashboard mode (auto/custom/adaptive) silently reverts to
// the passthrough default on every restart. Previously this was only wired into
// the unused `server-init.ts`, so it never ran in production.
const { hydrateThinkingBudgetConfig } =
await import("@omniroute/open-sse/services/thinkingBudget.ts");
if (hydrateThinkingBudgetConfig(settings)) {
console.log("[STARTUP] Thinking-Budget config restored from settings");
}
const seededModelAliases = await seedDefaultModelAliases();
console.log(
`[STARTUP] Model alias seed: applied=${seededModelAliases.applied.length}, skipped=${seededModelAliases.skipped.length}, failed=${seededModelAliases.failed.length}`
);
startSessionAccountAffinityCleanup();
const migration = await migrateCodexConnectionDefaultsFromLegacySettings();
if (migration.migrated) {
console.log(
`[STARTUP] Migrated Codex connection defaults for ${migration.updatedConnectionIds.length} connection(s)`
);
if (settings.cloudEnabled === true) {
const [{ syncToCloud }, { getConsistentMachineId }] = await Promise.all([
import("@/lib/cloudSync"),
import("@/shared/utils/machineId"),
]);
const machineId = await getConsistentMachineId();
await syncToCloud(machineId);
console.log("[STARTUP] Synced migrated Codex connection defaults to cloud");
}
}
startRuntimeConfigHotReload();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not restore runtime settings:", msg);
}
// Proactively start the credential-health sweep at boot so stale web-session
// connections (cookies that expired overnight) get re-probed and recovered on
// startup — instead of staying red until the first real request lazily imports
// the on-demand credentialGate. Idempotent; self-disables via
// OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK and its cadence is tunable via
// CREDENTIAL_HEALTH_CHECK_INTERVAL. NOTE: this MUST live here (the real Next.js
// instrumentation startup), NOT in the unused src/server-init.ts.
try {
const { initCredentialHealthCheck } = await import("@/lib/credentialHealth/scheduler");
initCredentialHealthCheck();
console.log("[STARTUP] Credential health scheduler started");
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not start credential health scheduler:", msg);
}
try {
const { initAuditLog, cleanupExpiredLogs } = await import("@/lib/compliance/index");
initAuditLog();
console.log("[COMPLIANCE] Audit log table initialized");
const cleanup = await cleanupExpiredLogs();
if (
cleanup.deletedUsage ||
cleanup.deletedCallLogs ||
cleanup.deletedProxyLogs ||
cleanup.deletedRequestDetailLogs ||
cleanup.deletedAuditLogs ||
cleanup.deletedMcpAuditLogs
) {
console.log("[COMPLIANCE] Expired log cleanup:", cleanup);
}
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[COMPLIANCE] Could not initialize audit log:", msg);
}
// Storage-configured scheduled VACUUM (#4437): registers the timer from
// Settings > System & Storage and persists lastVacuumAt for the UI.
try {
const { initVacuumScheduler } = await import("@/lib/db/vacuumScheduler");
initVacuumScheduler();
console.log("[STARTUP] Scheduled VACUUM initialized (#4437)");
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Could not initialize vacuum scheduler (non-fatal):", msg);
}
// Warm the model catalog's durable, apiKey-independent sub-caches at
// startup — see warmModelCatalogCache() for why the top-level Response
// cache alone doesn't deliver this. Fire-and-forget, non-fatal.
void warmModelCatalogCache();
if (!isBackgroundServicesDisabled()) {
// All services are independent — run in parallel for faster cold start.
await Promise.allSettled([
import("@/lib/services/bootstrap").then(async (m) => {
await m.bootstrapEmbeddedServices();
console.log("[STARTUP] Embedded services bootstrap complete");
}).catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Embedded services bootstrap failed (non-fatal):", msg);
}),
import("@/lib/services/embedWsProxy").then((m) => m.initEmbedWsProxy())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Embed WS proxy failed to start (non-fatal):", msg);
}),
import("@omniroute/open-sse/services/autoRefreshDaemon").then((m) => m.autoRefreshDaemon.start())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Auto-refresh daemon failed to start (non-fatal):", msg);
}),
// Proactive connection-cooldown recovery (#8): re-validate connections whose
// transient `rate_limited_until` window has elapsed OUTSIDE the request hot path,
// so the first request after a cooldown does not pay the probe latency.
import("@/lib/quota/connectionRecovery").then((m) => m.initConnectionRecoveryScheduler())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Connection recovery scheduler failed to start (non-fatal):", msg);
}),
// Arena ELO sync: model intelligence from the Arena AI leaderboard, powering the
// Free Provider Rankings page. On by default; non-blocking, never fatal.
import("@/lib/arenaEloSync").then(async (m) => {
const started = await m.initArenaEloSync();
if (started) console.log("[STARTUP] Arena ELO sync initialized");
}).catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Arena ELO sync failed to start (non-fatal):", msg);
}),
// Pricing sync: opt-in external pricing data (self-gated by PRICING_SYNC_ENABLED inside
// initPricingSync). Non-blocking, never fatal.
import("@/lib/pricingSync").then((m) => m.initPricingSync())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Pricing sync failed to start (non-fatal):", msg);
}),
// models.dev capability sync: opt-in via Settings > AI (self-gated by
// settings.modelsDevSyncEnabled inside initModelsDevSync). Non-blocking, never fatal.
import("@/lib/modelsDevSync").then((m) => m.initModelsDevSync())
.catch((err: unknown) => {
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. Never fatal.
import("@/lib/contextWindowResolver").then((m) => m.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);
}),
// TV6 typed memory decay: optional periodic sweep of decayed episodic memories.
// Doubly opt-in (no-op unless MEMORY_TYPED_DECAY_ENABLED=true AND
// MEMORY_TYPED_DECAY_SWEEP_INTERVAL>0). Never deletes by default. Never fatal.
import("@/lib/memory/typedDecay").then((m) => m.startMemoryDecaySweep())
.catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] memory decay sweep failed to start (non-fatal):", msg);
}),
// Real-time dashboard WebSocket daemon (port 20132): powers Combo Studio Live,
// the Home live-pulse, and Live Compression. Side-effect import triggers the
// flag-gated auto-start (OMNIROUTE_ENABLE_LIVE_WS, default ON).
import("@/server/ws/liveServer").then(() => {
console.log("[STARTUP] Live dashboard WebSocket daemon bootstrap invoked");
}).catch((err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
console.warn("[STARTUP] Live dashboard WebSocket daemon failed to start (non-fatal):", msg);
}),
]);
}
markServerReady();
}