Files
OmniRoute/src/types/databaseSettings.ts
diegosouzapw c2709ea784 fix(cache): make the vector layer opt-in and restore tip cache/discovery contracts (#14159)
The re-land of #12630 validated only its own tests and broke 15 existing ones.
Three root causes, all fixed in production code so the tip's tests are untouched:

1. Dual-layer manager was ON by default. `DEFAULT_SEMANTIC_CACHE_CONFIG.enabled`
   was `true` and the DB bridge wired it to the pre-existing `semanticCacheEnabled`
   toggle (also default `true`), so every temperature=0 request called an embedding
   endpoint (lemonade @ localhost:13305 by default) on lookup AND store — two extra
   fetches per chat call (chat-combo-live-test, issue-agent-route-execution).
   Fix: new `semanticCacheVectorEnabled` setting (default false; types, DB keys,
   cache-config route, settings UI sub-toggle) and the bridge only enables the
   manager when BOTH toggles are on. Manager default is now `enabled: false`.
   With the opt-in off, chatCore behaves exactly like the legacy SQLite cache.

2. `X-OmniRoute-Cache` value changed from `HIT` to `HIT (exact)`/`HIT (semantic)`,
   and `cacheSource: "semantic_similarity"` fell through attemptLogging's
   `"semantic" | "upstream"` narrowing as "upstream". Fix: keep `HIT` verbatim
   (similarity hits are distinguished by X-OmniRoute-Cache-Similarity) and log
   both hit types as `cacheSource: "semantic"`. The PR's edits to
   chatcore-semantic-cache.test.ts are reverted to the tip version.

3. Model discovery stamped `modelType: "chat"` + `supportedInputTypes: ["text"]`
   on every model (import-mode diff noise, 6 catalog tests), and endpoint-based
   modality inference tagged a `["chat","embeddings"]` model as embedding-only
   (`apiFormat: "embeddings"`), dropping it from the chat catalog. Fix: an explicit
   chat endpoint vetoes the embedding/rerank/image heuristics; chat models keep
   the tip's row shape (metadata only stamped for non-chat modalities).

Also:
- Restore the tip's `isTruncatedStreamBody` dep in streamingSemanticCacheStore and
  widen the predicate to the assembled-object body chatCore actually passes (the
  PR's object-shaped streaming cases are appended to the tip's test file).
- Pass `provider` from chatCore to both cache store paths — the manager scopes
  entries per provider on lookup, so writes without it could never hit.
- test-embedding route: `validateBody()` has no `.response`; invalid payloads
  returned `undefined` (TS2339 in api-typecheck). Now a proper 400.
- New guard: tests/unit/semantic-cache-vector-layer-opt-in.test.ts.
2026-09-19 02:38:09 -03:00

183 lines
5.7 KiB
TypeScript

/**
* Database performance optimization settings stored in SQLite key-value pairs.
* User-configurable aggregation, retention, and optimization settings.
*/
export interface DatabaseSettings {
/** 1. Location (read-only display) */
location: {
databasePath: string;
dataDir: string;
walSizeBytes: number;
schemaVersion: number;
};
/** 2. Logs (what gets captured) */
logs: {
detailedLogsEnabled: boolean;
callLogPipelineEnabled: boolean;
maxDetailSizeKb: number;
ringBufferSize: number;
};
/** 3. Backup (backup/restore/import/export) */
backup: {
autoBackupEnabled: boolean;
autoBackupFrequency: "never" | "daily" | "weekly" | "monthly";
keepLastNBackups: number;
};
/** 4. Cache (moved from CacheSettingsTab) */
cache: {
semanticCacheEnabled: boolean;
semanticCacheMaxSize: number;
semanticCacheTTL: number;
/**
* Opt-in for the dual-layer vector-similarity cache (#14159). Off by default:
* it makes an embedding call per cacheable request, so it must never be on
* for an operator who only enabled the legacy exact-match cache.
*/
semanticCacheVectorEnabled?: boolean;
semanticCacheBackend?: "memory" | "redis";
semanticCacheThreshold?: number;
semanticCacheEmbeddingProvider?: string;
semanticCacheEmbeddingModel?: string;
semanticCacheEmbeddingDimension?: number;
semanticCacheEmbeddingBaseUrl?: string;
semanticCacheEmbeddingApiKey?: string;
semanticCacheRedisUrl?: string;
semanticCacheRedisPrefix?: string;
semanticCacheRequireZeroTemp?: boolean;
promptCacheEnabled: boolean;
promptCacheStrategy: "auto" | "system-only" | "manual";
alwaysPreserveClientCache: "auto" | "always" | "never";
/** Model catalog /v1/models response cache TTL in milliseconds. */
modelCatalogCacheTtlMs: number;
};
/** 5. Retention (per-table cleanup policies) */
retention: {
quotaSnapshots: number;
compressionAnalytics: number;
mcpAudit: number;
configAudit: number;
a2aEvents: number;
callLogs: number;
conversationTurnNodes: number;
usageHistory: number;
memoryEntries: number;
domainCostHistory: number;
compressionCacheStats: number;
xpAuditLog: number;
compressionRunTelemetry: number;
autoCleanupEnabled: boolean;
};
/** 6. Compression (aggregation) */
aggregation: {
enabled: boolean;
rawDataRetentionDays: number;
granularity: "hourly" | "daily" | "weekly";
};
/** 7. Optimization (auto_vacuum, VACUUM, page/cache) */
optimization: {
autoVacuumMode: "NONE" | "FULL" | "INCREMENTAL";
scheduledVacuum: "never" | "daily" | "weekly" | "monthly";
vacuumHour: number;
pageSize: number;
cacheSize: number;
optimizeOnStartup: boolean;
};
/** Read-only stats */
stats: {
databaseSizeBytes: number;
pageCount: number;
freelistCount: number;
lastVacuumAt: string | null;
lastOptimizationAt: string | null;
integrityCheck: "ok" | "error" | null;
/**
* #13432 — non-null while the configured `optimization.autoVacuumMode`
* has not yet been applied to the live SQLite file. Cleared once the
* vacuum scheduler's next scheduled run reconciles it.
*/
autoVacuumDrift: { configured: string; live: string } | null;
/** Pages freed by the most recent bounded `PRAGMA incremental_vacuum` batch, or null. */
lastReclaimedPages: number | null;
};
}
/** Default database settings */
export const DEFAULT_DATABASE_SETTINGS: Omit<DatabaseSettings, "location" | "stats"> = {
logs: {
detailedLogsEnabled: false,
callLogPipelineEnabled: false,
maxDetailSizeKb: 10,
ringBufferSize: 500,
},
backup: {
autoBackupEnabled: false,
autoBackupFrequency: "never",
keepLastNBackups: 5,
},
cache: {
semanticCacheEnabled: true,
semanticCacheMaxSize: 1000,
semanticCacheTTL: 1800000,
semanticCacheVectorEnabled: false,
semanticCacheBackend: "memory",
semanticCacheThreshold: 0.8,
semanticCacheEmbeddingProvider: "lemonade",
semanticCacheEmbeddingModel: "harrier-oss-v1-0.6b",
semanticCacheEmbeddingDimension: 1024,
semanticCacheEmbeddingBaseUrl: "",
semanticCacheEmbeddingApiKey: "",
semanticCacheRedisUrl: "",
semanticCacheRedisPrefix: "omniroute:semcache:",
semanticCacheRequireZeroTemp: true,
promptCacheEnabled: true,
promptCacheStrategy: "auto",
alwaysPreserveClientCache: "auto",
// Keep in sync with CATALOG_CACHE_TTL_MS_DEFAULT
// (src/app/api/v1/models/catalogCache.ts) — this value is what actually takes
// effect, since catalog.ts reads it as `dbSettings.cache?.modelCatalogCacheTtlMs
// ?? CATALOG_CACHE_TTL_MS_DEFAULT` and the `??` never falls through while a
// default is declared here. Guarded by tests/unit/v1-models-catalog-ttl.test.ts.
modelCatalogCacheTtlMs: 60_000,
},
retention: {
quotaSnapshots: 7,
compressionAnalytics: 30,
mcpAudit: 30,
configAudit: 30,
a2aEvents: 30,
callLogs: 30,
// Default matches callLogs (30) so merging this knob changes no behavior for
// existing installs — operators can lower it independently if they want a
// shorter reconnect-anchor window than their call-log retention (#12453).
conversationTurnNodes: 30,
usageHistory: 30,
memoryEntries: 30,
domainCostHistory: 30,
compressionCacheStats: 30,
xpAuditLog: 30,
compressionRunTelemetry: 30,
autoCleanupEnabled: true,
},
aggregation: {
enabled: true,
rawDataRetentionDays: 30,
granularity: "daily",
},
optimization: {
autoVacuumMode: "FULL",
scheduledVacuum: "weekly",
vacuumHour: 2,
pageSize: 4096,
cacheSize: 65536,
optimizeOnStartup: true,
},
};