diff --git a/AGENTS.md b/AGENTS.md index 627064d5b1..d468ae5848 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -289,8 +289,7 @@ When creating _any_ validation tests or one-off logic scripts, default to `scrip ### Database - **Always** go through `src/lib/db/` domain modules — **never** write raw SQL in routes or handlers -- **Never** add logic to `src/lib/localDb.ts` (re-export layer only) -- **Never** barrel-import from `localDb.ts` — import specific `db/` modules instead +- **Never** barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules - DB singleton: `getDbInstance()` from `src/lib/db/core.ts` (WAL journaling) - Migrations: `src/lib/db/migrations/` — versioned SQL files, idempotent, run in transactions @@ -355,8 +354,7 @@ Documentation must describe verified behavior, not plausible behavior. 1. Create `src/lib/db/yourModule.ts` — import `getDbInstance` from `./core.ts` 2. Export CRUD functions for your domain table(s) 3. Add migration in `src/lib/db/migrations/` if new tables needed -4. Re-export from `src/lib/localDb.ts` (add to the re-export list only) -5. Write tests +4. Write tests ### Adding a New MCP Tool @@ -668,7 +666,7 @@ the stale-enforcement added in Fase 6A.3. ## Hard Rules 1. Never commit secrets or credentials -2. Never add logic to `localDb.ts` +2. Never barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules 3. Never use `eval()` / `new Function()` / implied eval 4. Never commit directly to `main` 5. Never write raw SQL in routes — use `src/lib/db/` modules diff --git a/changelog.d/fixes/11704-prepublish-npx-cmd-win32.md b/changelog.d/fixes/11704-prepublish-npx-cmd-win32.md new file mode 100644 index 0000000000..9a020462cc --- /dev/null +++ b/changelog.d/fixes/11704-prepublish-npx-cmd-win32.md @@ -0,0 +1 @@ +- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704)) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index ad571e9e24..97d9c608a5 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -230,11 +230,6 @@ "count": 1 } }, - "open-sse/handlers/chatCore/comboContextCache.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/handlers/chatCore/executorHelpers.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -465,9 +460,6 @@ "@typescript-eslint/no-explicit-any": { "count": 17 }, - "no-restricted-imports": { - "count": 2 - }, "no-restricted-syntax": { "count": 1 } @@ -510,21 +502,11 @@ "count": 21 } }, - "open-sse/services/combo/concurrencyCaps.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/services/combo/providerWildcard.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "open-sse/services/combo/quotaExhaustionCutoff.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/services/comboAgentMiddleware.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -621,11 +603,6 @@ "count": 2 } }, - "open-sse/services/keyGroupAuth.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/services/manifestAdapter.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 @@ -689,9 +666,6 @@ "open-sse/services/tokenLimitCounter.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "open-sse/services/tokenRefresh.ts": { @@ -827,11 +801,6 @@ "count": 1 } }, - "open-sse/utils/proxyFallback.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "open-sse/utils/setupPolyfill.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -1223,126 +1192,31 @@ "count": 1 } }, - "src/app/(dashboard)/home/page.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/assess/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/auth/oidc/callback/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/auth/oidc/login/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/batches/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/batches/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/cache/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/cli-tools/claude-settings/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/cli-tools/cline-settings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/cli-tools/codex-settings/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/cli-tools/keys/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/cli-tools/kilo-settings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/cli/connect/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/combos/duplicate/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 } }, - "src/app/api/combos/reorder/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/combos/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/combos/test/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/compression/compare/verify/route.ts": { "no-restricted-imports": { "count": 1 } }, - "src/app/api/db-backups/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/evals/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/evals/suites/[suiteId]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/evals/suites/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/files/[id]/content/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/files/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/github-skills/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1353,61 +1227,24 @@ "count": 1 } }, - "src/app/api/keys/[id]/regenerate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/keys/[id]/reveal/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/keys/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/keys/groups/[id]/keys/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 4 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/keys/groups/[id]/permissions/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 5 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/keys/groups/[id]/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 9 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/keys/groups/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/keys/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/memory/reindex/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/memory/route.ts": { @@ -1418,34 +1255,6 @@ "src/app/api/middleware/hooks/[name]/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/middleware/hooks/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/model-combo-mappings/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/model-combo-mappings/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/models/test-all/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/monitoring/health/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/oauth/[provider]/[action]/route.ts": { @@ -1458,26 +1267,6 @@ "count": 1 } }, - "src/app/api/pricing/models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/pricing/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/provider-models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/login/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1486,29 +1275,6 @@ "src/app/api/providers/[id]/models/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 9 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/refresh/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/sync-models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/[id]/test/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/providers/bulk-web-session/route.ts": { @@ -1516,96 +1282,11 @@ "count": 1 } }, - "src/app/api/providers/bulk/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/client/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/command-code/auth/apply/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/import/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/quota-windows/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/providers/validate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/groups/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/groups/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/keys/[id]/models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/plans/[connectionId]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/plans/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/pools/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/pools/[id]/usage/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/pools/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/quota/preview/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/rate-limits/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/relay/tokens/[id]/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 } }, - "src/app/api/resilience/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/search/stats/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -1616,162 +1297,14 @@ "count": 1 } }, - "src/app/api/settings/__tests__/memory.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/__tests__/settings.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/authz-inventory/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/auto-disable-accounts/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/combo-defaults/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/export-json/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/free-proxies/stats/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/memory/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/settings/obsidian/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 } }, - "src/app/api/settings/payload-rules/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/[id]/repair-relay/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/assignments/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/auto-test/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/batch-activate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/batch-delete/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/bulk-assign/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/bulk-import/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/health/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/migrate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/pool/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxies/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/cloudflare-deploy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/deno-deploy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/test/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/proxy/vercel-deploy/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/settings/qdrant/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/settings/quota-store/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/settings/route.ts": { @@ -1779,27 +1312,9 @@ "count": 1 } }, - "src/app/api/settings/system-prompt/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/settings/thinking-budget/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/skills/collect/chaos/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/sync/cloud/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/api/sync/initialize/route.ts": { @@ -1812,114 +1327,34 @@ "count": 1 } }, - "src/app/api/token-health/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/tools/agent-bridge/server/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/tools/traffic-inspector/ws/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/translator/send/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/translator/translate/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/usage/analytics/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/usage/quota/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/usage/token-limits/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/_helpers/apiKeyScope.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/audio/speech/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/v1/audio/transcriptions/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/[id]/cancel/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/delete-completed/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/batches/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/chat/completions/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/v1/combos/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/embeddings/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/app/api/v1/files/[id]/content/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/files/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/files/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/images/edits/route.ts": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "src/app/api/v1/images/generations/route.ts": { @@ -1927,26 +1362,6 @@ "count": 1 } }, - "src/app/api/v1/management/proxies/assignments/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/management/proxies/bulk-assign/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/management/proxies/health/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/v1/management/proxies/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/v1/messages/count_tokens/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -1963,9 +1378,6 @@ "src/app/api/v1/models/catalog.ts": { "@typescript-eslint/no-unused-vars": { "count": 10 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/api/v1/models/catalogCache.ts": { @@ -2033,32 +1445,9 @@ "count": 1 } }, - "src/app/api/v1beta/models/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/webhooks/[id]/deliveries/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/app/api/webhooks/[id]/route.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/api/webhooks/[id]/test/route.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 2 - } - }, - "src/app/api/webhooks/route.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/app/docs/components/FeedbackWidget.tsx": { @@ -2096,11 +1485,6 @@ "count": 1 } }, - "src/domain/quotaCache.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/hooks/useLiveDashboard.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2119,11 +1503,6 @@ "count": 1 } }, - "src/lib/api/proxyRegistryRouteHandlers.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/cli-helper/config-generator/claude.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2164,16 +1543,6 @@ "count": 2 } }, - "src/lib/cloudSync.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/combos/builderOptions.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/combos/controlCenter.ts": { "no-restricted-syntax": { "count": 1 @@ -2184,11 +1553,6 @@ "count": 1 } }, - "src/lib/container.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/copilot/engine.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -2202,9 +1566,6 @@ "src/lib/credentialHealth/scheduler.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/lib/db/apiKeys.ts": { @@ -2227,11 +1588,6 @@ "count": 2 } }, - "src/lib/db/compressionCombos.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "src/lib/db/core.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 @@ -2257,11 +1613,6 @@ "count": 2 } }, - "src/lib/db/middleware.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 2 - } - }, "src/lib/db/migrationRunner.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -2327,21 +1678,6 @@ "count": 1 } }, - "src/lib/evals/runtime.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/freeProxyProviders/scheduler.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/freeProxyProviders/syncCycle.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/guardrails/promptInjection.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -2352,26 +1688,11 @@ "count": 1 } }, - "src/lib/idempotencyLayer.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/images/imageRouteModel.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/jobRegistry/index.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/lib/localHealthCheck.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/memory/__tests__/generic-backend.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 4 @@ -2382,21 +1703,11 @@ "count": 1 } }, - "src/lib/memory/embedding/index.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/memory/genericBackend.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/lib/memory/reindex.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/memory/retrieval.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2407,16 +1718,6 @@ "count": 2 } }, - "src/lib/memory/store.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/memory/vectorStore.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/middleware/registry.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -2431,9 +1732,6 @@ "@typescript-eslint/no-unused-vars": { "count": 1 }, - "no-restricted-imports": { - "count": 1 - }, "no-restricted-syntax": { "count": 1 } @@ -2493,32 +1791,9 @@ "count": 3 } }, - "src/lib/oauth/utils/agyAuthImport.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/oauth/utils/claudeAuthFile.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/oauth/utils/claudeAuthImport.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/oauth/utils/codexAuthFile.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/oauth/utils/codexAuthImport.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/lib/obsidian/api.ts": { @@ -2546,11 +1821,6 @@ "count": 2 } }, - "src/lib/providerModels/managedAvailableModels.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/providers/validation.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2566,45 +1836,19 @@ "count": 1 } }, - "src/lib/proxyHealth/scheduler.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/quota/planResolver.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/quota/quotaAdapters.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 } }, - "src/lib/quota/quotaCombos.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/quota/quotaKey.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/quota/redisQuotaStore.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/lib/quota/sqliteQuotaStore.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/lib/semanticCache.ts": { @@ -2617,11 +1861,6 @@ "count": 1 } }, - "src/lib/services/quotaAutoPing.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/skills/a2a.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 @@ -2662,11 +1901,6 @@ "count": 1 } }, - "src/lib/sync/bundle.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/tailscaleTunnel.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2677,16 +1911,6 @@ "count": 1 } }, - "src/lib/tokenHealthCheck.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/lib/tokenHealthCheckCopilot.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/usage/apiKeySelfService.ts": { "no-restricted-syntax": { "count": 1 @@ -2702,11 +1926,6 @@ "count": 1 } }, - "src/lib/usage/codexResetCredits.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/usage/costCalculator.ts": { "no-restricted-syntax": { "count": 1 @@ -2750,11 +1969,6 @@ "count": 1 } }, - "src/lib/ws/handshake.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/lib/zed-oauth/keychain-reader.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2775,11 +1989,6 @@ "count": 3 } }, - "src/models/index.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/server/ws/liveServer.ts": { "@typescript-eslint/no-unused-vars": { "count": 5 @@ -2896,11 +2105,6 @@ "count": 1 } }, - "src/shared/services/apiKeyResolver.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/shared/services/backupService.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -2909,27 +2113,11 @@ "src/shared/services/cloudSyncScheduler.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/shared/services/initializeCloudSync.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/shared/services/modelSyncScheduler.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/shared/utils/apiAuth.ts": { - "no-restricted-imports": { - "count": 1 } }, "src/shared/utils/apiKey.ts": { @@ -2940,9 +2128,6 @@ "src/shared/utils/apiKeyPolicy.ts": { "@typescript-eslint/no-unused-vars": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 } }, "src/shared/utils/cloud.ts": { @@ -3040,11 +2225,6 @@ "count": 11 } }, - "src/sse/handlers/autoRouting.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "src/sse/handlers/chat.ts": { "@typescript-eslint/no-unused-vars": { "count": 9 @@ -3053,24 +2233,6 @@ "src/sse/handlers/chatHelpers.ts": { "@typescript-eslint/no-unused-vars": { "count": 3 - }, - "no-restricted-imports": { - "count": 1 - } - }, - "src/sse/services/model.ts": { - "no-restricted-imports": { - "count": 2 - } - }, - "src/sse/services/noAuthProviderSettings.ts": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/sse/services/tokenRefresh.ts": { - "no-restricted-imports": { - "count": 1 } }, "tests/benchmarks/pipeline-accuracy.test.ts": { @@ -3264,11 +2426,6 @@ "count": 2 } }, - "tests/integration/files-api.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "tests/integration/fingerprint-expansion.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -3416,11 +2573,6 @@ "count": 1 } }, - "tests/integration/traffic-inspector-hosts.test.ts": { - "@typescript-eslint/no-unused-vars": { - "count": 1 - } - }, "tests/integration/upstream-cli-smoke.int.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -3698,11 +2850,6 @@ "count": 1 } }, - "tests/unit/batch-deletion.test.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "tests/unit/batch-page-static.test.ts": { "@typescript-eslint/no-unused-vars": { "count": 1 @@ -4929,9 +4076,6 @@ }, "@typescript-eslint/no-unused-vars": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "tests/unit/fix-tool-adjacency.test.ts": { diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 6b0f6876c2..50678ddc89 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -514,7 +514,7 @@ For the full stealth playbook and operational guidance, see Primary state DB (SQLite): - Core infra: `src/lib/db/core.ts` (better-sqlite3, migrations, WAL) -- Re-export facade: `src/lib/localDb.ts` (thin compatibility layer for callers) +- DB access: import specific `src/lib/db/*` modules directly (the old `localDb.ts` barrel was removed) - file: `${DATA_DIR}/storage.sqlite` (or `$XDG_CONFIG_HOME/omniroute/storage.sqlite` when set, else `~/.omniroute/storage.sqlite`) - entities (tables + KV namespaces): providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt** @@ -888,7 +888,7 @@ flowchart LR ### Persistence - `src/lib/db/*`: persistent config/state and domain persistence on SQLite -- `src/lib/localDb.ts`: compatibility re-export for DB modules +- `src/lib/db/*`: import specific modules directly — no barrel (the old `localDb.ts` re-export layer was removed) - `src/lib/usageDb.ts`: usage history/call logs facade on top of SQLite tables ## Provider Executor Coverage (Strategy Pattern) diff --git a/docs/architecture/CODEBASE_DOCUMENTATION.md b/docs/architecture/CODEBASE_DOCUMENTATION.md index 5e0118bfb2..b0fc980777 100644 --- a/docs/architecture/CODEBASE_DOCUMENTATION.md +++ b/docs/architecture/CODEBASE_DOCUMENTATION.md @@ -313,7 +313,7 @@ table groups the actual directories and notable top-level files. Top-level files in `src/lib/`: -- `localDb.ts` — re-export layer only. **Never** add logic here. +- The old `localDb.ts` barrel was removed — consumers import specific `src/lib/db/*` modules directly. - `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts` - `oneproxyRotator.ts`, `oneproxySync.ts` - `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts` @@ -759,7 +759,7 @@ See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in 2. Export CRUD functions for your domain. 3. If new tables: add a migration under `src/lib/db/migrations/`, numbered sequentially, idempotent, transactional. -4. Re-export from `src/lib/localDb.ts` (re-export only — **no logic**). +4. Importers use direct imports from `@/lib/db/yourModule` (no barrel — the old `localDb.ts` re-export layer was removed). 5. Add tests under `tests/unit/`. ### Add a new MCP tool @@ -790,7 +790,7 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l - **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over inference for cross-module boundaries. - **Database**: never write raw SQL in routes or handlers — always go through - `src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`. + `src/lib/db/` modules. Never barrel-import — use specific `src/lib/db/*` modules directly. - **DB-entity typing (#3512)**: a function that writes or reads a DB table's row shape should take/return a named TS interface mirroring that table's columns 1:1, not `any` or an inline anonymous type at the call site. Land @@ -824,7 +824,7 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l ## 12. Hard Rules (from CLAUDE.md) 1. Never commit secrets or credentials. -2. Never add logic to `src/lib/localDb.ts`. +2. Never barrel-import — use specific `src/lib/db/*` modules directly. 3. Never use `eval()` / `new Function()` / implied eval. 4. Never commit directly to `main`. 5. Never write raw SQL in routes — always go through `src/lib/db/` modules. diff --git a/docs/ops/CONTRIBUTION_GOLDEN_PATH.md b/docs/ops/CONTRIBUTION_GOLDEN_PATH.md index 8a404963d7..fa171a4890 100644 --- a/docs/ops/CONTRIBUTION_GOLDEN_PATH.md +++ b/docs/ops/CONTRIBUTION_GOLDEN_PATH.md @@ -158,7 +158,7 @@ status. CI performs the broader package artifact and ecosystem checks. **Contracts** -- Domain modules under `src/lib/db/`; `src/lib/localDb.ts` remains a re-export layer only. +- Domain modules under `src/lib/db/`; import specific modules directly (the old `localDb.ts` re-export layer was removed). - Numbered, idempotent SQL migrations under `src/lib/db/migrations/`, transaction safety, upgrade behavior, indexes, and every caller affected by the schema. - Routes and handlers never issue raw SQL directly. diff --git a/next.config.mjs b/next.config.mjs index a9e0087f92..2049739ab5 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -329,11 +329,12 @@ const nextConfig = { // TODO: Re-enable after fixing all sub-component useTranslations scope issues ignoreBuildErrors: true, }, - webpack(config, { webpack }) { + webpack(config, { dev, webpack }) { config.ignoreWarnings = [ ...(config.ignoreWarnings || []), isNextIntlExtractorDynamicImportWarning, ]; + const nextDefaultSplitChunks = config.optimization?.splitChunks; config.optimization = config.optimization || {}; config.optimization.splitChunks = { ...config.optimization.splitChunks, @@ -392,6 +393,9 @@ const nextConfig = { }, }, }; + // Next's development defaults are tuned for incremental route compilation. + // Retain the custom vendor topology for production without imposing it on dev. + if (dev) config.optimization.splitChunks = nextDefaultSplitChunks; if (isMinimalBuild) { // Mirror the turbopack.resolveAlias entries for webpack-built artifacts. diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index b82bf41cb8..d2e1785c34 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -1427,7 +1427,7 @@ export async function handleChatCore({ }; if ((isCombo && comboName) || routingComboId) { try { - const { getComboByName } = await import("../../src/lib/localDb"); + const { getComboByName } = await import("@/lib/db/combos"); let comboConfig = await getComboByName(comboName); if (!comboConfig && comboName?.startsWith("combo/")) { comboConfig = await getComboByName(comboName.substring(6)); @@ -1918,7 +1918,7 @@ export async function handleChatCore({ if (isCombo && comboName) { log?.info?.("CONTEXT", `Attempting to resolve combo limits for comboName=${comboName}`); try { - const { getComboByName } = await import("../../src/lib/localDb"); + const { getComboByName } = await import("@/lib/db/combos"); const { resolveComboTargets } = await import("../services/combo.ts"); let comboConfig = await getComboByName(comboName); if (!comboConfig && comboName.startsWith("combo/")) { @@ -4292,13 +4292,14 @@ export async function handleChatCore({ let quotaCooldownMs = kimiRateLimitResetAt ? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0) : retryAfterMs || COOLDOWN_MS.rateLimit; - const deferAntigravityQuotaStateToCaller = - shouldDeferAntigravityQuotaStateToCaller( - provider, - typeof onStreamFailure === "function" - ); - const isAntigravityQuotaFamily = - shouldDeferAntigravityQuotaStateToCaller(provider, true); + const deferAntigravityQuotaStateToCaller = shouldDeferAntigravityQuotaStateToCaller( + provider, + typeof onStreamFailure === "function" + ); + const isAntigravityQuotaFamily = shouldDeferAntigravityQuotaStateToCaller( + provider, + true + ); let coreOwnedAntigravityLockout: { cooldownMs: number; failureCount: number; diff --git a/open-sse/handlers/chatCore/comboContextCache.ts b/open-sse/handlers/chatCore/comboContextCache.ts index 1a4f2c5b2c..266591b976 100644 --- a/open-sse/handlers/chatCore/comboContextCache.ts +++ b/open-sse/handlers/chatCore/comboContextCache.ts @@ -1,5 +1,4 @@ -import { getUpstreamProxyConfig } from "@/lib/localDb"; -import type { FallbackBackend } from "@/lib/db/upstreamProxy"; +import { FallbackBackend, getUpstreamProxyConfig } from "@/lib/db/upstreamProxy"; /** * Module-level cache for upstream proxy config (shared across all requests). @@ -29,7 +28,8 @@ const COMBOS_CACHE_TTL = 10_000; export async function getCombosCached(): Promise { const now = Date.now(); - const { getCombos, getCombosCacheVersion } = await import("@/lib/localDb"); + const { getCombos } = await import("@/lib/db/combos"); + const { getCombosCacheVersion } = await import("@/lib/db/readCache"); const version = getCombosCacheVersion(); // A combo write (create/update/delete/reorder) bumps the shared version via // invalidateDbCache("combos"); when it no longer matches our snapshot we drop diff --git a/open-sse/handlers/responseTranslator.ts b/open-sse/handlers/responseTranslator.ts index 931b944403..5989874527 100644 --- a/open-sse/handlers/responseTranslator.ts +++ b/open-sse/handlers/responseTranslator.ts @@ -265,7 +265,15 @@ export function translateNonStreamingResponse( if (toolCalls.length > 0) { message.tool_calls = toolCalls; } - if (message.content === undefined) { + if ( + (!message.content || + (typeof message.content === "string" && message.content.trim().length === 0)) && + toolCalls.length === 0 && + replayableReasoningContent && + replayableReasoningContent.trim().length > 0 + ) { + message.content = replayableReasoningContent; + } else if (message.content === undefined) { message.content = ""; } diff --git a/open-sse/mcp-server/server.ts b/open-sse/mcp-server/server.ts index 89f7b6385d..73e3a387dc 100644 --- a/open-sse/mcp-server/server.ts +++ b/open-sse/mcp-server/server.ts @@ -90,7 +90,7 @@ import { clampMcpAccessibilityConfig, type McpAccessibilityConfig, } from "../services/compression/engines/mcpAccessibility/constants.ts"; -import { getDbInstance } from "../../src/lib/db/core.ts"; +import { getDbInstance, ensureDbInitialized } from "../../src/lib/db/core.ts"; import { normalizeQuotaResponse } from "../../src/shared/contracts/quota.ts"; import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts"; import { sanitizeErrorMessage } from "../utils/error.ts"; @@ -1527,10 +1527,10 @@ export function createMcpServer(options?: CreateMcpServerOptions): McpServer { * Called when `omniroute --mcp` is used. */ export async function startMcpStdio(): Promise { + await ensureDbInitialized(); // Stdout is reserved for JSON-RPC — bin/mcpStdioConsoleGuard.mjs is preloaded via // `node --import` (see bin/mcp-server.mjs) so console.log/warn already redirect to - // stderr before this module's own imports evaluate (DB init happens as a side effect of - // createMcpServer()'s tool registration, earlier than any code placed here could catch). + // stderr before this module's own imports evaluate. const server = createMcpServer(); const transport = new StdioServerTransport(); const version = process.env.npm_package_version || "1.8.1"; diff --git a/open-sse/services/__tests__/volumeDetector.test.ts b/open-sse/services/__tests__/volumeDetector.test.ts index 7b71289478..37e278a82c 100644 --- a/open-sse/services/__tests__/volumeDetector.test.ts +++ b/open-sse/services/__tests__/volumeDetector.test.ts @@ -3,7 +3,7 @@ import { describe, it, expect, vi } from "vitest"; // Mock the DB so recommendStrategyOverride sees adaptiveVolumeRouting = true. // Without this the real getSettings() throws (no SQLite in test env), the // catch block fires, and the function returns noOverride before any rule runs. -vi.mock("@/lib/localDb", () => ({ +vi.mock("@/lib/db/settings", () => ({ getSettings: vi.fn().mockResolvedValue({ adaptiveVolumeRouting: true }), })); diff --git a/open-sse/services/batchProcessor.ts b/open-sse/services/batchProcessor.ts index e9fe915afd..7b242e4851 100644 --- a/open-sse/services/batchProcessor.ts +++ b/open-sse/services/batchProcessor.ts @@ -1,22 +1,20 @@ import { v4 as uuidv4 } from "uuid"; -import type { BatchItemCheckpoint, BatchRecord } from "@/lib/localDb"; import { + type BatchItemCheckpoint, + type BatchRecord, countBatchItemCheckpoints, - createFile, - deleteFile, ensureBatchItemCheckpoints, - getApiKeyById, getBatch, - getFileContent, getPendingBatches, getTerminalBatches, listBatchItemCheckpoints, - listFiles, markBatchItemError, markBatchItemProcessing, markBatchItemResult, updateBatch, -} from "@/lib/localDb"; +} from "@/lib/db/batches"; +import { createFile, deleteFile, getFileContent, listFiles } from "@/lib/db/files"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { dispatch } from "@/lib/batches/dispatch"; import type { SupportedBatchEndpoint } from "@/shared/constants/batchEndpoints"; import { DEFAULT_BATCH_EXPIRATION_SECONDS } from "@/shared/constants/batch"; diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index efdbd7c152..e184220e16 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -370,7 +370,7 @@ export function clearStaleLKGP( ): void { void (async () => { try { - const { clearLKGP } = await import("@/lib/localDb"); + const { clearLKGP } = await import("@/lib/db/settings"); const promises: Promise[] = [clearLKGP(comboName, comboId || comboName)]; if (executionKey) { promises.push(clearLKGP(comboName, executionKey)); @@ -427,7 +427,7 @@ export async function buildAutoCandidates( // apply, so auto-routing behavior is unchanged. const quotaCutoffEnabled = (resilienceSettings ?? resolveResilienceSettings(null))?.quotaPreflight?.enabled === true; - const { getPricingForModel } = await import("../../src/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); const quotaPromises = new Map>(); let historicalLatencyStats: Record = {}; try { @@ -1640,7 +1640,8 @@ async function handleComboChatInner({ lastModel, modelStr, `Model routing: ${lastModel} → ${modelStr}`, - existingHandoff + existingHandoff, + universalHandoffConfig.relayMode ); } } @@ -1969,7 +1970,7 @@ async function handleComboChatInner({ const connId = effectiveConnectionId || undefined; void (async () => { try { - const { setLKGP } = await import("../../src/lib/localDb"); + const { setLKGP } = await import("@/lib/db/settings"); await Promise.all([ setLKGP(combo.name, target.executionKey, provider, connId), setLKGP(combo.name, combo.id || combo.name, provider, connId), @@ -3566,7 +3567,7 @@ async function handleRoundRobinCombo({ const connId = effectiveConnectionId || undefined; void (async () => { try { - const { setLKGP } = await import("../../src/lib/localDb"); + const { setLKGP } = await import("@/lib/db/settings"); await Promise.all([ setLKGP(combo.name, target.executionKey, provider, connId), setLKGP(combo.name, combo.id || combo.name, provider, connId), diff --git a/open-sse/services/combo/applyStrategyOrdering.ts b/open-sse/services/combo/applyStrategyOrdering.ts index e077082c5e..43fb1dd964 100644 --- a/open-sse/services/combo/applyStrategyOrdering.ts +++ b/open-sse/services/combo/applyStrategyOrdering.ts @@ -67,7 +67,7 @@ export async function applyStrategyOrdering( if (strategy === "lkgp") { try { - const { getLKGP } = await import("../../../src/lib/localDb"); + const { getLKGP } = await import("@/lib/db/settings"); const lkgpProvider = await getLKGP(combo.name, combo.id || combo.name); if (lkgpProvider) { diff --git a/open-sse/services/combo/concurrencyCaps.ts b/open-sse/services/combo/concurrencyCaps.ts index 3384352abb..f37cb3de30 100644 --- a/open-sse/services/combo/concurrencyCaps.ts +++ b/open-sse/services/combo/concurrencyCaps.ts @@ -19,7 +19,7 @@ * shrinking (Quality Gate / #3501). */ -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { effectiveMaxConcurrency } from "./comboPredicates.ts"; import type { ResolvedComboTarget } from "./types.ts"; diff --git a/open-sse/services/combo/quotaExhaustionCutoff.ts b/open-sse/services/combo/quotaExhaustionCutoff.ts index 016e78ab63..2dc78dbe1b 100644 --- a/open-sse/services/combo/quotaExhaustionCutoff.ts +++ b/open-sse/services/combo/quotaExhaustionCutoff.ts @@ -19,7 +19,7 @@ import { type PreflightQuotaThresholds, type QuotaInfo, } from "../quotaPreflight.ts"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { resolveResilienceSettings, type ResilienceSettings, @@ -109,8 +109,7 @@ export async function resolveQuotaExhaustionCutoffForTarget( let connection: Record | undefined; try { connection = (await getCachedProviderConnectionById(connectionId)) as - | Record - | undefined; + Record | undefined; } catch { connection = undefined; } diff --git a/open-sse/services/combo/resolveAutoStrategy.ts b/open-sse/services/combo/resolveAutoStrategy.ts index 45ef4b5c60..84ee43c43b 100644 --- a/open-sse/services/combo/resolveAutoStrategy.ts +++ b/open-sse/services/combo/resolveAutoStrategy.ts @@ -245,7 +245,7 @@ export async function resolveAutoStrategyOrder( let lastKnownGoodProvider: string | undefined; try { - const { getLKGP } = await import("../../../src/lib/localDb"); + const { getLKGP } = await import("@/lib/db/settings"); const lkgp = await getLKGP(combo.name, combo.id || combo.name); if (lkgp) lastKnownGoodProvider = lkgp.provider; } catch (err) { diff --git a/open-sse/services/combo/targetSorters.ts b/open-sse/services/combo/targetSorters.ts index 7ddd3e4509..384b776bf3 100644 --- a/open-sse/services/combo/targetSorters.ts +++ b/open-sse/services/combo/targetSorters.ts @@ -65,7 +65,7 @@ export function orderTargetsForWeightedFallback { try { - const { getPricingForModel } = await import("../../../src/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); const withCost = await Promise.all( models.map(async (modelStr) => { const parsed = parseModel(modelStr); diff --git a/open-sse/services/contextHandoff.ts b/open-sse/services/contextHandoff.ts index 72ec1effa4..9ac2ffdd14 100644 --- a/open-sse/services/contextHandoff.ts +++ b/open-sse/services/contextHandoff.ts @@ -48,6 +48,7 @@ export interface ContextRelayConfig { handoffThreshold?: number; handoffProviders?: string[]; maxMessagesForSummary?: number; + relayMode?: "schema-locked" | "standard"; } export interface UniversalHandoffConfig { @@ -65,6 +66,7 @@ export interface UniversalHandoffConfig { ttlMinutes: number; /** Preserve existing system prompt when injecting handoff */ preserveSystemPrompt: boolean; + relayMode?: "schema-locked" | "standard"; } export const DEFAULT_UNIVERSAL_HANDOFF_CONFIG: UniversalHandoffConfig = { @@ -155,6 +157,8 @@ export function resolveUniversalHandoffConfig( "preserveSystemPrompt", DEFAULT_UNIVERSAL_HANDOFF_CONFIG.preserveSystemPrompt ), + relayMode: + getString("relayMode", "standard") === "schema-locked" ? "schema-locked" : "standard", }; } export interface ParsedHandoffContent { @@ -192,6 +196,7 @@ export function resolveContextRelayConfig( Number.isFinite(rawMaxMessages) && rawMaxMessages >= 5 && rawMaxMessages <= 100 ? Math.round(rawMaxMessages) : DEFAULT_MAX_MESSAGES_FOR_SUMMARY, + relayMode: config?.relayMode === "schema-locked" ? "schema-locked" : "standard", }; } @@ -232,7 +237,8 @@ function formatMessagesForPrompt(messages: MessageLike[]): string { export function selectMessagesForSummary( messages: MessageLike[], - maxMessages: number + maxMessages: number, + relayMode?: "schema-locked" | "standard" ): MessageLike[] { const validMessages = messages.filter((m) => m && typeof m === "object"); const system = validMessages.filter( @@ -242,15 +248,23 @@ export function selectMessagesForSummary( (m) => typeof m.role !== "string" || (m.role !== "system" && m.role !== "developer") ); - const recentMessages = [...system, ...nonSystem.slice(-maxMessages)]; + const recentMessages = + relayMode === "schema-locked" + ? [...nonSystem.slice(-maxMessages)] + : [...system, ...nonSystem.slice(-maxMessages)]; let working = [...recentMessages]; - while (working.length > system.length + 1) { + const minWorkingLength = relayMode === "schema-locked" ? 1 : system.length + 1; + + while (working.length > minWorkingLength) { const history = formatMessagesForPrompt(working); if (estimateTokens(history) <= MAX_HISTORY_TOKENS_FOR_SUMMARY) { return working; } - working = [...system, ...working.slice(system.length + 1)]; + working = + relayMode === "schema-locked" + ? working.slice(1) + : [...system, ...working.slice(system.length + 1)]; } const fallbackHistory = formatMessagesForPrompt(working); @@ -258,7 +272,7 @@ export function selectMessagesForSummary( // If there are system messages, return them so the caller can still produce context. // If there are no system messages (system=[]), fall back to the single most-recent // non-system message rather than returning [] which would silently drop the handoff. - if (system.length > 0) { + if (relayMode !== "schema-locked" && system.length > 0) { return system; } const lastNonSystem = nonSystem[nonSystem.length - 1]; @@ -386,7 +400,8 @@ async function generateHandoffAsync(options: { const summaryModel = relayConfig.handoffModel || options.model; const selectedMessages = selectMessagesForSummary( Array.isArray(options.messages) ? options.messages : [], - relayConfig.maxMessagesForSummary + relayConfig.maxMessagesForSummary, + relayConfig.relayMode ); const historyText = formatMessagesForPrompt(selectedMessages); if (!historyText) return; @@ -499,7 +514,8 @@ The context above contains a concise summary of the prior work. Continue seamles export function injectHandoffIntoBody( body: Record, - payload: HandoffPayload + payload: HandoffPayload, + _relayMode?: "schema-locked" | "standard" ): Record { const handoffContent = buildHandoffSystemMessage(payload); const isResponsesRequest = @@ -680,12 +696,14 @@ async function generateUniversalHandoffAsync(options: { handoffModel: string; ttlMs: number; maxMessages: number; + relayMode?: "schema-locked" | "standard"; providerAllowlist: string[]; handleSingleModel: (body: Record, modelStr: string) => Promise; }): Promise { const selectedMessages = selectMessagesForSummary( Array.isArray(options.messages) ? options.messages : [], - options.maxMessages + options.maxMessages, + options.relayMode ); const historyText = formatMessagesForPrompt(selectedMessages); if (!historyText) return "unavailable"; @@ -789,6 +807,7 @@ export function maybeGenerateUniversalHandoff(options: { handoffModel: options.universalConfig.handoffModel || options.currModel, ttlMs, maxMessages: options.universalConfig.maxMessagesForSummary, + relayMode: options.universalConfig.relayMode, providerAllowlist: options.universalConfig.providerAllowlist, handleSingleModel: options.handleSingleModel, }) @@ -812,7 +831,8 @@ export function injectUniversalHandoffBody( prevModel: string, currModel: string, reason: string, - existingPayload?: HandoffPayload | null + existingPayload?: HandoffPayload | null, + _relayMode?: "schema-locked" | "standard" ): Record { const handoffContent = buildUniversalHandoffSystemMessage( prevModel, diff --git a/open-sse/services/keyGroupAuth.ts b/open-sse/services/keyGroupAuth.ts index e6c34559a2..831af4d866 100644 --- a/open-sse/services/keyGroupAuth.ts +++ b/open-sse/services/keyGroupAuth.ts @@ -12,7 +12,7 @@ * - Provider-specific rules: "openai/gpt-4" */ -import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/localDb"; +import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/db/apiKeyGroups"; export interface KeyGroupAuthResult { /** Whether the request is authorized */ diff --git a/open-sse/services/model.ts b/open-sse/services/model.ts index 2c28b60953..ec0080ef83 100644 --- a/open-sse/services/model.ts +++ b/open-sse/services/model.ts @@ -332,7 +332,7 @@ function getProviderIdFromConnection(connection: unknown) { async function getActiveProviderSet() { try { - const { getCachedProviderConnections } = await import("@/lib/localDb"); + const { getCachedProviderConnections } = await import("@/lib/db/readCache"); const conns = (await getCachedProviderConnections()) as unknown[]; const providers = conns .map(getProviderIdFromConnection) @@ -345,7 +345,7 @@ async function getActiveProviderSet() { async function getActiveSyncedProvidersForModel(modelId: string) { try { - const { getActiveProvidersWithSyncedModel } = await import("@/lib/localDb"); + const { getActiveProvidersWithSyncedModel } = await import("@/lib/db/models"); const providers = await getActiveProvidersWithSyncedModel(modelId); return providers .map(resolveProviderAlias) @@ -403,7 +403,7 @@ function isTruthyEnv(value: string | undefined) { async function getPreferClaudeCodeForUnprefixedClaudeModels() { try { - const { getCachedSettings } = await import("@/lib/localDb"); + const { getCachedSettings } = await import("@/lib/db/readCache"); const settings = (await getCachedSettings()) as Record; if (typeof settings.preferClaudeCodeForUnprefixedClaudeModels === "boolean") { return settings.preferClaudeCodeForUnprefixedClaudeModels; diff --git a/open-sse/services/payloadRules.ts b/open-sse/services/payloadRules.ts index ad9a6415ad..651ae95f24 100644 --- a/open-sse/services/payloadRules.ts +++ b/open-sse/services/payloadRules.ts @@ -223,7 +223,7 @@ export function clearPayloadRulesConfigOverride() { // silently reverting to the (usually empty) file config. async function loadPayloadRulesFromSettings(): Promise { try { - const { getCachedSettings } = await import("@/lib/localDb"); + const { getCachedSettings } = await import("@/lib/db/readCache"); const settings = (await getCachedSettings()) as { payloadRules?: unknown }; const raw = settings?.payloadRules; if (raw === null || raw === undefined) return null; diff --git a/open-sse/services/rateLimitManager.ts b/open-sse/services/rateLimitManager.ts index bd793ae1ca..a8d99d0480 100644 --- a/open-sse/services/rateLimitManager.ts +++ b/open-sse/services/rateLimitManager.ts @@ -338,7 +338,8 @@ export async function initializeRateLimits() { applyBottleneckHeartbeatPatch(); try { - const { getCachedProviderConnections, getSettings } = await import("@/lib/localDb"); + const { getCachedProviderConnections } = await import("@/lib/db/readCache"); + const { getSettings } = await import("@/lib/db/settings"); const [connections, settings] = await Promise.all([ getCachedProviderConnections(), getSettings(), @@ -385,7 +386,7 @@ export async function applyRequestQueueSettings(nextSettings: RequestQueueSettin currentRequestQueueSettings = { ...nextSettings }; // Global policy changes invalidate snapshots from the previous generation. preservedReplacementSettings.clear(); - const { getCachedProviderConnections } = await import("@/lib/localDb"); + const { getCachedProviderConnections } = await import("@/lib/db/readCache"); const connections = await getCachedProviderConnections(); // Also discard any snapshot created while the asynchronous DB read yielded. preservedReplacementSettings.clear(); diff --git a/open-sse/services/tokenLimitCounter.ts b/open-sse/services/tokenLimitCounter.ts index cd69a9385b..707658238c 100644 --- a/open-sse/services/tokenLimitCounter.ts +++ b/open-sse/services/tokenLimitCounter.ts @@ -20,7 +20,7 @@ import { getTokenLimitsForRequest, logTokenLimitReset, type TokenLimit, -} from "@/lib/localDb"; +} from "@/lib/db/tokenLimits"; interface CacheEntry { windowStart: string; @@ -295,8 +295,7 @@ export function recordTokenUsage( ORDER BY window_start DESC LIMIT 1` ) .get(limit.id, windowStart) as - | { window_start?: string; tokens_used?: number } - | undefined; + { window_start?: string; tokens_used?: number } | undefined; const prevTokens = priorRow && typeof priorRow.tokens_used === "number" ? priorRow.tokens_used : 0; if (prevTokens > 0) { diff --git a/open-sse/services/volumeDetector.ts b/open-sse/services/volumeDetector.ts index 56a1e78450..9bb16d1335 100644 --- a/open-sse/services/volumeDetector.ts +++ b/open-sse/services/volumeDetector.ts @@ -155,7 +155,7 @@ export async function recommendStrategyOverride( // Check if adaptive routing is enabled globally try { - const { getSettings } = await import("@/lib/localDb"); + const { getSettings } = await import("@/lib/db/settings"); const settings = await getSettings(); if (!settings.adaptiveVolumeRouting) { return noOverride; diff --git a/open-sse/utils/networkProxy.ts b/open-sse/utils/networkProxy.ts index 82a95c5e0a..1499a918e6 100644 --- a/open-sse/utils/networkProxy.ts +++ b/open-sse/utils/networkProxy.ts @@ -20,7 +20,7 @@ async function getConfig() { if (_cachedConfig && now < _cacheExpiry) return _cachedConfig; try { - const { getProxyConfig } = await import("../../src/lib/localDb"); + const { getProxyConfig } = await import("@/lib/db/settings"); _cachedConfig = await getProxyConfig(); _cacheExpiry = now + 30_000; // Cache for 30s return _cachedConfig; diff --git a/open-sse/utils/proxyFallback.ts b/open-sse/utils/proxyFallback.ts index 6d7590034c..5590dbd16a 100644 --- a/open-sse/utils/proxyFallback.ts +++ b/open-sse/utils/proxyFallback.ts @@ -10,7 +10,8 @@ import { fetch as undiciFetch } from "undici"; import { createProxyDispatcher, normalizeProxyUrl } from "./proxyDispatcher.ts"; -import { resolveProxyForScopeFromRegistry, listProxies, listOneproxyProxies } from "@/lib/localDb"; +import { resolveProxyForScopeFromRegistry, listProxies } from "@/lib/db/proxies"; +import { listOneproxyProxies } from "@/lib/db/oneproxy"; import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags"; // --------------------------------------------------------------------------- diff --git a/scripts/build/prepublish.ts b/scripts/build/prepublish.ts index 1625318cc4..2ea8e6cb89 100644 --- a/scripts/build/prepublish.ts +++ b/scripts/build/prepublish.ts @@ -328,10 +328,10 @@ const chatGptWebCodexMcpDestFile = join( if (existsSync(chatGptWebCodexMcpSrcFile)) { console.log(" 🔨 Bundling ChatGPT Web (Codex) MCP bridge..."); mkdirSync(dirname(chatGptWebCodexMcpDestFile), { recursive: true }); - execFileSync( - NPX_BIN, + runBuildTool( + "esbuild", + "esbuild", [ - "esbuild", "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.ts", "--bundle", "--platform=node", diff --git a/scripts/check/check-db-rules.mjs b/scripts/check/check-db-rules.mjs index 6697e81687..1a92176c3e 100644 --- a/scripts/check/check-db-rules.mjs +++ b/scripts/check/check-db-rules.mjs @@ -1,89 +1,24 @@ #!/usr/bin/env node // scripts/check/check-db-rules.mjs -// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Três verificações: -// (a) Todo módulo de domínio em src/lib/db/*.ts deve ser re-exportado por -// src/lib/localDb.ts (camada de compat). Um módulo db NOVO que não é -// re-exportado (e não está congelado) falha — força a decisão consciente -// de expor ou justificar (Hard Rule #2). -// (b) src/lib/localDb.ts é APENAS camada de re-export: nada de lógica -// (function/class/arrow de negócio). Mata o anti-padrão de "só uma -// funçãozinha aqui" que vira regra de negócio fora dos módulos db/. +// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Uma verificação: // (c) Nenhum SQL cru em src/app/api/**/route.ts ou open-sse/handlers/*.ts. // SQL deve viver em src/lib/db/ (Hard Rule #5). Ofensores pré-existentes // são congelados; QUALQUER novo SQL cru em rota/handler falha. -// Stale-enforcement (6A.3): entradas em INTENTIONALLY_INTERNAL / EXTERNAL_DB_ALLOWED -// que não suprimem nenhuma violação real → gate falha com instrução de remoção. +// As antigas verificações (a) re-export completo via src/lib/localDb.ts e +// (b) localDb.ts sem lógica foram REMOVIDAS: o barrel src/lib/localDb.ts foi +// deletado (#11795) — consumidores importam módulos src/lib/db/* diretamente +// (regra "never barrel-import", aplicada por eslint no-restricted-imports). +// Stale-enforcement (6A.3): entradas em EXTERNAL_DB_ALLOWED que não suprimem +// nenhuma violação real → gate falha com instrução de remoção. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { assertNoStale } from "./lib/allowlist.mjs"; const cwd = process.cwd(); -const DB_DIR = path.join(cwd, "src/lib/db"); -const LOCAL_DB = path.join(cwd, "src/lib/localDb.ts"); const API_DIR = path.join(cwd, "src/app/api"); const HANDLERS_DIR = path.join(cwd, "open-sse/handlers"); -// (a) Módulos db/ que NÃO são re-exportados por localDb.ts por DESIGN (Hard Rule #2: -// "Never barrel-import from localDb.ts — import specific db/ modules instead"). -// Cada entrada aqui foi auditada e é consumida via import direto de "@/lib/db/X" -// (estático ou dinâmico) pelos seus consumidores — exatamente o padrão correto. -// Re-exportar esses módulos via localDb.ts INCENTIVARIA o anti-padrão proibido. -// O gate ainda bloqueia QUALQUER módulo db/ NOVO que não seja re-exportado E não -// esteja nessa lista — mantendo a decisão consciente obrigatória (Hard Rule #2). -// Legenda de classificação: -// type-only = exporta apenas tipos (sem runtime API), não há o que re-exportar -// db-internal = importado apenas dentro de src/lib/db/ (coordenação interna) -// intentionally-internal = consumido por import direto fora de db/ (correto per Rule #2) -// DEAD? = zero importers encontrados na auditoria de 2026-06-11; não deletar -// sem investigação — pode ser reserva de schema ou F2 pendente -export const INTENTIONALLY_INTERNAL = new Set([ - "_rowTypes", // type-only: 5 importers internos em db/ (AgentBridge/Inspector row types) - "accessTokens", // intentionally-internal: 4 rotas /api/cli/* (connect, whoami, tokens, tokens/[id]) + server/authz/accessTokenAuth.ts via import direto "@/lib/db/accessTokens" (Rule #2) - "apiKeyColumnFallbacks", // db-internal: importado só por db/apiKeys.ts (API_KEY_COLUMN_FALLBACKS — fallbacks de coluna split do apiKeys.ts) - "apiKeyUsageLimitFields", // db-internal: importado só por db/apiKeys.ts (helpers de campo de limite de uso split do apiKeys.ts; mig 101) - "backupRetention", // db-internal: importado só por db/backup.ts e db/migrationRunner.ts (política de retenção compartilhada; mora fora de backup.ts porque core.ts importa migrationRunner.ts — importar backup.ts de lá fecharia um ciclo, #10421) - "caseMapping", // db-internal: importado só por db/core.ts (toSnakeCase/toCamelCase/objToSnake — column-mapping snake↔camel split do core.ts, #4947) - "cleanup", // intentionally-internal: 3 API routes (purge-quota-snapshots, purge-call-logs, purge-detailed-logs) - "cliToolState", // intentionally-internal: 14+ API routes em /api/cli-tools/*-settings - "comboForecast", // intentionally-internal: src/lib/usage/comboForecast.ts - "commandCodeAuth", // intentionally-internal: 5 API routes em /api/providers/command-code/auth/* - "compression", // intentionally-internal: 2 API routes (settings/compression, context/rtk/config) - "compressionDetailNormalizers", // db-internal: importado só por db/compression.ts (normalizeSessionDedupConfig/normalizeCcrConfig/buildDetailConfigDefaults/applyDetailConfigUpdate — normalizadores do detail-config split do compression.ts, #8404) - "connectionRuntimeState", // intentionally-internal: warmupScheduler sqlite/redis stores importam diretamente de @/lib/db/connectionRuntimeState (Rule #2) - "vacuumScheduler", // intentionally-internal: src/instrumentation-node.ts (dynamic import, lifecycle wiring per Rule #2) - "detailedLogs", // intentionally-internal: 3 callers (callLogs.ts, logs/detail route, embeddings handler) - "discovery", // DEAD?: 0 importers na auditoria de 2026-06-11; lib/discovery/index.ts não usa db/discovery - "domainState", // intentionally-internal: 5 callers (batchWriter, circuitBreaker, costRules, fallbackPolicy, lockoutPolicy) - "encryption", // intentionally-internal: 8+ callers (container, webhookDispatcher, cloudAgent/credentials, services/apiKey, 4+ routes, open-sse) - "healthCheck", // db-internal: importado por db/core.ts (runDbHealthCheck) - "jsonMigration", // intentionally-internal: src/app/api/settings/import-json/route.ts - "migrationRunner", // db-internal: importado por db/core.ts (runMigrations ao inicializar o DB) - "modelCapabilityOverrides", // intentionally-internal: src/app/api/model-capability-overrides/route.ts via import direto "@/lib/db/modelCapabilityOverrides" (#6727 — evita empurrar localDb.ts para o cap de 800 linhas) - "notion", // intentionally-internal: settings/notion API route + open-sse/mcp-server/tools/notionTools.ts - "obsidian", // intentionally-internal: src/lib/obsidianSync.ts + settings/obsidian route + MCP obsidianTools.ts - "optimizationSettings", // db-internal: imported by db/core.ts for SQLite PRAGMA application helpers that require the live adapter - "pluginMetrics", // DEAD? (production): write path não foi conectado ainda (documentado no cabeçalho do módulo); testado por tests/unit/plugins-metrics.test.ts - "prompts", // DEAD? (production): zero callers de produção encontrados; domínio domain/prompts.ts é independente; testado por tests/integration/proxy-pipeline.test.ts - "probeUtils", // db-internal: importado so por db/core.ts (retryProbeIfTransient no caminho da corruption-probe, #9541); testado por tests/unit/probe-9541-repro.test.ts - "providerNodeSelect", // db-internal: importado só por db/providers.ts (selectProviderNodeForConnection — lógica pura de seleção de provider node split do providers.ts, #4421) - "providerStats", // intentionally-internal: src/app/api/provider-stats/route.ts - "proxyLatency", // intentionally-internal: imported directly by src/lib/db/proxies.ts (anti-barrel, #6798) - "proxySubscriptions", // db-internal: importado só por db/proxies.ts (addProxiesToScopePool — split do proxies.ts para ficar sob o cap de tamanho congelado, #7299); a função já é re-exportada por proxies.ts (que localDb.ts re-exporta) - "recovery", // intentionally-internal: bin/cli/runtime.mjs (import() dinâmico) + tests - "schemaColumns", // db-internal: importado só por db/core.ts (ensureProviderConnections/UsageHistory/CallLogsColumns + hasColumn/hasTable/getTableColumns — schema-column reconciliation split do core.ts, #4948) - "secrets", // intentionally-internal: src/instrumentation-node.ts (import() dinâmico na inicialização) - "serviceModels", // intentionally-internal: 3 callers (services/modelSync, services/bootstrap, /api/services/9router/models) - "stateReset", // db-internal: 3 callers dentro de src/lib/db/ (core, backup, apiKeys) para coordenação de reset - "stats", // intentionally-internal: src/app/api/settings/database/refresh-stats/route.ts - "tierConfig", // intentionally-internal: open-sse/services/tierResolver.ts (require() dinâmico) - "webSessionDedup", // db-internal: importado só por db/providers.ts (webSessionCredentialKey/parseProviderSpecificData — helpers puros de dedup de credencial web-session split do providers.ts, #3368 PR6) -]); - -// Alias para retrocompatibilidade com os testes existentes que importam KNOWN_UNEXPORTED. -// O comportamento do gate é idêntico — só o nome e os comentários mudaram (#3499). -export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL; - // (c) Leituras de SQL contra bancos EXTERNOS, permitidas por design (#3500). // Esta rota NÃO consulta o DB do OmniRoute (getDbInstance) — ela abre o // SQLite de OUTRO aplicativo (Kiro) para auto-importar credenciais. @@ -97,16 +32,13 @@ export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL; // precisa de entrada aqui: o SQL contra o state.vscdb externo do Cursor vive // em src/lib/cursor/tokenExtractor.ts, fora do escopo desta checagem (que só // varre src/app/api/**/route.ts e open-sse/handlers/*.ts). -const EXTERNAL_DB_ALLOWED = new Set([ +export const EXTERNAL_DB_ALLOWED = new Set([ "src/app/api/oauth/kiro/auto-import/route.ts", // read-only no SQLite do Kiro (DB externo) ]); // Alias de retrocompatibilidade (testes/consumidores que importam KNOWN_RAW_SQL). // Comportamento do gate idêntico — só o nome e o enquadramento mudaram (#3500). -const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED; - -// Módulos sempre excluídos da checagem (a): não são domínio re-exportável. -const DB_MODULE_EXCLUDE = new Set(["core", "localDb", "index"]); +export const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED; function walk(dir, acc = []) { if (!fs.existsSync(dir)) return acc; @@ -118,59 +50,6 @@ function walk(dir, acc = []) { return acc; } -// Lista os módulos de domínio em src/lib/db (top-level *.ts), excluindo -// core/localDb/index, *.d.ts e qualquer subdiretório (migrations/, adapters/, __tests__/). -export function collectDbModules(dbDir = DB_DIR) { - if (!fs.existsSync(dbDir)) return []; - return fs - .readdirSync(dbDir, { withFileTypes: true }) - .filter((e) => e.isFile() && /\.ts$/.test(e.name) && !/\.d\.ts$/.test(e.name)) - .map((e) => e.name.replace(/\.ts$/, "")) - .filter((name) => !DB_MODULE_EXCLUDE.has(name)) - .sort(); -} - -// Extrai os nomes de módulo re-exportados de localDb.ts a partir de -// `... from "./db/X"` (cobre export {…}, export * e export type {…}). -export function extractReexportedModules(localDbSource) { - const re = /from\s+["']\.\/db\/([A-Za-z0-9_]+)["']/g; - const out = new Set(); - let m; - while ((m = re.exec(localDbSource))) out.add(m[1]); - return out; -} - -// (a) Módulos db/ que não são re-exportados e não estão na lista de -// intencionalmente-internos (INTENTIONALLY_INTERNAL). O gate falha para -// qualquer módulo NOVO que não seja re-exportado nem justificado. -export function findMissingReexports(dbModules, reexported, allowlist = INTENTIONALLY_INTERNAL) { - return dbModules.filter((mod) => !reexported.has(mod) && !allowlist.has(mod)); -} - -// (b) localDb.ts deve conter SOMENTE import/export + comentários (sem lógica). -// Remove comentários e strings, depois procura declarações de runtime. -export function hasLogic(localDbSource) { - const stripped = localDbSource - // comentários de bloco - .replace(/\/\*[\s\S]*?\*\//g, "") - // comentários de linha - .replace(/\/\/[^\n]*/g, "") - // template strings - .replace(/`(?:\\[\s\S]|[^\\`])*`/g, '""') - // strings simples/duplas (paths de import etc.) - .replace(/"(?:\\.|[^"\\])*"/g, '""') - .replace(/'(?:\\.|[^'\\])*'/g, '""'); - - // function/class declaradas, ou atribuição a função (const X = (…) =>, const X = function). - const logicPatterns = [ - /(^|[^.\w])function\s+[A-Za-z_$]/, // function decl (não method .foo()) - /(^|[^.\w])class\s+[A-Za-z_$]/, // class decl - /(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s*)?\(/, // const X = (…) ... (arrow/call) - /(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s+)?function\b/, // const X = function - ]; - return logicPatterns.some((rx) => rx.test(stripped)); -} - // SQL cru é sempre uma STRING passada a db.prepare()/exec(): casamos os padrões // SÓ dentro de literais de string (não em código JS — `import … from`, `.set(`, // `new Set(`, `delete x` etc. são falsos positivos se varrermos o código todo). @@ -198,7 +77,7 @@ export function extractStringLiterals(code) { // tira as aspas/crases delimitadoras out.push(m[0].slice(1, -1)); } - return out.join("\n\n"); // separador que nenhum padrão SQL atravessa + return out.join("\n\u0000\n"); // separador que nenhum padrão SQL atravessa } // (c) Arquivos com SQL cru dentro de literais de string (linhas não-comentário), @@ -217,7 +96,7 @@ export function findRawSql(files, allowlist = KNOWN_RAW_SQL) { // Match each literal independently. Joining literals before scanning would // turn harmless code such as `update(...)` plus a later `"set"` string into // a false UPDATE ... SET SQL match. - const literals = extractStringLiterals(stripComments(src)).split("\n\0\n"); + const literals = extractStringLiterals(stripComments(src)).split("\n\u0000\n"); if (literals.some((literal) => SQL_PATTERNS.some((rx) => rx.test(literal)))) { offenders.push(rel); } @@ -239,33 +118,6 @@ export function collectSqlScanFiles(apiDir = API_DIR, handlersDir = HANDLERS_DIR function main() { const failures = []; - const localDbSource = fs.readFileSync(LOCAL_DB, "utf8"); - - // (a) re-export completeness - const dbModules = collectDbModules(); - const reexported = extractReexportedModules(localDbSource); - - // Live unexported modules BEFORE allowlist filtering (needed for stale-enforcement). - const liveUnexported = dbModules.filter((mod) => !reexported.has(mod)); - assertNoStale(INTENTIONALLY_INTERNAL, liveUnexported, "check-db-rules:unexported"); - - const missing = findMissingReexports(dbModules, reexported); - if (missing.length) { - failures.push( - `[#2 re-export] ${missing.length} módulo(s) db/ não re-exportado(s) por src/lib/localDb.ts:\n` + - missing.map((m) => ` ✗ src/lib/db/${m}.ts`).join("\n") + - `\n → re-exporte de src/lib/localDb.ts (apenas a lista de re-export, nada de lógica)` + - ` ou adicione a INTENTIONALLY_INTERNAL com justificativa (import direto de "@/lib/db/${missing[0]}").` - ); - } - - // (b) localDb sem lógica - if (hasLogic(localDbSource)) { - failures.push( - `[#2 sem-lógica] src/lib/localDb.ts contém lógica (function/class/arrow). É camada de` + - ` re-export apenas — mova a lógica para um módulo src/lib/db/.` - ); - } // (c) SQL cru fora de db/ // Live raw-SQL offenders BEFORE allowlist filtering (needed for stale-enforcement). @@ -289,8 +141,8 @@ function main() { } if (!process.exitCode) { console.log( - `[check-db-rules] OK (${dbModules.length} módulos db/, ${reexported.size} re-exportados, ` + - `${INTENTIONALLY_INTERNAL.size} intencionalmente-internos (Rule #2); ${EXTERNAL_DB_ALLOWED.size} leituras de DB externo permitidas (#3500))` + `[check-db-rules] OK (${scanFiles.length} arquivos varridos; ` + + `${EXTERNAL_DB_ALLOWED.size} leituras de DB externo permitidas (#3500))` ); } } diff --git a/src/app/(dashboard)/home/page.tsx b/src/app/(dashboard)/home/page.tsx index dc9433973e..7580d67c16 100644 --- a/src/app/(dashboard)/home/page.tsx +++ b/src/app/(dashboard)/home/page.tsx @@ -1,5 +1,5 @@ import { getMachineId } from "@/shared/utils/machine"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import HomePageClient from "../dashboard/HomePageClient"; import BootstrapBanner from "../dashboard/BootstrapBanner"; import KimiSponsorBanner from "../dashboard/KimiSponsorBanner"; diff --git a/src/app/api/auth/oidc/callback/route.ts b/src/app/api/auth/oidc/callback/route.ts index fbd5a8df71..45e2836f94 100644 --- a/src/app/api/auth/oidc/callback/route.ts +++ b/src/app/api/auth/oidc/callback/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getCachedSettings, updateSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; +import { updateSettings } from "@/lib/db/settings"; import { SignJWT, jwtVerify, createRemoteJWKSet } from "jose"; import { cookies } from "next/headers"; import { timingSafeCompare } from "@/shared/utils/timingSafeCompare"; diff --git a/src/app/api/auth/oidc/login/route.ts b/src/app/api/auth/oidc/login/route.ts index a49dbbe431..fdb7387dcd 100644 --- a/src/app/api/auth/oidc/login/route.ts +++ b/src/app/api/auth/oidc/login/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; /** * GET /api/auth/oidc/login diff --git a/src/app/api/batches/[id]/route.ts b/src/app/api/batches/[id]/route.ts index 5d4a73f3d2..8ae3240df3 100644 --- a/src/app/api/batches/[id]/route.ts +++ b/src/app/api/batches/[id]/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; -import { getBatch } from "@/lib/localDb"; +import { getBatch } from "@/lib/db/batches"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request, { params }: { params: { id: string } }) { diff --git a/src/app/api/batches/route.ts b/src/app/api/batches/route.ts index 4c8c54d46e..afd0a28ebb 100644 --- a/src/app/api/batches/route.ts +++ b/src/app/api/batches/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; -import { listBatches } from "@/lib/localDb"; +import { listBatches } from "@/lib/db/batches"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request) { diff --git a/src/app/api/cache/route.ts b/src/app/api/cache/route.ts index 6413626fc2..6644651b68 100644 --- a/src/app/api/cache/route.ts +++ b/src/app/api/cache/route.ts @@ -9,7 +9,7 @@ import { } from "@/lib/semanticCache"; import { getIdempotencyStats } from "@/lib/idempotencyLayer"; import { getCacheMetrics, getCacheTrend } from "@/lib/db/settings"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; diff --git a/src/app/api/cli-tools/claude-settings/route.ts b/src/app/api/cli-tools/claude-settings/route.ts index 1a3d735f3f..b603049412 100644 --- a/src/app/api/cli-tools/claude-settings/route.ts +++ b/src/app/api/cli-tools/claude-settings/route.ts @@ -14,7 +14,7 @@ import { normalizeClaudeBaseUrl } from "@/shared/services/claudeCliConfig"; import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState"; import { cliSettingsEnvSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -import { getApiKeyById } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { readJsoncConfig } from "../_lib/jsoncConfig"; // Get claude settings path based on OS diff --git a/src/app/api/cli-tools/codex-settings/route.ts b/src/app/api/cli-tools/codex-settings/route.ts index 0212880f7d..40a094c840 100644 --- a/src/app/api/cli-tools/codex-settings/route.ts +++ b/src/app/api/cli-tools/codex-settings/route.ts @@ -13,7 +13,7 @@ import { createMultiBackup } from "@/shared/services/backupService"; import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState"; import { cliModelConfigSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -import { getApiKeyById } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { normalizeCodexBaseUrl } from "@/shared/utils/codexBaseUrl"; import { migrateCodexFeatureFlags } from "@/shared/utils/codexConfig"; diff --git a/src/app/api/cli-tools/keys/route.ts b/src/app/api/cli-tools/keys/route.ts index 36ccd7c40f..c26556f609 100644 --- a/src/app/api/cli-tools/keys/route.ts +++ b/src/app/api/cli-tools/keys/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth"; -import { getApiKeys } from "@/lib/localDb"; +import { getApiKeys } from "@/lib/db/apiKeys"; import { maskStoredApiKey } from "@/lib/apiKeyExposure"; // GET /api/cli-tools/keys - List API keys with raw values for authenticated CLI tools UI only diff --git a/src/app/api/cli/connect/route.ts b/src/app/api/cli/connect/route.ts index 7a16e686b4..1cae2c918a 100644 --- a/src/app/api/cli/connect/route.ts +++ b/src/app/api/cli/connect/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { getAuditRequestContext, logAuditEvent } from "@/lib/compliance/index"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import { ensurePersistentManagementPasswordHash, getStoredManagementPassword, diff --git a/src/app/api/combos/reorder/route.ts b/src/app/api/combos/reorder/route.ts index ee66db0edd..3e1c850e5a 100644 --- a/src/app/api/combos/reorder/route.ts +++ b/src/app/api/combos/reorder/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { reorderCombos, isCloudEnabled } from "@/lib/localDb"; +import { reorderCombos } from "@/lib/db/combos"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { reorderCombosSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/combos/route.ts b/src/app/api/combos/route.ts index c13466a800..592cb8f65e 100644 --- a/src/app/api/combos/route.ts +++ b/src/app/api/combos/route.ts @@ -1,11 +1,6 @@ import { NextResponse } from "next/server"; -import { - getCombos, - getCombosCount, - createCombo, - getComboByName, - isCloudEnabled, -} from "@/lib/localDb"; +import { getCombos, getCombosCount, createCombo, getComboByName } from "@/lib/db/combos"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { validateCompositeTiersConfig } from "@/lib/combos/compositeTiers"; diff --git a/src/app/api/combos/test/route.ts b/src/app/api/combos/test/route.ts index bd73410d77..d44a3a3bd4 100644 --- a/src/app/api/combos/test/route.ts +++ b/src/app/api/combos/test/route.ts @@ -1,7 +1,8 @@ import { randomUUID } from "node:crypto"; import { NextResponse } from "next/server"; import { buildComboTestRequestBody, extractComboTestResponseText } from "@/lib/combos/testHealth"; -import { getComboByName, getCombos, pickApiKeyForInternalUse } from "@/lib/localDb"; +import { getComboByName, getCombos } from "@/lib/db/combos"; +import { pickApiKeyForInternalUse } from "@/lib/db/apiKeys"; import { getRuntimePorts } from "@/lib/runtime/ports"; import { resolveNestedComboTargets } from "@omniroute/open-sse/services/combo.ts"; import { testComboSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/db-backups/route.ts b/src/app/api/db-backups/route.ts index 04661e7d37..1d94aef455 100644 --- a/src/app/api/db-backups/route.ts +++ b/src/app/api/db-backups/route.ts @@ -8,7 +8,7 @@ import { setDbBackupMaxFiles, getDbBackupRetentionDays, setDbBackupRetentionDays, -} from "@/lib/localDb"; +} from "@/lib/db/backup"; import { dbBackupCleanupSchema, dbBackupRestoreSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { isAuthenticated } from "@/shared/utils/apiAuth"; diff --git a/src/app/api/evals/route.ts b/src/app/api/evals/route.ts index 58ca8ab23d..3415c71fcd 100644 --- a/src/app/api/evals/route.ts +++ b/src/app/api/evals/route.ts @@ -1,6 +1,7 @@ import { randomUUID } from "node:crypto"; import { NextResponse } from "next/server"; -import { getEvalScorecard, listEvalRuns, getApiKeys } from "@/lib/localDb"; +import { getEvalScorecard, listEvalRuns } from "@/lib/db/evals"; +import { getApiKeys } from "@/lib/db/apiKeys"; import { listSuites, runSuite, createScorecard } from "@/lib/evals/evalRunner"; import { buildEvalTargetOptions, runEvalSuiteAgainstTarget } from "@/lib/evals/runtime"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/evals/suites/[suiteId]/route.ts b/src/app/api/evals/suites/[suiteId]/route.ts index b62d8c98a7..b5b2957334 100644 --- a/src/app/api/evals/suites/[suiteId]/route.ts +++ b/src/app/api/evals/suites/[suiteId]/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { deleteCustomEvalSuite, getCustomEvalSuite, saveCustomEvalSuite } from "@/lib/localDb"; +import { deleteCustomEvalSuite, getCustomEvalSuite, saveCustomEvalSuite } from "@/lib/db/evals"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { evalSuiteSaveSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/evals/suites/route.ts b/src/app/api/evals/suites/route.ts index 15046941e8..ad93940a73 100644 --- a/src/app/api/evals/suites/route.ts +++ b/src/app/api/evals/suites/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { saveCustomEvalSuite } from "@/lib/localDb"; +import { saveCustomEvalSuite } from "@/lib/db/evals"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { evalSuiteSaveSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/files/[id]/content/route.ts b/src/app/api/files/[id]/content/route.ts index 2a671eb46d..e742db653d 100644 --- a/src/app/api/files/[id]/content/route.ts +++ b/src/app/api/files/[id]/content/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getFile, getFileContent } from "@/lib/localDb"; +import { getFile, getFileContent } from "@/lib/db/files"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { diff --git a/src/app/api/files/route.ts b/src/app/api/files/route.ts index fb34312f19..27949f1748 100644 --- a/src/app/api/files/route.ts +++ b/src/app/api/files/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { listFiles } from "@/lib/localDb"; +import { listFiles } from "@/lib/db/files"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request) { diff --git a/src/app/api/keys/[id]/regenerate/route.ts b/src/app/api/keys/[id]/regenerate/route.ts index 6a0fa81e75..b5f3505d3c 100644 --- a/src/app/api/keys/[id]/regenerate/route.ts +++ b/src/app/api/keys/[id]/regenerate/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { regenerateApiKey } from "@/lib/localDb"; +import { regenerateApiKey } from "@/lib/db/apiKeys"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import * as log from "@/sse/utils/logger"; diff --git a/src/app/api/keys/[id]/reveal/route.ts b/src/app/api/keys/[id]/reveal/route.ts index 489fa5e9cf..dd3c1f09a9 100644 --- a/src/app/api/keys/[id]/reveal/route.ts +++ b/src/app/api/keys/[id]/reveal/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getApiKeyById } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; import { isApiKeyRevealEnabled } from "@/lib/apiKeyExposure"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import * as log from "@/sse/utils/logger"; diff --git a/src/app/api/keys/[id]/route.ts b/src/app/api/keys/[id]/route.ts index f3df18a230..f1e354d290 100644 --- a/src/app/api/keys/[id]/route.ts +++ b/src/app/api/keys/[id]/route.ts @@ -3,9 +3,9 @@ import { deleteApiKey, getApiKeyById, updateApiKeyPermissions, - isCloudEnabled, ApiKeyPolicyInvariantError, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeys"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { updateKeyPermissionsSchema } from "@/shared/validation/schemas"; @@ -162,10 +162,13 @@ export async function PATCH(request, { params }) { }); } catch (error) { if (error instanceof ApiKeyPolicyInvariantError) { - return NextResponse.json(buildErrorBody(400, error.message, null, { - type: "lease_error", - code: error.code, - }), { status: 400 }); + return NextResponse.json( + buildErrorBody(400, error.message, null, { + type: "lease_error", + code: error.code, + }), + { status: 400 } + ); } log.error("keys", "Error updating key permissions", error); return NextResponse.json({ error: "Failed to update permissions" }, { status: 500 }); diff --git a/src/app/api/keys/groups/[id]/keys/route.ts b/src/app/api/keys/groups/[id]/keys/route.ts index 04ff6775c2..db2456142e 100644 --- a/src/app/api/keys/groups/[id]/keys/route.ts +++ b/src/app/api/keys/groups/[id]/keys/route.ts @@ -1,6 +1,11 @@ import { NextResponse } from "next/server"; import { z } from "zod"; -import { addKeyToGroup, removeKeyFromGroup, getGroupMembers, getKeyGroup } from "@/lib/localDb"; +import { + addKeyToGroup, + removeKeyFromGroup, + getGroupMembers, + getKeyGroup, +} from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; type RouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/keys/groups/[id]/permissions/route.ts b/src/app/api/keys/groups/[id]/permissions/route.ts index d9f7391e8a..dce04e0b1e 100644 --- a/src/app/api/keys/groups/[id]/permissions/route.ts +++ b/src/app/api/keys/groups/[id]/permissions/route.ts @@ -5,7 +5,7 @@ import { removeGroupPermission, getGroupPermissions, getKeyGroup, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; type RouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/keys/groups/[id]/route.ts b/src/app/api/keys/groups/[id]/route.ts index e8289a0121..6cea718ba3 100644 --- a/src/app/api/keys/groups/[id]/route.ts +++ b/src/app/api/keys/groups/[id]/route.ts @@ -9,7 +9,7 @@ import { removeGroupPermission, addKeyToGroup, removeKeyFromGroup, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; type RouteParams = { params: Promise<{ id: string }> }; diff --git a/src/app/api/keys/groups/route.ts b/src/app/api/keys/groups/route.ts index 87c0b4f289..f0766dc59e 100644 --- a/src/app/api/keys/groups/route.ts +++ b/src/app/api/keys/groups/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { z } from "zod"; -import { getAllKeyGroups, createKeyGroup, getKeyGroup } from "@/lib/localDb"; +import { getAllKeyGroups, createKeyGroup, getKeyGroup } from "@/lib/db/apiKeyGroups"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; const createKeyGroupSchema = z.object({ diff --git a/src/app/api/keys/route.ts b/src/app/api/keys/route.ts index d50b4c9d17..11f4b94b97 100644 --- a/src/app/api/keys/route.ts +++ b/src/app/api/keys/route.ts @@ -3,9 +3,9 @@ import { getApiKeys, getApiKeysCount, createApiKey, - isCloudEnabled, updateApiKeyPermissions, -} from "@/lib/localDb"; +} from "@/lib/db/apiKeys"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { createKeySchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/memory/reindex/route.ts b/src/app/api/memory/reindex/route.ts index bdd51678ce..cbf2b8bc6b 100644 --- a/src/app/api/memory/reindex/route.ts +++ b/src/app/api/memory/reindex/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { MemoryReindexSchema } from "@/shared/schemas/memory"; import { runReindexBatch, getReindexPending } from "@/lib/memory/reindex"; -import { markAllMemoriesNeedReindex } from "@/lib/localDb"; +import { markAllMemoriesNeedReindex } from "@/lib/db/memoryVec"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; import { logger } from "@omniroute/open-sse/utils/logger.ts"; @@ -19,7 +19,7 @@ export async function POST(request: Request) { } catch { return NextResponse.json( { error: { message: "Invalid JSON body", details: [] } }, - { status: 400 }, + { status: 400 } ); } diff --git a/src/app/api/middleware/hooks/[name]/route.ts b/src/app/api/middleware/hooks/[name]/route.ts index 647cb523cc..ad7b50e43e 100644 --- a/src/app/api/middleware/hooks/[name]/route.ts +++ b/src/app/api/middleware/hooks/[name]/route.ts @@ -5,7 +5,7 @@ import { updateMiddlewareHook, deleteMiddlewareHook, getHookLogs, -} from "@/lib/localDb"; +} from "@/lib/db/middleware"; import { registerHook, unregisterHook, updateHook } from "@/lib/middleware/registry"; import type { HookConfig } from "@/lib/middleware/types"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; @@ -106,7 +106,10 @@ export async function PUT(request: Request, { params }: RouteParams) { return NextResponse.json({ hook: saved }); } catch (error: any) { console.error("[API] PUT /api/middleware/hooks/[name] error:", error); - return NextResponse.json({ error: sanitizeErrorMessage(error) || "Failed to update hook" }, { status: 500 }); + return NextResponse.json( + { error: sanitizeErrorMessage(error) || "Failed to update hook" }, + { status: 500 } + ); } } diff --git a/src/app/api/middleware/hooks/route.ts b/src/app/api/middleware/hooks/route.ts index ea8b40ef69..e630dc8c8b 100644 --- a/src/app/api/middleware/hooks/route.ts +++ b/src/app/api/middleware/hooks/route.ts @@ -5,7 +5,7 @@ import { createMiddlewareHook, getMiddlewareHook, getHookLogs, -} from "@/lib/localDb"; +} from "@/lib/db/middleware"; import { registerHook, getAllHooks } from "@/lib/middleware/registry"; import type { HookConfig, CreateHookRequest } from "@/lib/middleware/types"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; @@ -115,6 +115,9 @@ export async function POST(request: Request) { return NextResponse.json({ hook: saved }, { status: 201 }); } catch (error: any) { console.error("[API] POST /api/middleware/hooks error:", error); - return NextResponse.json({ error: sanitizeErrorMessage(error) || "Failed to create hook" }, { status: 500 }); + return NextResponse.json( + { error: sanitizeErrorMessage(error) || "Failed to create hook" }, + { status: 500 } + ); } } diff --git a/src/app/api/model-combo-mappings/[id]/route.ts b/src/app/api/model-combo-mappings/[id]/route.ts index 33393201f8..750c10cccc 100644 --- a/src/app/api/model-combo-mappings/[id]/route.ts +++ b/src/app/api/model-combo-mappings/[id]/route.ts @@ -11,7 +11,7 @@ import { updateModelComboMapping, deleteModelComboMapping, getModelComboMappingById, -} from "@/lib/localDb"; +} from "@/lib/db/modelComboMappings"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; const updateMappingSchema = z.object({ diff --git a/src/app/api/model-combo-mappings/route.ts b/src/app/api/model-combo-mappings/route.ts index ccc65170be..0d7605b529 100644 --- a/src/app/api/model-combo-mappings/route.ts +++ b/src/app/api/model-combo-mappings/route.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { NextResponse } from "next/server"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { createModelComboMapping, getModelComboMappings } from "@/lib/localDb"; +import { createModelComboMapping, getModelComboMappings } from "@/lib/db/modelComboMappings"; import { paginationSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { validatedJsonBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/models/test-all/route.ts b/src/app/api/models/test-all/route.ts index 5492b40a27..9601745016 100644 --- a/src/app/api/models/test-all/route.ts +++ b/src/app/api/models/test-all/route.ts @@ -14,7 +14,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { DEFAULT_MODEL_TEST_TIMEOUT_MS, runSingleModelTest } from "@/lib/api/modelTestRunner"; -import { setModelIsHidden } from "@/lib/localDb"; +import { setModelIsHidden } from "@/lib/db/models"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { getSettings } from "@/lib/db/settings"; import { isFreeModel, providerHasFreeModels } from "@/shared/utils/freeModels"; diff --git a/src/app/api/monitoring/health/route.ts b/src/app/api/monitoring/health/route.ts index b3e031b348..f913dbb2c5 100644 --- a/src/app/api/monitoring/health/route.ts +++ b/src/app/api/monitoring/health/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getProviderConnections, getCachedSettings } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; +import { getCachedSettings } from "@/lib/db/readCache"; import { buildHealthPayload } from "@/lib/monitoring/observability"; import { readRunningBuildSha } from "@/lib/monitoring/buildSha"; import { APP_CONFIG } from "@/shared/constants/config"; diff --git a/src/app/api/pricing/models/route.ts b/src/app/api/pricing/models/route.ts index bf25fe1e0e..0544a979d6 100644 --- a/src/app/api/pricing/models/route.ts +++ b/src/app/api/pricing/models/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from "next/server"; import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts"; -import { getAllCustomModels, getAllSyncedAvailableModels, getPricing } from "@/lib/localDb"; +import { getAllCustomModels, getAllSyncedAvailableModels } from "@/lib/db/models"; +import { getPricing } from "@/lib/db/settings"; import { getProviderPrefixIndex } from "@/lib/providerNodePrefixes"; function asRecord(value: unknown): Record { diff --git a/src/app/api/pricing/route.ts b/src/app/api/pricing/route.ts index 7db9659574..e1849e672b 100644 --- a/src/app/api/pricing/route.ts +++ b/src/app/api/pricing/route.ts @@ -6,7 +6,7 @@ import { updatePricing, resetPricing, resetAllPricing, -} from "@/lib/localDb"; +} from "@/lib/db/settings"; import { updatePricingSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/provider-models/route.ts b/src/app/api/provider-models/route.ts index 007ab7673a..beba1dbc1f 100644 --- a/src/app/api/provider-models/route.ts +++ b/src/app/api/provider-models/route.ts @@ -11,7 +11,7 @@ import { mergeModelCompatOverride, getHiddenModelsByProvider, type ModelCompatPatch, -} from "@/lib/localDb"; +} from "@/lib/db/models"; import { getModelContextOverrideRecord, setModelContextOverride, diff --git a/src/app/api/providers/[id]/login/route.ts b/src/app/api/providers/[id]/login/route.ts index be62a774d7..ec9cb5376c 100644 --- a/src/app/api/providers/[id]/login/route.ts +++ b/src/app/api/providers/[id]/login/route.ts @@ -7,7 +7,8 @@ */ import { NextRequest, NextResponse } from "next/server"; -import { getCachedProviderConnectionById, updateProviderConnection } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { updateProviderConnection } from "@/lib/db/providers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { clampLoginTimeoutMs } from "@/lib/api/loginTimeout"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; diff --git a/src/app/api/providers/[id]/models/route.ts b/src/app/api/providers/[id]/models/route.ts index ee2baeb393..5bcc721109 100755 --- a/src/app/api/providers/[id]/models/route.ts +++ b/src/app/api/providers/[id]/models/route.ts @@ -11,11 +11,8 @@ import { resolveAlibabaProviderModelsUrl } from "@/shared/constants/alibabaProvi import { getStaticModelsForProvider } from "@/lib/providers/staticModels"; import { providerUsesCuratedModelsOnly } from "@/lib/providers/modelListingCapability"; import { mergeModelsWithCustomPrecedence } from "@/lib/providers/modelMetadataPrecedence"; -import { - getCachedProviderConnectionById, - getModelIsHidden, - resolveProxyForProvider, -} from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { SAFE_OUTBOUND_FETCH_PRESETS, SafeOutboundFetchError, @@ -86,7 +83,7 @@ import { import { buildProviderModelsUrl, getDiscoveryClientVersionOptions } from "./discoveryClientVersion"; import { getAdobeModels } from "./adobeFireflyDiscovery"; import { parseGeminiModelsList } from "@/lib/providerModels/geminiModelsParser"; -import { getSyncedAvailableModels, getCustomModels } from "@/lib/db/models"; +import { getSyncedAvailableModels, getCustomModels, getModelIsHidden } from "@/lib/db/models"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { fetchCursorAgentModels } from "@/lib/providerModels/cursorAgent"; import { fetchCursorAvailableModels } from "@/lib/providerModels/cursorAvailableModels"; diff --git a/src/app/api/providers/[id]/refresh/route.ts b/src/app/api/providers/[id]/refresh/route.ts index 830e90aa81..85f68930f5 100644 --- a/src/app/api/providers/[id]/refresh/route.ts +++ b/src/app/api/providers/[id]/refresh/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { updateProviderConnection } from "@/lib/db/providers"; import { getAccessToken, diff --git a/src/app/api/providers/[id]/route.ts b/src/app/api/providers/[id]/route.ts index 2534bd5852..5085f8fb13 100644 --- a/src/app/api/providers/[id]/route.ts +++ b/src/app/api/providers/[id]/route.ts @@ -4,12 +4,9 @@ import { getProviderAuditTarget, summarizeProviderConnectionForAudit, } from "@/lib/compliance/providerAudit"; -import { - getCachedProviderConnectionById, - updateProviderConnection, - deleteProviderConnection, - isCloudEnabled, -} from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { updateProviderConnection, deleteProviderConnection } from "@/lib/db/providers"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { updateProviderConnectionSchema } from "@/shared/validation/schemas"; diff --git a/src/app/api/providers/[id]/sync-models/route.ts b/src/app/api/providers/[id]/sync-models/route.ts index 7d75906360..59f2de8998 100644 --- a/src/app/api/providers/[id]/sync-models/route.ts +++ b/src/app/api/providers/[id]/sync-models/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { deleteImportedCustomModels, deleteSyncedAvailableModelsForProvider, diff --git a/src/app/api/providers/[id]/test/route.ts b/src/app/api/providers/[id]/test/route.ts index 7688eb3d06..cc663a95c8 100644 --- a/src/app/api/providers/[id]/test/route.ts +++ b/src/app/api/providers/[id]/test/route.ts @@ -1,12 +1,9 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; -import { - getCachedProviderConnectionById, - updateProviderConnection, - isCloudEnabled, - resolveProxyForConnection, -} from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { updateProviderConnection } from "@/lib/db/providers"; +import { isCloudEnabled, resolveProxyForConnection } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud } from "@/lib/cloudSync"; import { validateProviderApiKey } from "@/lib/providers/validation"; diff --git a/src/app/api/providers/bulk/route.ts b/src/app/api/providers/bulk/route.ts index 6799ff4f9e..13920e587d 100644 --- a/src/app/api/providers/bulk/route.ts +++ b/src/app/api/providers/bulk/route.ts @@ -28,7 +28,8 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { isManagedProviderConnectionId } from "@/lib/providers/catalog"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { validateProviderApiKey } from "@/lib/providers/validation"; -import { getProxyForLevel, resolveProxyForProvider } from "@/lib/localDb"; +import { getProxyForLevel } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse"; diff --git a/src/app/api/providers/client/route.ts b/src/app/api/providers/client/route.ts index 84875c2808..7a9f65d206 100644 --- a/src/app/api/providers/client/route.ts +++ b/src/app/api/providers/client/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; // GET /api/providers/client - List all connections for client (includes sensitive fields for sync) export async function GET() { diff --git a/src/app/api/providers/command-code/auth/apply/route.ts b/src/app/api/providers/command-code/auth/apply/route.ts index 3aee6b1a6e..82d13f24cd 100644 --- a/src/app/api/providers/command-code/auth/apply/route.ts +++ b/src/app/api/providers/command-code/auth/apply/route.ts @@ -1,10 +1,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { consumeCommandCodeAuthSecret } from "@/lib/db/commandCodeAuth"; -import { - createProviderConnection, - updateProviderConnection, -} from "@/lib/db/providers"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { createProviderConnection, updateProviderConnection } from "@/lib/db/providers"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { sanitizeProviderSpecificDataForResponse } from "@/lib/providers/requestDefaults"; import { commandCodeApplySchema, noStoreJson, stateHashFromState } from "../shared"; diff --git a/src/app/api/providers/import/route.ts b/src/app/api/providers/import/route.ts index c74d85cfbc..dcedfc9c9b 100644 --- a/src/app/api/providers/import/route.ts +++ b/src/app/api/providers/import/route.ts @@ -27,7 +27,8 @@ import { import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { validateProviderApiKey } from "@/lib/providers/validation"; -import { getProxyForLevel, resolveProxyForProvider } from "@/lib/localDb"; +import { getProxyForLevel } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse"; diff --git a/src/app/api/providers/quota-windows/route.ts b/src/app/api/providers/quota-windows/route.ts index d4a0d842f5..90c1be810b 100644 --- a/src/app/api/providers/quota-windows/route.ts +++ b/src/app/api/providers/quota-windows/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { getAllProviderQuotaWindows } from "@omniroute/open-sse/services/quotaPreflight.ts"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import { resolveResilienceSettings } from "@/lib/resilience/settings"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/providers/validate/route.ts b/src/app/api/providers/validate/route.ts index ef5565bf13..7d92d4ac92 100644 --- a/src/app/api/providers/validate/route.ts +++ b/src/app/api/providers/validate/route.ts @@ -8,7 +8,8 @@ import { isAnthropicCompatibleProvider, } from "@/shared/constants/providers"; import { validateProviderApiKey } from "@/lib/providers/validation"; -import { getProxyForLevel, resolveProxyForProvider } from "@/lib/localDb"; +import { getProxyForLevel } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { validateProviderApiKeySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { runWithProxyContextOrDirect } from "@omniroute/open-sse/utils/proxyFetch.ts"; diff --git a/src/app/api/quota/groups/[id]/route.ts b/src/app/api/quota/groups/[id]/route.ts index 7496dbdd6d..174617cb55 100644 --- a/src/app/api/quota/groups/[id]/route.ts +++ b/src/app/api/quota/groups/[id]/route.ts @@ -22,7 +22,8 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { GroupRenameSchema } from "@/shared/schemas/quota"; -import { renameGroup, deleteGroup, getGroup, getPoolsByGroup } from "@/lib/localDb"; +import { renameGroup, deleteGroup, getGroup } from "@/lib/db/quotaGroups"; +import { getPoolsByGroup } from "@/lib/db/quotaPools"; export const dynamic = "force-dynamic"; @@ -55,10 +56,7 @@ export async function PATCH(request: Request, { params }: RouteParams): Promise< await syncQuotaCombos(pool.id); } catch (err) { // Guard: combo-sync failure must never break group rename callers. - console.warn( - "[quota-groups] syncQuotaCombos failed (non-fatal):", - (err as Error)?.message, - ); + console.warn("[quota-groups] syncQuotaCombos failed (non-fatal):", (err as Error)?.message); } } diff --git a/src/app/api/quota/groups/route.ts b/src/app/api/quota/groups/route.ts index 04962ebe9f..e5b61ecc72 100644 --- a/src/app/api/quota/groups/route.ts +++ b/src/app/api/quota/groups/route.ts @@ -15,7 +15,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { GroupCreateSchema } from "@/shared/schemas/quota"; -import { listGroups, createGroup } from "@/lib/localDb"; +import { listGroups, createGroup } from "@/lib/db/quotaGroups"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/quota/keys/[id]/models/route.ts b/src/app/api/quota/keys/[id]/models/route.ts index d4297cfa51..4c2435bde1 100644 --- a/src/app/api/quota/keys/[id]/models/route.ts +++ b/src/app/api/quota/keys/[id]/models/route.ts @@ -19,7 +19,8 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { getApiKeyById, getCombos } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; +import { getCombos } from "@/lib/db/combos"; import { resolveQuotaKeyScope } from "@/lib/quota/quotaKey"; import { filterModelsToQuotaPools } from "@/lib/quota/quotaCombos"; @@ -39,9 +40,7 @@ export async function GET(request: Request, { params }: RouteParams): Promise).allowedQuotas, - ) + const allowedQuotas: string[] = Array.isArray((key as Record).allowedQuotas) ? ((key as Record).allowedQuotas as string[]) : []; diff --git a/src/app/api/quota/plans/[connectionId]/route.ts b/src/app/api/quota/plans/[connectionId]/route.ts index b6fa352f04..94d48017ae 100644 --- a/src/app/api/quota/plans/[connectionId]/route.ts +++ b/src/app/api/quota/plans/[connectionId]/route.ts @@ -20,10 +20,10 @@ import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { PlanUpsertSchema } from "@/shared/schemas/quota"; import { - getProviderPlan, - upsertProviderPlan, - deleteProviderPlan, -} from "@/lib/localDb"; + getPlan as getProviderPlan, + upsertPlan as upsertProviderPlan, + deletePlan as deleteProviderPlan, +} from "@/lib/db/providerPlans"; import { resolvePlan } from "@/lib/quota/planResolver"; import { resolveConnectionProvider } from "@/lib/quota/connectionProvider"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; diff --git a/src/app/api/quota/plans/route.ts b/src/app/api/quota/plans/route.ts index 92fba4fadc..7dc0903e2d 100644 --- a/src/app/api/quota/plans/route.ts +++ b/src/app/api/quota/plans/route.ts @@ -17,7 +17,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { listProviderPlans } from "@/lib/localDb"; +import { listPlans as listProviderPlans } from "@/lib/db/providerPlans"; import { knownProviders, getKnownPlan } from "@/lib/quota/planRegistry"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/quota/pools/[id]/route.ts b/src/app/api/quota/pools/[id]/route.ts index 40ef94336b..bcc346891f 100644 --- a/src/app/api/quota/pools/[id]/route.ts +++ b/src/app/api/quota/pools/[id]/route.ts @@ -15,7 +15,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { PoolUpdateSchema } from "@/shared/schemas/quota"; -import { getPool, updatePool, deletePool } from "@/lib/localDb"; +import { getPool, updatePool, deletePool } from "@/lib/db/quotaPools"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; import { reconcilePoolExclusivity } from "@/lib/quota/quotaKey"; diff --git a/src/app/api/quota/pools/[id]/usage/route.ts b/src/app/api/quota/pools/[id]/usage/route.ts index cb03f4a04c..858285d170 100644 --- a/src/app/api/quota/pools/[id]/usage/route.ts +++ b/src/app/api/quota/pools/[id]/usage/route.ts @@ -15,7 +15,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; -import { getPool } from "@/lib/localDb"; +import { getPool } from "@/lib/db/quotaPools"; import { getQuotaStore } from "@/lib/quota/QuotaStore"; import { resolvePlan } from "@/lib/quota/planResolver"; import { resolveConnectionProvider } from "@/lib/quota/connectionProvider"; diff --git a/src/app/api/quota/pools/route.ts b/src/app/api/quota/pools/route.ts index 0a7cd72020..7f759f2b54 100644 --- a/src/app/api/quota/pools/route.ts +++ b/src/app/api/quota/pools/route.ts @@ -16,7 +16,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { PoolCreateSchema } from "@/shared/schemas/quota"; -import { listPools, createPool, ensurePool } from "@/lib/localDb"; +import { listPools, createPool, ensurePool } from "@/lib/db/quotaPools"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; export const dynamic = "force-dynamic"; @@ -66,7 +66,10 @@ export async function POST(request: Request): Promise { requestId: ctx.requestId, }); - return NextResponse.json({ pool, ...(ensured ? { created: ensured.created, updated: ensured.updated } : {}) }, { status: ensured?.created === false ? 200 : 201 }); + return NextResponse.json( + { pool, ...(ensured ? { created: ensured.created, updated: ensured.updated } : {}) }, + { status: ensured?.created === false ? 200 : 201 } + ); } catch (err) { const message = err instanceof Error ? err.message : "Failed to create pool"; return NextResponse.json(buildErrorBody(500, message), { status: 500 }); diff --git a/src/app/api/quota/preview/route.ts b/src/app/api/quota/preview/route.ts index 242289fdbf..e2c78e92dc 100644 --- a/src/app/api/quota/preview/route.ts +++ b/src/app/api/quota/preview/route.ts @@ -25,7 +25,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { QuotaPreviewQuerySchema } from "@/shared/schemas/quota"; -import { getPool } from "@/lib/localDb"; +import { getPool } from "@/lib/db/quotaPools"; import { enforceQuotaShare } from "@/lib/quota/enforce"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/rate-limits/route.ts b/src/app/api/rate-limits/route.ts index dbfe87dabf..a7c4d3ee00 100644 --- a/src/app/api/rate-limits/route.ts +++ b/src/app/api/rate-limits/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; import { getAllModelLockouts } from "@omniroute/open-sse/services/accountFallback.ts"; import { getCacheStats } from "@omniroute/open-sse/services/signatureCache.ts"; -import { getProviderConnections, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; import { enableRateLimitProtection, disableRateLimitProtection, diff --git a/src/app/api/resilience/route.ts b/src/app/api/resilience/route.ts index 2881041748..7b89aade8f 100644 --- a/src/app/api/resilience/route.ts +++ b/src/app/api/resilience/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getCachedSettings, getSettings, updateSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { buildLegacyResilienceCompat, mergeResilienceSettings, diff --git a/src/app/api/settings/__tests__/memory.test.ts b/src/app/api/settings/__tests__/memory.test.ts index a865425de7..cb8f63f0c0 100644 --- a/src/app/api/settings/__tests__/memory.test.ts +++ b/src/app/api/settings/__tests__/memory.test.ts @@ -4,7 +4,7 @@ vi.mock("../../../../shared/utils/apiAuth", () => ({ isAuthenticated: vi.fn(), })); -vi.mock("../../../../lib/localDb", () => ({ +vi.mock("@/lib/db/settings", () => ({ getSettings: vi.fn(), updateSettings: vi.fn(), })); @@ -19,7 +19,7 @@ vi.mock("../../../../lib/memory/settings", async () => { import { GET, PUT } from "../memory/route"; import { isAuthenticated } from "../../../../shared/utils/apiAuth"; -import { getSettings, updateSettings } from "../../../../lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { invalidateMemorySettingsCache } from "../../../../lib/memory/settings"; function createRequest(method: "GET" | "PUT", body?: unknown) { diff --git a/src/app/api/settings/__tests__/settings.test.ts b/src/app/api/settings/__tests__/settings.test.ts index ce7d1c579a..85e6f4f3cc 100644 --- a/src/app/api/settings/__tests__/settings.test.ts +++ b/src/app/api/settings/__tests__/settings.test.ts @@ -1,9 +1,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { PATCH } from "../route"; -// Mock the localDb functions used in the route -vi.mock("../../../../lib/localDb", () => { - const original = vi.importActual("../../../../lib/localDb"); +// Mock the settings db functions used in the route +vi.mock("@/lib/db/settings", () => { + const original = vi.importActual("@/lib/db/settings"); return { ...original, getSettings: vi.fn(), @@ -11,7 +11,7 @@ vi.mock("../../../../lib/localDb", () => { }; }); -import { getSettings, updateSettings } from "../../../../lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; // Helper to create a Request with JSON body function createPatchRequest(body: unknown) { diff --git a/src/app/api/settings/authz-inventory/route.ts b/src/app/api/settings/authz-inventory/route.ts index 4b309a992e..6f012a96d7 100644 --- a/src/app/api/settings/authz-inventory/route.ts +++ b/src/app/api/settings/authz-inventory/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { isAuthRequired, isDashboardSessionAuthenticated } from "@/shared/utils/apiAuth"; import { createErrorResponse } from "@/lib/api/errorResponse"; import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; diff --git a/src/app/api/settings/auto-disable-accounts/route.ts b/src/app/api/settings/auto-disable-accounts/route.ts index b7e6daa28a..ccb3a4294b 100644 --- a/src/app/api/settings/auto-disable-accounts/route.ts +++ b/src/app/api/settings/auto-disable-accounts/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { updateAutoDisableAccountsSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/combo-defaults/route.ts b/src/app/api/settings/combo-defaults/route.ts index 643be54c0e..176e46bbfb 100644 --- a/src/app/api/settings/combo-defaults/route.ts +++ b/src/app/api/settings/combo-defaults/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { updateComboDefaultsSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/export-json/route.ts b/src/app/api/settings/export-json/route.ts index 83af8abc26..8b894875ae 100644 --- a/src/app/api/settings/export-json/route.ts +++ b/src/app/api/settings/export-json/route.ts @@ -1,11 +1,9 @@ import { NextResponse } from "next/server"; -import { - getSettings, - getProviderConnections, - getCachedProviderNodes, - getCombos, - getApiKeys, -} from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; +import { getProviderConnections } from "@/lib/db/providers"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; +import { getCombos } from "@/lib/db/combos"; +import { getApiKeys } from "@/lib/db/apiKeys"; import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth"; import { getAllUsageHistory, @@ -75,9 +73,10 @@ export async function GET(request: Request) { // #6328: honor hidePaidModels at the export boundary so backup files // cannot silently smuggle paid model ids back in on import. - const combos = rawSettings.hidePaidModels === true - ? filterPaidComboSteps(combosRaw as Array<{ models?: unknown }>) - : combosRaw; + const combos = + rawSettings.hidePaidModels === true + ? filterPaidComboSteps(combosRaw as Array<{ models?: unknown }>) + : combosRaw; const exportData: Record = { settings: safeSettings, diff --git a/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts b/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts index 2e13845746..6e3187be87 100644 --- a/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts +++ b/src/app/api/settings/free-proxies/[id]/add-to-pool/route.ts @@ -1,7 +1,7 @@ import { request as undiciRequest } from "undici"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/localDb"; +import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/db/freeProxies"; import { createProxyDispatcher, proxyConfigToUrl, diff --git a/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts b/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts index c7d2108d5e..984fc6f4be 100644 --- a/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts +++ b/src/app/api/settings/free-proxies/bulk-add-to-pool/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { freeProxyBulkAddSchema } from "@/shared/validation/freeProxySchemas"; -import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/localDb"; +import { getFreeProxyById, promoteFreeProxyToPool } from "@/lib/db/freeProxies"; import { createProxyDispatcher, proxyConfigToUrl, diff --git a/src/app/api/settings/free-proxies/route.ts b/src/app/api/settings/free-proxies/route.ts index a36132535f..9bc4af1c89 100644 --- a/src/app/api/settings/free-proxies/route.ts +++ b/src/app/api/settings/free-proxies/route.ts @@ -9,7 +9,7 @@ import { clearFreeProxiesBySource, getFreeProxyStats, getFreeProxySyncErrors, -} from "@/lib/localDb"; +} from "@/lib/db/freeProxies"; import type { FreeProxySourceId } from "@/lib/freeProxyProviders/types"; export async function GET(request: Request) { diff --git a/src/app/api/settings/free-proxies/stats/route.ts b/src/app/api/settings/free-proxies/stats/route.ts index 3a7ecdb4e5..700a0f4cba 100644 --- a/src/app/api/settings/free-proxies/stats/route.ts +++ b/src/app/api/settings/free-proxies/stats/route.ts @@ -1,6 +1,6 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getFreeProxyStats } from "@/lib/localDb"; +import { getFreeProxyStats } from "@/lib/db/freeProxies"; import { getAllProviders } from "@/lib/freeProxyProviders"; import { isFreeProxyAutoSyncEnabled, diff --git a/src/app/api/settings/memory/route.ts b/src/app/api/settings/memory/route.ts index 084771d258..6bf0df0a90 100644 --- a/src/app/api/settings/memory/route.ts +++ b/src/app/api/settings/memory/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { isAuthenticated } from "@/shared/utils/apiAuth"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { MemorySettingsExtendedSchema } from "@/shared/schemas/memory"; @@ -35,7 +35,7 @@ export async function PUT(request: NextRequest) { } catch { return NextResponse.json( { error: { message: "Invalid JSON body", details: [] } }, - { status: 400 }, + { status: 400 } ); } diff --git a/src/app/api/settings/payload-rules/route.ts b/src/app/api/settings/payload-rules/route.ts index 8a1a5a7bcd..7a632e7e6a 100644 --- a/src/app/api/settings/payload-rules/route.ts +++ b/src/app/api/settings/payload-rules/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { updateSettings } from "@/lib/localDb"; +import { updateSettings } from "@/lib/db/settings"; import { getPayloadRulesConfig, normalizePayloadRulesConfig, diff --git a/src/app/api/settings/proxies/[id]/repair-relay/route.ts b/src/app/api/settings/proxies/[id]/repair-relay/route.ts index 3c2b075866..ee6a228707 100644 --- a/src/app/api/settings/proxies/[id]/repair-relay/route.ts +++ b/src/app/api/settings/proxies/[id]/repair-relay/route.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getProxyById, updateProxy } from "@/lib/localDb"; +import { getProxyById, updateProxy } from "@/lib/db/proxies"; import { decrypt } from "@/lib/db/encryption"; import { isRelayProxyType, relayRepairMode } from "@/lib/db/proxies/mappers"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/settings/proxies/assignments/route.ts b/src/app/api/settings/proxies/assignments/route.ts index 39e3d188ba..990f082ed2 100644 --- a/src/app/api/settings/proxies/assignments/route.ts +++ b/src/app/api/settings/proxies/assignments/route.ts @@ -1,4 +1,5 @@ -import { assignProxyToScope, getProxyAssignments, resolveProxyForConnection } from "@/lib/localDb"; +import { assignProxyToScope, getProxyAssignments } from "@/lib/db/proxies"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { proxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/settings/proxies/auto-test/route.ts b/src/app/api/settings/proxies/auto-test/route.ts index 4d7b4f3a4e..17748b5e44 100644 --- a/src/app/api/settings/proxies/auto-test/route.ts +++ b/src/app/api/settings/proxies/auto-test/route.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { deleteProxyById, listProxies, updateProxy } from "@/lib/localDb"; +import { deleteProxyById, listProxies, updateProxy } from "@/lib/db/proxies"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createProxyDispatcher, proxyConfigToUrl } from "@omniroute/open-sse/utils/proxyDispatcher"; diff --git a/src/app/api/settings/proxies/batch-activate/route.ts b/src/app/api/settings/proxies/batch-activate/route.ts index 9bc11f1999..f086d3f335 100644 --- a/src/app/api/settings/proxies/batch-activate/route.ts +++ b/src/app/api/settings/proxies/batch-activate/route.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { updateProxy } from "@/lib/localDb"; +import { updateProxy } from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; @@ -28,12 +28,20 @@ export async function POST(request: Request) { try { rawBody = await request.json(); } catch { - return createErrorResponse({ status: 400, message: "Invalid JSON body", type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: "Invalid JSON body", + type: "invalid_request", + }); } const validation = validateBody(batchActivateSchema, rawBody); if (isValidationFailure(validation)) { - return createErrorResponse({ status: 400, message: validation.error.message, type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: validation.error.message, + type: "invalid_request", + }); } const { ids, status } = validation.data; @@ -51,12 +59,20 @@ export async function POST(request: Request) { results.push({ id, success: false, error: "Proxy not found" }); } } catch (err) { - results.push({ id, success: false, error: err instanceof Error ? err.message : "Unknown error" }); + results.push({ + id, + success: false, + error: err instanceof Error ? err.message : "Unknown error", + }); } } if (updatedCount > 0) { - try { clearDispatcherCache(); } catch { /* non-critical */ } + try { + clearDispatcherCache(); + } catch { + /* non-critical */ + } } return Response.json({ diff --git a/src/app/api/settings/proxies/batch-delete/route.ts b/src/app/api/settings/proxies/batch-delete/route.ts index ac343fb953..81fe2b40f1 100644 --- a/src/app/api/settings/proxies/batch-delete/route.ts +++ b/src/app/api/settings/proxies/batch-delete/route.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { deleteProxyById } from "@/lib/localDb"; +import { deleteProxyById } from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; @@ -22,12 +22,20 @@ export async function POST(request: Request) { try { rawBody = await request.json(); } catch { - return createErrorResponse({ status: 400, message: "Invalid JSON body", type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: "Invalid JSON body", + type: "invalid_request", + }); } const validation = validateBody(batchDeleteSchema, rawBody); if (isValidationFailure(validation)) { - return createErrorResponse({ status: 400, message: validation.error.message, type: "invalid_request" }); + return createErrorResponse({ + status: 400, + message: validation.error.message, + type: "invalid_request", + }); } const { ids, force } = validation.data; @@ -45,15 +53,28 @@ export async function POST(request: Request) { results.push({ id, success: false, error: "Proxy not found" }); } } catch (err) { - results.push({ id, success: false, error: err instanceof Error ? err.message : "Unknown error" }); + results.push({ + id, + success: false, + error: err instanceof Error ? err.message : "Unknown error", + }); } } if (deletedCount > 0) { - try { clearDispatcherCache(); } catch { /* non-critical */ } + try { + clearDispatcherCache(); + } catch { + /* non-critical */ + } } - return Response.json({ success: deletedCount > 0, deleted: deletedCount, failed: ids.length - deletedCount, results }); + return Response.json({ + success: deletedCount > 0, + deleted: deletedCount, + failed: ids.length - deletedCount, + results, + }); } catch (error) { return createErrorResponseFromUnknown(error, "Failed to batch delete proxies"); } diff --git a/src/app/api/settings/proxies/bulk-assign/route.ts b/src/app/api/settings/proxies/bulk-assign/route.ts index bcd104bacf..b322ab335c 100644 --- a/src/app/api/settings/proxies/bulk-assign/route.ts +++ b/src/app/api/settings/proxies/bulk-assign/route.ts @@ -1,4 +1,4 @@ -import { bulkAssignProxyToScope } from "@/lib/localDb"; +import { bulkAssignProxyToScope } from "@/lib/db/proxies"; import { bulkProxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/settings/proxies/bulk-import/route.ts b/src/app/api/settings/proxies/bulk-import/route.ts index 9f3c3307de..0dbf359477 100644 --- a/src/app/api/settings/proxies/bulk-import/route.ts +++ b/src/app/api/settings/proxies/bulk-import/route.ts @@ -1,4 +1,4 @@ -import { upsertProxy } from "@/lib/localDb"; +import { upsertProxy } from "@/lib/db/proxies"; import { bulkImportProxiesSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/settings/proxies/health/route.ts b/src/app/api/settings/proxies/health/route.ts index e12c10a8b3..a23152f56f 100644 --- a/src/app/api/settings/proxies/health/route.ts +++ b/src/app/api/settings/proxies/health/route.ts @@ -1,4 +1,4 @@ -import { getProxyHealthStats } from "@/lib/localDb"; +import { getProxyHealthStats } from "@/lib/db/proxies"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/proxies/migrate/route.ts b/src/app/api/settings/proxies/migrate/route.ts index c90a194c45..5e09e2e868 100644 --- a/src/app/api/settings/proxies/migrate/route.ts +++ b/src/app/api/settings/proxies/migrate/route.ts @@ -1,4 +1,4 @@ -import { migrateLegacyProxyConfigToRegistry } from "@/lib/localDb"; +import { migrateLegacyProxyConfigToRegistry } from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { z } from "zod"; diff --git a/src/app/api/settings/proxies/pool/route.ts b/src/app/api/settings/proxies/pool/route.ts index 57894dd4a5..bb2f96a1a3 100644 --- a/src/app/api/settings/proxies/pool/route.ts +++ b/src/app/api/settings/proxies/pool/route.ts @@ -4,11 +4,8 @@ import { getScopeProxyPool, getScopeRotationStrategy, setScopeRotationStrategy, -} from "@/lib/localDb"; -import { - proxyPoolMemberSchema, - proxyRotationStrategySchema, -} from "@/shared/validation/schemas"; +} from "@/lib/db/proxies"; +import { proxyPoolMemberSchema, proxyRotationStrategySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; diff --git a/src/app/api/settings/proxies/route.ts b/src/app/api/settings/proxies/route.ts index 30854fa180..fe3153629d 100644 --- a/src/app/api/settings/proxies/route.ts +++ b/src/app/api/settings/proxies/route.ts @@ -1,4 +1,4 @@ -import { listProxies } from "@/lib/localDb"; +import { listProxies } from "@/lib/db/proxies"; import { handleProxyCreate, handleProxyDelete, diff --git a/src/app/api/settings/proxy/cloudflare-deploy/route.ts b/src/app/api/settings/proxy/cloudflare-deploy/route.ts index 7f8f7a1ef6..e4eef2081a 100644 --- a/src/app/api/settings/proxy/cloudflare-deploy/route.ts +++ b/src/app/api/settings/proxy/cloudflare-deploy/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { cloudflareDeploySchema } from "@/shared/validation/freeProxySchemas"; -import { createProxy } from "@/lib/localDb"; +import { createProxy } from "@/lib/db/proxies"; import { encrypt } from "@/lib/db/encryption"; import { buildCloudflareWorkerScript, diff --git a/src/app/api/settings/proxy/deno-deploy/route.ts b/src/app/api/settings/proxy/deno-deploy/route.ts index 0c277e5d62..3c10c59ca8 100644 --- a/src/app/api/settings/proxy/deno-deploy/route.ts +++ b/src/app/api/settings/proxy/deno-deploy/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { denoDeploySchema } from "@/shared/validation/freeProxySchemas"; -import { createProxy } from "@/lib/localDb"; +import { createProxy } from "@/lib/db/proxies"; import { encrypt } from "@/lib/db/encryption"; import { isPrivateRelayHostname } from "@/lib/proxyRelay/privateHostname"; diff --git a/src/app/api/settings/proxy/route.ts b/src/app/api/settings/proxy/route.ts index 0a8c1064b8..d05d0eb4c2 100755 --- a/src/app/api/settings/proxy/route.ts +++ b/src/app/api/settings/proxy/route.ts @@ -4,9 +4,8 @@ import { getProxyForLevel, deleteProxyForLevel, resolveProxyForConnection, - getProxyAssignments, - getProxyById, -} from "../../../../lib/localDb"; +} from "@/lib/db/settings"; +import { getProxyAssignments, getProxyById } from "@/lib/db/proxies"; import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher"; import { updateProxyConfigSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; @@ -68,9 +67,7 @@ function getRegistryScopeForLevel( return undefined; } - return PROXY_LEVEL_TO_REGISTRY_SCOPE[ - level as keyof typeof PROXY_LEVEL_TO_REGISTRY_SCOPE - ]; + return PROXY_LEVEL_TO_REGISTRY_SCOPE[level as keyof typeof PROXY_LEVEL_TO_REGISTRY_SCOPE]; } async function getRegistryProxyForLevel(level: string, id: string | null) { diff --git a/src/app/api/settings/proxy/test/route.ts b/src/app/api/settings/proxy/test/route.ts index c2ceef616b..8c4a5c0421 100644 --- a/src/app/api/settings/proxy/test/route.ts +++ b/src/app/api/settings/proxy/test/route.ts @@ -10,8 +10,8 @@ import { probeEchoTargets } from "@/lib/proxyEchoTarget"; import { testProxySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; -import { getProxyById } from "@/lib/localDb"; -import { extractRelayAuth } from "@/lib/db/proxies"; + +import { extractRelayAuth, getProxyById } from "@/lib/db/proxies"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { buildRelayTestResult } from "./relayTestResult"; diff --git a/src/app/api/settings/proxy/vercel-deploy/route.ts b/src/app/api/settings/proxy/vercel-deploy/route.ts index 6debb6e7e6..a510d8f969 100644 --- a/src/app/api/settings/proxy/vercel-deploy/route.ts +++ b/src/app/api/settings/proxy/vercel-deploy/route.ts @@ -3,7 +3,7 @@ import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { vercelDeploySchema } from "@/shared/validation/freeProxySchemas"; -import { createProxy } from "@/lib/localDb"; +import { createProxy } from "@/lib/db/proxies"; import { encrypt } from "@/lib/db/encryption"; // Shared SSRF-safe relay-path resolver — the same pure guard embedded in the // Deno Deploy worker. Both edge relays must enforce identical path validation, diff --git a/src/app/api/settings/qdrant/route.ts b/src/app/api/settings/qdrant/route.ts index eff1e2ab01..33f4f78e00 100644 --- a/src/app/api/settings/qdrant/route.ts +++ b/src/app/api/settings/qdrant/route.ts @@ -3,7 +3,7 @@ import { isAuthenticated } from "@/shared/utils/apiAuth"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { QdrantSettingsUpdateSchema } from "@/shared/schemas/qdrant"; import { getQdrantConfig, normalizeQdrantConfig } from "@/lib/memory/qdrant"; -import { updateSettings, getSettings } from "@/lib/localDb"; +import { updateSettings, getSettings } from "@/lib/db/settings"; import { invalidateMemorySettingsCache } from "@/lib/memory/settings"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; @@ -56,7 +56,7 @@ export async function PUT(request: NextRequest) { } catch { return NextResponse.json( { error: { message: "Invalid JSON body", details: [] } }, - { status: 400 }, + { status: 400 } ); } diff --git a/src/app/api/settings/quota-store/route.ts b/src/app/api/settings/quota-store/route.ts index 631d4eb220..f05ee8c4fd 100644 --- a/src/app/api/settings/quota-store/route.ts +++ b/src/app/api/settings/quota-store/route.ts @@ -23,7 +23,7 @@ import { NextResponse } from "next/server"; import { buildErrorBody } from "@omniroute/open-sse/utils/error"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { QuotaStoreSettingsSchema } from "@/shared/schemas/quota"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { logAuditEvent, getAuditRequestContext } from "@/lib/compliance/index"; import { resetQuotaStoreSingleton } from "@/lib/quota/QuotaStore"; diff --git a/src/app/api/settings/system-prompt/route.ts b/src/app/api/settings/system-prompt/route.ts index 63f1209ac2..01c958effb 100644 --- a/src/app/api/settings/system-prompt/route.ts +++ b/src/app/api/settings/system-prompt/route.ts @@ -3,7 +3,7 @@ import { setSystemPromptConfig, getSystemPromptConfig, } from "@omniroute/open-sse/services/systemPrompt.ts"; -import { updateSettings } from "@/lib/localDb"; +import { updateSettings } from "@/lib/db/settings"; import { updateSystemPromptSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/settings/thinking-budget/route.ts b/src/app/api/settings/thinking-budget/route.ts index 5f2d5932bc..485ea73247 100644 --- a/src/app/api/settings/thinking-budget/route.ts +++ b/src/app/api/settings/thinking-budget/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { setThinkingBudgetConfig, getThinkingBudgetConfig, diff --git a/src/app/api/skills/collect/chaos/route.ts b/src/app/api/skills/collect/chaos/route.ts index 04268993ec..35075ced5e 100644 --- a/src/app/api/skills/collect/chaos/route.ts +++ b/src/app/api/skills/collect/chaos/route.ts @@ -27,7 +27,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { validateApiKey, getApiKeyMetadata } from "@/lib/localDb"; +import { validateApiKey, getApiKeyMetadata } from "@/lib/db/apiKeys"; import { getChaosConfig } from "@/lib/chaos/chaosConfig"; import { executeChaosRun, type ChaosRunResult } from "@/lib/chaos/chaosExecutor"; import * as log from "@/sse/utils/logger"; @@ -122,7 +122,10 @@ export async function POST(request: Request) { const globalConfig = await getChaosConfig(); if (!globalConfig.enabled) { return NextResponse.json( - buildErrorBody(400, "Chaos Mode is not enabled globally. Enable it in Dashboard → Chaos Mode."), + buildErrorBody( + 400, + "Chaos Mode is not enabled globally. Enable it in Dashboard → Chaos Mode." + ), { status: 400 } ); } diff --git a/src/app/api/sync/cloud/route.ts b/src/app/api/sync/cloud/route.ts index 2f37d15a9d..44890fc510 100644 --- a/src/app/api/sync/cloud/route.ts +++ b/src/app/api/sync/cloud/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; -import { getApiKeys, createApiKey, pickApiKeyForInternalUse, updateSettings } from "@/lib/localDb"; +import { getApiKeys, createApiKey, pickApiKeyForInternalUse } from "@/lib/db/apiKeys"; +import { updateSettings } from "@/lib/db/settings"; import { getConsistentMachineId } from "@/shared/utils/machineId"; import { syncToCloud, fetchWithTimeout, CLOUD_URL } from "@/lib/cloudSync"; import fs from "fs/promises"; @@ -53,7 +54,10 @@ export async function GET() { return NextResponse.json({ enabled: true, connected: false }); } } catch (error: unknown) { - return NextResponse.json({ enabled: false, error: sanitizeErrorMessage(error) }, { status: 500 }); + return NextResponse.json( + { enabled: false, error: sanitizeErrorMessage(error) }, + { status: 500 } + ); } } @@ -67,7 +71,12 @@ export async function POST(request: any) { rawBody = await request.json(); } catch { return NextResponse.json( - { error: { message: "Invalid request", details: [{ field: "body", message: "Invalid JSON body" }] } }, + { + error: { + message: "Invalid request", + details: [{ field: "body", message: "Invalid JSON body" }], + }, + }, { status: 400 } ); } @@ -92,7 +101,10 @@ export async function POST(request: any) { } // Sync first — only enable if sync succeeds const enableResult = await syncAndVerify(machineId, createdKey?.key, keys); - const enableBody = await enableResult.clone().json().catch(() => ({})); + const enableBody = await enableResult + .clone() + .json() + .catch(() => ({})); // Only persist cloudEnabled if sync succeeded (body.success exists) if (enableBody.success) { await updateSettings({ cloudEnabled: true }); @@ -125,10 +137,7 @@ async function syncAndVerify(machineId: string, createdKey: any, existingKeys: a // Step 1: Sync data to cloud const syncResult: any = await syncToCloud(machineId, createdKey); if (syncResult.error) { - return NextResponse.json( - { error: `Cloud sync failed: ${syncResult.error}` }, - { status: 502 } - ); + return NextResponse.json({ error: `Cloud sync failed: ${syncResult.error}` }, { status: 502 }); } // Build the cloud URL for the frontend to use diff --git a/src/app/api/token-health/route.ts b/src/app/api/token-health/route.ts index b1176aefd3..b5e62e28f3 100644 --- a/src/app/api/token-health/route.ts +++ b/src/app/api/token-health/route.ts @@ -5,7 +5,7 @@ * Used by TokenHealthBadge in the Header. */ -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; export async function GET() { diff --git a/src/app/api/tools/agent-bridge/server/route.ts b/src/app/api/tools/agent-bridge/server/route.ts index bffa281380..b53aee0703 100644 --- a/src/app/api/tools/agent-bridge/server/route.ts +++ b/src/app/api/tools/agent-bridge/server/route.ts @@ -18,7 +18,7 @@ import { import path from "path"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; import { createErrorResponse } from "@/lib/api/errorResponse"; -import { pickApiKeyForInternalUse } from "@/lib/localDb"; +import { pickApiKeyForInternalUse } from "@/lib/db/apiKeys"; /** * Resolve the OmniRoute API key the spawned MITM child (`server.cjs`) uses to @@ -69,7 +69,9 @@ export async function POST(request: Request): Promise { try { if (action === "start") { const suppliedPassword = - typeof raw.sudoPassword === "string" ? normalizeMitmSudoPasswordInput(raw.sudoPassword) : ""; + typeof raw.sudoPassword === "string" + ? normalizeMitmSudoPasswordInput(raw.sudoPassword) + : ""; if (suppliedPassword) setCachedPassword(suppliedPassword); const apiKey = await resolveRouterApiKey(rawApiKey); const { startMitm } = await import("@/mitm/manager.runtime"); @@ -106,7 +108,9 @@ export async function POST(request: Request): Promise { const result = await installCertResult(sudoPassword, certPath); if (result.installed) { const suppliedPassword = - typeof raw.sudoPassword === "string" ? normalizeMitmSudoPasswordInput(raw.sudoPassword) : ""; + typeof raw.sudoPassword === "string" + ? normalizeMitmSudoPasswordInput(raw.sudoPassword) + : ""; if (process.platform !== "win32" && suppliedPassword) { setCachedPassword(suppliedPassword); } diff --git a/src/app/api/translator/send/route.ts b/src/app/api/translator/send/route.ts index 3678509fd5..3dd51fb880 100644 --- a/src/app/api/translator/send/route.ts +++ b/src/app/api/translator/send/route.ts @@ -5,7 +5,7 @@ import { detectFormat, getTargetFormat, } from "@omniroute/open-sse/services/provider.ts"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { toJsonErrorPayload } from "@/shared/utils/upstreamError"; import { logTranslationEvent } from "@/lib/translatorEvents"; diff --git a/src/app/api/translator/translate/route.ts b/src/app/api/translator/translate/route.ts index 8c14ea33a4..37b20df450 100644 --- a/src/app/api/translator/translate/route.ts +++ b/src/app/api/translator/translate/route.ts @@ -7,7 +7,7 @@ import { } from "@omniroute/open-sse/services/provider.ts"; import { translateRequest } from "@omniroute/open-sse/translator/index.ts"; import { FORMATS } from "@omniroute/open-sse/translator/formats.ts"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { translatorTranslateSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/app/api/usage/quota/route.ts b/src/app/api/usage/quota/route.ts index 346c439478..cae0e7becb 100644 --- a/src/app/api/usage/quota/route.ts +++ b/src/app/api/usage/quota/route.ts @@ -1,5 +1,5 @@ import { NextResponse } from "next/server"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { getLearnedLimits, getRateLimitStatus, diff --git a/src/app/api/usage/token-limits/route.ts b/src/app/api/usage/token-limits/route.ts index 18d5ab664e..5eb490e7fb 100644 --- a/src/app/api/usage/token-limits/route.ts +++ b/src/app/api/usage/token-limits/route.ts @@ -13,14 +13,15 @@ import { NextResponse } from "next/server"; import { setTokenLimitSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { buildErrorBody, sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; + import { + TokenLimit, listTokenLimits, upsertTokenLimit, deleteTokenLimit, getWindowUsage, resetWindowIfElapsed, -} from "@/lib/localDb"; -import type { TokenLimit } from "@/lib/db/tokenLimits"; +} from "@/lib/db/tokenLimits"; export async function GET(request: Request) { try { diff --git a/src/app/api/v1/_helpers/apiKeyScope.ts b/src/app/api/v1/_helpers/apiKeyScope.ts index 18eeb04c6b..4d238a7818 100644 --- a/src/app/api/v1/_helpers/apiKeyScope.ts +++ b/src/app/api/v1/_helpers/apiKeyScope.ts @@ -1,4 +1,4 @@ -import { getApiKeyMetadata } from "@/lib/localDb"; +import { getApiKeyMetadata } from "@/lib/db/apiKeys"; import { extractApiKey } from "@/sse/services/auth"; import { isDashboardSessionAuthenticated } from "@/shared/utils/apiAuth"; diff --git a/src/app/api/v1/audio/transcriptions/route.ts b/src/app/api/v1/audio/transcriptions/route.ts index c059098bc0..0a8ce034ae 100644 --- a/src/app/api/v1/audio/transcriptions/route.ts +++ b/src/app/api/v1/audio/transcriptions/route.ts @@ -24,7 +24,8 @@ import { } from "@/app/api/v1/_shared/rateLimit"; import { attachOmniRouteMetaToResponse } from "@/domain/omnirouteResponseMeta"; import { generateRequestId } from "@/shared/utils/requestId"; -import { getComboByName, getCombos, getDatabaseSettings } from "@/lib/localDb"; +import { getComboByName, getCombos } from "@/lib/db/combos"; +import { getDatabaseSettings } from "@/lib/db/databaseSettings"; import { handleComboChat } from "@omniroute/open-sse/services/combo.ts"; import { log } from "@omniroute/open-sse/utils/logger.ts"; diff --git a/src/app/api/v1/batches/[id]/cancel/route.ts b/src/app/api/v1/batches/[id]/cancel/route.ts index fec9aa39c9..3222f0f0d8 100644 --- a/src/app/api/v1/batches/[id]/cancel/route.ts +++ b/src/app/api/v1/batches/[id]/cancel/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getBatch, updateBatch } from "@/lib/localDb"; +import { getBatch, updateBatch } from "@/lib/db/batches"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; import { formatBatchResponse } from "../../formatBatchResponse"; diff --git a/src/app/api/v1/batches/[id]/route.ts b/src/app/api/v1/batches/[id]/route.ts index a536902a78..7ce3867906 100644 --- a/src/app/api/v1/batches/[id]/route.ts +++ b/src/app/api/v1/batches/[id]/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getBatch, deleteBatch } from "@/lib/localDb"; +import { getBatch, deleteBatch } from "@/lib/db/batches"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; import { formatBatchResponse } from "../formatBatchResponse"; diff --git a/src/app/api/v1/batches/delete-completed/route.ts b/src/app/api/v1/batches/delete-completed/route.ts index 794f96cad4..0659253e09 100644 --- a/src/app/api/v1/batches/delete-completed/route.ts +++ b/src/app/api/v1/batches/delete-completed/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { deleteCompletedBatches } from "@/lib/localDb"; +import { deleteCompletedBatches } from "@/lib/db/batches"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/batches/route.ts b/src/app/api/v1/batches/route.ts index 9b2be67f6c..f64a46d972 100644 --- a/src/app/api/v1/batches/route.ts +++ b/src/app/api/v1/batches/route.ts @@ -1,5 +1,6 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { createBatch, getFile, listBatches, countBatches } from "@/lib/localDb"; +import { createBatch, listBatches, countBatches } from "@/lib/db/batches"; +import { getFile } from "@/lib/db/files"; import { v1BatchCreateSchema } from "@/shared/validation/schemas"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/combos/route.ts b/src/app/api/v1/combos/route.ts index 91a4a4fb34..5c84d13e16 100644 --- a/src/app/api/v1/combos/route.ts +++ b/src/app/api/v1/combos/route.ts @@ -8,7 +8,7 @@ * routing details (account/connection ids, weights, internal labels). */ import { NextResponse } from "next/server"; -import { getCombos } from "@/lib/localDb"; +import { getCombos } from "@/lib/db/combos"; import { errorResponse } from "@omniroute/open-sse/utils/error.ts"; import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts"; import { extractApiKey, isValidApiKey } from "@/sse/services/auth"; diff --git a/src/app/api/v1/files/[id]/content/route.ts b/src/app/api/v1/files/[id]/content/route.ts index 7e7fc79c1a..33bf4fdab4 100644 --- a/src/app/api/v1/files/[id]/content/route.ts +++ b/src/app/api/v1/files/[id]/content/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getFile, getFileContent } from "@/lib/localDb"; +import { getFile, getFileContent } from "@/lib/db/files"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/files/[id]/route.ts b/src/app/api/v1/files/[id]/route.ts index 66565e52ec..953903cca4 100644 --- a/src/app/api/v1/files/[id]/route.ts +++ b/src/app/api/v1/files/[id]/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { getFile, deleteFile, formatFileResponse } from "@/lib/localDb"; +import { getFile, deleteFile, formatFileResponse } from "@/lib/db/files"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/files/route.ts b/src/app/api/v1/files/route.ts index 2b9d02e49a..4550755a15 100644 --- a/src/app/api/v1/files/route.ts +++ b/src/app/api/v1/files/route.ts @@ -1,5 +1,5 @@ import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors"; -import { createFile, listFiles, formatFileResponse, countFiles } from "@/lib/localDb"; +import { createFile, listFiles, formatFileResponse, countFiles } from "@/lib/db/files"; import { NextResponse } from "next/server"; import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope"; diff --git a/src/app/api/v1/images/edits/route.ts b/src/app/api/v1/images/edits/route.ts index b70c747276..63da01ed1b 100644 --- a/src/app/api/v1/images/edits/route.ts +++ b/src/app/api/v1/images/edits/route.ts @@ -30,7 +30,7 @@ import { validateCodexImageEditReferences, } from "@/lib/images/imageRouteModel"; import { isMicrosoftDesignerWebProviderRetiredError } from "@/shared/constants/designerWebRetirement"; -import { resolveProxyForConnection } from "@/lib/localDb"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { isCodexFreePlan } from "@omniroute/open-sse/executors/codex/tools.ts"; import { diff --git a/src/app/api/v1/management/proxies/assignments/route.ts b/src/app/api/v1/management/proxies/assignments/route.ts index 1653952827..c082347b25 100644 --- a/src/app/api/v1/management/proxies/assignments/route.ts +++ b/src/app/api/v1/management/proxies/assignments/route.ts @@ -1,4 +1,5 @@ -import { assignProxyToScope, getProxyAssignments, resolveProxyForConnection } from "@/lib/localDb"; +import { assignProxyToScope, getProxyAssignments } from "@/lib/db/proxies"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { proxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/v1/management/proxies/bulk-assign/route.ts b/src/app/api/v1/management/proxies/bulk-assign/route.ts index 32a3434d23..5c8278b4a1 100644 --- a/src/app/api/v1/management/proxies/bulk-assign/route.ts +++ b/src/app/api/v1/management/proxies/bulk-assign/route.ts @@ -1,4 +1,4 @@ -import { bulkAssignProxyToScope } from "@/lib/localDb"; +import { bulkAssignProxyToScope } from "@/lib/db/proxies"; import { bulkProxyAssignmentSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; diff --git a/src/app/api/v1/management/proxies/health/route.ts b/src/app/api/v1/management/proxies/health/route.ts index e12c10a8b3..a23152f56f 100644 --- a/src/app/api/v1/management/proxies/health/route.ts +++ b/src/app/api/v1/management/proxies/health/route.ts @@ -1,4 +1,4 @@ -import { getProxyHealthStats } from "@/lib/localDb"; +import { getProxyHealthStats } from "@/lib/db/proxies"; import { createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; diff --git a/src/app/api/v1/management/proxies/route.ts b/src/app/api/v1/management/proxies/route.ts index 2eaccbd496..336fadeecc 100644 --- a/src/app/api/v1/management/proxies/route.ts +++ b/src/app/api/v1/management/proxies/route.ts @@ -1,4 +1,4 @@ -import { listProxies } from "@/lib/localDb"; +import { listProxies } from "@/lib/db/proxies"; import { handleProxyCreate, handleProxyDelete, diff --git a/src/app/api/v1/me/status/route.ts b/src/app/api/v1/me/status/route.ts index 2cae418d83..c41fd3df73 100644 --- a/src/app/api/v1/me/status/route.ts +++ b/src/app/api/v1/me/status/route.ts @@ -18,7 +18,7 @@ export async function GET(request: Request) { const apiKey = extractBearerToken(request); if (!apiKey) return authError(401); - const { validateApiKey, getApiKeyMetadata } = await import("@/lib/localDb"); + const { validateApiKey, getApiKeyMetadata } = await import("@/lib/db/apiKeys"); const valid = await validateApiKey(apiKey); if (!valid) return authError(401); diff --git a/src/app/api/v1/models/catalog.ts b/src/app/api/v1/models/catalog.ts index 62644fc6b5..2abcdd7070 100644 --- a/src/app/api/v1/models/catalog.ts +++ b/src/app/api/v1/models/catalog.ts @@ -1,14 +1,7 @@ import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models"; import { NOAUTH_PROVIDERS } from "@/shared/constants/providers"; -import { - getCachedRawProviderConnections, - getCombos, - getAllCustomModels, - getSettings, - getCachedProviderNodes, - getModelAliases, - getHiddenModelsByProvider, -} from "@/lib/localDb"; +import { getCombos } from "@/lib/db/combos"; +import { getSettings } from "@/lib/db/settings"; import { getUserDatabaseSettings } from "@/lib/db/databaseSettings"; import { createLazyConnectionView } from "@/lib/db/providers/lazyConnectionView"; import { extractAliasBackedModels } from "./aliasBackedModels"; @@ -49,9 +42,16 @@ import { getSyncedAvailableModelsByConnection, SYNCED_AVAILABLE_MODELS_MALFORMED, type SyncedAvailableModel, + getAllCustomModels, + getModelAliases, + getHiddenModelsByProvider, } from "@/lib/db/models"; import { getAllActiveSyncedModels } from "@/lib/db/models/activeSyncedCatalog"; -import { getModelCatalogCacheVersion } from "@/lib/db/readCache"; +import { + getModelCatalogCacheVersion, + getCachedRawProviderConnections, + getCachedProviderNodes, +} from "@/lib/db/readCache"; import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels"; import { providerUsesCuratedModelsOnly, @@ -342,7 +342,12 @@ async function buildUnifiedModelsResponseCore( // explicitly — a disabled router rejects every auto/* id with a 400, so // listing them offers the client a choice that cannot succeed. const hideAuto = settings.hideAutoCombos === true || settings.autoRoutingEnabled === false; - const shouldHidePaid = (providerKey: string, modelId: string, pricing?: unknown, isFree?: boolean): boolean => { + const shouldHidePaid = ( + providerKey: string, + modelId: string, + pricing?: unknown, + isFree?: boolean + ): boolean => { if (!hidePaid) return false; const provider = aliasToProviderId[providerKey] || providerKey; // isFree:true is the first door — custom row kept even when its provider is outside FREE_MODEL_BUDGETS. @@ -1590,7 +1595,12 @@ async function buildUnifiedModelsResponseCore( // Custom entries do not carry pricing, so shouldHidePaid() decides // via FREE_MODEL_IDS_BY_PROVIDER — matches synced/PROVIDER_MODELS. if ( - shouldHidePaid(canonicalProviderId, modelId, (model as { pricing?: unknown }).pricing, (model as any).isFree) + shouldHidePaid( + canonicalProviderId, + modelId, + (model as { pricing?: unknown }).pricing, + (model as any).isFree + ) ) continue; if (shouldHideByExposure(canonicalProviderId, modelId)) continue; diff --git a/src/app/api/v1beta/models/route.ts b/src/app/api/v1beta/models/route.ts index a82719a12b..bf9ddec175 100644 --- a/src/app/api/v1beta/models/route.ts +++ b/src/app/api/v1beta/models/route.ts @@ -5,7 +5,7 @@ import { getAllSyncedAvailableModels, getSyncedAvailableModels, } from "@/lib/db/models"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { getResolvedModelCapabilities } from "@/lib/modelCapabilities"; import { getSyncedCapabilities } from "@/lib/modelsDevSync"; import { mergeCustomModelMetadata } from "@/lib/providers/modelMetadataPrecedence"; diff --git a/src/app/api/webhooks/[id]/deliveries/route.ts b/src/app/api/webhooks/[id]/deliveries/route.ts index 0f7aee3516..1723c2eaf2 100644 --- a/src/app/api/webhooks/[id]/deliveries/route.ts +++ b/src/app/api/webhooks/[id]/deliveries/route.ts @@ -5,7 +5,8 @@ import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhook, getDeliveries } from "@/lib/localDb"; +import { getWebhook } from "@/lib/db/webhooks"; +import { getDeliveries } from "@/lib/db/webhookDeliveries"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) { diff --git a/src/app/api/webhooks/[id]/route.ts b/src/app/api/webhooks/[id]/route.ts index 98fc6e79c7..fab9daac12 100644 --- a/src/app/api/webhooks/[id]/route.ts +++ b/src/app/api/webhooks/[id]/route.ts @@ -8,7 +8,7 @@ import { z } from "zod"; import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhook, updateWebhookRecord, deleteWebhook } from "@/lib/localDb"; +import { getWebhook, updateWebhook as updateWebhookRecord, deleteWebhook } from "@/lib/db/webhooks"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { encryptMetadata } from "@/lib/webhookDispatcher"; diff --git a/src/app/api/webhooks/[id]/test/route.ts b/src/app/api/webhooks/[id]/test/route.ts index 7e81f9e9c6..618bf49836 100644 --- a/src/app/api/webhooks/[id]/test/route.ts +++ b/src/app/api/webhooks/[id]/test/route.ts @@ -5,14 +5,14 @@ import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhook } from "@/lib/localDb"; +import { getWebhook, recordWebhookDelivery } from "@/lib/db/webhooks"; import { decryptMetadata } from "@/lib/webhookDispatcher"; import { buildSlackPayload } from "@/lib/webhooks/integrations/slack"; import { buildTelegramUrl, buildTelegramPayload } from "@/lib/webhooks/integrations/telegram"; import { buildDiscordPayload } from "@/lib/webhooks/integrations/discord"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { insertDelivery } from "@/lib/db/webhookDeliveries"; -import { recordWebhookDelivery } from "@/lib/localDb"; +import { getWebhook, recordWebhookDelivery } from "@/lib/db/webhooks"; import { isPrivateHost, OutboundUrlGuardError } from "@/shared/network/outboundUrlGuard"; import { parseAndValidateWebhookUrl } from "@/shared/network/outboundUrlGuardPolicy"; import crypto from "crypto"; diff --git a/src/app/api/webhooks/route.ts b/src/app/api/webhooks/route.ts index 2cb018e13f..c31b5c71fa 100644 --- a/src/app/api/webhooks/route.ts +++ b/src/app/api/webhooks/route.ts @@ -7,7 +7,7 @@ import { z } from "zod"; import { NextResponse } from "next/server"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error"; -import { getWebhooks, createWebhook } from "@/lib/localDb"; +import { getWebhooks, createWebhook } from "@/lib/db/webhooks"; import { validateBody, isValidationFailure } from "@/shared/validation/helpers"; import { requireManagementAuth } from "@/lib/api/requireManagementAuth"; import { encryptMetadata } from "@/lib/webhookDispatcher"; diff --git a/src/app/globals.css b/src/app/globals.css index 939fc9ada7..5a2b558236 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,4 @@ -@import "tailwindcss"; +@import "tailwindcss" source(none); @import "fumadocs-ui/css/neutral.css"; @import "fumadocs-ui/css/preset.css"; /* Self-hosted Material Symbols icon font (#3695): the Google Fonts CDN @@ -10,14 +10,12 @@ @import "material-symbols/outlined.css"; @source "../../node_modules/fumadocs-ui/css/generated/*.css"; -/* Tailwind v4 auto-detection cannot scan directories with parentheses - (e.g. Next.js route groups like "(dashboard)"). Explicit @source - directives ensure all utility classes in route groups are included. */ -@source "../app/(dashboard)"; +/* Keep Tailwind v4 from scanning the entire repository. The UI lives in the + App Router and shared component trees; third-party Fumadocs sources remain + explicit because node_modules is ignored by automatic detection. */ +@source "../app"; +@source "../shared"; @source "../../node_modules/fumadocs-ui/dist/**/*.js"; -@source not "../../*.sqlite*"; -@source not "../../.claude*"; -@source not "../../.claude-memory"; @custom-variant dark (&:where(.dark, .dark *)); diff --git a/src/domain/quotaCache.ts b/src/domain/quotaCache.ts index 3d5cf1a2e5..377512ee13 100644 --- a/src/domain/quotaCache.ts +++ b/src/domain/quotaCache.ts @@ -17,7 +17,8 @@ */ import { getUsageForProvider } from "@omniroute/open-sse/services/usage.ts"; -import { getCachedProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { runWithProxyContext } from "@omniroute/open-sse/utils/proxyFetch.ts"; import { safePercentage } from "@/shared/utils/formatting"; import { diff --git a/src/lib/a2a/skills/providerDiscovery.ts b/src/lib/a2a/skills/providerDiscovery.ts index bec8a116ac..75c22dc7c2 100644 --- a/src/lib/a2a/skills/providerDiscovery.ts +++ b/src/lib/a2a/skills/providerDiscovery.ts @@ -120,7 +120,7 @@ export interface ProviderDiscoveryResult { export async function executeProviderDiscovery(task: A2ATask): Promise { const [{ getProviderConnections }, { getAllCircuitBreakerStatuses }] = await Promise.all([ - import("@/lib/localDb"), + import("@/lib/db/providers"), import("@/shared/utils/circuitBreaker"), ]); diff --git a/src/lib/api/proxyRegistryRouteHandlers.ts b/src/lib/api/proxyRegistryRouteHandlers.ts index b5b2e9bf4e..c6d3c7ea23 100644 --- a/src/lib/api/proxyRegistryRouteHandlers.ts +++ b/src/lib/api/proxyRegistryRouteHandlers.ts @@ -6,7 +6,7 @@ import { getProxyWhereUsed, updateProxy, updateProxyAndAssign, -} from "@/lib/localDb"; +} from "@/lib/db/proxies"; import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse"; import { createProxyRegistrySchema, updateProxyRegistrySchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; diff --git a/src/lib/cloudSync.ts b/src/lib/cloudSync.ts index c26459f71d..48084d0025 100644 --- a/src/lib/cloudSync.ts +++ b/src/lib/cloudSync.ts @@ -1,5 +1,5 @@ import crypto from "crypto"; -import { getProviderConnections, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; import { buildConfigSyncEnvelope, toLegacyCloudSyncPayload } from "@/lib/sync/bundle"; const CLOUD_URL = process.env.CLOUD_URL || process.env.NEXT_PUBLIC_CLOUD_URL; diff --git a/src/lib/combos/builderOptions.ts b/src/lib/combos/builderOptions.ts index 625356ab70..0f1cb529cc 100644 --- a/src/lib/combos/builderOptions.ts +++ b/src/lib/combos/builderOptions.ts @@ -1,12 +1,7 @@ -import { - getAllCustomModels, - getAllSyncedAvailableModels, - getCombos, - getModelIsHidden, - getProviderConnections, - getProviderNodes, - getSettings, -} from "@/lib/localDb"; +import { getAllCustomModels, getAllSyncedAvailableModels, getModelIsHidden } from "@/lib/db/models"; +import { getCombos } from "@/lib/db/combos"; +import { getProviderConnections, getProviderNodes } from "@/lib/db/providers"; +import { getSettings } from "@/lib/db/settings"; import { getAccountDisplayName, getProviderDisplayName } from "@/lib/display/names"; import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels"; import { getResolvedModelCapabilities } from "@/lib/modelCapabilities"; diff --git a/src/lib/config/runtimeSettings.ts b/src/lib/config/runtimeSettings.ts index bf615749ba..0cb93c8fee 100644 --- a/src/lib/config/runtimeSettings.ts +++ b/src/lib/config/runtimeSettings.ts @@ -21,6 +21,7 @@ export type RuntimeReloadSection = | "corsOrigins" | "ccBridgeTransforms" | "systemTransforms" + | "systemPrompt" | "authzBypass" | "bannedSignals"; @@ -48,6 +49,7 @@ interface RuntimeSettingsSnapshot { corsOrigins: string; ccBridgeTransforms: unknown; systemTransforms: unknown; + systemPrompt: unknown; authzBypass: AuthzBypassSnapshot; customBannedSignals: string[]; providerErrorRules: Record | null; @@ -75,6 +77,7 @@ const DEFAULT_RUNTIME_SETTINGS_SNAPSHOT: RuntimeSettingsSnapshot = { corsOrigins: "", ccBridgeTransforms: null, systemTransforms: null, + systemPrompt: null, authzBypass: DEFAULT_AUTHZ_BYPASS_SNAPSHOT, customBannedSignals: [], providerErrorRules: null, @@ -93,7 +96,6 @@ function isTruthyEnvFlag(value: string | undefined): boolean { return new Set(["1", "true", "yes", "on"]).has(value.trim().toLowerCase()); } - function toRecord(value: unknown): JsonRecord { return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; } @@ -276,6 +278,9 @@ export function buildRuntimeSettingsSnapshot( corsOrigins: typeof settings.corsOrigins === "string" ? settings.corsOrigins : "", ccBridgeTransforms: parseStoredJson(settings.ccBridgeTransforms, "ccBridgeTransforms"), systemTransforms: parseStoredJson(settings.systemTransforms, "systemTransforms"), + systemPrompt: settings.systemPrompt + ? parseStoredJson(settings.systemPrompt, "systemPrompt") + : null, authzBypass: normalizeAuthzBypass(settings), customBannedSignals: normalizeStringArray(settings.customBannedSignals), providerErrorRules: normalizeOperatorProviderErrorRules(settings.providerErrorRules), @@ -404,6 +409,21 @@ async function applySystemTransformsSection(systemTransforms: unknown) { setSystemTransformsConfig(systemTransforms); } +async function applySystemPromptSection(systemPrompt: unknown) { + const { setSystemPromptConfig } = await import("@omniroute/open-sse/services/systemPrompt.ts"); + + if (systemPrompt && typeof systemPrompt === "object") { + setSystemPromptConfig(systemPrompt as Record); + } else { + setSystemPromptConfig({ + enabled: false, + prefixPrompt: "", + suffixPrompt: "", + prompt: "", + }); + } +} + async function applyModelsDevSyncSection( previousSnapshot: RuntimeSettingsSnapshot, currentSnapshot: RuntimeSettingsSnapshot, @@ -562,6 +582,11 @@ export async function applyRuntimeSettings( markChanged("systemTransforms"); } + if (force || hasChanged(currentSnapshot.systemPrompt, previousSnapshot.systemPrompt)) { + await applySystemPromptSection(currentSnapshot.systemPrompt); + markChanged("systemPrompt"); + } + if (force || hasChanged(currentSnapshot.authzBypass, previousSnapshot.authzBypass)) { applyAuthzBypassSection(currentSnapshot.authzBypass); markChanged("authzBypass"); diff --git a/src/lib/container.ts b/src/lib/container.ts index f3aa1af3f8..2f59af30e5 100644 --- a/src/lib/container.ts +++ b/src/lib/container.ts @@ -23,7 +23,7 @@ import { encrypt, encryptConnectionFields, } from "./db/encryption.ts"; -import { getSettings } from "./localDb.ts"; +import { getSettings } from "@/lib/db/settings"; import { getCircuitBreaker } from "../shared/utils/circuitBreaker.ts"; import { recordTelemetry, RequestTelemetry } from "../shared/utils/requestTelemetry.ts"; diff --git a/src/lib/copilot/systemPrompt.ts b/src/lib/copilot/systemPrompt.ts index 442e779394..d8626bab78 100644 --- a/src/lib/copilot/systemPrompt.ts +++ b/src/lib/copilot/systemPrompt.ts @@ -64,7 +64,7 @@ Client → API Route (/v1/chat/completions) - \`src/lib/db/\`: 45+ domain-specific modules - \`core.ts\`: Singleton better-sqlite3 with WAL journaling - \`migrationRunner.ts\`: Versioned SQL migrations (55+ files) -- \`localDb.ts\`: Re-export layer only — no logic +- \`src/lib/db/*\`: domain modules only — no barrel layer ### Key Modules - **open-sse/**: Core streaming engine (handlers, executors, translator) diff --git a/src/lib/credentialHealth/scheduler.ts b/src/lib/credentialHealth/scheduler.ts index 62b2d41f4a..15fb56c876 100644 --- a/src/lib/credentialHealth/scheduler.ts +++ b/src/lib/credentialHealth/scheduler.ts @@ -18,7 +18,7 @@ */ import { testSingleConnection } from "@/app/api/providers/[id]/test/route"; -import { getProviderConnections } from "@/lib/localDb"; +import { getProviderConnections } from "@/lib/db/providers"; import { setCredentialHealth, removeCredentialHealth, diff --git a/src/lib/db/AGENTS.md b/src/lib/db/AGENTS.md index 4b5a3eb545..c133d0b90b 100644 --- a/src/lib/db/AGENTS.md +++ b/src/lib/db/AGENTS.md @@ -11,7 +11,7 @@ Live count: `ls src/lib/db/*.ts | wc -l` (currently 117). Migrations: `ls src/li - **`core.ts`** — `getDbInstance()` returns singleton `better-sqlite3` with WAL journaling. Exports `rowToCamel()` (snake_case → camelCase), `encryptConnectionFields()` for provider credentials at rest. `SCHEMA_SQL` defines **17 base tables** (verify: `grep -c "CREATE TABLE" src/lib/db/core.ts` minus 1 for `_omniroute_migrations`). - **`migrationRunner.ts`** — Applies versioned SQL files from `db/migrations/` inside transactions. Tracks applied migrations in `_omniroute_migrations`. Each migration is idempotent. - **`db/migrations/`** — 148 SQL files (`001_initial_schema.sql` → `153_radar_local_model_state.sql`; numbering has intentional gaps). Each runs in a transaction, never fails partially. -- **`localDb.ts`** — Re-export layer only. Never add logic here. +- The old `localDb.ts` barrel has been removed — consumers must import from the owning named module below. ## Key Domain Modules @@ -56,15 +56,13 @@ Full list: `ls src/lib/db/*.ts | wc -l` (115 files). Drift detection: `npm run c ## Adding a New Domain Module 1. Create `src/lib/db/[module].ts` with CRUD functions -2. Export from `src/lib/localDb.ts` (add re-export) -3. If new tables: create migration in `db/migrations/NNN_[description].sql` -4. Migration runs automatically at startup via `migrationRunner.ts` -5. Add unit tests in `tests/unit/db/` +2. If new tables: create migration in `db/migrations/NNN_[description].sql` +3. Migration runs automatically at startup via `migrationRunner.ts` +4. Add unit tests in `tests/unit/db/` ## Anti-Patterns - Raw SQL in routes — always use domain module functions - Direct `prepare()` statements outside `db/` — breaks modularity -- Adding logic to `localDb.ts` — re-export layer only - Barrel-importing from `localDb.ts` — import specific modules instead - Skipping migrations for schema changes — all changes go through `db/migrations/` diff --git a/src/lib/db/apiKeyGroups.ts b/src/lib/db/apiKeyGroups.ts index 584ee628dc..ad1395e1f5 100644 --- a/src/lib/db/apiKeyGroups.ts +++ b/src/lib/db/apiKeyGroups.ts @@ -72,7 +72,7 @@ export function createKeyGroup(name: string, description = ""): KeyGroup { const now = new Date().toISOString(); db.prepare( - "INSERT INTO key_groups (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", + "INSERT INTO key_groups (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)" ).run(id, name, description, now, now); return getKeyGroup(id)!; @@ -80,7 +80,7 @@ export function createKeyGroup(name: string, description = ""): KeyGroup { export function updateKeyGroup( id: string, - updates: { name?: string; description?: string; isActive?: boolean }, + updates: { name?: string; description?: string; isActive?: boolean } ): KeyGroup | undefined { const existing = getKeyGroup(id); if (!existing) return undefined; @@ -132,7 +132,7 @@ export function getGroupPermissions(groupId: string): GroupModelPermission[] { const db = getDbInstance() as any; const rows = db .prepare( - "SELECT * FROM group_model_permissions WHERE group_id = ? ORDER BY access_type ASC, model_pattern ASC", + "SELECT * FROM group_model_permissions WHERE group_id = ? ORDER BY access_type ASC, model_pattern ASC" ) .all(groupId) as any[]; return rows.map(rowToPermission); @@ -142,7 +142,7 @@ export function addGroupPermission( groupId: string, modelPattern: string, accessType: "allow" | "deny", - provider?: string, + provider?: string ): GroupModelPermission { const db = getDbInstance() as any; const id = randomUUID(); @@ -150,7 +150,7 @@ export function addGroupPermission( const result = db .prepare( - "INSERT INTO group_model_permissions (id, group_id, model_pattern, provider, access_type, created_at) VALUES (?, ?, ?, ?, ?, ?)", + "INSERT INTO group_model_permissions (id, group_id, model_pattern, provider, access_type, created_at) VALUES (?, ?, ?, ?, ?, ?)" ) .run(id, groupId, modelPattern, provider || null, accessType, now); @@ -169,17 +169,6 @@ export function removeGroupPermission(permissionId: string): boolean { } return result.changes > 0; } - -export function clearGroupPermissions(groupId: string): void { - const db = getDbInstance() as any; - const result = db.prepare("DELETE FROM group_model_permissions WHERE group_id = ?").run(groupId); - if (result.changes > 0) { - invalidateModelCatalogCache(); - } -} - -// ── Key Group Members ──────────────────────────────────────────────────── - export function getGroupMembers(groupId: string): KeyGroupMember[] { const db = getDbInstance() as any; const rows = db @@ -197,7 +186,7 @@ export function getKeyGroupsForApiKey(keyId: string): KeyGroup[] { INNER JOIN key_group_members m ON g.id = m.group_id WHERE m.key_id = ? AND g.is_active = 1 ORDER BY g.name ASC - `, + ` ) .all(keyId) as any[]; return rows.map(rowToGroup); @@ -252,7 +241,7 @@ export interface ModelAccessCheck { export function checkKeyModelAccess( keyId: string, model: string, - provider?: string, + provider?: string ): ModelAccessCheck { const groups = getKeyGroupsForApiKey(keyId); if (groups.length === 0) { @@ -270,7 +259,7 @@ export function checkKeyModelAccess( SELECT * FROM group_model_permissions WHERE group_id IN (${placeholders}) ORDER BY access_type ASC - `, + ` ) .all(...groupIds) as any[]; @@ -281,7 +270,7 @@ export function checkKeyModelAccess( (p) => p.accessType === "deny" && matchesModelPattern(p.modelPattern, model) && - (!p.provider || p.provider === provider), + (!p.provider || p.provider === provider) ); if (denyRules.length > 0) { @@ -293,7 +282,7 @@ export function checkKeyModelAccess( (p) => p.accessType === "allow" && matchesModelPattern(p.modelPattern, model) && - (!p.provider || p.provider === provider), + (!p.provider || p.provider === provider) ); if (allowRules.length > 0) { diff --git a/src/lib/db/ccrBlocks.ts b/src/lib/db/ccrBlocks.ts index 56d49c131a..21397ebb15 100644 --- a/src/lib/db/ccrBlocks.ts +++ b/src/lib/db/ccrBlocks.ts @@ -136,8 +136,3 @@ export function countCcrBlocks(principalId?: string): number { ) as { n: number } | undefined; return row?.n ?? 0; } - -/** Test seam: the write throttle is module state and has to be resettable between tests. */ -export function resetCcrBlockPruneCounter(): void { - writesSincePrune = 0; -} diff --git a/src/lib/db/compressionCombos.ts b/src/lib/db/compressionCombos.ts index 7fdbf0a588..6825009c73 100644 --- a/src/lib/db/compressionCombos.ts +++ b/src/lib/db/compressionCombos.ts @@ -41,10 +41,6 @@ function defaultCompressionComboPipeline(): CompressionPipelineStep[] { ]; } -function toRecord(value: unknown): JsonRecord { - return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {}; -} - function parseJsonArray(value: unknown, fallback: T[]): T[] { if (Array.isArray(value)) return value as T[]; if (typeof value !== "string") return fallback; @@ -389,18 +385,6 @@ export function assignRoutingCombo(compressionComboId: string, routingComboId: s backupDbFile("pre-write"); return true; } - -export function unassignRoutingCombo(compressionComboId: string, routingComboId: string): boolean { - ensureCompressionComboTables(); - const result = getDbInstance() - .prepare( - "DELETE FROM compression_combo_assignments WHERE compression_combo_id = ? AND routing_combo_id = ?" - ) - .run(compressionComboId, routingComboId); - if (result.changes > 0) backupDbFile("pre-write"); - return result.changes > 0; -} - // Static stackPriority map — mirrors the values defined in each engine file. // Using a static map avoids cross-workspace imports (open-sse → src/lib/db) that // would introduce a circular dependency detected by check:cycles. diff --git a/src/lib/db/gamification.ts b/src/lib/db/gamification.ts index b55cd4cf21..8bb5fba4aa 100644 --- a/src/lib/db/gamification.ts +++ b/src/lib/db/gamification.ts @@ -46,16 +46,6 @@ export interface UserBadge { badgeCategory?: string | null; badgeRarity?: string; } - -export interface XpAuditLogEntry { - id: number; - apiKeyId: string; - action: string; - xpEarned: number; - metadata: string | null; - createdAt: string; -} - export interface TokenLedgerEntry { id: number; fromApiKeyId: string; diff --git a/src/lib/db/middleware.ts b/src/lib/db/middleware.ts index 1485196920..cc7460e729 100644 --- a/src/lib/db/middleware.ts +++ b/src/lib/db/middleware.ts @@ -7,8 +7,7 @@ */ import { getDbInstance } from "@/lib/db/core"; -import { rowToCamel } from "@/lib/db/core"; -import type { HookConfig, HookConfigRow, HookLogEntry, HookScope } from "@/lib/middleware/types"; +import type { HookConfig, HookConfigRow, HookLogEntry } from "@/lib/middleware/types"; // ── Helpers ─────────────────────────────────────────────────────────────── @@ -70,27 +69,13 @@ export function getEnabledMiddlewareHooks(): HookConfig[] { return rows.map(rowToHookConfig); } -/** - * Get scoped hooks for a given combo ID. - */ -export function getComboMiddlewareHooks(comboId: string): HookConfig[] { - const db = getDbInstance() as any; - const rows = db - .prepare( - "SELECT * FROM middleware_hooks WHERE enabled = 1 AND (scope_type = 'global' OR (scope_type = 'combo' AND combo_id = ?)) ORDER BY priority ASC" - ) - .all(comboId) as HookConfigRow[]; - return rows.map(rowToHookConfig); -} - /** * Get a single hook by name. */ export function getMiddlewareHook(name: string): HookConfig | undefined { const db = getDbInstance() as any; const row = db.prepare("SELECT * FROM middleware_hooks WHERE name = ?").get(name) as - | HookConfigRow - | undefined; + HookConfigRow | undefined; return row ? rowToHookConfig(row) : undefined; } @@ -170,34 +155,6 @@ export function recordHookExecution(name: string, error?: string): void { ).run(name); } } - -// ── Log Operations ──────────────────────────────────────────────────────── - -/** - * Insert a hook execution log entry. - */ -export function insertHookLog(entry: HookLogEntry): void { - const db = getDbInstance() as any; - db.prepare( - ` - INSERT INTO middleware_logs (id, hook_name, request_id, duration_ms, mutated, skipped, error, timestamp) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - ` - ).run( - entry.id, - entry.hookName, - entry.requestId, - entry.durationMs, - entry.mutated ? 1 : 0, - entry.skipped ? 1 : 0, - entry.error || null, - entry.timestamp - ); -} - -/** - * Get hook execution logs, optionally filtered by hook name. - */ export function getHookLogs(hookName?: string, limit = 50): HookLogEntry[] { const db = getDbInstance() as any; let rows: any[]; @@ -219,21 +176,3 @@ export function getHookLogs(hookName?: string, limit = 50): HookLogEntry[] { timestamp: r.timestamp, })); } - -/** - * Clean up old hook logs (keep last N entries). - */ -export function cleanupHookLogs(maxEntries = 10000): number { - const db = getDbInstance() as any; - // Delete logs beyond the max, keeping the most recent - const result = db - .prepare( - ` - DELETE FROM middleware_logs WHERE id NOT IN ( - SELECT id FROM middleware_logs ORDER BY timestamp DESC LIMIT ? - ) - ` - ) - .run(maxEntries); - return result.changes; -} diff --git a/src/lib/db/quotaModelCaps.ts b/src/lib/db/quotaModelCaps.ts index 86306122d2..474c7724aa 100644 --- a/src/lib/db/quotaModelCaps.ts +++ b/src/lib/db/quotaModelCaps.ts @@ -82,25 +82,6 @@ export function getModelCap(poolId: string, apiKeyId: string, model: string): Mo .get(poolId, apiKeyId, model); return row ? rowToModelCap(row) : null; } - -/** - * List all model caps for a given (pool, key) pair. - */ -export function listModelCaps(poolId: string, apiKeyId: string): ModelCap[] { - const rows = getDb() - .prepare( - `SELECT pool_id, api_key_id, model, cap_value, cap_unit - FROM quota_allocation_model_caps - WHERE pool_id = ? AND api_key_id = ?` - ) - .all(poolId, apiKeyId); - return rows.map(rowToModelCap); -} - -/** - * Insert or replace a model cap. - * cap_value must be > 0 (enforced by DB CHECK constraint). - */ export function setModelCap(cap: ModelCap): void { getDb() .prepare( @@ -113,16 +94,3 @@ export function setModelCap(cap: ModelCap): void { ) .run(cap.poolId, cap.apiKeyId, cap.model, cap.capValue, cap.capUnit); } - -/** - * Remove the cap for a specific (pool, key, model) triple. - * No-op if it does not exist. - */ -export function deleteModelCap(poolId: string, apiKeyId: string, model: string): void { - getDb() - .prepare( - `DELETE FROM quota_allocation_model_caps - WHERE pool_id = ? AND api_key_id = ? AND model = ?` - ) - .run(poolId, apiKeyId, model); -} diff --git a/src/lib/db/sessionAccountAffinity.ts b/src/lib/db/sessionAccountAffinity.ts index 9a284d1265..9b8c4fbfba 100644 --- a/src/lib/db/sessionAccountAffinity.ts +++ b/src/lib/db/sessionAccountAffinity.ts @@ -204,9 +204,3 @@ export function startSessionAccountAffinityCleanup(): void { }, CLEANUP_INTERVAL_MS); if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) cleanupTimer.unref?.(); } - -export function stopSessionAccountAffinityCleanupForTests(): void { - if (!cleanupTimer) return; - clearInterval(cleanupTimer); - cleanupTimer = null; -} diff --git a/src/lib/db/upstreamProxy.ts b/src/lib/db/upstreamProxy.ts index ae7ed9a8e1..84bf3e0a11 100644 --- a/src/lib/db/upstreamProxy.ts +++ b/src/lib/db/upstreamProxy.ts @@ -129,15 +129,6 @@ function rowToConfig(record: Record): UpstreamProxyConfig { updatedAt: record.updated_at as string, }; } - -export async function getUpstreamProxyConfigs() { - const db = getDbInstance(); - const rows = db - .prepare("SELECT * FROM upstream_proxy_config ORDER BY provider_id") - .all() as UpstreamProxyRow[]; - return rows.map((row) => rowToConfig(toRecord(row))); -} - export async function getUpstreamProxyConfig(providerId: string) { const db = getDbInstance(); const row = db diff --git a/src/lib/evals/runtime.ts b/src/lib/evals/runtime.ts index 9dc728f16f..d3ba6b0a5b 100644 --- a/src/lib/evals/runtime.ts +++ b/src/lib/evals/runtime.ts @@ -1,7 +1,8 @@ import { POST as postChatCompletion } from "@/app/api/v1/chat/completions/route"; import type { PersistedEvalRun, EvalTargetType } from "@/lib/db/evals"; import { saveEvalRun } from "@/lib/db/evals"; -import { getApiKeyById, getCombos } from "@/lib/localDb"; +import { getApiKeyById } from "@/lib/db/apiKeys"; +import { getCombos } from "@/lib/db/combos"; import { getSuite, listSuites, runSuite } from "./evalRunner"; export interface EvalTargetInput { diff --git a/src/lib/freeProxyProviders/scheduler.ts b/src/lib/freeProxyProviders/scheduler.ts index 36e38ba647..634cfb8d92 100644 --- a/src/lib/freeProxyProviders/scheduler.ts +++ b/src/lib/freeProxyProviders/scheduler.ts @@ -22,7 +22,7 @@ */ import { getEnabledProviders } from "@/lib/freeProxyProviders"; -import { getFreeProxyStats } from "@/lib/localDb"; +import { getFreeProxyStats } from "@/lib/db/freeProxies"; import { runFreeProxySyncCycle, type FreeProxySyncCycleResult } from "./syncCycle"; const STARTUP_DELAY_MS = 5_000; diff --git a/src/lib/freeProxyProviders/syncCycle.ts b/src/lib/freeProxyProviders/syncCycle.ts index 666b6234e5..d194091b0c 100644 --- a/src/lib/freeProxyProviders/syncCycle.ts +++ b/src/lib/freeProxyProviders/syncCycle.ts @@ -3,7 +3,7 @@ import { recordFreeProxySync, clearFreeProxySyncErrors, recordFreeProxySyncErrors, -} from "@/lib/localDb"; +} from "@/lib/db/freeProxies"; import type { FreeProxyProvider } from "@/lib/freeProxyProviders/types"; export interface FreeProxySyncCycleResult { diff --git a/src/lib/guardrails/visionBridge.ts b/src/lib/guardrails/visionBridge.ts index db56035386..c24729a3c9 100644 --- a/src/lib/guardrails/visionBridge.ts +++ b/src/lib/guardrails/visionBridge.ts @@ -53,7 +53,7 @@ export async function getComboVisionBridgeDecision( model: string ): Promise { try { - const { getComboByName } = await import("@/lib/localDb"); + const { getComboByName } = await import("@/lib/db/combos"); const { resolveComboForModel } = await import("@/lib/db/modelComboMappings"); // 1. Try to find combo by exact name match diff --git a/src/lib/idempotencyLayer.ts b/src/lib/idempotencyLayer.ts index 628541cb80..da997c0016 100644 --- a/src/lib/idempotencyLayer.ts +++ b/src/lib/idempotencyLayer.ts @@ -10,7 +10,7 @@ * @module lib/idempotencyLayer */ -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; const DEFAULT_WINDOW_MS = 5000; diff --git a/src/lib/images/imageRouteModel.ts b/src/lib/images/imageRouteModel.ts index 2c7f0ab3e5..41f414e0ef 100644 --- a/src/lib/images/imageRouteModel.ts +++ b/src/lib/images/imageRouteModel.ts @@ -18,7 +18,7 @@ import { parseImageModel } from "@omniroute/open-sse/config/imageRegistry.ts"; import { resolveComboTargets } from "@omniroute/open-sse/services/combo.ts"; import { getComboByName, getCombos } from "@/lib/db/combos"; -import { getCachedProviderNodes } from "@/lib/localDb"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; import { assertMicrosoftDesignerWebProviderAvailable } from "@/shared/constants/designerWebRetirement"; import { assertCommonChatGptWebModelAvailable } from "@/shared/constants/chatgptWebRetirement"; diff --git a/src/lib/localDb.ts b/src/lib/localDb.ts deleted file mode 100755 index 761e2db4eb..0000000000 --- a/src/lib/localDb.ts +++ /dev/null @@ -1,850 +0,0 @@ -/** - * localDb.js — Re-export layer for backward compatibility. - * - * All 27+ consumer files import from "@/lib/localDb". - * This thin layer re-exports everything from the domain-specific DB modules, - * so zero consumer changes are needed. - */ - -export { - // Provider Connections - getProviderConnections, - getProviderConnectionsCount, - getProviderConnectionById, - createProviderConnection, - updateProviderConnection, - resetConnectionBackoff, - clearConnectionErrorIfUnchanged, - touchConnectionLastUsed, - deleteProviderConnection, - deleteProviderConnections, - deleteProviderConnectionsByProvider, - reorderProviderConnections, - cleanupProviderConnections, - getProviderNodes, - getProviderNodesCount, - getProviderNodeById, - resolveProviderNodeForConnection, - createProviderNode, - updateProviderNode, - deleteProviderNode, - // T05: Rate-limit DB persistence (survives token refresh) - setConnectionRateLimitUntil, - isConnectionRateLimited, - getRateLimitedConnections, - clearStaleCrashCooldowns, - // T13: Stale quota display fix (zero out usage after window resets) - getEffectiveQuotaUsage, - formatResetCountdown, -} from "./db/providers"; - -export { - // Model Aliases - getModelAliases, - setModelAlias, - deleteModelAlias, - deleteModelAliasesForProvider, - - // MITM Alias - getMitmAlias, - setMitmAliasAll, - - // Custom Models - getCustomModels, - getAllCustomModels, - addCustomModel, - replaceCustomModels, - removeCustomModel, - updateCustomModel, - getModelCompatOverrides, - mergeModelCompatOverride, - removeModelCompatOverride, - getModelNormalizeToolCallId, - getModelPreserveOpenAIDeveloperRole, - getModelUpstreamExtraHeaders, - getModelIsHidden, - setModelIsHidden, - getHiddenModelsByProvider, - // Synced Available Models - getSyncedAvailableModels, - getAllSyncedAvailableModels, - getActiveProvidersWithSyncedModel, - replaceSyncedAvailableModelsForConnection, - deleteSyncedAvailableModelsForConnection, - deleteSyncedAvailableModelsForProvider, - removeSyncedAvailableModel, -} from "./db/models"; - -export type { ModelCompatPerProtocol, ModelCompatPatch, SyncedAvailableModel } from "./db/models"; - -export { - // Combos - getCombos, - getCombosCount, - getComboById, - getComboByName, - getComboByNameInsensitive, - createCombo, - updateCombo, - reorderCombos, - deleteCombo, -} from "./db/combos"; -export * from "./db/ccrBlocks"; -export * from "./db/compressionCacheStats"; -export * from "./db/compressionCombos"; -export * from "./db/compressionContextBudget"; -export * from "./db/compressionRunTelemetry"; -export * from "./db/jobRegistryDb"; -export * from "./db/modelContextOverrides"; -export * from "./db/responsesContinuationStore"; - -export { - getApiKeys, - getApiKeysCount, - getApiKeyById, - createApiKey, - deleteApiKey, - validateApiKey, - getApiKeyMetadata, - updateApiKeyPermissions, - regenerateApiKey, - isModelAllowedForKey, - pickApiKeyForInternalUse, - clearApiKeyCaches, - resetApiKeyState, - ApiKeyPolicyInvariantError, -} from "./db/apiKeys"; - -export { - // Evals - saveEvalRun, - listEvalRuns, - getEvalScorecard, - listCustomEvalSuites, - getCustomEvalSuite, - saveCustomEvalSuite, - deleteCustomEvalSuite, - serializeEvalTargetKey, -} from "./db/evals"; - -export type { - EvalCaseRecord, - EvalSuiteRecord, - EvalTargetType, - EvalTargetDescriptor, - EvalRunSummary, - PersistedEvalRun, -} from "./db/evals"; - -export { - // Settings - getSettings, - getSettingsRevision, - updateSettings, - isCloudEnabled, - - // LKGP (Last Known Good Provider) (#919) - getLKGP, - setLKGP, - clearLKGP, - - // Pricing - getPricing, - getPricingWithSources, - getPricingForModel, - updatePricing, - resetPricing, - resetAllPricing, - - // Proxy Config - getProxyConfig, - getProxyForLevel, - setProxyForLevel, - deleteProxyForLevel, - resolveProxyForConnection, - setProxyConfig, -} from "./db/settings"; - -export type { PricingSource, PricingSourceMap } from "./db/settings"; - -export { - getDatabaseSettings, - getUserDatabaseSettings, - updateDatabaseSettings, -} from "./db/databaseSettings"; - -export type { UserDatabaseSettings } from "./db/databaseSettings"; - -export * from "./db/exclusiveConnectionLeases"; - -export { - // Proxy Registry - listProxies, - getProxyById, - createProxy, - createProxyAndAssign, - updateProxy, - updateProxyAndAssign, - upsertProxy, - deleteProxyById, - getProxyAssignments, - getProxyWhereUsed, - assignProxyToScope, - addProxyToScopePool, - removeProxyFromScopePool, - getScopeProxyPool, - setScopeRotationStrategy, - getScopeRotationStrategy, - resolveProxyForConnectionFromRegistry, - resolveProxyForProvider, - resolveProxyForScopeFromRegistry, - migrateLegacyProxyConfigToRegistry, - getProxyHealthStats, - bulkAssignProxyToScope, -} from "./db/proxies"; - -export { - // Pricing Sync - getSyncedPricing, - saveSyncedPricing, - clearSyncedPricing, - syncPricingFromSources, - getSyncStatus, - initPricingSync, - startPeriodicSync, - stopPeriodicSync, -} from "./pricingSync"; - -export { - // Backup Management - backupDbFile, - cleanupDbBackups, - getDbBackupMaxFiles, - setDbBackupMaxFiles, - getDbBackupRetentionDays, - setDbBackupRetentionDays, - listDbBackups, - restoreDbBackup, - // Export-All / Import helpers (#3500 slice 5) - exportAllSummaryRows, - getTableNamesFromAdapter, - countImportedRows, -} from "./db/backup"; - -export type { ExportAllRows } from "./db/backup"; - -export { - // Skills DB operations (#3500 slice 5) - updateSkill, -} from "./db/skills"; - -export type { SkillPatch } from "./db/skills"; - -export { - // Read Cache (cached wrappers for hot-read paths) - getCachedSettings, - getCachedPricing, - getCachedProviderConnections, - getCachedRawProviderConnections, - getCachedProviderConnectionById, - getCachedProviderNodes, - getCachedLKGP, - setCachedLKGP, - invalidateDbCache, - getCombosCacheVersion, -} from "./db/readCache"; - -export { - // Registered Keys Provisioning (#464) - issueRegisteredKey, - getRegisteredKey, - listRegisteredKeys, - revokeRegisteredKey, - validateRegisteredKey, - incrementRegisteredKeyUsage, - checkQuota, - setProviderKeyLimit, - setAccountKeyLimit, - getProviderKeyLimit, - getAccountKeyLimit, -} from "./db/registeredKeys"; - -export type { - RegisteredKey, - RegisteredKeyWithSecret, - ProviderKeyLimit, - AccountKeyLimit, - QuotaCheckResult, - IssueKeyParams, -} from "./db/registeredKeys"; - -export { - // Model-Combo Mappings (#563) - getModelComboMappings, - getModelComboMappingById, - createModelComboMapping, - updateModelComboMapping, - deleteModelComboMapping, - resolveComboForModel, -} from "./db/modelComboMappings"; - -export { - // Files - createFile, - getFile, - getFileContent, - listFiles, - countFiles, - formatFileResponse, - deleteFile, -} from "./db/files"; - -export { - // Batches - createBatch, - getBatch, - updateBatch, - listBatches, - countBatches, - getPendingBatches, - getTerminalBatches, - ensureBatchItemCheckpoints, - countBatchItemCheckpoints, - listBatchItemCheckpoints, - markBatchItemProcessing, - markBatchItemResult, - markBatchItemError, - deleteBatch, - deleteCompletedBatches, -} from "./db/batches"; - -export type { FileRecord } from "./db/files"; -export type { BatchItemCheckpoint, BatchRecord } from "./db/batches"; - -export type { ModelComboMapping } from "./db/modelComboMappings"; -export * from "./db/reasoningRoutingRules"; -export * from "./db/autoCandidateOverrides"; -export { - // Webhooks - getWebhooks, - getWebhook, - getEnabledWebhooks, - createWebhook, - updateWebhook as updateWebhookRecord, - deleteWebhook, - recordWebhookDelivery, - disableWebhooksWithHighFailures, -} from "./db/webhooks"; - -export type { Webhook, WebhookKind } from "./db/webhooks"; - -export { insertDelivery, getDeliveries } from "./db/webhookDeliveries"; - -export { - upsertDiscoveryResult, - getDiscoveryResults, - getDiscoveryResultById, - markVerified, - deleteDiscoveryResult, -} from "./db/discoveryResults"; - -export type { - DiscoveryResult, - DiscoveryMethod, - DiscoveryAuthType, - DiscoveryRiskLevel, - DiscoveryStatus, -} from "./db/discoveryResults"; -export type { WebhookDelivery } from "./db/webhookDeliveries"; - -export { - saveQuotaSnapshot, - getQuotaSnapshots, - getAggregatedSnapshots, - cleanupOldSnapshots, -} from "./db/quotaSnapshots"; - -export * from "./db/sessionAccountAffinity"; -export * from "./db/quotaResetEvents"; - -export type { QuotaSnapshotRow, ProviderUtilizationPoint } from "@/shared/types/utilization"; - -export { - getVersionManagerStatus, - getVersionManagerTool, - upsertVersionManagerTool, - updateVersionManagerTool, - deleteVersionManagerTool, - updateToolHealth, - updateToolVersion, - setToolStatus, - getServiceRow, - updateServiceField, -} from "./db/versionManager"; - -export { - listSyncTokens, - getSyncTokenById, - getSyncTokenByHash, - createSyncTokenRecord, - revokeSyncToken, - touchSyncTokenLastUsed, -} from "./db/syncTokens"; - -export { - getUpstreamProxyConfigs, - getUpstreamProxyConfig, - upsertUpstreamProxyConfig, - updateUpstreamProxyConfig, - deleteUpstreamProxyConfig, - getProvidersByMode, - getFallbackChainForProvider, - validateProxyUrl, -} from "./db/upstreamProxy"; - -export { - getProviderLimitsCache, - getAllProviderLimitsCache, - setProviderLimitsCache, - setProviderLimitsCacheBatch, - deleteProviderLimitsCache, -} from "./db/providerLimits"; - -export type { ProviderLimitsCacheEntry } from "./db/providerLimits"; - -export { - getPersistedCreditBalance, - getAllPersistedCreditBalances, - persistCreditBalance, -} from "./db/creditBalance"; - -export { - insertCompressionAnalyticsRow, - getCompressionAnalyticsSummary, -} from "./db/compressionAnalytics"; - -export type { - CompressionAnalyticsRow, - CompressionAnalyticsSummary, -} from "./db/compressionAnalytics"; - -export { - // Reasoning Replay Cache (#1628) - setReasoningCache, - getReasoningCache, - deleteReasoningCache, - clearAllReasoningCache, -} from "./db/reasoningCache"; - -export type { ReasoningCacheEntry, ReasoningCacheStats } from "./db/reasoningCache"; - -export { - // 1proxy Integration (#1788) - listOneproxyProxies, - getOneproxyStats, - upsertOneproxyProxy, - getOneproxyProxyById, - deleteOneproxyProxy, - clearAllOneproxyProxies, - getOneproxyProxyForRotation, - markOneproxyProxyFailed, -} from "./db/oneproxy"; - -export type { OneproxyProxyRecord, OneproxyStats } from "./db/oneproxy"; - -export { - getSessionAccountAffinity, - upsertSessionAccountAffinity, - touchSessionAccountAffinity, - deleteSessionAccountAffinity, - evictSessionAccountAffinityForConnection, - cleanupStaleSessionAccountAffinities, - startSessionAccountAffinityCleanup, - stopSessionAccountAffinityCleanupForTests, -} from "./db/sessionAccountAffinity"; - -export { - // Gamification & Leaderboard - updateScore, - getRank, - getTopN, - addXp, - getXp, - updateLevel, - unlockBadge, - hasBadge, - getBadges, - getBadgeDefinitions, - transferTokens, - getBalance, - getHistory, - createInviteToken, - getInviteByCode, - redeemInvite, - revokeInvite, - connectServer, - disconnectServer, - listServers, - getConnectedServerByKeyHash, -} from "./db/gamification"; - -export type { - LeaderboardRow, - UserLevelRow, - BadgeDefinition, - UserBadge, - XpAuditLogEntry, - TokenLedgerEntry, - InviteToken, - CommunityServer, -} from "./db/gamification"; - -export * from "./db/featureFlags"; - -export { - upsertHandoff, - getHandoff, - deleteHandoff, - cleanupExpiredHandoffs, - hasActiveHandoff, - recordSessionModelUsage, - getLastSessionModel, -} from "./db/contextHandoffs"; - -export type { HandoffPayload } from "./db/contextHandoffs"; - -export { - getAllMiddlewareHooks, - getEnabledMiddlewareHooks, - getComboMiddlewareHooks, - getMiddlewareHook, - createMiddlewareHook, - updateMiddlewareHook, - deleteMiddlewareHook, - recordHookExecution, - insertHookLog, - getHookLogs, - cleanupHookLogs, -} from "./db/middleware"; - -export { - getAllKeyGroups, - getKeyGroup, - getKeyGroupWithPermissions, - createKeyGroup, - updateKeyGroup, - deleteKeyGroup, - getGroupPermissions, - addGroupPermission, - removeGroupPermission, - clearGroupPermissions, - getGroupMembers, - getKeyGroupsForApiKey, - addKeyToGroup, - removeKeyFromGroup, - checkKeyModelAccess, -} from "./db/apiKeyGroups"; - -export { - createRelayToken, - getRelayTokens, - getRelayToken, - getRelayTokenByHash, - updateRelayToken, - deleteRelayToken, - toggleRelayToken, - checkRateLimit, - recordRelayUsage, - getRelayUsage, - getRelayLogs, -} from "./db/relayProxies"; - -export type { - RelayToken, - RelayTokenRow, - RelayLogRow, - CreateRelayTokenInput, - RelayTokenWithSecret, -} from "./db/relayProxies"; - -export { - upsertFreeProxy, - listFreeProxies, - countFreeProxies, - listFreeProxiesBySource, - getFreeProxyById, - markFreeProxyInPool, - promoteFreeProxyToPool, - deleteFreeProxy, - clearFreeProxiesBySource, - pruneStaleFreeProxies, - getFreeProxyStats, - recordFreeProxySync, - recordFreeProxySyncErrors, - clearFreeProxySyncErrors, - getFreeProxySyncErrors, -} from "./db/freeProxies"; - -export type { FreeProxyRecord, FreeProxyStats, FreeProxySyncErrors } from "./db/freeProxies"; - -export { - listPlaygroundPresets, - getPlaygroundPreset, - createPlaygroundPreset, - updatePlaygroundPreset, - deletePlaygroundPreset, -} from "./db/playgroundPresets"; - -export type { PlaygroundPresetListItem } from "./db/playgroundPresets"; -// Plan 21 — Memory Engine Redesign -export { - getMemoryVecMeta, - setMemoryVecMeta, - markMemoryNeedsReindex, - markAllMemoriesNeedReindex, - getMemoryReindexQueue, - countMemoryReindexPending, - type MemoryVecMeta, -} from "./db/memoryVec"; -// T-A-F2: AgentBridge state/mappings/bypass + Inspector custom hosts/sessions -export * from "./db/agentBridgeState"; -export * from "./db/agentBridgeMappings"; -export * from "./db/agentBridgeBypass"; -export * from "./db/inspectorCustomHosts"; -export * from "./db/inspectorSessions"; -export * from "./db/omp"; -// Quota Sharing — Group B (planos 16+22) -export { - listPools, - getPool, - getPoolsByGroup, - ensurePool, - createPool, - updatePool, - deletePool, - upsertAllocations, - listAllocationsForApiKey, -} from "./db/quotaPools"; -// Quota per-(key, model) caps — Group B Fase 3 #7 -export { getModelCap, listModelCaps, setModelCap, deleteModelCap } from "./db/quotaModelCaps"; - -export { - // Quota Groups (B2) - createGroup, - getGroup, - getGroupName, - listGroups, - renameGroup, - deleteGroup, -} from "./db/quotaGroups"; - -export type { QuotaGroup } from "./db/quotaGroups"; -export { - getBucket, - incrementBucket, - getPair, - sumPoolDimension, - gcOlderThan as gcQuotaConsumption, -} from "./db/quotaConsumption"; -export { - getPlan as getProviderPlan, - listPlans as listProviderPlans, - upsertPlan as upsertProviderPlan, - deletePlan as deleteProviderPlan, -} from "./db/providerPlans"; - -export { - // Per-API-Key Token Limits (migration 073) - upsertTokenLimit, - listTokenLimits, - getTokenLimitsForRequest, - deleteTokenLimit, - getWindowUsage, - incrementWindowTokens, - resetWindowIfElapsed, - logTokenLimitReset, -} from "./db/tokenLimits"; - -export type { - TokenLimit, - TokenLimitScopeType, - UpsertTokenLimitInput, - TokenWindowState, -} from "./db/tokenLimits"; - -export { - insertPlugin, - getPluginById, - getPluginByName, - listPlugins, - updatePluginStatus, - updatePluginConfig, - deletePlugin, - pluginExists, -} from "./db/plugins"; - -export type { PluginRow, PluginCreateInput } from "./db/plugins"; - -export { - getApiKeyContextSource, - setApiKeyContextSource, - deleteApiKeyContextSource, - listApiKeyContextSources, -} from "./db/apiKeyContextSources"; -export type { ApiKeyContextSource } from "./db/apiKeyContextSources"; -export * from "./db/localCorpus"; -export { sumUsageTokensThisMonth } from "./db/usageSummary"; - -export { - // Model Intelligence (task-fitness scores) - getModelIntelligence, - getModelIntelligenceBySource, - upsertModelIntelligence, - deleteModelIntelligence, - deleteExpiredIntelligence, - deleteModelIntelligenceBySource, - listModelIntelligence, - bulkUpsertModelIntelligence, - getResolvedTaskFitness, - setUserFitnessOverrideEntry, - deleteUserFitnessOverrideEntry, -} from "./db/modelIntelligence"; - -export type { ModelIntelligenceEntry } from "./db/modelIntelligence"; - -export { - getProviderMetrics, - getSearchProviderStats, - getRecentSearchLogs, - getSearchAggregateStats, - getSearchProviderCounts, - getFallbackStats, -} from "./db/callLogStats"; -export type { - ProviderMetricRow, - SearchProviderStatRow, - SearchRecentRow, - SearchAggregateStats, - SearchProviderCountRow, - FallbackStatsRow, -} from "./db/callLogStats"; - -export { - buildUnifiedSource, - buildPresetUnifiedSource, - getUsageSummary, - getDailyUsage, - getDailyCostRows, - getHeatmapRows, - getModelUsageRows, - getProviderCostRows, - getProviderUsageRows, - getAccountCostRows, - getAccountUsageRows, - getApiKeyUsageRows, - getServiceTierUsageRows, - getApiKeyMetadataRows, - getWeeklyPatternRows, - getPresetCostModelRows, - getAllUsageHistory, - getAllDomainCostHistory, - getAllDomainBudgets, -} from "./db/usageAnalytics"; -export type { - AnalyticsParams, - BuildUnifiedSourceOptions, - UnifiedSourceResult, - UsageSummaryRow, - DailyUsageRow, - DailyCostRow, - HeatmapRow, - ModelUsageRow, - ProviderCostRow, - ProviderUsageRow, - AccountCostRow, - AccountUsageRow, - ApiKeyUsageRow, - ServiceTierUsageRow, - ApiKeyMetadataRow, - WeeklyPatternRow, - PresetCostModelRow, -} from "./db/usageAnalytics"; - -// --------------------------------------------------------------------------- -// call_logs auto-routing analytics (#3500 slice 4) -// --------------------------------------------------------------------------- -export { - getAutoRoutingTotalCount, - getAutoRoutingVariantBreakdown, - getAutoRoutingTopProviders, -} from "./db/usageLogs"; -export type { - AutoRoutingTotalResult, - AutoRoutingVariantRow, - AutoRoutingTopProviderRow, -} from "./db/usageLogs"; - -// --------------------------------------------------------------------------- -// semantic_cache — cache entries CRUD (#3500 slice 4) -// --------------------------------------------------------------------------- -export { - listSemanticCacheEntries, - deleteSemanticCacheBySignature, - deleteSemanticCacheByModel, -} from "./db/semanticCache"; -export type { - SemanticCacheEntry, - SemanticCacheListOptions, - SemanticCacheListResult, - DeleteSemanticCacheBySignatureResult, - DeleteSemanticCacheByModelResult, -} from "./db/semanticCache"; - -// --------------------------------------------------------------------------- -// proxy_logs — export query (#3500 slice 4) -// --------------------------------------------------------------------------- -export { exportProxyLogsSince } from "./db/proxyLogs"; -// --------------------------------------------------------------------------- -// Per-connection 429 cooldown wrappers (#5957 / #5958 — Issue 1 follow-ups) -// Logic lives in db/providers/rateLimit.ts (Hard Rule #2 — localDb is re-export -// only); re-exported here for the historical localDb import contract. -// --------------------------------------------------------------------------- -export { markConnectionRateLimitedUntil, clearConnectionRateLimit } from "./db/providers"; -// Provider param filters — denylist/allowlist config per provider/model (#6625) -export * from "./db/paramFilters"; -export * from "./db/interceptionRules"; // Per-model web-search/web-fetch interception rules (#3384) -export * from "./db/relayProbeStats"; // Relay probe latency/health stats (#6909) -export * from "./db/ccDiscoveryAliases"; // Claude Code discovery-alias gate (flag + per-provider/model overrides) -export * from "./db/ccDiscoveryMetrics"; // Claude Code discovery-alias usage counters (alias requests + discovery hits) -export * from "./db/functionalGatewayMirrors"; // Functional-gateway mirror gate (flag + per-provider/model overrides) -// Radar client — local feed cache + settings (opt-in, encrypted supporter key) -export { - getRadarCache, - setRadarCache, - getRadarSettings, - setRadarOptIn, - setRadarKey, - getRadarReferralsCache, - setRadarReferralsCache, - getRadarOffersCache, - setRadarOffersCache, - getRadarIntelCache, - setRadarIntelCache, - listRadarLocalModelState, - setRadarLocalModelOverride, - clearRadarLocalModelOverride, - setRadarModelTombstone, - getRadarLocalMergeState, -} from "./db/radar"; -export type { - RadarCache, - RadarSettings, - RadarReferralsCache, - RadarOffersCache, - RadarIntelCache, - RadarLocalModelState, - RadarLocalModelOverridePatch, - RadarLocalMergeState, -} from "./db/radar"; -export * from "./db/conductorBridge"; // OmniConductor hub mirror — SSE cursor (PRD Conductor RF1) -export * from "./db/agenticConversations"; // Multi-turn conversation id tracking (X-ConversationId) diff --git a/src/lib/localHealthCheck.ts b/src/lib/localHealthCheck.ts index a496d40399..1853fc910c 100644 --- a/src/lib/localHealthCheck.ts +++ b/src/lib/localHealthCheck.ts @@ -11,7 +11,7 @@ * Uses Promise.allSettled so one slow/down node doesn't block others. */ -import { getCachedProviderNodes } from "@/lib/localDb"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; import { isAutomatedTestProcess } from "@/shared/utils/testProcess"; // ── Types ──────────────────────────────────────────────────────────────── @@ -38,7 +38,6 @@ function isBuildProcess(): boolean { return typeof process !== "undefined" && process.env.NEXT_PHASE === "phase-production-build"; } - // ── State (globalThis survives HMR re-evaluation) ─────────────────────── declare global { diff --git a/src/lib/memory/embedding/index.ts b/src/lib/memory/embedding/index.ts index 9adf55f69e..8205b75524 100644 --- a/src/lib/memory/embedding/index.ts +++ b/src/lib/memory/embedding/index.ts @@ -5,7 +5,7 @@ import { type EmbeddingProviderNodeRow, } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { getProviderCredentials } from "@/sse/services/auth"; -import { getCachedProviderNodes } from "@/lib/localDb"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; import type { MemorySettingsExtended } from "@/shared/schemas/memory"; import type { EmbeddingResolution, diff --git a/src/lib/memory/reindex.ts b/src/lib/memory/reindex.ts index da7317b10a..8d459edaa7 100644 --- a/src/lib/memory/reindex.ts +++ b/src/lib/memory/reindex.ts @@ -7,7 +7,7 @@ import { getMemoryReindexQueue, countMemoryReindexPending, markMemoryNeedsReindex, -} from "@/lib/localDb"; +} from "@/lib/db/memoryVec"; import { resolveEmbeddingSource, embed } from "./embedding"; import { getVectorStore } from "./vectorStore"; import { getMemorySettings } from "./settings"; @@ -23,9 +23,7 @@ const log = logger("MEMORY_REINDEX"); * * @returns { processed: number; errors: number } */ -export async function runReindexBatch( - limit = 100 -): Promise<{ processed: number; errors: number }> { +export async function runReindexBatch(limit = 100): Promise<{ processed: number; errors: number }> { const queue = getMemoryReindexQueue(limit); if (queue.length === 0) { diff --git a/src/lib/memory/store.ts b/src/lib/memory/store.ts index 8f95b84776..e0441eb3b2 100644 --- a/src/lib/memory/store.ts +++ b/src/lib/memory/store.ts @@ -10,7 +10,7 @@ import { sanitizeErrorMessage } from "../../../open-sse/utils/error.ts"; import { resolveEmbeddingSource, embed } from "./embedding"; import { getVectorStore } from "./vectorStore"; import { getMemorySettings } from "./settings"; -import { markMemoryNeedsReindex } from "@/lib/localDb"; +import { markMemoryNeedsReindex } from "@/lib/db/memoryVec"; const log = logger("MEMORY_STORE"); diff --git a/src/lib/memory/vectorStore.ts b/src/lib/memory/vectorStore.ts index 92f706a3d4..6996b84024 100644 --- a/src/lib/memory/vectorStore.ts +++ b/src/lib/memory/vectorStore.ts @@ -14,7 +14,7 @@ import { setMemoryVecMeta, markAllMemoriesNeedReindex, countMemoryReindexPending, -} from "../localDb"; +} from "@/lib/db/memoryVec"; import { getDbInstance } from "../db/core"; import { logger } from "../../../open-sse/utils/logger.ts"; import { sanitizeErrorMessage } from "../../../open-sse/utils/error.ts"; @@ -54,7 +54,7 @@ export interface VectorStore { vector: Float32Array, queryText: string, topK: number, - apiKeyId?: string, + apiKeyId?: string ): Promise; /** Stats for UI Engine status. */ stats(): Promise<{ @@ -151,7 +151,7 @@ function recreateFromMeta(): boolean { const db = getDbInstance(); const q = storedVecQuantization(meta.embeddingSignature); db.exec( - `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(meta.activeDim, q)})`, + `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(meta.activeDim, q)})` ); return true; } @@ -189,7 +189,7 @@ class VectorStoreImpl implements VectorStore { const q = storedVecQuantization(meta.embeddingSignature); try { db.exec( - `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(dim, q)})`, + `CREATE VIRTUAL TABLE IF NOT EXISTS vec_memories USING vec0(embedding ${vecColumnType(dim, q)})` ); setMemoryVecMeta({ vecLoaded: true, activeDim: dim }); return { ready: true, reason: `vec_memories created with dim=${dim} (${q})` }; @@ -207,8 +207,7 @@ class VectorStoreImpl implements VectorStore { // Map UUID memoryId → INTEGER rowid (the rowid is used as the FK into vec_memories). const row = db.prepare("SELECT rowid FROM memories WHERE id = ?").get(memoryId) as - | { rowid: number } - | undefined; + { rowid: number } | undefined; if (!row) { throw new Error(`memory not found: ${memoryId}`); @@ -222,7 +221,7 @@ class VectorStoreImpl implements VectorStore { db.prepare("DELETE FROM vec_memories WHERE rowid = ?").run(BigInt(row.rowid)); db.prepare(`INSERT INTO vec_memories(rowid, embedding) VALUES (?, ${vecValueExpr(q)})`).run( BigInt(row.rowid), - encodeVector(vector), + encodeVector(vector) ); } catch (err: unknown) { // Self-heal: a concurrent ensureReady()'s reset raced ahead of us and @@ -232,7 +231,7 @@ class VectorStoreImpl implements VectorStore { db.prepare("DELETE FROM vec_memories WHERE rowid = ?").run(BigInt(row.rowid)); db.prepare(`INSERT INTO vec_memories(rowid, embedding) VALUES (?, ${vecValueExpr(q)})`).run( BigInt(row.rowid), - encodeVector(vector), + encodeVector(vector) ); } } @@ -241,7 +240,7 @@ class VectorStoreImpl implements VectorStore { const db = getDbInstance(); try { db.prepare( - "DELETE FROM vec_memories WHERE rowid = (SELECT rowid FROM memories WHERE id = ?)", + "DELETE FROM vec_memories WHERE rowid = (SELECT rowid FROM memories WHERE id = ?)" ).run(memoryId); } catch (err: unknown) { if (!isMissingVecTableError(err)) throw err; @@ -254,7 +253,7 @@ class VectorStoreImpl implements VectorStore { async searchVector( vector: Float32Array, topK: number, - apiKeyId?: string, + apiKeyId?: string ): Promise { const db = getDbInstance(); const k = topK > 0 ? topK : TOP_K_DEFAULT; @@ -268,12 +267,12 @@ class VectorStoreImpl implements VectorStore { WHERE v.embedding MATCH ${vecValueExpr(q)} AND ($apiKeyId IS NULL OR m.api_key_id = $apiKeyId) AND k = ? - ORDER BY v.distance ASC`, + ORDER BY v.distance ASC` ) .all(encodeVector(vector), { apiKeyId: apiKeyId ?? null }, k) as Array<{ - memory_id: string; - distance: number; - }>; + memory_id: string; + distance: number; + }>; return rows.map((r) => ({ memoryId: r.memory_id, @@ -286,7 +285,7 @@ class VectorStoreImpl implements VectorStore { vector: Float32Array, queryText: string, topK: number, - apiKeyId?: string, + apiKeyId?: string ): Promise { const db = getDbInstance(); const k = topK > 0 ? topK : TOP_K_DEFAULT; @@ -338,23 +337,16 @@ class VectorStoreImpl implements VectorStore { SELECT memory_id, vec_rank, fts_rank, vec_distance, fts_score, rrf_score FROM fused ORDER BY rrf_score DESC - LIMIT ?`, + LIMIT ?` ) - .all( - encodeVector(vector), - { apiKeyId: apiKeyId ?? null }, - k, - queryText, - k, - k, - ) as Array<{ - memory_id: string; - vec_rank: number | null; - fts_rank: number | null; - vec_distance: number | null; - fts_score: number | null; - rrf_score: number; - }>; + .all(encodeVector(vector), { apiKeyId: apiKeyId ?? null }, k, queryText, k, k) as Array<{ + memory_id: string; + vec_rank: number | null; + fts_rank: number | null; + vec_distance: number | null; + fts_score: number | null; + rrf_score: number; + }>; return rows.map((r) => ({ memoryId: r.memory_id, @@ -376,8 +368,7 @@ class VectorStoreImpl implements VectorStore { try { const db = getDbInstance(); const row = db.prepare("SELECT COUNT(*) AS cnt FROM vec_memories").get() as - | { cnt: number } - | undefined; + { cnt: number } | undefined; rowCount = row?.cnt ?? 0; } catch { // vec_memories may not exist yet — not an error, just 0 rows. @@ -431,7 +422,7 @@ export function getVectorStore(): VectorStore | null { // Test seam: VECTOR_STORE_DISABLE_VEC=true forces null (simulates cloud/WASM environment). if (process.env["VECTOR_STORE_DISABLE_VEC"] === "true") { log.warn( - "VECTOR_STORE_DISABLE_VEC is set — sqlite-vec disabled. Degrading to FTS5 keyword search.", + "VECTOR_STORE_DISABLE_VEC is set — sqlite-vec disabled. Degrading to FTS5 keyword search." ); _instance = null; return null; @@ -445,7 +436,7 @@ export function getVectorStore(): VectorStore | null { if (!raw || typeof raw.loadExtension !== "function") { log.warn( "sqlite-vec not loaded: db driver does not support loadExtension (cloud/WASM backend). " + - "Degrading to FTS5 keyword search.", + "Degrading to FTS5 keyword search." ); _instance = null; return null; diff --git a/src/lib/modelsDevSync.ts b/src/lib/modelsDevSync.ts index c4c0f2f273..1400ad7ed4 100644 --- a/src/lib/modelsDevSync.ts +++ b/src/lib/modelsDevSync.ts @@ -770,7 +770,7 @@ export async function initModelsDevSync(): Promise { return; } - const { getSettings } = await import("./localDb"); + const { getSettings } = await import("@/lib/db/settings"); const settings = await getSettings(); if (!isModelsDevSyncEnvForcedOn() && settings.modelsDevSyncEnabled !== true) { diff --git a/src/lib/monitoring/providerHealthAutopilot.ts b/src/lib/monitoring/providerHealthAutopilot.ts index e29844da36..b5fed7d413 100644 --- a/src/lib/monitoring/providerHealthAutopilot.ts +++ b/src/lib/monitoring/providerHealthAutopilot.ts @@ -1,7 +1,7 @@ import { createHash } from "crypto"; import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { clearProviderFailure, clearModelLock } from "@omniroute/open-sse/services/accountFallback"; import { resolveProviderAlias } from "@omniroute/open-sse/services/model"; diff --git a/src/lib/oauth/utils/agyAuthImport.ts b/src/lib/oauth/utils/agyAuthImport.ts index d0a526eb23..c7c4e42a3c 100644 --- a/src/lib/oauth/utils/agyAuthImport.ts +++ b/src/lib/oauth/utils/agyAuthImport.ts @@ -2,7 +2,7 @@ import { getProviderConnections, createProviderConnection, updateProviderConnection, -} from "@/lib/localDb"; +} from "@/lib/db/providers"; import { AGY_CONFIG } from "@/lib/oauth/constants/oauth"; import { getAntigravityContentHeaders, diff --git a/src/lib/oauth/utils/claudeAuthFile.ts b/src/lib/oauth/utils/claudeAuthFile.ts index 681c0eaab0..0f265e9039 100644 --- a/src/lib/oauth/utils/claudeAuthFile.ts +++ b/src/lib/oauth/utils/claudeAuthFile.ts @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { createBackup } from "@/shared/services/backupService"; import { getCliConfigPaths } from "@/shared/services/cliRuntime"; import { @@ -172,7 +172,9 @@ export function buildClaudeAuthPayload(connection: ClaudeConnectionLike): Claude } async function resolveFreshClaudeConnection(connectionId: string): Promise { - const connection = (await getCachedProviderConnectionById(connectionId)) as ClaudeConnectionLike | null; + const connection = (await getCachedProviderConnectionById( + connectionId + )) as ClaudeConnectionLike | null; if (!connection) { throw new ClaudeAuthFileError("Connection not found", 404, "not_found"); } diff --git a/src/lib/oauth/utils/claudeAuthImport.ts b/src/lib/oauth/utils/claudeAuthImport.ts index fb0a6aa000..2a5a444436 100644 --- a/src/lib/oauth/utils/claudeAuthImport.ts +++ b/src/lib/oauth/utils/claudeAuthImport.ts @@ -3,7 +3,7 @@ import { getProviderConnections, createProviderConnection, updateProviderConnection, -} from "@/lib/localDb"; +} from "@/lib/db/providers"; import { getClaudeCodeUserAgent } from "@/shared/constants/claudeCodeClient"; import { ClaudeAuthFileError } from "@/lib/oauth/utils/claudeAuthFile"; diff --git a/src/lib/oauth/utils/codexAuthFile.ts b/src/lib/oauth/utils/codexAuthFile.ts index e17875766e..8b505d7b38 100644 --- a/src/lib/oauth/utils/codexAuthFile.ts +++ b/src/lib/oauth/utils/codexAuthFile.ts @@ -1,6 +1,6 @@ import fs from "fs/promises"; import path from "path"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { createBackup } from "@/shared/services/backupService"; import { getCliConfigPaths } from "@/shared/services/cliRuntime"; import { @@ -197,7 +197,9 @@ function buildCodexAuthPayload(connection: CodexConnectionLike): CodexAuthFilePa } async function resolveFreshCodexConnection(connectionId: string): Promise { - const connection = (await getCachedProviderConnectionById(connectionId)) as CodexConnectionLike | null; + const connection = (await getCachedProviderConnectionById( + connectionId + )) as CodexConnectionLike | null; if (!connection) { throw new CodexAuthFileError("Connection not found", 404, "not_found"); } @@ -377,7 +379,11 @@ export type CodexAuthWriteDecision = export async function writeCodexAuthFileToLocalCliIfNeeded( connectionId: string, options: { force?: boolean } = {} -): Promise<{ decision: CodexAuthWriteDecision; authPath: string | null; result?: Awaited> }> { +): Promise<{ + decision: CodexAuthWriteDecision; + authPath: string | null; + result?: Awaited>; +}> { const paths = getCliConfigPaths("codex"); const authPath = paths?.auth ?? null; diff --git a/src/lib/oauth/utils/codexAuthImport.ts b/src/lib/oauth/utils/codexAuthImport.ts index 3ac22e09ca..4d16a3d1e6 100644 --- a/src/lib/oauth/utils/codexAuthImport.ts +++ b/src/lib/oauth/utils/codexAuthImport.ts @@ -2,7 +2,7 @@ import { getProviderConnections, createProviderConnection, updateProviderConnection, -} from "@/lib/localDb"; +} from "@/lib/db/providers"; import { CodexAuthFileError } from "@/lib/oauth/utils/codexAuthFile"; import { pickCodexConnectionForUser } from "@/lib/oauth/utils/codexConnectionSelection"; diff --git a/src/lib/providerModels/managedAvailableModels.ts b/src/lib/providerModels/managedAvailableModels.ts index b193fa4066..407022644c 100644 --- a/src/lib/providerModels/managedAvailableModels.ts +++ b/src/lib/providerModels/managedAvailableModels.ts @@ -2,9 +2,9 @@ import { deleteModelAlias, getModelAliases, getModelIsHidden, - getProviderNodeById, setModelAlias, -} from "@/lib/localDb"; +} from "@/lib/db/models"; +import { getProviderNodeById } from "@/lib/db/providers"; import { getProviderAlias, isAnthropicCompatibleProvider, diff --git a/src/lib/proxyEgress.ts b/src/lib/proxyEgress.ts index 1996bec763..af7ab5ded5 100644 --- a/src/lib/proxyEgress.ts +++ b/src/lib/proxyEgress.ts @@ -313,7 +313,7 @@ export async function diagnoseAllEgressIps(deps?: { const getConnections = deps?.getConnections ?? (async () => { - const { getProviderConnections } = await import("./localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); return (await getProviderConnections({ authType: "oauth" })) as Array<{ id: string; provider: string; diff --git a/src/lib/proxyHealth/scheduler.ts b/src/lib/proxyHealth/scheduler.ts index dc30453a34..8bf026ac33 100644 --- a/src/lib/proxyHealth/scheduler.ts +++ b/src/lib/proxyHealth/scheduler.ts @@ -22,7 +22,7 @@ * same threshold, not independently tunable. */ -import { deleteProxyById, listProxies, updateProxy } from "@/lib/localDb"; +import { deleteProxyById, listProxies, updateProxy } from "@/lib/db/proxies"; import { isProxyLogIncludeIps } from "@/lib/proxyLogger"; import { getRecentEgressSharingSummary, diff --git a/src/lib/quota/connectionProvider.ts b/src/lib/quota/connectionProvider.ts index 3255bffad6..df81842aa7 100644 --- a/src/lib/quota/connectionProvider.ts +++ b/src/lib/quota/connectionProvider.ts @@ -18,7 +18,7 @@ export async function resolveConnectionProvider(connectionId: string): Promise { try { // Lazy import — avoids circular deps and keeps the module loadable without a full DB. - const { getCachedProviderConnectionById } = await import("@/lib/localDb"); + const { getCachedProviderConnectionById } = await import("@/lib/db/readCache"); if (typeof getCachedProviderConnectionById === "function") { const conn = await getCachedProviderConnectionById(connectionId); if (conn && typeof (conn as { provider?: string }).provider === "string") { diff --git a/src/lib/quota/planResolver.ts b/src/lib/quota/planResolver.ts index a8a02647a7..aa04f3199b 100644 --- a/src/lib/quota/planResolver.ts +++ b/src/lib/quota/planResolver.ts @@ -12,7 +12,7 @@ * Part of: Group B — Quota Sharing Engine (plan 22, frente F6). */ -import { getProviderPlan } from "@/lib/localDb"; +import { getPlan as getProviderPlan } from "@/lib/db/providerPlans"; import { getKnownPlan } from "./planRegistry"; import type { ProviderPlan } from "./dimensions"; diff --git a/src/lib/quota/quotaCombos.ts b/src/lib/quota/quotaCombos.ts index 39dbe75a4a..07b7e12a28 100644 --- a/src/lib/quota/quotaCombos.ts +++ b/src/lib/quota/quotaCombos.ts @@ -12,7 +12,7 @@ import { getPool } from "@/lib/db/quotaPools"; import { getGroupName } from "@/lib/db/quotaGroups"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { getCombos, createCombo, diff --git a/src/lib/quota/quotaKey.ts b/src/lib/quota/quotaKey.ts index 7207798bcc..9f80068120 100644 --- a/src/lib/quota/quotaKey.ts +++ b/src/lib/quota/quotaKey.ts @@ -8,7 +8,7 @@ */ import { getPool, getPoolsByGroup } from "@/lib/db/quotaPools"; -import { getCachedProviderConnectionById } from "@/lib/localDb"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; import { getApiKeyById, updateApiKeyPermissions } from "@/lib/db/apiKeys"; import { quotaGroupSlug } from "./quotaModelNaming"; import { getGroupName } from "@/lib/db/quotaGroups"; @@ -163,7 +163,7 @@ export async function reconcilePoolExclusivity( poolId: string, prevApiKeyIds: string[], nextApiKeyIds: string[], - exclusive: boolean, + exclusive: boolean ): Promise { const affectedIds = new Set([...prevApiKeyIds, ...nextApiKeyIds]); @@ -173,7 +173,7 @@ export async function reconcilePoolExclusivity( if (!keyRow) continue; const currentQuotas: string[] = Array.isArray( - (keyRow as Record).allowedQuotas, + (keyRow as Record).allowedQuotas ) ? ((keyRow as Record).allowedQuotas as string[]) : []; diff --git a/src/lib/quota/redisQuotaStore.ts b/src/lib/quota/redisQuotaStore.ts index d9d17309ed..7ff3588a1e 100644 --- a/src/lib/quota/redisQuotaStore.ts +++ b/src/lib/quota/redisQuotaStore.ts @@ -16,10 +16,7 @@ * Part of: Group B — Quota Sharing Engine (plan 22, frente F6). */ -import { - getPool, - listAllocationsForApiKey, -} from "@/lib/localDb"; +import { getPool, listAllocationsForApiKey } from "@/lib/db/quotaPools"; import { WINDOW_MS, dimensionKeyToString } from "./dimensions"; import type { DimensionKey } from "./dimensions"; import type { QuotaStore, PoolUsageSnapshot } from "./types"; diff --git a/src/lib/quota/saturationSignals.ts b/src/lib/quota/saturationSignals.ts index 93d3d42690..0eee4331dc 100644 --- a/src/lib/quota/saturationSignals.ts +++ b/src/lib/quota/saturationSignals.ts @@ -306,10 +306,7 @@ async function fetchCodexSaturation( return Math.min(1, Math.max(0, quota.percentUsed ?? 0)); } -async function fetchBailianSaturation( - connectionId: string, - dim: DimensionSpec -): Promise { +async function fetchBailianSaturation(connectionId: string, dim: DimensionSpec): Promise { const mod = await import("@omniroute/open-sse/services/bailianQuotaFetcher"); const quota = await mod.fetchBailianQuota(connectionId); if (!quota) return 0; @@ -318,13 +315,13 @@ async function fetchBailianSaturation( let pct = 0; switch (dim.window) { case "5h": - pct = (q.window5h as Record)?.percentUsed as number ?? 0; + pct = ((q.window5h as Record)?.percentUsed as number) ?? 0; break; case "weekly": - pct = (q.windowWeekly as Record)?.percentUsed as number ?? 0; + pct = ((q.windowWeekly as Record)?.percentUsed as number) ?? 0; break; case "monthly": - pct = (q.windowMonthly as Record)?.percentUsed as number ?? 0; + pct = ((q.windowMonthly as Record)?.percentUsed as number) ?? 0; break; default: pct = (q.percentUsed as number) ?? 0; @@ -361,15 +358,13 @@ interface AnthropicSaturationDeps { let _anthropicDepsOverride: AnthropicSaturationDeps | null = null; /** Test-only: inject ({loadConnection, fetchUsage}); pass null to restore. */ -export function __setAnthropicSaturationDepsForTests( - deps: AnthropicSaturationDeps | null -): void { +export function __setAnthropicSaturationDepsForTests(deps: AnthropicSaturationDeps | null): void { _anthropicDepsOverride = deps; } async function defaultAnthropicDeps(): Promise { const [localDbMod, usageMod] = await Promise.all([ - import("@/lib/localDb"), + import("@/lib/db/readCache"), import("@omniroute/open-sse/services/usage"), ]); return { @@ -423,10 +418,7 @@ function planUtilizationFromUsage(usage: unknown, window: QuotaWindow): number | return Math.min(1, Math.max(0, used / 100)); } -async function fetchAnthropicSaturation( - connectionId: string, - dim: DimensionSpec -): Promise { +async function fetchAnthropicSaturation(connectionId: string, dim: DimensionSpec): Promise { // Try the REAL plan-window utilization first (5h / weekly), via the same // /api/oauth/usage path usage.ts already uses. This is the signal fairShare // actually needs for Claude Pro/Max — the per-minute request headers do not @@ -478,19 +470,13 @@ export function __setGenericUsageFetcherForTests(fetcher: GenericUsageFetcher | _genericUsageFetcherOverride = fetcher; } -async function defaultGenericUsageFetch( - connectionId: string, - provider: string -): Promise { +async function defaultGenericUsageFetch(connectionId: string, provider: string): Promise { const mod = await import("@omniroute/open-sse/services/usage"); const conn = { id: connectionId, provider } as Parameters[0]; return mod.getUsageForProvider(conn); } -async function fetchGenericSaturation( - connectionId: string, - provider: string -): Promise { +async function fetchGenericSaturation(connectionId: string, provider: string): Promise { // 1. Real usage percent is authoritative when present (a provider that // actually reports utilization beats the burst-window token headers). try { @@ -501,9 +487,8 @@ async function fetchGenericSaturation( // Prefer the normalized quota shape (handles nested `quotas` map for // Antigravity / Claude / etc.). Fall back to legacy top-level fields. - const { convertUsageToQuotaInfo } = await import( - "@omniroute/open-sse/services/genericQuotaFetcher" - ); + const { convertUsageToQuotaInfo } = + await import("@omniroute/open-sse/services/genericQuotaFetcher"); const quota = convertUsageToQuotaInfo(result); if (quota && Number.isFinite(quota.percentUsed)) { return Math.min(1, Math.max(0, quota.percentUsed)); @@ -570,7 +555,10 @@ export async function getSaturation( break; } } catch (err) { - log.warn({ err: (err as Error)?.message, connectionId, provider }, "saturation fetch failed — failing open with 0"); + log.warn( + { err: (err as Error)?.message, connectionId, provider }, + "saturation fetch failed — failing open with 0" + ); value = 0; } diff --git a/src/lib/quota/sqliteQuotaStore.ts b/src/lib/quota/sqliteQuotaStore.ts index dab40da91f..8685e68c85 100644 --- a/src/lib/quota/sqliteQuotaStore.ts +++ b/src/lib/quota/sqliteQuotaStore.ts @@ -15,7 +15,8 @@ * Part of: Group B — Quota Sharing Engine (plan 22, frente F6). */ -import { getPool, getBucket, incrementBucket, getPair, sumPoolDimension } from "@/lib/localDb"; +import { getPool } from "@/lib/db/quotaPools"; +import { getBucket, incrementBucket, getPair, sumPoolDimension } from "@/lib/db/quotaConsumption"; import { WINDOW_MS, dimensionKeyToString } from "./dimensions"; import type { DimensionKey } from "./dimensions"; import type { QuotaStore, PoolUsageSnapshot } from "./types"; diff --git a/src/lib/services/quotaAutoPing.ts b/src/lib/services/quotaAutoPing.ts index 4a9ce8fe3e..21c6466b52 100644 --- a/src/lib/services/quotaAutoPing.ts +++ b/src/lib/services/quotaAutoPing.ts @@ -25,7 +25,8 @@ import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; import { getExecutor } from "@omniroute/open-sse/executors/index.ts"; import type { BaseExecutor } from "@omniroute/open-sse/executors/base"; import { getCodexUsage } from "@omniroute/open-sse/services/usage/codex.ts"; -import { getSettings, getProviderConnections, updateProviderConnection } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { refreshAndUpdateCredentials } from "@/lib/usage/providerLimits"; import { getCircuitBreaker } from "@/shared/utils/circuitBreaker"; @@ -57,17 +58,12 @@ export interface QuotaAutoPingConnection { export interface QuotaAutoPingDeps { getSettings: () => Promise; - getProviderConnections: ( - filter: JsonRecord - ) => Promise; + getProviderConnections: (filter: JsonRecord) => Promise; updateProviderConnection: (id: string, data: JsonRecord) => Promise; refreshAndUpdateCredentials: ( connection: QuotaAutoPingConnection ) => Promise<{ connection: QuotaAutoPingConnection }>; - getCodexUsage: ( - accessToken?: string, - providerSpecificData?: JsonRecord - ) => Promise; + getCodexUsage: (accessToken?: string, providerSpecificData?: JsonRecord) => Promise; getExecutor: (provider: string) => Promise; canExecuteProvider: (provider: string) => boolean; isConnectionUnavailableToAuxiliaryActivity: (connectionId: string) => Promise; @@ -152,11 +148,7 @@ function wasPingedRecently( return Number.isFinite(lastPingAtMs) && nowMs - lastPingAtMs < intervalMs; } -function shouldSkipAfterFailure( - state: QuotaAutoPingState, - key: string, - nowMs: number -): boolean { +function shouldSkipAfterFailure(state: QuotaAutoPingState, key: string, nowMs: number): boolean { const failedAt = state.failureCache[key]; return Boolean(failedAt) && nowMs - failedAt < QUOTA_AUTOPING_FAILURE_COOLDOWN_MS; } @@ -272,8 +264,8 @@ async function isPingCandidateBlocked( // resetAt — the guard is preserved here for that case. return Boolean( !providerConfig.pingWhenResetAtSlides && - cachedReset && - nowMs < new Date(cachedReset).getTime() - QUOTA_AUTOPING_REFRESH_AHEAD_MS + cachedReset && + nowMs < new Date(cachedReset).getTime() - QUOTA_AUTOPING_REFRESH_AHEAD_MS ); } @@ -331,7 +323,18 @@ async function pingConnection( ): Promise { const key = cacheKey(provider, connection.id); const cachedReset = state.resetCache[key]; - if (await isPingCandidateBlocked(connection, provider, providerConfig, deps, state, key, cachedReset, nowMs)) { + if ( + await isPingCandidateBlocked( + connection, + provider, + providerConfig, + deps, + state, + key, + cachedReset, + nowMs + ) + ) { return; } @@ -346,7 +349,9 @@ async function pingConnection( state.resetCache[key] = resetAt; const resetKey = normalizeResetKey(resetAt); - if (!shouldSendPing(providerConfig, quotas, quota, cachedReset, resetAt, current, resetKey, nowMs)) { + if ( + !shouldSendPing(providerConfig, quotas, quota, cachedReset, resetAt, current, resetKey, nowMs) + ) { return; } @@ -371,8 +376,7 @@ function getEnabledConnectionIds( ): Record { return ( ((settings[providerConfig.settingsKey] as JsonRecord | undefined)?.connections as - | Record - | undefined) || {} + Record | undefined) || {} ); } diff --git a/src/lib/sync/bundle.ts b/src/lib/sync/bundle.ts index 3f13edf8fa..46e5100b3a 100644 --- a/src/lib/sync/bundle.ts +++ b/src/lib/sync/bundle.ts @@ -1,13 +1,11 @@ import { createHash } from "crypto"; -import { - getApiKeys, - getCombos, - getModelAliases, - getProviderConnections, - getCachedProviderNodes, - getSettings, - getReasoningRoutingRules, -} from "@/lib/localDb"; +import { getApiKeys } from "@/lib/db/apiKeys"; +import { getCombos } from "@/lib/db/combos"; +import { getModelAliases } from "@/lib/db/models"; +import { getProviderConnections } from "@/lib/db/providers"; +import { getCachedProviderNodes } from "@/lib/db/readCache"; +import { getSettings } from "@/lib/db/settings"; +import { getReasoningRoutingRules } from "@/lib/db/reasoningRoutingRules"; type JsonRecord = Record; diff --git a/src/lib/tokenHealthCheck.ts b/src/lib/tokenHealthCheck.ts index b3a4e2e074..2820a28e23 100644 --- a/src/lib/tokenHealthCheck.ts +++ b/src/lib/tokenHealthCheck.ts @@ -11,13 +11,9 @@ * updates the DB, and logs the result. */ -import { - getProviderConnections, - getCachedProviderConnectionById, - updateProviderConnection, - getSettings, - resolveProxyForConnection, -} from "@/lib/localDb"; +import { getProviderConnections, updateProviderConnection } from "@/lib/db/providers"; +import { getCachedProviderConnectionById } from "@/lib/db/readCache"; +import { getSettings, resolveProxyForConnection } from "@/lib/db/settings"; import { getAccessToken, getDeprecationNotice, diff --git a/src/lib/tokenHealthCheckCopilot.ts b/src/lib/tokenHealthCheckCopilot.ts index f2c58ef362..bd1a9ea078 100644 --- a/src/lib/tokenHealthCheckCopilot.ts +++ b/src/lib/tokenHealthCheckCopilot.ts @@ -10,7 +10,7 @@ * frozen file-size budget (see config/quality/file-size-baseline.json). */ -import { getProviderConnectionById, updateProviderConnection } from "@/lib/localDb"; +import { getProviderConnectionById, updateProviderConnection } from "@/lib/db/providers"; import { refreshCopilotToken } from "@omniroute/open-sse/services/tokenRefresh.ts"; type HealthCheckLogger = { @@ -30,8 +30,17 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: { getConnectionLogLabel: (conn: { name?: string; email?: string; id?: string }) => string; logPrefix: string; }): Promise { - const { conn, result, proxyConfig, healthCheckLog, log, logWarn, logError, getConnectionLogLabel, logPrefix } = - params; + const { + conn, + result, + proxyConfig, + healthCheckLog, + log, + logWarn, + logError, + getConnectionLogLabel, + logPrefix, + } = params; if (String(conn.provider || "").toLowerCase() !== "github") return; @@ -58,7 +67,11 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: { log(`${logPrefix} Refreshing GitHub Copilot sub-token for ${getConnectionLogLabel(conn)}`); try { - const copilotResult = await refreshCopilotToken(accessTokenForCopilot, healthCheckLog, proxyConfig); + const copilotResult = await refreshCopilotToken( + accessTokenForCopilot, + healthCheckLog, + proxyConfig + ); if (copilotResult?.token) { await updateProviderConnection(conn.id, { providerSpecificData: { @@ -69,7 +82,9 @@ export async function refreshGithubCopilotSubTokenIfNeeded(params: { }); log(`${logPrefix} ✓ GitHub Copilot sub-token refreshed for ${getConnectionLogLabel(conn)}`); } else { - logWarn(`${logPrefix} ✗ GitHub Copilot sub-token refresh failed for ${getConnectionLogLabel(conn)}`); + logWarn( + `${logPrefix} ✗ GitHub Copilot sub-token refresh failed for ${getConnectionLogLabel(conn)}` + ); } } catch (copilotErr) { logError(`${logPrefix} Error refreshing Copilot sub-token:`, copilotErr?.message || copilotErr); diff --git a/src/lib/tokenHealthCheckCursor.ts b/src/lib/tokenHealthCheckCursor.ts index beee152554..3950062a05 100644 --- a/src/lib/tokenHealthCheckCursor.ts +++ b/src/lib/tokenHealthCheckCursor.ts @@ -8,7 +8,7 @@ * import technique — tokenHealthCheck.ts-private helpers passed as params * rather than imported) but greenfield: type-checked normally, no * @ts-nocheck, and imports updateProviderConnection directly from its - * owning module rather than the localDb barrel (Hard Rule #2). + * owning db/ module (Hard Rule #2). */ import { updateProviderConnection } from "@/lib/db/providers"; diff --git a/src/lib/usage/apiKeySelfService.ts b/src/lib/usage/apiKeySelfService.ts index 1e7237e12c..2e9dc9ffa7 100644 --- a/src/lib/usage/apiKeySelfService.ts +++ b/src/lib/usage/apiKeySelfService.ts @@ -1,7 +1,4 @@ -import { - hasSelfAccountQuotaScope, - hasSelfUsageScope, -} from "@/shared/constants/selfServiceScopes"; +import { hasSelfAccountQuotaScope, hasSelfUsageScope } from "@/shared/constants/selfServiceScopes"; import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers"; type JsonRecord = Record; @@ -363,7 +360,7 @@ async function normalizeDeps(deps: ApiKeySelfServiceDeps): Promise const localDb = deps.getProviderConnectionById && deps.getProviderConnections ? null - : await import("@/lib/localDb"); + : await import("@/lib/db/providers"); const providerLimits = deps.fetchAndPersistProviderLimits ? null : await import("@/lib/usage/providerLimits"); @@ -396,11 +393,11 @@ export async function buildApiKeySelfServiceStatus( const tokens = aggregateTokens( resolvedDeps.getDbInstance() as DbLike, metadata.id, - cost.periodStartAt ?? new Date(getCurrentMonthWindow(resolvedDeps.now()).periodStartAt).toISOString() + cost.periodStartAt ?? + new Date(getCurrentMonthWindow(resolvedDeps.now()).periodStartAt).toISOString() ); const accountQuotas = await resolveAccountQuotas(metadata, resolvedDeps); - const accountQuota = - accountQuotas && accountQuotas.length === 1 ? accountQuotas[0] : undefined; + const accountQuota = accountQuotas && accountQuotas.length === 1 ? accountQuotas[0] : undefined; return { apiKey: { diff --git a/src/lib/usage/callLogs.ts b/src/lib/usage/callLogs.ts index 5e3c878c0c..029bc7ee2f 100644 --- a/src/lib/usage/callLogs.ts +++ b/src/lib/usage/callLogs.ts @@ -140,7 +140,7 @@ async function resolveAccountName(connectionId: string | null | undefined) { } try { - const { getProviderConnections } = await import("@/lib/localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); const connections = await getProviderConnections(); const conn = connections.find((item) => item.id === connectionId); if (conn) { @@ -160,7 +160,7 @@ async function resolveAccountName(connectionId: string | null | undefined) { async function resolveProviderPrefix(providerId: string): Promise { if (!providerId) return null; try { - const { getProviderNodeById } = await import("@/lib/localDb"); + const { getProviderNodeById } = await import("@/lib/db/providers"); const node = await getProviderNodeById(providerId); if (node && typeof node.prefix === "string" && node.prefix.trim().length > 0) { return node.prefix.trim(); diff --git a/src/lib/usage/codexResetCredits.ts b/src/lib/usage/codexResetCredits.ts index ab54b40a9d..9a341101b4 100644 --- a/src/lib/usage/codexResetCredits.ts +++ b/src/lib/usage/codexResetCredits.ts @@ -1,4 +1,5 @@ -import { getProviderConnectionById, resolveProxyForConnection } from "@/lib/localDb"; +import { getProviderConnectionById } from "@/lib/db/providers"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { fetchAndPersistProviderLimits, diff --git a/src/lib/usage/costCalculator.ts b/src/lib/usage/costCalculator.ts index 08151ac1aa..106b7f8c4c 100644 --- a/src/lib/usage/costCalculator.ts +++ b/src/lib/usage/costCalculator.ts @@ -187,7 +187,7 @@ export async function calculateCost( if (exactCostUsd !== null) return exactCostUsd; try { - const { getPricingForModel } = await import("@/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); // Try exact match first, then normalized model name let pricing = await getPricingForModel(provider, model); @@ -303,7 +303,7 @@ export async function calculateModalCost( ): Promise { if (!provider || !model) return 0; try { - const { getPricingForModel } = await import("@/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); let pricing = await getPricingForModel(provider, model); if (!pricing) { const normalized = normalizeModelName(model); diff --git a/src/lib/usage/internalUsageCommand.ts b/src/lib/usage/internalUsageCommand.ts index 37f5f009d1..e106cced51 100644 --- a/src/lib/usage/internalUsageCommand.ts +++ b/src/lib/usage/internalUsageCommand.ts @@ -99,7 +99,7 @@ async function normalizeDeps(deps: InternalUsageCommandDeps = {}): Promise { const [{ getCachedSettings }, { resolveResilienceSettings }] = await Promise.all([ - import("@/lib/localDb"), + import("@/lib/db/readCache"), import("@/lib/resilience/settings"), ]); const resilience = resolveResilienceSettings(await getCachedSettings()); @@ -829,7 +829,10 @@ export async function handleInternalUsageCommandHttpRequest( if (!apiKey || !(await resolvedDeps.isValidApiKey(apiKey))) { if (json) { return Response.json( - { allowed: false, error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE } } satisfies UsageCommandJson, + { + allowed: false, + error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE }, + } satisfies UsageCommandJson, { status: 401 } ); } @@ -840,7 +843,10 @@ export async function handleInternalUsageCommandHttpRequest( if (!metadata?.id) { if (json) { return Response.json( - { allowed: false, error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE } } satisfies UsageCommandJson, + { + allowed: false, + error: { message: USAGE_COMMAND_AUTH_REQUIRED_MESSAGE }, + } satisfies UsageCommandJson, { status: 401 } ); } @@ -850,7 +856,10 @@ export async function handleInternalUsageCommandHttpRequest( if (metadata.allowUsageCommand !== true) { if (json) { return Response.json( - { allowed: false, error: { message: USAGE_COMMAND_DISABLED_MESSAGE } } satisfies UsageCommandJson, + { + allowed: false, + error: { message: USAGE_COMMAND_DISABLED_MESSAGE }, + } satisfies UsageCommandJson, { status: 403 } ); } diff --git a/src/lib/usage/usageStats.ts b/src/lib/usage/usageStats.ts index b4be4d1967..573f66b2c0 100644 --- a/src/lib/usage/usageStats.ts +++ b/src/lib/usage/usageStats.ts @@ -285,7 +285,7 @@ export async function getUsageStats() { const sourceSql = buildUsageSourceSql(aggregationEnabled); const sourceParams = aggregationEnabled && cutoffDate ? [cutoffDate, cutoffDate] : []; - const { getProviderConnections } = await import("@/lib/localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); let allConnections: unknown[] = []; try { const loadedConnections = await getProviderConnections(); diff --git a/src/lib/usageAnalytics.ts b/src/lib/usageAnalytics.ts index 386a3d6e34..459d8e1895 100644 --- a/src/lib/usageAnalytics.ts +++ b/src/lib/usageAnalytics.ts @@ -149,7 +149,7 @@ export async function computeAnalytics( // Pre-fetch pricing for all unique (provider, model) pairs — one DB round-trip // per unique pair instead of one per entry, then compute costs synchronously. - const { getPricingForModel } = await import("@/lib/localDb"); + const { getPricingForModel } = await import("@/lib/db/settings"); const pricingCache = new Map | null>(); const uniquePairs = new Set(entries.map((e) => `${e.provider}|||${e.model}`)); await Promise.all( diff --git a/src/lib/ws/handshake.ts b/src/lib/ws/handshake.ts index 8fafc39b53..454fbe9457 100644 --- a/src/lib/ws/handshake.ts +++ b/src/lib/ws/handshake.ts @@ -1,5 +1,5 @@ import { jwtVerify } from "jose"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { validateApiKey } from "@/lib/db/apiKeys"; export const DEFAULT_WS_PATH = "/v1/ws"; diff --git a/src/models/index.ts b/src/models/index.ts index 90b3c78b11..39e8828c12 100755 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,4 +1,4 @@ -// Database Models - Export all from localDb +// Database Models - Export all from the owning db modules export { getProviderConnections, getProviderConnectionsCount, @@ -15,17 +15,16 @@ export { updateProviderNode, deleteProviderNode, deleteProviderConnectionsByProvider, +} from "@/lib/db/providers"; +export { getModelAliases, setModelAlias, deleteModelAlias, deleteModelAliasesForProvider, getMitmAlias, setMitmAliasAll, - getApiKeys, - createApiKey, - deleteApiKey, - validateApiKey, - isCloudEnabled, - resolveProxyForProvider, getHiddenModelsByProvider, -} from "@/lib/localDb"; +} from "@/lib/db/models"; +export { getApiKeys, createApiKey, deleteApiKey, validateApiKey } from "@/lib/db/apiKeys"; +export { isCloudEnabled } from "@/lib/db/settings"; +export { resolveProxyForProvider } from "@/lib/db/proxies"; diff --git a/src/shared/services/apiKeyResolver.ts b/src/shared/services/apiKeyResolver.ts index e79bc9bd99..003be6443f 100644 --- a/src/shared/services/apiKeyResolver.ts +++ b/src/shared/services/apiKeyResolver.ts @@ -1,4 +1,4 @@ -import { getApiKeyById, createApiKey } from "@/lib/localDb"; +import { getApiKeyById, createApiKey } from "@/lib/db/apiKeys"; import { getConsistentMachineId } from "@/shared/utils/machineId"; export async function resolveApiKey( diff --git a/src/shared/services/cloudSyncScheduler.ts b/src/shared/services/cloudSyncScheduler.ts index d56e5cd237..ec2605d0f9 100644 --- a/src/shared/services/cloudSyncScheduler.ts +++ b/src/shared/services/cloudSyncScheduler.ts @@ -1,5 +1,5 @@ import { getConsistentMachineId } from "@/shared/utils/machineId"; -import { isCloudEnabled } from "@/lib/localDb"; +import { isCloudEnabled } from "@/lib/db/settings"; import { getRuntimePorts } from "@/lib/runtime/ports"; const { dashboardPort } = getRuntimePorts(); diff --git a/src/shared/services/initializeCloudSync.ts b/src/shared/services/initializeCloudSync.ts index 78fdc7b4ad..631b4391ae 100644 --- a/src/shared/services/initializeCloudSync.ts +++ b/src/shared/services/initializeCloudSync.ts @@ -1,5 +1,6 @@ import { getCloudSyncScheduler } from "@/shared/services/cloudSyncScheduler"; -import { isCloudEnabled, cleanupProviderConnections } from "@/lib/localDb"; +import { isCloudEnabled } from "@/lib/db/settings"; +import { cleanupProviderConnections } from "@/lib/db/providers"; /** * Initialize cloud sync scheduler diff --git a/src/shared/services/modelSyncScheduler.ts b/src/shared/services/modelSyncScheduler.ts index f45bc194f4..c63a5e50ac 100644 --- a/src/shared/services/modelSyncScheduler.ts +++ b/src/shared/services/modelSyncScheduler.ts @@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto"; import { Agent, buildConnector, fetch as undiciFetch, type Dispatcher } from "undici"; -import { getSettings, updateSettings } from "@/lib/localDb"; +import { getSettings, updateSettings } from "@/lib/db/settings"; import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation"; import { getRuntimePorts } from "@/lib/runtime/ports"; @@ -151,7 +151,7 @@ async function getAutoSyncConnections(): Promise< Array<{ id: string; provider: string; name?: string }> > { try { - const { getProviderConnections } = await import("@/lib/localDb"); + const { getProviderConnections } = await import("@/lib/db/providers"); const connections = await getProviderConnections(); const autoSyncConnections: Array<{ id: string; provider: string; name?: string }> = []; for (const conn of connections) { diff --git a/src/shared/utils/apiAuth.ts b/src/shared/utils/apiAuth.ts index 8a40c493de..d53c72ea9e 100644 --- a/src/shared/utils/apiAuth.ts +++ b/src/shared/utils/apiAuth.ts @@ -9,7 +9,7 @@ import { jwtVerify } from "jose"; import { cookies } from "next/headers"; -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { isPublicApiRoute } from "@/shared/constants/publicApiRoutes"; import { extractApiKey } from "@/sse/services/auth"; diff --git a/src/shared/utils/apiKeyPolicy.ts b/src/shared/utils/apiKeyPolicy.ts index ebd23c9ead..49cb628bb0 100644 --- a/src/shared/utils/apiKeyPolicy.ts +++ b/src/shared/utils/apiKeyPolicy.ts @@ -9,12 +9,8 @@ */ import { extractApiKey } from "@/sse/services/auth"; -import { - getApiKeyMetadata, - getComboByName, - isModelAllowedForKey, - getApiKeyById, -} from "@/lib/localDb"; +import { getApiKeyMetadata, isModelAllowedForKey, getApiKeyById } from "@/lib/db/apiKeys"; +import { getComboByName } from "@/lib/db/combos"; import { isDashboardSessionAuthenticated } from "./apiAuth"; import { resolveComboForModel } from "@/lib/db/modelComboMappings"; import { checkBudget } from "@/domain/costRules"; diff --git a/src/shared/validation/schemas/combo.ts b/src/shared/validation/schemas/combo.ts index 7182d92213..843b637897 100644 --- a/src/shared/validation/schemas/combo.ts +++ b/src/shared/validation/schemas/combo.ts @@ -200,6 +200,7 @@ export const comboRuntimeConfigSchema = z fallbackCompressionMode: compressionModeSchema.optional(), fallbackCompressionThreshold: z.coerce.number().int().min(0).max(2_000_000).optional(), predictiveTtftMs: z.coerce.number().int().min(0).max(300000).optional(), + relayMode: z.enum(["schema-locked", "standard"]).optional(), // Auto-Combo / LKGP Extensions candidatePool: z.array(z.string().min(1)).optional(), weights: scoringWeightsSchema.optional(), diff --git a/src/sse/handlers/autoRouting.ts b/src/sse/handlers/autoRouting.ts index 8920c71d33..d76b1e2340 100644 --- a/src/sse/handlers/autoRouting.ts +++ b/src/sse/handlers/autoRouting.ts @@ -14,7 +14,7 @@ import { isValidModelFamily, type ModelFamily, } from "@omniroute/open-sse/services/autoCombo/modelFamily.ts"; -import { getCachedSettings } from "@/lib/localDb"; +import { getCachedSettings } from "@/lib/db/readCache"; import * as log from "../utils/logger"; export type AutoRoutingState = { diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index d504d67086..3852ee1d46 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -59,6 +59,7 @@ import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS, } from "@omniroute/open-sse/config/providerModels.ts"; +import { getPassthroughProviders } from "@omniroute/open-sse/config/providerRegistry.ts"; import * as log from "../utils/logger"; import { checkAndRefreshToken } from "../services/tokenRefresh"; import { createHookContext, runHooks, initPreRequestRegistry } from "@/lib/middleware/registry"; @@ -1098,7 +1099,7 @@ async function handleChatImplementation( return credentials; })(), cachedSettings: settings, - providerId: target?.providerId ?? null, + providerId: target?.providerId ?? (target as any)?.provider ?? null, correlationId: reqId, conversationId, modelPinned: (target as any)?.modelPinned ?? false, @@ -1389,7 +1390,7 @@ async function handleSingleModelChat( comboExecutionKey: null, skipUpstreamRetry: resolvedTarget?.failoverBeforeRetry === true, allowRateLimitedConnection: resolvedTarget?.allowRateLimitedConnection === true, - providerId: resolvedTarget?.providerId ?? null, + providerId: resolvedTarget?.providerId ?? (resolvedTarget as any)?.provider ?? null, correlationId: runtimeOptions?.correlationId ?? null, reasoningTransportFallback: redirectCombo.config?.reasoningTransportFallback === "skip" ? "skip" : "drop", @@ -1738,10 +1739,18 @@ async function handleSingleModelChat( // defaultModel, resolve the bare name to that real model ID before the // upstream call so the provider receives a concrete model rather than the // placeholder. A "/"-qualified model name is always left untouched. - const effectiveModel = + let effectiveModel = resolveBareModelToConnectionDefault(modelStr, model, credentials.defaultModel) ?? model; let requestBody = effectiveModel !== model ? { ...body, model: `${provider}/${effectiveModel}` } : body; + + // If the combo explicitly overrode the provider to a passthrough provider, we + // must preserve the original unstripped modelStr so that proxy providers + // (e.g., cline, kilocode) get the exact string they expect. + if (provider !== resolvedProvider && getPassthroughProviders().has(provider)) { + effectiveModel = modelStr; + requestBody = { ...body, model: modelStr }; + } if (!runtimeOptions.reasoningDecision && runtimeOptions.reasoningIntent) { const connectionRouting = await applyConnectionReasoningRule({ requestBody, diff --git a/src/sse/handlers/chatHelpers.ts b/src/sse/handlers/chatHelpers.ts index f1db55f6e8..958d934573 100644 --- a/src/sse/handlers/chatHelpers.ts +++ b/src/sse/handlers/chatHelpers.ts @@ -1,4 +1,8 @@ -import { getModelInfo, getComboForModel, getModelInfoOrRetirementResponse } from "../services/model"; +import { + getModelInfo, + getComboForModel, + getModelInfoOrRetirementResponse, +} from "../services/model"; import { clearAccountError, markAccountUnavailable } from "../services/auth"; import { connectionHasExtraKeys } from "@omniroute/open-sse/services/apiKeyRotator.ts"; import { createBuiltinAutoCombo } from "@omniroute/open-sse/services/autoCombo/builtinCatalog.ts"; @@ -26,7 +30,7 @@ import { isTlsFingerprintActive, type AppliedProxySink, } from "@omniroute/open-sse/utils/proxyFetch.ts"; -import { resolveProxyForConnection } from "@/lib/localDb"; +import { resolveProxyForConnection } from "@/lib/db/settings"; import { hasBlockingProxyAssignment } from "@/lib/db/proxies"; import { CircuitBreakerOpenError, diff --git a/src/sse/services/model.ts b/src/sse/services/model.ts index b28e3e5d2f..a1fe37f238 100644 --- a/src/sse/services/model.ts +++ b/src/sse/services/model.ts @@ -1,15 +1,9 @@ // Re-export from open-sse with localDb integration -import { - getModelAliases, - getComboByName, - getComboById, - getComboByNameInsensitive, - getCachedProviderNodes, - getCustomModels, -} from "@/lib/localDb"; +import { getModelAliases, getCustomModels } from "@/lib/db/models"; +import { getComboByName, getComboById, getComboByNameInsensitive } from "@/lib/db/combos"; +import { getCachedProviderNodes, getCachedSettings } from "@/lib/db/readCache"; import { getSyncedAutoAliases } from "@/lib/providerModels/syncedAutoAliases.ts"; -import { getCachedSettings } from "@/lib/localDb"; import { getActiveSyncedCatalog } from "@/lib/db/models/activeSyncedCatalog"; import { getModelCompatOverrides } from "@/lib/db/models/compat"; import { getNoAuthHydrationProviderIds } from "./noAuthProviderSiblings"; @@ -660,7 +654,7 @@ export async function getComboForModel(modelStr) { // 2. NEW — check model-combo mappings table (pattern match) try { - const { resolveComboForModel } = await import("@/lib/localDb"); + const { resolveComboForModel } = await import("@/lib/db/modelComboMappings"); const mapped = await resolveComboForModel(baseModelStr || modelStr); if (mapped && (mapped as any).models?.length > 0) { return mapped; diff --git a/src/sse/services/noAuthProviderSettings.ts b/src/sse/services/noAuthProviderSettings.ts index dee365c5f1..f700e2dc6f 100644 --- a/src/sse/services/noAuthProviderSettings.ts +++ b/src/sse/services/noAuthProviderSettings.ts @@ -1,4 +1,4 @@ -import { getSettings } from "@/lib/localDb"; +import { getSettings } from "@/lib/db/settings"; import { isProviderBlockedByIdOrAlias } from "@/shared/utils/noAuthProviders"; import * as log from "../utils/logger"; diff --git a/src/sse/services/tokenRefresh.ts b/src/sse/services/tokenRefresh.ts index 4955cb8247..f7e65b1173 100755 --- a/src/sse/services/tokenRefresh.ts +++ b/src/sse/services/tokenRefresh.ts @@ -1,10 +1,8 @@ // Re-export from open-sse with local logger import * as log from "../utils/logger"; -import { - updateProviderConnection, - resolveProxyForConnection, - resolveProxyForProvider, -} from "@/lib/localDb"; +import { updateProviderConnection } from "@/lib/db/providers"; +import { resolveProxyForConnection } from "@/lib/db/settings"; +import { resolveProxyForProvider } from "@/lib/db/proxies"; import { TOKEN_EXPIRY_BUFFER_MS as BUFFER_MS, getRefreshLeadMs as _getRefreshLeadMs, diff --git a/stryker.conf.json b/stryker.conf.json index e89ddaf143..c391cb3c9e 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -39,9 +39,7 @@ "incremental": true, "incrementalFile": "reports/mutation/stryker-incremental.json", "testRunner": "tap", - "plugins": [ - "@stryker-mutator/tap-runner" - ], + "plugins": ["@stryker-mutator/tap-runner"], "tap": { "testFiles": [ "tests/unit/10085-compatible-generic-vs-uuid-credential.test.ts", @@ -266,6 +264,7 @@ "tests/unit/issue-7071-ollama-session-quota.test.ts", "tests/unit/kimi-quota-reset-recovery.test.ts", "tests/unit/least-used-rotation-10945.test.ts", + "tests/unit/lkgp-stale-pin-exhaustion-11911.test.ts", "tests/unit/livews-forward-backoff-4604.test.ts", "tests/unit/management-auth-hardening.test.ts", "tests/unit/mark-account-unavailable-numeric-epoch-guard.test.ts", @@ -512,11 +511,7 @@ ".worktrees", ".stryker-tmp" ], - "reporters": [ - "progress", - "html", - "json" - ], + "reporters": ["progress", "html", "json"], "htmlReporter": { "fileName": "reports/mutation/mutation.html" }, diff --git a/tests/integration/all-statuses-route.test.ts b/tests/integration/all-statuses-route.test.ts index 4c3924020c..dcda79eb2d 100644 --- a/tests/integration/all-statuses-route.test.ts +++ b/tests/integration/all-statuses-route.test.ts @@ -20,7 +20,8 @@ process.env.API_KEY_SECRET = "test-all-statuses-secret"; // Import DB modules after setting DATA_DIR const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); // Import cliTools modules (batchStatusCache for cache tests) diff --git a/tests/integration/api-keys.test.ts b/tests/integration/api-keys.test.ts index 51e667e1bd..4e82c15874 100644 --- a/tests/integration/api-keys.test.ts +++ b/tests/integration/api-keys.test.ts @@ -12,7 +12,8 @@ process.env.CLOUD_URL = "http://cloud.example"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const listRoute = await import("../../src/app/api/keys/route.ts"); const keyRoute = await import("../../src/app/api/keys/[id]/route.ts"); diff --git a/tests/integration/api-routes-critical.test.ts b/tests/integration/api-routes-critical.test.ts index 7f04c9245b..ccc8df4fc2 100644 --- a/tests/integration/api-routes-critical.test.ts +++ b/tests/integration/api-routes-critical.test.ts @@ -11,7 +11,10 @@ process.env.API_KEY_SECRET = "test-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { assignProxyToScope, createProxy } = await import("@/lib/db/proxies"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const localDb = { updateSettings, assignProxyToScope, createProviderConnection, createProxy }; const proxiesRoute = await import("../../src/app/api/v1/management/proxies/route.ts"); const settingsProxyRoute = await import("../../src/app/api/settings/proxy/route.ts"); const settingsMitmRoute = await import("../../src/app/api/settings/mitm/route.ts"); diff --git a/tests/integration/cli-settings-codewhale.test.ts b/tests/integration/cli-settings-codewhale.test.ts index ed843a1373..a9f2e4789e 100644 --- a/tests/integration/cli-settings-codewhale.test.ts +++ b/tests/integration/cli-settings-codewhale.test.ts @@ -20,7 +20,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-codewhale"; process.env.JWT_SECRET = "test-jwt-secret-codewhale"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/codewhale-settings/route.ts"); diff --git a/tests/integration/cli-settings-deepseek-tui.test.ts b/tests/integration/cli-settings-deepseek-tui.test.ts index a4d7ffee60..2560511750 100644 --- a/tests/integration/cli-settings-deepseek-tui.test.ts +++ b/tests/integration/cli-settings-deepseek-tui.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-deepseek-tui"; process.env.JWT_SECRET = "test-jwt-secret-deepseek-tui"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/deepseek-tui-settings/route.ts"); diff --git a/tests/integration/cli-settings-forge.test.ts b/tests/integration/cli-settings-forge.test.ts index 8cb4b7538e..a0d30d92a5 100644 --- a/tests/integration/cli-settings-forge.test.ts +++ b/tests/integration/cli-settings-forge.test.ts @@ -16,7 +16,8 @@ process.env.JWT_SECRET = "test-jwt-secret-forge"; // Import DB reset helpers (must be before route import) const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route handlers const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/forge-settings/route.ts"); diff --git a/tests/integration/cli-settings-grok-build.test.ts b/tests/integration/cli-settings-grok-build.test.ts index 8ab679d9c2..31c696aa70 100644 --- a/tests/integration/cli-settings-grok-build.test.ts +++ b/tests/integration/cli-settings-grok-build.test.ts @@ -29,7 +29,8 @@ process.env.OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE = "1"; // Import DB reset helpers (must be before route import) const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); // Import route handlers diff --git a/tests/integration/cli-settings-jcode.test.ts b/tests/integration/cli-settings-jcode.test.ts index 6d1930aa83..d05002813a 100644 --- a/tests/integration/cli-settings-jcode.test.ts +++ b/tests/integration/cli-settings-jcode.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-jcode"; process.env.JWT_SECRET = "test-jwt-secret-jcode"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/jcode-settings/route.ts"); diff --git a/tests/integration/cli-settings-letta.test.ts b/tests/integration/cli-settings-letta.test.ts index d6a07ddc69..53c950d3da 100644 --- a/tests/integration/cli-settings-letta.test.ts +++ b/tests/integration/cli-settings-letta.test.ts @@ -20,7 +20,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-letta"; process.env.JWT_SECRET = "test-jwt-secret-letta"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/letta-settings/route.ts"); diff --git a/tests/integration/cli-settings-omp.test.ts b/tests/integration/cli-settings-omp.test.ts index c07715ff4f..75ce683f23 100644 --- a/tests/integration/cli-settings-omp.test.ts +++ b/tests/integration/cli-settings-omp.test.ts @@ -22,7 +22,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-omp"; process.env.JWT_SECRET = "test-jwt-secret-omp"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/omp-settings/route.ts"); diff --git a/tests/integration/cli-settings-pi.test.ts b/tests/integration/cli-settings-pi.test.ts index 29133efd51..71984da16d 100644 --- a/tests/integration/cli-settings-pi.test.ts +++ b/tests/integration/cli-settings-pi.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-pi"; process.env.JWT_SECRET = "test-jwt-secret-pi"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/pi-settings/route.ts"); diff --git a/tests/integration/cli-settings-smelt.test.ts b/tests/integration/cli-settings-smelt.test.ts index 8395b4f1eb..4290e12a18 100644 --- a/tests/integration/cli-settings-smelt.test.ts +++ b/tests/integration/cli-settings-smelt.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-smelt"; process.env.JWT_SECRET = "test-jwt-secret-smelt"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/smelt-settings/route.ts"); diff --git a/tests/integration/combo-live/cost-and-fusion.live.test.ts b/tests/integration/combo-live/cost-and-fusion.live.test.ts index 8a1b59e04e..525b049137 100644 --- a/tests/integration/combo-live/cost-and-fusion.live.test.ts +++ b/tests/integration/combo-live/cost-and-fusion.live.test.ts @@ -61,10 +61,7 @@ const PROVIDER_DEFAULT_MODELS: Record = { async function isHealthy(conn: LiveConnection): Promise { if (!h.LIVE_ENABLED) return false; - const model = - conn.model ?? - PROVIDER_DEFAULT_MODELS[conn.provider] ?? - `${conn.provider}/default`; + const model = conn.model ?? PROVIDER_DEFAULT_MODELS[conn.provider] ?? `${conn.provider}/default`; const directModel = `${conn.provider}/${model}`; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), WARMUP_TIMEOUT_MS); @@ -90,14 +87,18 @@ async function isHealthy(conn: LiveConnection): Promise { /** * Pick up to `n` confirmed-healthy connections, preferring fast/cheap providers. */ -async function pickConfirmedHealthy( - n: number, - preferred?: string[] -): Promise { +async function pickConfirmedHealthy(n: number, preferred?: string[]): Promise { if (!h.LIVE_ENABLED) return []; const conns = await (h as any).listLiveConnections(); - const PREFERRED_ORDER = - preferred ?? ["groq", "cerebras", "opencode-go", "deepseek", "gemini", "together", "openrouter"]; + const PREFERRED_ORDER = preferred ?? [ + "groq", + "cerebras", + "opencode-go", + "deepseek", + "gemini", + "together", + "openrouter", + ]; const sorted = [...conns].sort((a: LiveConnection, b: LiveConnection) => { const ai = PREFERRED_ORDER.indexOf(a.provider); const bi = PREFERRED_ORDER.indexOf(b.provider); @@ -118,7 +119,7 @@ async function pickConfirmedHealthy( */ async function resolveInputCost(provider: string, model: string): Promise { try { - const { getPricingForModel } = await import("../../../src/lib/localDb.ts"); + const { getPricingForModel } = await import("@/lib/db/settings"); const pricing = await getPricingForModel(provider, model); const cost = Number((pricing as any)?.input); return Number.isFinite(cost) ? cost : Infinity; @@ -164,317 +165,344 @@ after(async () => { // Test 1: cost-optimized — cheaper real provider served first // --------------------------------------------------------------------------- -test("live cost-optimized — cheaper real provider served first", { - skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", -}, async () => { - if (!h.LIVE_ENABLED) return; +test( + "live cost-optimized — cheaper real provider served first", + { + skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", + }, + async () => { + if (!h.LIVE_ENABLED) return; - // We prefer groq+deepseek: distinct model names (easy to confirm served), - // and deepseek has a known non-zero price ($0.28/M) while groq free models - // land at $0 in the registry. The cost sorter puts $0 before $0.28, so if - // we list deepseek FIRST and groq SECOND, a correct sort gives us groq first. - // - // We fall back to cerebras if groq is unhealthy. - const candidates = await pickConfirmedHealthy(2, [ - "groq", "cerebras", "deepseek", "opencode-go", - ]); - - // We need exactly 2 healthy connections with DISTINCT providers. - const seen = new Set(); - const uniqueCandidates = candidates.filter((c: LiveConnection) => { - if (seen.has(c.provider)) return false; - seen.add(c.provider); - return true; - }); - - if (uniqueCandidates.length < 2) { - console.log( - `[cost-optimized skip] Only ${uniqueCandidates.length} distinct healthy provider(s) — need ≥2. Skipping.` - ); - return; - } - - const [a, b] = uniqueCandidates; - const aModel = a.model ?? PROVIDER_DEFAULT_MODELS[a.provider] ?? `${a.provider}/default`; - const bModel = b.model ?? PROVIDER_DEFAULT_MODELS[b.provider] ?? `${b.provider}/default`; - - // Resolve pricing from the live DB snapshot. - const aCost = await resolveInputCost(a.provider, aModel); - const bCost = await resolveInputCost(b.provider, bModel); - - console.log( - `[cost-optimized] Candidates: ${a.provider}/${aModel} ($${aCost}/M) vs ${b.provider}/${bModel} ($${bCost}/M)` - ); - - // If both are Infinity (no pricing data for either), we can't prove cost ordering. - if (!Number.isFinite(aCost) && !Number.isFinite(bCost)) { - console.log( - `[cost-optimized skip] Neither ${a.provider} nor ${b.provider} has resolvable catalog ` + - `pricing in the live DB snapshot — cannot prove cost ordering. Skipping.` - ); - return; - } - - // If both are equal (including both $0), we can't prove reordering. - if (aCost === bCost) { - console.log( - `[cost-optimized skip] Both providers have equal pricing ($${aCost}/M each) — ` + - `cannot prove reordering. Skipping.` - ); - return; - } - - // Identify cheap vs pricey. - const [cheapConn, priceyConn] = - aCost <= bCost ? [a, b] : [b, a]; - const [cheapCost, priceyCost] = - aCost <= bCost ? [aCost, bCost] : [bCost, aCost]; - const cheapModel = - cheapConn.model ?? PROVIDER_DEFAULT_MODELS[cheapConn.provider] ?? `${cheapConn.provider}/default`; - const priceyModel = - priceyConn.model ?? PROVIDER_DEFAULT_MODELS[priceyConn.provider] ?? `${priceyConn.provider}/default`; - - console.log( - `[cost-optimized] Cheap: ${cheapConn.provider}/${cheapModel} ($${cheapCost}/M), ` + - `Pricey: ${priceyConn.provider}/${priceyModel} ($${priceyCost}/M)` - ); - - // Create combo with PRICEY first — a correct cost sorter must reorder to CHEAP first. - const comboName = `__live-smoke-cost-opt-${Date.now()}__`; - const combo = await (h as any).combosDb.createCombo({ - name: comboName, - strategy: "cost-optimized", - // Pricey listed FIRST: proves the sorter reorders, not just uses the given order. - models: [(h as any).comboModelFor(priceyConn), (h as any).comboModelFor(cheapConn)], - config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 }, - }); - - try { - const response = await (h as any).handleChat( - (h as any).buildRequest({ - body: (h as any).liveBody(comboName, { - messages: [{ role: "user", content: `ping ${uniqueNonce("cost-opt")}` }], - }), - }) - ); - - assert.equal(response.status, 200, `Expected HTTP 200, got ${response.status}`); - const text = await (h as any).readCompletionText(response); - assert.ok(text.length > 0, "Expected non-empty completion text from cost-optimized combo"); - - // Collect served-provider signals. - const headerProvider = (h as any).servedProvider(response); - const bodyProvider = await (h as any).servedProviderFromBody(response); - const rawModel = await readResponseModel(response); - - console.log( - `[cost-optimized] served: header=${headerProvider ?? "(absent)"}, ` + - `body=${bodyProvider ?? "(absent)"}, model="${rawModel ?? "(n/a)"}"` - ); - - // PRIMARY ASSERTION: served provider must be the cheap one, not the pricey one. - // The cost sorter should have put the cheap provider first despite it being listed second. + // We prefer groq+deepseek: distinct model names (easy to confirm served), + // and deepseek has a known non-zero price ($0.28/M) while groq free models + // land at $0 in the registry. The cost sorter puts $0 before $0.28, so if + // we list deepseek FIRST and groq SECOND, a correct sort gives us groq first. // - // We use three signals in priority order: - // 1. X-OmniRoute-Selected-Connection-Id header (fallback paths only — may be absent on 200). - // 2. Body model field provider prefix (e.g. "groq/model" → "groq"). - // 3. Raw model string comparison (model name matches cheap provider's known model). + // We fall back to cerebras if groq is unhealthy. + const candidates = await pickConfirmedHealthy(2, [ + "groq", + "cerebras", + "deepseek", + "opencode-go", + ]); - // Signal 1: header. - if (headerProvider !== undefined) { - assert.equal( - headerProvider, - cheapConn.provider, - `[header] Expected cheap provider "${cheapConn.provider}" to serve, got "${headerProvider}". ` + - `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` - ); + // We need exactly 2 healthy connections with DISTINCT providers. + const seen = new Set(); + const uniqueCandidates = candidates.filter((c: LiveConnection) => { + if (seen.has(c.provider)) return false; + seen.add(c.provider); + return true; + }); + + if (uniqueCandidates.length < 2) { console.log( - `[cost-optimized PASS via header] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized skip] Only ${uniqueCandidates.length} distinct healthy provider(s) — need ≥2. Skipping.` ); return; } - // Signal 2: body provider prefix. - if (bodyProvider !== undefined) { - assert.equal( - bodyProvider, - cheapConn.provider, - `[body prefix] Expected cheap provider "${cheapConn.provider}" to serve, got "${bodyProvider}". ` + - `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` - ); + const [a, b] = uniqueCandidates; + const aModel = a.model ?? PROVIDER_DEFAULT_MODELS[a.provider] ?? `${a.provider}/default`; + const bModel = b.model ?? PROVIDER_DEFAULT_MODELS[b.provider] ?? `${b.provider}/default`; + + // Resolve pricing from the live DB snapshot. + const aCost = await resolveInputCost(a.provider, aModel); + const bCost = await resolveInputCost(b.provider, bModel); + + console.log( + `[cost-optimized] Candidates: ${a.provider}/${aModel} ($${aCost}/M) vs ${b.provider}/${bModel} ($${bCost}/M)` + ); + + // If both are Infinity (no pricing data for either), we can't prove cost ordering. + if (!Number.isFinite(aCost) && !Number.isFinite(bCost)) { console.log( - `[cost-optimized PASS via body prefix] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized skip] Neither ${a.provider} nor ${b.provider} has resolvable catalog ` + + `pricing in the live DB snapshot — cannot prove cost ordering. Skipping.` ); return; } - // Signal 3: raw model string. - if (rawModel !== undefined) { - // If the response model matches the cheap provider's model name, the cheap provider served. - if (rawModel === cheapModel || rawModel.endsWith(`/${cheapModel}`)) { + // If both are equal (including both $0), we can't prove reordering. + if (aCost === bCost) { + console.log( + `[cost-optimized skip] Both providers have equal pricing ($${aCost}/M each) — ` + + `cannot prove reordering. Skipping.` + ); + return; + } + + // Identify cheap vs pricey. + const [cheapConn, priceyConn] = aCost <= bCost ? [a, b] : [b, a]; + const [cheapCost, priceyCost] = aCost <= bCost ? [aCost, bCost] : [bCost, aCost]; + const cheapModel = + cheapConn.model ?? + PROVIDER_DEFAULT_MODELS[cheapConn.provider] ?? + `${cheapConn.provider}/default`; + const priceyModel = + priceyConn.model ?? + PROVIDER_DEFAULT_MODELS[priceyConn.provider] ?? + `${priceyConn.provider}/default`; + + console.log( + `[cost-optimized] Cheap: ${cheapConn.provider}/${cheapModel} ($${cheapCost}/M), ` + + `Pricey: ${priceyConn.provider}/${priceyModel} ($${priceyCost}/M)` + ); + + // Create combo with PRICEY first — a correct cost sorter must reorder to CHEAP first. + const comboName = `__live-smoke-cost-opt-${Date.now()}__`; + const combo = await (h as any).combosDb.createCombo({ + name: comboName, + strategy: "cost-optimized", + // Pricey listed FIRST: proves the sorter reorders, not just uses the given order. + models: [(h as any).comboModelFor(priceyConn), (h as any).comboModelFor(cheapConn)], + config: { maxRetries: 0, retryDelayMs: 0, stickyRoundRobinLimit: 1 }, + }); + + try { + const response = await (h as any).handleChat( + (h as any).buildRequest({ + body: (h as any).liveBody(comboName, { + messages: [{ role: "user", content: `ping ${uniqueNonce("cost-opt")}` }], + }), + }) + ); + + assert.equal(response.status, 200, `Expected HTTP 200, got ${response.status}`); + const text = await (h as any).readCompletionText(response); + assert.ok(text.length > 0, "Expected non-empty completion text from cost-optimized combo"); + + // Collect served-provider signals. + const headerProvider = (h as any).servedProvider(response); + const bodyProvider = await (h as any).servedProviderFromBody(response); + const rawModel = await readResponseModel(response); + + console.log( + `[cost-optimized] served: header=${headerProvider ?? "(absent)"}, ` + + `body=${bodyProvider ?? "(absent)"}, model="${rawModel ?? "(n/a)"}"` + ); + + // PRIMARY ASSERTION: served provider must be the cheap one, not the pricey one. + // The cost sorter should have put the cheap provider first despite it being listed second. + // + // We use three signals in priority order: + // 1. X-OmniRoute-Selected-Connection-Id header (fallback paths only — may be absent on 200). + // 2. Body model field provider prefix (e.g. "groq/model" → "groq"). + // 3. Raw model string comparison (model name matches cheap provider's known model). + + // Signal 1: header. + if (headerProvider !== undefined) { + assert.equal( + headerProvider, + cheapConn.provider, + `[header] Expected cheap provider "${cheapConn.provider}" to serve, got "${headerProvider}". ` + + `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` + ); console.log( - `[cost-optimized PASS via model field] model="${rawModel}" matches cheap provider ` + - `${cheapConn.provider}/${cheapModel} (cost $${cheapCost}/M < $${priceyCost}/M)` + `[cost-optimized PASS via header] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` ); return; } - // If the response model matches the pricey provider's model name, the cost sort failed. - if (rawModel === priceyModel || rawModel.endsWith(`/${priceyModel}`)) { - assert.fail( - `[model field] Pricey provider "${priceyConn.provider}" (model="${priceyModel}", ` + - `$${priceyCost}/M) served BEFORE cheap "${cheapConn.provider}" (model="${cheapModel}", ` + - `$${cheapCost}/M) — cost sorter did not reorder correctly.` + // Signal 2: body provider prefix. + if (bodyProvider !== undefined) { + assert.equal( + bodyProvider, + cheapConn.provider, + `[body prefix] Expected cheap provider "${cheapConn.provider}" to serve, got "${bodyProvider}". ` + + `Cost sorter may not have reordered: cheap=$${cheapCost}/M, pricey=$${priceyCost}/M` ); + console.log( + `[cost-optimized PASS via body prefix] ${cheapConn.provider} served (cost $${cheapCost}/M < $${priceyCost}/M)` + ); + return; } - // Model field present but does not match either known model (provider echoes an - // aliased or prefixed name). We cannot distinguish which provider served. - console.warn( - `[cost-optimized] Signal ambiguous: rawModel="${rawModel}" does not match ` + - `"${cheapModel}" or "${priceyModel}". Got 200 + non-empty text; cannot confirm ` + - `cheap provider served. Recording as diagnostic-pass (cost gap confirmed: ` + - `$${cheapCost}/M vs $${priceyCost}/M).` - ); - return; - } + // Signal 3: raw model string. + if (rawModel !== undefined) { + // If the response model matches the cheap provider's model name, the cheap provider served. + if (rawModel === cheapModel || rawModel.endsWith(`/${cheapModel}`)) { + console.log( + `[cost-optimized PASS via model field] model="${rawModel}" matches cheap provider ` + + `${cheapConn.provider}/${cheapModel} (cost $${cheapCost}/M < $${priceyCost}/M)` + ); + return; + } - // No signal at all — 200 + non-empty but we cannot confirm which provider served. - console.warn( - `[cost-optimized] All provider signals absent (header=absent, body=absent, model=absent). ` + - `Got HTTP 200 + non-empty text. Cost gap confirmed ($${cheapCost}/M vs $${priceyCost}/M) ` + - `but serving provider not identifiable. Recording as diagnostic-pass.` - ); - } finally { - if (typeof combo?.id === "string") { - await (h as any).combosDb.deleteCombo(combo.id as string); + // If the response model matches the pricey provider's model name, the cost sort failed. + if (rawModel === priceyModel || rawModel.endsWith(`/${priceyModel}`)) { + assert.fail( + `[model field] Pricey provider "${priceyConn.provider}" (model="${priceyModel}", ` + + `$${priceyCost}/M) served BEFORE cheap "${cheapConn.provider}" (model="${cheapModel}", ` + + `$${cheapCost}/M) — cost sorter did not reorder correctly.` + ); + } + + // Model field present but does not match either known model (provider echoes an + // aliased or prefixed name). We cannot distinguish which provider served. + console.warn( + `[cost-optimized] Signal ambiguous: rawModel="${rawModel}" does not match ` + + `"${cheapModel}" or "${priceyModel}". Got 200 + non-empty text; cannot confirm ` + + `cheap provider served. Recording as diagnostic-pass (cost gap confirmed: ` + + `$${cheapCost}/M vs $${priceyCost}/M).` + ); + return; + } + + // No signal at all — 200 + non-empty but we cannot confirm which provider served. + console.warn( + `[cost-optimized] All provider signals absent (header=absent, body=absent, model=absent). ` + + `Got HTTP 200 + non-empty text. Cost gap confirmed ($${cheapCost}/M vs $${priceyCost}/M) ` + + `but serving provider not identifiable. Recording as diagnostic-pass.` + ); + } finally { + if (typeof combo?.id === "string") { + await (h as any).combosDb.deleteCombo(combo.id as string); + } } } -}); +); // --------------------------------------------------------------------------- // Test 2: fusion — panel fans out + judge synthesizes one answer // --------------------------------------------------------------------------- -test("live fusion — panel fans out and judge synthesizes one answer", { - skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", -}, async () => { - if (!h.LIVE_ENABLED) return; +test( + "live fusion — panel fans out and judge synthesizes one answer", + { + skip: !h.LIVE_ENABLED && "RUN_COMBO_LIVE!=1", + }, + async () => { + if (!h.LIVE_ENABLED) return; - // Cost guard: pick ≤3 panel providers. Use cheapest/most reliable. - const candidates = await pickConfirmedHealthy(3, [ - "groq", "cerebras", "opencode-go", "deepseek", "gemini", "together", - ]); + // Cost guard: pick ≤3 panel providers. Use cheapest/most reliable. + const candidates = await pickConfirmedHealthy(3, [ + "groq", + "cerebras", + "opencode-go", + "deepseek", + "gemini", + "together", + ]); - // Need at least 2 distinct healthy providers to run a real fusion. - const seen = new Set(); - const panelConns = candidates - .filter((c: LiveConnection) => { - if (seen.has(c.provider)) return false; - seen.add(c.provider); - return true; - }) - .slice(0, 3); // cap at 3 to limit cost - - if (panelConns.length < 2) { - console.log( - `[fusion skip] Only ${panelConns.length} distinct healthy provider(s) confirmed — ` + - `need ≥2 for a real panel. Skipping.` - ); - return; - } - - // Judge = first (cheapest) panel provider — cheap enough for a 16-token synthesis call. - const judgeConn = panelConns[0]; - const judgeModel = - judgeConn.model ?? - PROVIDER_DEFAULT_MODELS[judgeConn.provider] ?? - `${judgeConn.provider}/default`; - const judgeModelStr = `${judgeConn.provider}/${judgeModel}`; - - const panelModels = panelConns.map((c: LiveConnection) => (h as any).comboModelFor(c)); - console.log( - `[fusion] Panel (${panelConns.length}): ${panelConns.map((c: LiveConnection) => c.provider).join(", ")} | ` + - `judge: ${judgeModelStr}` - ); - - const comboName = `__live-smoke-fusion-${Date.now()}__`; - const combo = await (h as any).combosDb.createCombo({ - name: comboName, - strategy: "fusion", - models: panelModels, - config: { - maxRetries: 0, - retryDelayMs: 0, - judgeModel: judgeModelStr, - fusionTuning: { - minPanel: 2, - panelHardTimeoutMs: 90_000, - }, - }, - }); - - try { - const response = await (h as any).handleChat( - (h as any).buildRequest({ - body: (h as any).liveBody(comboName, { - messages: [{ role: "user", content: `ping ${uniqueNonce("fusion")} — reply in one short sentence` }], - // max_tokens:16 is the harness default via liveBody(); the judge call will - // also be bounded, keeping the panel + judge calls cheap. - }), + // Need at least 2 distinct healthy providers to run a real fusion. + const seen = new Set(); + const panelConns = candidates + .filter((c: LiveConnection) => { + if (seen.has(c.provider)) return false; + seen.add(c.provider); + return true; }) - ); + .slice(0, 3); // cap at 3 to limit cost - assert.equal(response.status, 200, `Expected HTTP 200 from fusion combo, got ${response.status}`); + if (panelConns.length < 2) { + console.log( + `[fusion skip] Only ${panelConns.length} distinct healthy provider(s) confirmed — ` + + `need ≥2 for a real panel. Skipping.` + ); + return; + } - const text = await (h as any).readCompletionText(response); - assert.ok( - text.length > 0, - "Expected a non-empty synthesized completion from the fusion judge" - ); - - // The fusion response comes from the JUDGE call. - // The body's `model` field should reflect the judge model, confirming the judge ran. - const rawModel = await readResponseModel(response); - const bodyProvider = await (h as any).servedProviderFromBody(response); + // Judge = first (cheapest) panel provider — cheap enough for a 16-token synthesis call. + const judgeConn = panelConns[0]; + const judgeModel = + judgeConn.model ?? + PROVIDER_DEFAULT_MODELS[judgeConn.provider] ?? + `${judgeConn.provider}/default`; + const judgeModelStr = `${judgeConn.provider}/${judgeModel}`; + const panelModels = panelConns.map((c: LiveConnection) => (h as any).comboModelFor(c)); console.log( - `[fusion] synthesized text (first 80 chars): "${text.slice(0, 80)}" | ` + - `model="${rawModel ?? "(n/a)"}" | body provider=${bodyProvider ?? "(absent)"}` + `[fusion] Panel (${panelConns.length}): ${panelConns.map((c: LiveConnection) => c.provider).join(", ")} | ` + + `judge: ${judgeModelStr}` ); - // SIGNAL ANALYSIS — panel/judge evidence. - // The judge model string is judgeModelStr (e.g. "groq/llama-3.1-8b-instant"). - // If rawModel matches the judge's model name, the judge ran. - if (rawModel !== undefined) { - const judgeRan = - rawModel === judgeModel || - rawModel === judgeModelStr || - rawModel.endsWith(`/${judgeModel}`); - if (judgeRan) { - console.log( - `[fusion EVIDENCE] Judge confirmed: response model="${rawModel}" matches judge ${judgeModelStr}` - ); - } else { - // Model field doesn't match judge — may be aliased or provider returns own name. - console.warn( - `[fusion] response model="${rawModel}" does not exactly match judge "${judgeModelStr}". ` + - `Panel synthesis still assumed from HTTP 200 + non-empty text.` - ); + const comboName = `__live-smoke-fusion-${Date.now()}__`; + const combo = await (h as any).combosDb.createCombo({ + name: comboName, + strategy: "fusion", + models: panelModels, + config: { + maxRetries: 0, + retryDelayMs: 0, + judgeModel: judgeModelStr, + fusionTuning: { + minPanel: 2, + panelHardTimeoutMs: 90_000, + }, + }, + }); + + try { + const response = await (h as any).handleChat( + (h as any).buildRequest({ + body: (h as any).liveBody(comboName, { + messages: [ + { + role: "user", + content: `ping ${uniqueNonce("fusion")} — reply in one short sentence`, + }, + ], + // max_tokens:16 is the harness default via liveBody(); the judge call will + // also be bounded, keeping the panel + judge calls cheap. + }), + }) + ); + + assert.equal( + response.status, + 200, + `Expected HTTP 200 from fusion combo, got ${response.status}` + ); + + const text = await (h as any).readCompletionText(response); + assert.ok( + text.length > 0, + "Expected a non-empty synthesized completion from the fusion judge" + ); + + // The fusion response comes from the JUDGE call. + // The body's `model` field should reflect the judge model, confirming the judge ran. + const rawModel = await readResponseModel(response); + const bodyProvider = await (h as any).servedProviderFromBody(response); + + console.log( + `[fusion] synthesized text (first 80 chars): "${text.slice(0, 80)}" | ` + + `model="${rawModel ?? "(n/a)"}" | body provider=${bodyProvider ?? "(absent)"}` + ); + + // SIGNAL ANALYSIS — panel/judge evidence. + // The judge model string is judgeModelStr (e.g. "groq/llama-3.1-8b-instant"). + // If rawModel matches the judge's model name, the judge ran. + if (rawModel !== undefined) { + const judgeRan = + rawModel === judgeModel || + rawModel === judgeModelStr || + rawModel.endsWith(`/${judgeModel}`); + if (judgeRan) { + console.log( + `[fusion EVIDENCE] Judge confirmed: response model="${rawModel}" matches judge ${judgeModelStr}` + ); + } else { + // Model field doesn't match judge — may be aliased or provider returns own name. + console.warn( + `[fusion] response model="${rawModel}" does not exactly match judge "${judgeModelStr}". ` + + `Panel synthesis still assumed from HTTP 200 + non-empty text.` + ); + } + } + + // The fusion panel had ≥2 members — at least 2 distinct upstream calls happened + // before the judge synthesized. We can't easily introspect individual panel calls + // from the test layer (they're internal to fusion.ts / combo.ts). The 200 + non-empty + // text from the judge is the authoritative proof of fusion completing. + console.log( + `[fusion PASS] Panel(${panelConns.length}) → judge(${judgeModelStr}) synthesis: HTTP 200, ` + + `${text.length} chars. Provider signal from body: ${bodyProvider ?? "absent (model name has no slash prefix)"}.` + ); + } finally { + if (typeof combo?.id === "string") { + await (h as any).combosDb.deleteCombo(combo.id as string); } } - - // The fusion panel had ≥2 members — at least 2 distinct upstream calls happened - // before the judge synthesized. We can't easily introspect individual panel calls - // from the test layer (they're internal to fusion.ts / combo.ts). The 200 + non-empty - // text from the judge is the authoritative proof of fusion completing. - console.log( - `[fusion PASS] Panel(${panelConns.length}) → judge(${judgeModelStr}) synthesis: HTTP 200, ` + - `${text.length} chars. Provider signal from body: ${bodyProvider ?? "absent (model name has no slash prefix)"}.` - ); - } finally { - if (typeof combo?.id === "string") { - await (h as any).combosDb.deleteCombo(combo.id as string); - } } -}); +); diff --git a/tests/integration/files-api.test.ts b/tests/integration/files-api.test.ts index e83cf5c840..914aae6f2a 100644 --- a/tests/integration/files-api.test.ts +++ b/tests/integration/files-api.test.ts @@ -1,6 +1,6 @@ import { describe, it, afterEach } from "node:test"; import assert from "node:assert"; -import { createFile, listFiles, deleteFile, getFile } from "@/lib/localDb"; +import { createFile, listFiles, deleteFile, getFile } from "@/lib/db/files"; describe("Files API - Integration Tests", () => { afterEach(() => { diff --git a/tests/integration/memory-embedding-providers.test.ts b/tests/integration/memory-embedding-providers.test.ts index 74bf51fe79..9f26b588f6 100644 --- a/tests/integration/memory-embedding-providers.test.ts +++ b/tests/integration/memory-embedding-providers.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-embedding-providers"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route AFTER setting DATA_DIR const embeddingProvidersRoute = diff --git a/tests/integration/memory-engine-status.test.ts b/tests/integration/memory-engine-status.test.ts index 3cff56385b..8bd612a347 100644 --- a/tests/integration/memory-engine-status.test.ts +++ b/tests/integration/memory-engine-status.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-engine-status"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route AFTER setting DATA_DIR const engineStatusRoute = await import("../../src/app/api/memory/engine-status/route.ts"); diff --git a/tests/integration/memory-reindex.test.ts b/tests/integration/memory-reindex.test.ts index 4eb2188c98..199416bfc8 100644 --- a/tests/integration/memory-reindex.test.ts +++ b/tests/integration/memory-reindex.test.ts @@ -20,7 +20,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-reindex"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const memoryStore = await import("../../src/lib/memory/store.ts"); const reindexRoute = await import("../../src/app/api/memory/reindex/route.ts"); diff --git a/tests/integration/memory-retrieve-preview.test.ts b/tests/integration/memory-retrieve-preview.test.ts index 36c4437d05..efa8483cdb 100644 --- a/tests/integration/memory-retrieve-preview.test.ts +++ b/tests/integration/memory-retrieve-preview.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-retrieve-preview"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // Import route AFTER setting DATA_DIR const retrieveRoute = await import("../../src/app/api/memory/retrieve-preview/route.ts"); diff --git a/tests/integration/memory-route-put.test.ts b/tests/integration/memory-route-put.test.ts index 3d952cb002..42be96ecc4 100644 --- a/tests/integration/memory-route-put.test.ts +++ b/tests/integration/memory-route-put.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-for-memory-put"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // ── Dynamic import of route module (after DATA_DIR set) ── const memoryIdRoute = await import("../../src/app/api/memory/[id]/route.ts"); diff --git a/tests/integration/memory-summarize.test.ts b/tests/integration/memory-summarize.test.ts index fa8c3b131e..812c99dca8 100644 --- a/tests/integration/memory-summarize.test.ts +++ b/tests/integration/memory-summarize.test.ts @@ -19,7 +19,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-summarize"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const memoryStore = await import("../../src/lib/memory/store.ts"); const summarizeRoute = await import("../../src/app/api/memory/summarize/route.ts"); diff --git a/tests/integration/provider-journey.contract.test.ts b/tests/integration/provider-journey.contract.test.ts index 655f6f7b8f..9e8bb53743 100644 --- a/tests/integration/provider-journey.contract.test.ts +++ b/tests/integration/provider-journey.contract.test.ts @@ -56,7 +56,10 @@ process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; process.env.INITIAL_PASSWORD = "provider-journey-bootstrap"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { updateProviderConnection } = await import("@/lib/db/providers"); +const { getCachedProviderNodes } = await import("@/lib/db/readCache"); +const localDb = { updateSettings, updateProviderConnection, getCachedProviderNodes }; const modelsDb = await import("../../src/lib/db/models.ts"); const providerNodesRoute = await import("../../src/app/api/provider-nodes/route.ts"); const providersRoute = await import("../../src/app/api/providers/route.ts"); diff --git a/tests/integration/qdrant-routes.test.ts b/tests/integration/qdrant-routes.test.ts index bc6c291adc..4a7636b9c2 100644 --- a/tests/integration/qdrant-routes.test.ts +++ b/tests/integration/qdrant-routes.test.ts @@ -26,7 +26,9 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-qdrant-routes"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings, getSettings } = await import("@/lib/db/settings"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const localDb = { updateSettings, getSettings, createProviderConnection }; const memorySettings = await import("../../src/lib/memory/settings.ts"); // ── Route imports ── diff --git a/tests/integration/quota-plans-crud.test.ts b/tests/integration/quota-plans-crud.test.ts index 77c2943303..fb4a391c09 100644 --- a/tests/integration/quota-plans-crud.test.ts +++ b/tests/integration/quota-plans-crud.test.ts @@ -23,7 +23,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-quota-plans-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const plansRoute = await import("../../src/app/api/quota/plans/route.ts"); const planIdRoute = await import("../../src/app/api/quota/plans/[connectionId]/route.ts"); diff --git a/tests/integration/quota-pool-usage-provider-resolution.test.ts b/tests/integration/quota-pool-usage-provider-resolution.test.ts index 533ddb642f..5aec4f4945 100644 --- a/tests/integration/quota-pool-usage-provider-resolution.test.ts +++ b/tests/integration/quota-pool-usage-provider-resolution.test.ts @@ -27,8 +27,8 @@ process.env.API_KEY_SECRET = "test-quota-usage-provider-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); -const { createPool, upsertAllocations, createProviderConnection } = localDb; +const { createPool, upsertAllocations } = await import("@/lib/db/quotaPools"); +const { createProviderConnection } = await import("@/lib/db/providers"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const usageRoute = await import("../../src/app/api/quota/pools/[id]/usage/route.ts"); diff --git a/tests/integration/quota-pools-crud.test.ts b/tests/integration/quota-pools-crud.test.ts index da3d3e8bb2..0b8fa51657 100644 --- a/tests/integration/quota-pools-crud.test.ts +++ b/tests/integration/quota-pools-crud.test.ts @@ -26,7 +26,8 @@ process.env.API_KEY_SECRET = "test-quota-pools-secret"; // Import in dependency order to ensure migrations run before routes const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const poolsRoute = await import("../../src/app/api/quota/pools/route.ts"); const poolIdRoute = await import("../../src/app/api/quota/pools/[id]/route.ts"); diff --git a/tests/integration/quota-pools-usage.test.ts b/tests/integration/quota-pools-usage.test.ts index 9672958e25..1da02998cd 100644 --- a/tests/integration/quota-pools-usage.test.ts +++ b/tests/integration/quota-pools-usage.test.ts @@ -26,9 +26,10 @@ process.env.API_KEY_SECRET = "test-quota-usage-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createPool, upsertAllocations } = await import("@/lib/db/quotaPools"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); -const { createPool, upsertAllocations } = localDb; const { getSqliteQuotaStore } = await import("../../src/lib/quota/sqliteQuotaStore.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const poolsRoute = await import("../../src/app/api/quota/pools/route.ts"); diff --git a/tests/integration/quota-preview.test.ts b/tests/integration/quota-preview.test.ts index 867d95ba7d..49a75f26bd 100644 --- a/tests/integration/quota-preview.test.ts +++ b/tests/integration/quota-preview.test.ts @@ -25,9 +25,10 @@ process.env.API_KEY_SECRET = "test-quota-preview-secret"; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createPool, upsertAllocations } = await import("@/lib/db/quotaPools"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); -const { createPool, upsertAllocations } = localDb; const { getSqliteQuotaStore } = await import("../../src/lib/quota/sqliteQuotaStore.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const previewRoute = await import("../../src/app/api/quota/preview/route.ts"); diff --git a/tests/integration/quota-store-settings.test.ts b/tests/integration/quota-store-settings.test.ts index aceaaca3e3..833b8389c8 100644 --- a/tests/integration/quota-store-settings.test.ts +++ b/tests/integration/quota-store-settings.test.ts @@ -26,7 +26,8 @@ delete process.env.QUOTA_STORE_REDIS_URL; process.env.QUOTA_STORE_DRIVER = "sqlite"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const compliance = await import("../../src/lib/compliance/index.ts"); const { resetQuotaStoreSingleton } = await import("../../src/lib/quota/QuotaStore.ts"); const settingsRoute = await import("../../src/app/api/settings/quota-store/route.ts"); diff --git a/tests/integration/traffic-inspector-hosts.test.ts b/tests/integration/traffic-inspector-hosts.test.ts index f6efcc57a6..f493f8edac 100644 --- a/tests/integration/traffic-inspector-hosts.test.ts +++ b/tests/integration/traffic-inspector-hosts.test.ts @@ -16,7 +16,6 @@ process.env.DATA_DIR = TEST_DATA_DIR; // Boot DB so migrations run const { resetDbInstance } = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); const hostsRoute = await import("../../src/app/api/tools/traffic-inspector/hosts/route.ts"); const hostDetailRoute = diff --git a/tests/unit/acp-agents-route.test.ts b/tests/unit/acp-agents-route.test.ts index a64a2f0f9d..b50032d9c1 100644 --- a/tests/unit/acp-agents-route.test.ts +++ b/tests/unit/acp-agents-route.test.ts @@ -10,7 +10,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "acp-agents-route-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const routeModule = await import("../../src/app/api/acp/agents/route.ts"); const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD; diff --git a/tests/unit/api-auth.test.ts b/tests/unit/api-auth.test.ts index a472e6db4d..9784a688a1 100644 --- a/tests/unit/api-auth.test.ts +++ b/tests/unit/api-auth.test.ts @@ -10,7 +10,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const apiAuth = await import("../../src/shared/utils/apiAuth.ts"); const { requireManagementAuth } = await import("../../src/lib/api/requireManagementAuth.ts"); diff --git a/tests/unit/api-key-usage-limits.test.ts b/tests/unit/api-key-usage-limits.test.ts index 7221cc883c..8fe8dfcb33 100644 --- a/tests/unit/api-key-usage-limits.test.ts +++ b/tests/unit/api-key-usage-limits.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "usage-limit-test-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const usageLimits = await import("../../src/lib/usage/apiKeyUsageLimits.ts"); diff --git a/tests/unit/api-keys-create-no-hang-6570.test.ts b/tests/unit/api-keys-create-no-hang-6570.test.ts index 3a5d5bd536..034107256e 100644 --- a/tests/unit/api-keys-create-no-hang-6570.test.ts +++ b/tests/unit/api-keys-create-no-hang-6570.test.ts @@ -27,7 +27,8 @@ process.env.CLOUD_URL = "http://cloud.example"; const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings, getSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings, getSettings }; const listRoute = await import("../../src/app/api/keys/route.ts"); async function resetStorage() { diff --git a/tests/unit/api-models-v1-models-mismatch-10615.test.ts b/tests/unit/api-models-v1-models-mismatch-10615.test.ts index f41976e346..aed82060e8 100644 --- a/tests/unit/api-models-v1-models-mismatch-10615.test.ts +++ b/tests/unit/api-models-v1-models-mismatch-10615.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { replaceSyncedAvailableModelsForConnection } = await import("@/lib/db/models"); +const localDb = { replaceSyncedAvailableModelsForConnection }; const modelsRoute = await import("../../src/app/api/models/route.ts"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); diff --git a/tests/unit/auto-combo-hidden-models-4558.test.ts b/tests/unit/auto-combo-hidden-models-4558.test.ts index f66da030fd..03e0e9292e 100644 --- a/tests/unit/auto-combo-hidden-models-4558.test.ts +++ b/tests/unit/auto-combo-hidden-models-4558.test.ts @@ -33,7 +33,7 @@ const { getModelIsHidden, mergeModelCompatOverride, updateCustomModel, -} = await import("../../src/lib/localDb.ts"); +} = await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { diff --git a/tests/unit/batch-deletion.test.ts b/tests/unit/batch-deletion.test.ts index 4a0043e721..1149c07730 100644 --- a/tests/unit/batch-deletion.test.ts +++ b/tests/unit/batch-deletion.test.ts @@ -1,14 +1,7 @@ import { describe, it } from "node:test"; import assert from "node:assert"; -import { - createFile, - createBatch, - getBatch, - deleteBatch, - deleteCompletedBatches, - getFile, - deleteFile, -} from "@/lib/localDb"; +import { createFile, getFile, deleteFile } from "@/lib/db/files"; +import { createBatch, getBatch, deleteBatch, deleteCompletedBatches } from "@/lib/db/batches"; describe("deleteBatch", () => { it("should delete a single batch and its associated files", () => { diff --git a/tests/unit/batch-file-download.test.ts b/tests/unit/batch-file-download.test.ts index 5cc891cb43..f67574a509 100644 --- a/tests/unit/batch-file-download.test.ts +++ b/tests/unit/batch-file-download.test.ts @@ -18,7 +18,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-secret-file-dl"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { createFile, deleteFile } = await import("@/lib/db/files"); +const localDb = { createFile, deleteFile }; const fileContentRoute = await import("../../src/app/api/files/[id]/content/route.ts"); async function resetStorage() { diff --git a/tests/unit/batch-processor.test.ts b/tests/unit/batch-processor.test.ts index 243fa6f904..f863f1b8ce 100644 --- a/tests/unit/batch-processor.test.ts +++ b/tests/unit/batch-processor.test.ts @@ -12,7 +12,25 @@ process.env.API_KEY_SECRET = "test-secret"; // We import these as modules to allow mocking const core = await import("@/lib/db/core.ts"); -const localDb = await import("@/lib/localDb"); +const { createFile, getFileContent } = await import("@/lib/db/files"); +const { + createBatch, + ensureBatchItemCheckpoints, + getBatch, + markBatchItemProcessing, + markBatchItemResult, + updateBatch, +} = await import("@/lib/db/batches"); +const localDb = { + createFile, + getFileContent, + createBatch, + ensureBatchItemCheckpoints, + getBatch, + markBatchItemProcessing, + markBatchItemResult, + updateBatch, +}; const { dispatch } = await import("@/lib/batches/dispatch"); const batchProcessor = await import("../../open-sse/services/batchProcessor.ts"); const { waitForAllBatches, getCachedHeaders, resetCachedHeaders } = batchProcessor; diff --git a/tests/unit/batch_api.test.ts b/tests/unit/batch_api.test.ts index 357e084bf6..f010b3a79c 100644 --- a/tests/unit/batch_api.test.ts +++ b/tests/unit/batch_api.test.ts @@ -8,22 +8,18 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-batch-api process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret-123"; +const { createFile, getFileContent, getFile, listFiles, formatFileResponse, deleteFile } = + await import("@/lib/db/files"); const { - createFile, createBatch, getBatch, - getFileContent, updateBatch, - createProviderConnection, - createApiKey, - getFile, - listFiles, - formatFileResponse, - deleteFile, getTerminalBatches, ensureBatchItemCheckpoints, markBatchItemResult, -} = await import("../../src/lib/localDb.ts"); +} = await import("@/lib/db/batches"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const { createApiKey } = await import("@/lib/db/apiKeys"); const { getDbInstance } = await import("../../src/lib/db/core.ts"); const { initBatchProcessor, @@ -580,7 +576,7 @@ test("List batches pagination and response format", async () => { const batchIds = batchOrder.map((entry) => entry.id); // 2. Test listBatches logic (direct DB call) - const { listBatches } = await import("../../src/lib/localDb"); + const { listBatches } = await import("@/lib/db/batches"); const allBatches = listBatches(apiKey.id, 10); assert.strictEqual(allBatches.length, 5); assert.strictEqual(allBatches[0].id, batchIds[0]); diff --git a/tests/unit/batch_results.test.ts b/tests/unit/batch_results.test.ts index 35eabb0c03..8f2d1f4e57 100644 --- a/tests/unit/batch_results.test.ts +++ b/tests/unit/batch_results.test.ts @@ -8,14 +8,10 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-batch-res process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-secret-123"; -const { - createFile, - createBatch, - getBatch, - getFileContent, - createProviderConnection, - createApiKey, -} = await import("../../src/lib/localDb.ts"); +const { createFile, getFileContent } = await import("@/lib/db/files"); +const { createBatch, getBatch } = await import("@/lib/db/batches"); +const { createProviderConnection } = await import("@/lib/db/providers"); +const { createApiKey } = await import("@/lib/db/apiKeys"); const { initBatchProcessor, stopBatchProcessor, waitForAllBatches, processPendingBatches } = await import("../../open-sse/services/batchProcessor.ts"); @@ -127,7 +123,7 @@ test("Batch processor produces output file for successful items", async () => { assert.ok(obj.response && typeof obj.response.status_code === "number"); // Output file should have an expiration timestamp set (30 days default) - const { getFile } = await import("../../src/lib/localDb.ts"); + const { getFile } = await import("@/lib/db/files"); const fileRow = getFile(currentBatch.outputFileId!); assert.ok(fileRow?.expiresAt && typeof fileRow.expiresAt === "number"); } diff --git a/tests/unit/build/build-tool-runner-win-shim.test.ts b/tests/unit/build/build-tool-runner-win-shim.test.ts index d1f3a06444..81f74c98c0 100644 --- a/tests/unit/build/build-tool-runner-win-shim.test.ts +++ b/tests/unit/build/build-tool-runner-win-shim.test.ts @@ -206,3 +206,28 @@ test("colocate-standalone.mjs never spawns the node_modules/.bin shim again", () "esbuild is spawned through the shared cross-platform runner" ); }); + +test("prepublish.ts bundles the ChatGPT Web Codex MCP bridge through runBuildTool (#11704)", () => { + // Node >= 20 on Windows refuses to spawn npx.cmd without a shell (EINVAL, + // CVE-2024-27980). The ChatGPT Web MCP esbuild step was the last raw + // execFileSync(NPX_BIN, ...) in prepublish.ts and crashed npm run build:cli. + const source = readFileSync( + new URL("../../../scripts/build/prepublish.ts", import.meta.url), + "utf8" + ); + const mcpPath = "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.ts"; + const idx = source.indexOf(mcpPath); + assert.notEqual(idx, -1, "ChatGPT Web Codex MCP source path is still bundled"); + + const window = source.slice(Math.max(0, idx - 400), idx); + assert.match( + window, + /runBuildTool\s*\(\s*"esbuild"\s*,\s*"esbuild"/, + "must spawn esbuild via runBuildTool() — raw npx.cmd is EINVAL on Node >= 20/Windows" + ); + assert.doesNotMatch( + window, + /execFileSync\s*\(\s*NPX_BIN/, + "must not spawn NPX_BIN (npx.cmd) to bundle the ChatGPT Web MCP bridge" + ); +}); diff --git a/tests/unit/chat-passthrough-model-routing.test.ts b/tests/unit/chat-passthrough-model-routing.test.ts new file mode 100644 index 0000000000..e643dca2a4 --- /dev/null +++ b/tests/unit/chat-passthrough-model-routing.test.ts @@ -0,0 +1,47 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { getPassthroughProviders } from "../../open-sse/config/providerRegistry.ts"; + +test("src/sse/handlers/chat.ts imports cleanly without module or syntax errors", async () => { + const chatHandler = await import("../../src/sse/handlers/chat.ts"); + assert.ok(chatHandler, "chat.ts handler module should export successfully"); +}); + +test("getPassthroughProviders includes expected proxy/passthrough providers", () => { + const providers = getPassthroughProviders(); + assert.ok(providers.has("cline"), "cline should be a passthrough provider"); + assert.ok(providers.has("kilocode"), "kilocode should be a passthrough provider"); +}); + +test("passthrough model routing preserves original unstripped modelStr when provider overrides to passthrough", () => { + const passthroughProviders = getPassthroughProviders(); + const provider: string = "cline"; + const resolvedProvider: string = "openai"; + const modelStr = "cline/gpt-4o-mini"; + const model = "gpt-4o-mini"; + const body = { model: "cline/gpt-4o-mini", messages: [] }; + + let effectiveModel = model; + let requestBody = { ...body, model: `cline/${effectiveModel}` }; + + if (provider !== resolvedProvider && passthroughProviders.has(provider)) { + effectiveModel = modelStr; + requestBody = { ...body, model: modelStr }; + } + + assert.equal(effectiveModel, "cline/gpt-4o-mini"); + assert.equal(requestBody.model, "cline/gpt-4o-mini"); +}); + +test("providerId falls back to target.provider when target.providerId is absent", () => { + const targetWithProviderId = { providerId: "prov_123", provider: "cline" }; + const targetWithProviderOnly = { provider: "kilocode" }; + const targetEmpty = {}; + + const resolveProviderId = (target: { providerId?: string; provider?: string }) => + target?.providerId ?? target?.provider ?? null; + + assert.equal(resolveProviderId(targetWithProviderId), "prov_123"); + assert.equal(resolveProviderId(targetWithProviderOnly), "kilocode"); + assert.equal(resolveProviderId(targetEmpty), null); +}); diff --git a/tests/unit/check-db-rules-classification.test.ts b/tests/unit/check-db-rules-classification.test.ts deleted file mode 100644 index 82ca183f0f..0000000000 --- a/tests/unit/check-db-rules-classification.test.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Regression guard for INTENTIONALLY_INTERNAL classification (#3499). - * - * Every module in INTENTIONALLY_INTERNAL must be genuinely consumed via a - * direct/dynamic import somewhere in src/, open-sse/, bin/, or within - * src/lib/db/ itself (for db-internal coordination modules like stateReset, - * healthCheck, migrationRunner), OR documented as type-only / DEAD?. - * - * Purpose: if a future cleanup removes the last consumer of a module that is - * still in INTENTIONALLY_INTERNAL, this test turns red and forces a conscious - * reclassification decision — the DEAD? category must be explicitly expanded, - * not silently accumulated. - * - * Hard Rule #18 compliance: this test was written BEFORE the fix was applied, - * confirmed to fail on the old framing, and now passes on the audited truth. - */ -import { test } from "node:test"; -import assert from "node:assert"; -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { INTENTIONALLY_INTERNAL } from "../../scripts/check/check-db-rules.mjs"; - -const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "../../.."); - -// ── Helpers ──────────────────────────────────────────────────────────────── - -/** - * Walk a directory tree, calling cb(absolutePath) for every file. - */ -function walkSync(dir: string, cb: (p: string) => void): void { - if (!fs.existsSync(dir)) return; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - // Skip well-known non-source dirs to keep the walk fast - if (["node_modules", ".git", ".next", "dist", "out"].includes(entry.name)) continue; - walkSync(full, cb); - } else { - cb(full); - } - } -} - -/** - * Returns true if any file in the search roots imports db/ (static or dynamic). - * Patterns matched: - * from "@/lib/db/" - * from "../db/" (any relative path ending /db/) - * import("@/lib/db/") - * require(".../db/") - * import(`${...}/db/.ts`) — dynamic template (bin/cli/runtime.mjs pattern) - */ -function hasImporter(mod: string, roots: string[]): boolean { - // Escape special regex chars in mod name (underscore-prefixed names like _rowTypes) - const escaped = mod.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const patterns = [ - // static: from "…/db/" - new RegExp(`from\\s+['""][^'"]+/db/${escaped}['"]`), - // dynamic: import("…/db/") or require("…/db/") - new RegExp(`(?:import|require)\\s*\\(\\s*['""][^'"]+/db/${escaped}['"]`), - // dynamic template: import(`…/db/.ts`) — bin/cli/runtime.mjs uses template literals - new RegExp(`import\\s*\\(\`[^'"\`]+/db/${escaped}\\.ts\`\\)`), - // dynamic via file:// URL helper: import(projectFileUrl("…/db/.ts")) — - // bin/cli/runtime.mjs since #11238 (Windows-safe file:// dynamic imports). - new RegExp( - `import\\s*\\(\\s*projectFileUrl\\(\\s*['""][^'"]+/db/${escaped}\\.ts['"]\\s*\\)\\s*\\)` - ), - // relative import within db/: from "./" or from "./" - new RegExp(`from\\s+['"]\\.\\.?/${escaped}['"]`), - ]; - // The canonical db module path — we must skip ONLY this exact file, not all files - // that happen to share the same basename (e.g. src/lib/usage/comboForecast.ts must - // NOT be skipped when searching for importers of src/lib/db/comboForecast.ts). - const dbModulePath = path.join(REPO_ROOT, "src", "lib", "db", `${mod}.ts`); - let found = false; - for (const root of roots) { - if (found) break; - walkSync(root, (filePath) => { - if (found) return; - // Only scan TS/JS/MJS source files - if (!/\.(ts|tsx|mjs|js|cjs)$/.test(filePath)) return; - // Skip only the db module itself (not same-name files in other dirs) - if (filePath === dbModulePath) return; - let src: string; - try { - src = fs.readFileSync(filePath, "utf8"); - } catch { - return; - } - if (patterns.some((rx) => rx.test(src))) found = true; - }); - } - return found; -} - -// ── Audit roots ──────────────────────────────────────────────────────────── - -// Modules marked "type-only": exports only TypeScript types; no runtime import -// needed — the ts compiler erases them. These are genuinely correct as-is. -const TYPE_ONLY = new Set(["_rowTypes"]); - -// Modules explicitly documented as DEAD? in the classification comments. -// They remain in INTENTIONALLY_INTERNAL for schema-reservation reasons. -// Flag them but do NOT fail — a separate decision is needed to remove them. -const DOCUMENTED_DEAD = new Set([ - "discovery", // DEAD?: 0 importers; lib/discovery/index.ts is independent - "pluginMetrics", // DEAD? (production): write path not yet wired (self-documented) - "prompts", // DEAD? (production): zero production callers; integration test only verifies interface shape -]); - -const SEARCH_ROOTS = [ - path.join(REPO_ROOT, "src"), - path.join(REPO_ROOT, "open-sse"), - path.join(REPO_ROOT, "bin"), - path.join(REPO_ROOT, "tests"), -]; - -// ── Tests ────────────────────────────────────────────────────────────────── - -test("INTENTIONALLY_INTERNAL is exported from check-db-rules.mjs", () => { - assert.ok( - INTENTIONALLY_INTERNAL instanceof Set, - "INTENTIONALLY_INTERNAL must be a Set exported from the gate script" - ); - assert.ok(INTENTIONALLY_INTERNAL.size > 0, "INTENTIONALLY_INTERNAL must not be empty"); -}); - -test("INTENTIONALLY_INTERNAL contains the expected 40 audited modules", () => { - const expected = [ - "_rowTypes", - "accessTokens", - "apiKeyColumnFallbacks", - "apiKeyUsageLimitFields", - "backupRetention", - "caseMapping", - "cleanup", - "cliToolState", - "comboForecast", - "commandCodeAuth", - "compression", - "compressionDetailNormalizers", - "connectionRuntimeState", - "detailedLogs", - "discovery", - "domainState", - "encryption", - "healthCheck", - "jsonMigration", - "migrationRunner", - "modelCapabilityOverrides", - "notion", - "obsidian", - "optimizationSettings", - "pluginMetrics", - "prompts", - "probeUtils", - "providerNodeSelect", - "providerStats", - "proxyLatency", - "proxySubscriptions", - "recovery", - "schemaColumns", - "secrets", - "serviceModels", - "stateReset", - "stats", - "tierConfig", - "vacuumScheduler", - "webSessionDedup", - ]; - for (const mod of expected) { - assert.ok( - INTENTIONALLY_INTERNAL.has(mod), - `Expected ${mod} to be in INTENTIONALLY_INTERNAL after audit` - ); - } - assert.equal( - INTENTIONALLY_INTERNAL.size, - expected.length, - `INTENTIONALLY_INTERNAL has ${INTENTIONALLY_INTERNAL.size} entries; expected ${expected.length}` - ); -}); - -test("every non-type-only, non-dead module in INTENTIONALLY_INTERNAL has ≥1 real importer", () => { - const failures: string[] = []; - for (const mod of INTENTIONALLY_INTERNAL) { - if (TYPE_ONLY.has(mod)) continue; // type-only: no runtime import expected - if (DOCUMENTED_DEAD.has(mod)) continue; // dead modules exempted with explicit flag - if (!hasImporter(mod, SEARCH_ROOTS)) { - failures.push(mod); - } - } - assert.deepEqual( - failures, - [], - `These INTENTIONALLY_INTERNAL modules have ZERO importers — either they became dead ` + - `(add to DOCUMENTED_DEAD with a DEAD? comment) or they were misclassified:\n ${failures.join(", ")}` - ); -}); - -test("type-only module _rowTypes is imported within src/lib/db/ by its consumers", () => { - // _rowTypes exports only TypeScript interfaces, so the import is always `import type`. - // It should be consumed within db/ itself (AgentBridge, Inspector CRUD modules). - const dbDir = path.join(REPO_ROOT, "src/lib/db"); - let found = false; - if (fs.existsSync(dbDir)) { - for (const entry of fs.readdirSync(dbDir, { withFileTypes: true })) { - if (!entry.isFile() || !/\.ts$/.test(entry.name)) continue; - if (entry.name === "_rowTypes.ts") continue; - const src = fs.readFileSync(path.join(dbDir, entry.name), "utf8"); - if (/from\s+['"]\.\/_rowTypes['"]/.test(src)) { - found = true; - break; - } - } - } - assert.ok( - found, - "_rowTypes must be imported (as type) by at least one sibling module in src/lib/db/" - ); -}); - -test("DOCUMENTED_DEAD modules are still in INTENTIONALLY_INTERNAL (dead list must stay honest)", () => { - for (const mod of DOCUMENTED_DEAD) { - assert.ok( - INTENTIONALLY_INTERNAL.has(mod), - `DOCUMENTED_DEAD module "${mod}" is no longer in INTENTIONALLY_INTERNAL — ` + - `remove it from DOCUMENTED_DEAD in this test if it was intentionally removed from the gate` - ); - } -}); diff --git a/tests/unit/check-db-rules.test.ts b/tests/unit/check-db-rules.test.ts index 081ce286e5..9e336e32aa 100644 --- a/tests/unit/check-db-rules.test.ts +++ b/tests/unit/check-db-rules.test.ts @@ -4,135 +4,20 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { - collectDbModules, - extractReexportedModules, - findMissingReexports, - hasLogic, extractStringLiterals, findRawSql, collectSqlScanFiles, - INTENTIONALLY_INTERNAL, - KNOWN_UNEXPORTED, + EXTERNAL_DB_ALLOWED, + KNOWN_RAW_SQL, } from "../../scripts/check/check-db-rules.mjs"; import { reportStaleEntries } from "../../scripts/check/lib/allowlist.mjs"; const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "../../.."); -const LOCAL_DB = path.join(REPO_ROOT, "src/lib/localDb.ts"); - -// ---------- (a) re-export completeness ---------- - -test("findMissingReexports: flags a NEW db module that is not re-exported", () => { - const dbModules = ["providers", "brandNewModule"]; - const reexported = new Set(["providers"]) as Set; - const allowlist = new Set(); - const missing = findMissingReexports(dbModules, reexported, allowlist) as string[]; - assert.deepEqual(missing, ["brandNewModule"]); -}); - -test("findMissingReexports: a re-exported module passes", () => { - const dbModules = ["providers"]; - const reexported = new Set(["providers"]) as Set; - const missing = findMissingReexports(dbModules, reexported, new Set()) as string[]; - assert.deepEqual(missing, []); -}); - -test("findMissingReexports: an allowlisted (frozen) module passes even if not re-exported", () => { - const dbModules = ["notion"]; - const reexported = new Set(); - const allowlist = new Set(["notion"]) as Set; - const missing = findMissingReexports(dbModules, reexported, allowlist) as string[]; - assert.deepEqual(missing, []); -}); - -test("extractReexportedModules: parses ./db/X from export forms", () => { - const src = [ - 'export { getCombos } from "./db/combos";', - 'export * from "./db/featureFlags";', - 'export type { Webhook } from "./db/webhooks";', - 'export { sumUsageTokensThisMonth } from "./db/usageSummary";', - // not a db module — must be ignored - 'export { initPricingSync } from "./pricingSync";', - ].join("\n"); - const mods = extractReexportedModules(src) as Set; - assert.equal(mods.has("combos"), true); - assert.equal(mods.has("featureFlags"), true); - assert.equal(mods.has("webhooks"), true); - assert.equal(mods.has("usageSummary"), true); - assert.equal(mods.has("pricingSync"), false); -}); - -test("collectDbModules: returns real modules and excludes core/localDb/index", () => { - const mods = collectDbModules() as string[]; - assert.ok(mods.includes("providers"), "expected providers module"); - assert.ok(mods.includes("combos"), "expected combos module"); - assert.equal(mods.includes("core"), false, "core must be excluded"); - assert.equal(mods.includes("localDb"), false, "localDb must be excluded"); - assert.equal(mods.includes("index"), false, "index must be excluded"); -}); - -// FREEZE GUARD: the live repo state must be green under the shipped allowlist. -test("live repo: no NEW unexported db modules beyond the frozen allowlist", async () => { - // Re-import the gate's frozen allowlist indirectly by running its default behavior: - // findMissingReexports with the gate default allowlist must be empty for the repo. - const dbModules = collectDbModules() as string[]; - const reexported = extractReexportedModules(fs.readFileSync(LOCAL_DB, "utf8")) as Set; - // Default allowlist (KNOWN_UNEXPORTED) is applied inside findMissingReexports. - const missing = findMissingReexports(dbModules, reexported) as string[]; - assert.deepEqual( - missing, - [], - `Unexported db module(s) not in KNOWN_UNEXPORTED: ${missing.join(", ")}` - ); -}); - -// ---------- (b) localDb has no logic ---------- - -test("hasLogic: false for a pure re-export layer", () => { - const src = [ - "// re-export layer", - 'export { a, b } from "./db/foo";', - 'export * from "./db/bar";', - 'export type { T } from "./db/baz";', - ].join("\n"); - assert.equal(hasLogic(src) as boolean, false); -}); - -test("hasLogic: true for a function declaration", () => { - const src = 'export { a } from "./db/foo";\nfunction doThing() { return 1; }'; - assert.equal(hasLogic(src) as boolean, true); -}); - -test("hasLogic: true for an arrow-function const", () => { - const src = 'export { a } from "./db/foo";\nconst helper = (x) => x + 1;'; - assert.equal(hasLogic(src) as boolean, true); -}); - -test("hasLogic: true for a class declaration", () => { - const src = 'export { a } from "./db/foo";\nclass Thing {}'; - assert.equal(hasLogic(src) as boolean, true); -}); - -test("hasLogic: SQL/logic-looking text inside comments or strings does not trip", () => { - const src = [ - "/* function notReal() {} */", - "// const fake = () => 1;", - 'export const SOURCE = "./db/foo";', // string only, no function on rhs - 'export { a } from "./db/foo";', - ].join("\n"); - // export const X = "string" is a value (not logic): the rhs is a string literal, - // so the arrow/call pattern must NOT match. - assert.equal(hasLogic(src) as boolean, false); -}); - -test("live repo: src/lib/localDb.ts contains no logic", () => { - const src = fs.readFileSync(LOCAL_DB, "utf8"); - assert.equal(hasLogic(src) as boolean, false); -}); // ---------- (c) no raw SQL outside db/ ---------- test("extractStringLiterals: returns only string bodies, ignoring code", () => { - const code = 'import { x } from "y";\nconst q = `SELECT * FROM t`;\nobj.set(1);'; + const code = 'import { x } from "y";\nconst q = `SELECT * FROM t`; obj.set(1);'; const literals = extractStringLiterals(code) as string; assert.ok(literals.includes("SELECT * FROM t"), "captures the template body"); assert.ok(literals.includes("y"), "captures the import path string"); @@ -143,7 +28,7 @@ test("findRawSql: flags a NEW route with raw SQL in a string literal", () => { const tmp = path.join(REPO_ROOT, ".tmp-check-db-rules-raw-sql.route.ts"); fs.writeFileSync( tmp, - 'const rows = db.prepare(`SELECT id FROM users WHERE x = ?`).all();\n', + "const rows = db.prepare(`SELECT id FROM users WHERE x = ?`).all();\n", "utf8" ); try { @@ -156,7 +41,11 @@ test("findRawSql: flags a NEW route with raw SQL in a string literal", () => { test("findRawSql: does NOT flag SQL that only appears in a comment", () => { const tmp = path.join(REPO_ROOT, ".tmp-check-db-rules-comment.route.ts"); - fs.writeFileSync(tmp, "// SELECT id FROM users -- documentation only\nexport const x = 1;\n", "utf8"); + fs.writeFileSync( + tmp, + "// SELECT id FROM users -- documentation only\nexport const x = 1;\n", + "utf8" + ); try { const offenders = findRawSql([tmp], new Set()) as string[]; assert.deepEqual(offenders, []); @@ -193,6 +82,10 @@ test("findRawSql: an allowlisted (frozen) offender passes", () => { assert.deepEqual(offenders, []); }); +test("KNOWN_RAW_SQL is an alias for EXTERNAL_DB_ALLOWED (retrocompat)", () => { + assert.equal(EXTERNAL_DB_ALLOWED, KNOWN_RAW_SQL); +}); + test("live repo: no NEW raw-SQL offenders beyond the frozen allowlist", () => { // findRawSql uses the gate default allowlist (KNOWN_RAW_SQL) when none is passed. const files = collectSqlScanFiles() as string[]; @@ -202,17 +95,6 @@ test("live repo: no NEW raw-SQL offenders beyond the frozen allowlist", () => { // --- stale-allowlist enforcement (6A.3) --- -test("stale-enforcement: INTENTIONALLY_INTERNAL entry no longer unexported is reported as stale", () => { - // Simulate a module that has now been re-exported (no longer unexported). - const liveUnexported: string[] = []; // module was re-exported - const stale = (reportStaleEntries as (a: Set, l: string[], g: string) => string[])( - new Set(["oldModule"]), - liveUnexported, - "check-db-rules:unexported" - ); - assert.deepEqual(stale, ["oldModule"]); -}); - test("stale-enforcement: EXTERNAL_DB_ALLOWED entry no longer has raw SQL is reported as stale", () => { // Simulate a file that no longer contains raw SQL (route was refactored). const liveRawSql: string[] = []; @@ -223,23 +105,3 @@ test("stale-enforcement: EXTERNAL_DB_ALLOWED entry no longer has raw SQL is repo ); assert.deepEqual(stale, ["src/app/api/oauth/cursor/auto-import/route.ts"]); }); - -test("stale-enforcement: live repo INTENTIONALLY_INTERNAL entries are all still unexported", () => { - // Every entry in INTENTIONALLY_INTERNAL must still be an unexported module. - // If it was re-exported (moved to localDb.ts), it must be removed from the allowlist. - const dbModules = collectDbModules() as string[]; - const reexported = extractReexportedModules( - fs.readFileSync(path.resolve(fileURLToPath(import.meta.url), "../../../src/lib/localDb.ts"), "utf8") - ) as Set; - const liveUnexported = dbModules.filter((mod) => !reexported.has(mod)); - const stale = (reportStaleEntries as (a: Set, l: string[], g: string) => string[])( - INTENTIONALLY_INTERNAL as Set, - liveUnexported, - "check-db-rules:unexported" - ); - assert.deepEqual(stale, [], `INTENTIONALLY_INTERNAL has stale entries: ${stale.join(", ")}`); -}); - -test("KNOWN_UNEXPORTED is an alias for INTENTIONALLY_INTERNAL (retrocompat)", () => { - assert.equal(INTENTIONALLY_INTERNAL, KNOWN_UNEXPORTED); -}); diff --git a/tests/unit/cli-program.test.ts b/tests/unit/cli-program.test.ts index 8dbc6f0d4c..9eee167125 100644 --- a/tests/unit/cli-program.test.ts +++ b/tests/unit/cli-program.test.ts @@ -124,7 +124,7 @@ test("program registers 'update' command", () => { assert.ok(cmd, "update command exists"); }); -test("nodes endpoint commands reserve --base-url for the global server target", () => { +test("nodes endpoint commands expose --endpoint and the legacy --base-url alias", () => { const program = createProgram(); const nodes = program.commands.find((c) => c.name() === "nodes"); assert.ok(nodes, "nodes command exists"); @@ -136,10 +136,9 @@ test("nodes endpoint commands reserve --base-url for the global server target", command.options.some((option) => option.long === "--endpoint"), `nodes ${commandName} exposes --endpoint for the provider-node URL` ); - assert.equal( + assert.ok( command.options.some((option) => option.long === "--base-url"), - false, - `nodes ${commandName} does not shadow the global --base-url option` + `nodes ${commandName} preserves the legacy --base-url alias` ); command.parseOptions([ ...(commandName === "update" ? ["node-1"] : []), diff --git a/tests/unit/cli-tools-crush.test.ts b/tests/unit/cli-tools-crush.test.ts index 4dbdbfde5e..7e58ce77f6 100644 --- a/tests/unit/cli-tools-crush.test.ts +++ b/tests/unit/cli-tools-crush.test.ts @@ -56,7 +56,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret-crush"; process.env.JWT_SECRET = "test-jwt-secret-crush"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const { GET, POST, DELETE } = await import("../../src/app/api/cli-tools/crush-settings/route.ts"); diff --git a/tests/unit/cloud-write-auth.test.ts b/tests/unit/cloud-write-auth.test.ts index 68c1ce3ca9..9c486d244e 100644 --- a/tests/unit/cloud-write-auth.test.ts +++ b/tests/unit/cloud-write-auth.test.ts @@ -20,7 +20,19 @@ type ProviderConnectionRecord = { }; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { resetApiKeyState, createApiKey } = await import("@/lib/db/apiKeys"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createProviderConnection, getProviderConnections } = await import("@/lib/db/providers"); +const { setModelAlias, getModelAliases } = await import("@/lib/db/models"); +const localDb = { + resetApiKeyState, + updateSettings, + createApiKey, + createProviderConnection, + getProviderConnections, + setModelAlias, + getModelAliases, +}; const credentialsRoute = await import("../../src/app/api/cloud/credentials/update/route.ts"); const aliasRoute = await import("../../src/app/api/cloud/models/alias/route.ts"); diff --git a/tests/unit/combo-model-lockout-honors-reset-1308.test.ts b/tests/unit/combo-model-lockout-honors-reset-1308.test.ts index c75d66ccb3..ad9e32ee29 100644 --- a/tests/unit/combo-model-lockout-honors-reset-1308.test.ts +++ b/tests/unit/combo-model-lockout-honors-reset-1308.test.ts @@ -27,7 +27,10 @@ const RESET_160H = 160 * HOUR + 27 * 60_000 + 24_000; // "160h27m24s" test("selectLockoutCooldownMs honors a parsed reset longer than the base cooldown", () => { // exponential backoff on, but a 160h upstream reset must win assert.equal( - selectLockoutCooldownMs(RESET_160H, { baseCooldownMs: 5 * 60_000, useExponentialBackoff: true }), + selectLockoutCooldownMs(RESET_160H, { + baseCooldownMs: 5 * 60_000, + useExponentialBackoff: true, + }), RESET_160H ); }); @@ -53,9 +56,18 @@ test("model lockout honors the long upstream reset end-to-end (#1308)", () => { baseCooldownMs: 5 * 60_000, useExponentialBackoff: true, }); - recordModelLockoutFailure("antigravity", "conn-1", "claude-sonnet-4-6", "rate_limit", 429, 5 * 60_000, null, { - exactCooldownMs: exact, - }); + recordModelLockoutFailure( + "antigravity", + "conn-1", + "claude-sonnet-4-6", + "rate_limit", + 429, + 5 * 60_000, + null, + { + exactCooldownMs: exact, + } + ); const info = getModelLockoutInfo("antigravity", "conn-1", "claude-sonnet-4-6"); assert.ok(info, "expected an active lockout"); // remaining should be ~160h, NOT the ~5min base cooldown @@ -118,7 +130,13 @@ test("Retry-After remains authoritative for model locks when connection hints ar ); assert.equal(result.retryHintSource, "header"); - assert.equal(result.quotaResetHintMs, 131 * HOUR); + const expectedResetMs = 131 * HOUR; + const actualResetMs = result.quotaResetHintMs; + assert.ok(typeof actualResetMs === "number", "expected a numeric Retry-After reset hint"); + assert.ok( + actualResetMs <= expectedResetMs && actualResetMs >= expectedResetMs - 1_000, + `expected Retry-After within one clock tick of ${expectedResetMs}ms, got ${actualResetMs}ms` + ); assert.equal(retryHintBypassesMaxCooldownMs(result.retryHintSource), true); }); diff --git a/tests/unit/context-handoff.test.ts b/tests/unit/context-handoff.test.ts index 8bbd7fac83..4cc4efd5aa 100644 --- a/tests/unit/context-handoff.test.ts +++ b/tests/unit/context-handoff.test.ts @@ -307,6 +307,63 @@ test("maybeGenerateHandoff allows a new attempt after a failed in-flight generat assert.equal(calls, 2); }); +test("selectMessagesForSummary handles schema-locked vs standard relayMode", () => { + const messages = [ + { role: "system", content: "System instruction" }, + { role: "user", content: "Msg 1" }, + { role: "assistant", content: "Msg 2" }, + { role: "user", content: "Msg 3" }, + ]; + + // Standard mode includes system message + const standard = contextHandoff.selectMessagesForSummary(messages, 2, "standard"); + assert.equal(standard[0].role, "system"); + assert.equal(standard.length, 3); // system + last 2 + + // Schema-locked mode excludes system message + const locked = contextHandoff.selectMessagesForSummary(messages, 2, "schema-locked"); + assert.equal(locked[0].role, "assistant"); + assert.equal(locked[0].content, "Msg 2"); + assert.equal(locked.length, 2); // only non-system slice +}); + +test("selectMessagesForSummary trims non-system messages and excludes system in schema-locked token overflow", () => { + // Input with system messages and huge non-system messages exceeding token limit + const hugeText = "x".repeat(35000); + const messages = [ + { role: "system", content: "System prompt" }, + { role: "developer", content: "Developer prompt" }, + { role: "user", content: `User msg 1: ${hugeText}` }, + { role: "assistant", content: `Assistant msg 2: ${hugeText}` }, + { role: "user", content: "User msg 3 short" }, + ]; + + const trimmedLocked = contextHandoff.selectMessagesForSummary(messages, 10, "schema-locked"); + // Should exclude system and developer messages + assert.ok(trimmedLocked.every((m) => m.role !== "system" && m.role !== "developer")); + // Should have trimmed down to non-overflowing messages (or last non-system) + assert.ok(trimmedLocked.length < 3); + assert.equal(trimmedLocked[trimmedLocked.length - 1].content, "User msg 3 short"); +}); + +test("resolveUniversalHandoffConfig correctly parses relayMode", async () => { + const comboSchema = await import("../../src/shared/validation/schemas/combo.ts"); + const parsedLocked = comboSchema.comboRuntimeConfigSchema.parse({ + relayMode: "schema-locked", + }); + assert.equal(parsedLocked.relayMode, "schema-locked"); + + const parsedStandard = comboSchema.comboRuntimeConfigSchema.parse({ + relayMode: "standard", + }); + assert.equal(parsedStandard.relayMode, "standard"); + + const resolvedConfig = contextHandoff.resolveUniversalHandoffConfig({ + relayMode: "schema-locked", + }); + assert.equal(resolvedConfig.relayMode, "schema-locked"); +}); + test("maybeGenerateHandoff respects explicit empty handoffProviders and skips generation", async () => { let called = false; diff --git a/tests/unit/dev-bundler-boundaries-12074.test.ts b/tests/unit/dev-bundler-boundaries-12074.test.ts new file mode 100644 index 0000000000..9e8c20065f --- /dev/null +++ b/tests/unit/dev-bundler-boundaries-12074.test.ts @@ -0,0 +1,77 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; + +const repoRoot = process.cwd(); +const nextConfigPath = path.join(repoRoot, "next.config.mjs"); + +async function loadNextConfig(label: string) { + return import(`${pathToFileURL(nextConfigPath).href}?case=${label}-${Date.now()}`); +} + +test("Tailwind scans only the UI source roots declared by OmniRoute (#12074 phase 1)", () => { + const css = readFileSync(path.join(repoRoot, "src/app/globals.css"), "utf8"); + + assert.match( + css, + /@import\s+"tailwindcss"\s+source\(none\);/, + "automatic repository-wide Tailwind source detection must stay disabled" + ); + assert.match(css, /@source\s+"\.\.\/app";/, "App Router components must stay scanned"); + assert.match(css, /@source\s+"\.\.\/shared";/, "shared UI components must stay scanned"); + assert.match( + css, + /@source\s+"\.\.\/\.\.\/node_modules\/fumadocs-ui\/dist\/\*\*\/\*\.js";/, + "Fumadocs runtime classes must stay scanned" + ); +}); + +test("webpack dev keeps Next defaults instead of production vendor cache groups (#12074 phase 1)", async () => { + const { default: nextConfig } = await loadNextConfig("dev-split-chunks"); + const originalSplitChunks = { + chunks: "async", + cacheGroups: { + framework: { name: "framework" }, + }, + }; + const config = { + context: repoRoot, + ignoreWarnings: [], + optimization: { splitChunks: originalSplitChunks }, + plugins: [], + }; + + nextConfig.webpack(config, { + dev: true, + isServer: false, + defaultLoaders: { babel: {} }, + webpack: {}, + }); + + assert.equal(config.optimization.splitChunks, originalSplitChunks); + const cacheGroups = config.optimization.splitChunks.cacheGroups as Record; + assert.equal(cacheGroups.recharts, undefined); + assert.equal(cacheGroups.fumadocs, undefined); +}); + +test("webpack production retains OmniRoute vendor cache groups (#12074 phase 1)", async () => { + const { default: nextConfig } = await loadNextConfig("production-split-chunks"); + const config = { + context: repoRoot, + ignoreWarnings: [], + optimization: { splitChunks: { cacheGroups: {} as Record } }, + plugins: [], + }; + + nextConfig.webpack(config, { + dev: false, + isServer: false, + defaultLoaders: { babel: {} }, + webpack: {}, + }); + + assert.ok(config.optimization.splitChunks.cacheGroups.recharts); + assert.ok(config.optimization.splitChunks.cacheGroups.fumadocs); +}); diff --git a/tests/unit/evals-route.test.ts b/tests/unit/evals-route.test.ts index c9e5b83e8c..399ddd1571 100644 --- a/tests/unit/evals-route.test.ts +++ b/tests/unit/evals-route.test.ts @@ -17,7 +17,9 @@ interface EvalsRoutePayload { } const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { resetApiKeyState, createApiKey } = await import("@/lib/db/apiKeys"); +const { saveCustomEvalSuite, saveEvalRun } = await import("@/lib/db/evals"); +const localDb = { resetApiKeyState, createApiKey, saveCustomEvalSuite, saveEvalRun }; const evalsRoute = await import("../../src/app/api/evals/route.ts"); const evalSuitesRoute = await import("../../src/app/api/evals/suites/route.ts"); const evalSuiteByIdRoute = await import("../../src/app/api/evals/suites/[suiteId]/route.ts"); diff --git a/tests/unit/file-deletion.test.ts b/tests/unit/file-deletion.test.ts index e74f480ef1..7553d57eb8 100644 --- a/tests/unit/file-deletion.test.ts +++ b/tests/unit/file-deletion.test.ts @@ -8,8 +8,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-file-dele const ORIGINAL_DATA_DIR = process.env.DATA_DIR; process.env.DATA_DIR = TEST_DATA_DIR; -const { createFile, deleteFile, listFiles, createBatch, getBatch, updateBatch } = - await import("@/lib/localDb"); +const { createFile, deleteFile, listFiles } = await import("@/lib/db/files"); +const { createBatch, getBatch, updateBatch } = await import("@/lib/db/batches"); const { getDbInstance, resetDbInstance } = await import("@/lib/db/core"); after(() => { diff --git a/tests/unit/file-expiration-policy.test.ts b/tests/unit/file-expiration-policy.test.ts index 1108287730..94fc2adb19 100644 --- a/tests/unit/file-expiration-policy.test.ts +++ b/tests/unit/file-expiration-policy.test.ts @@ -1,6 +1,6 @@ import { describe, it, before, afterEach } from "node:test"; import assert from "node:assert"; -import { createFile, getFile, listFiles, deleteFile } from "@/lib/localDb"; +import { createFile, getFile, listFiles, deleteFile } from "@/lib/db/files"; import { getDbInstance } from "@/lib/db/core.ts"; describe("File Expiration Policy", () => { diff --git a/tests/unit/fixes-p1.test.ts b/tests/unit/fixes-p1.test.ts index 93d76898d2..8da6e7058c 100644 --- a/tests/unit/fixes-p1.test.ts +++ b/tests/unit/fixes-p1.test.ts @@ -24,7 +24,8 @@ const backupDb = await import("../../src/lib/db/backup.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const combosDb = await import("../../src/lib/db/combos.ts"); const settingsDb = await import("../../src/lib/db/settings.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { createProxy } = await import("@/lib/db/proxies"); +const localDb = { createProxy }; const tokenRefresh = await import("../../open-sse/services/tokenRefresh.ts"); const proxyFetch = await import("../../open-sse/utils/proxyFetch.ts"); const proxyDispatcher = await import("../../open-sse/utils/proxyDispatcher.ts"); diff --git a/tests/unit/hidden-models-leak-v1-models-11300.test.ts b/tests/unit/hidden-models-leak-v1-models-11300.test.ts index 1de38b5b6f..f9ae10780e 100644 --- a/tests/unit/hidden-models-leak-v1-models-11300.test.ts +++ b/tests/unit/hidden-models-leak-v1-models-11300.test.ts @@ -31,7 +31,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const { mergeModelCompatOverride } = await import("../../src/lib/localDb.ts"); +const { mergeModelCompatOverride } = await import("@/lib/db/models"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { diff --git a/tests/unit/live-ws-public-url.test.ts b/tests/unit/live-ws-public-url.test.ts index 16fd8f7bb6..23f547cfce 100644 --- a/tests/unit/live-ws-public-url.test.ts +++ b/tests/unit/live-ws-public-url.test.ts @@ -14,7 +14,8 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-live-ws-public- const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const wsRoute = await import("../../src/app/api/v1/ws/route.ts"); function resetStorage() { diff --git a/tests/unit/managed-available-models.test.ts b/tests/unit/managed-available-models.test.ts index db79cbcab7..0b5475de93 100644 --- a/tests/unit/managed-available-models.test.ts +++ b/tests/unit/managed-available-models.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { getModelAliases, setModelAlias } = await import("@/lib/db/models"); +const localDb = { getModelAliases, setModelAlias }; const { compatibleProviderSupportsModelImport, getCompatibleFallbackModels } = await import("../../src/lib/providers/managedAvailableModels.ts"); const { diff --git a/tests/unit/managed-model-import.test.ts b/tests/unit/managed-model-import.test.ts index a3f1e1942d..acbae44a6f 100644 --- a/tests/unit/managed-model-import.test.ts +++ b/tests/unit/managed-model-import.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { getModelAliases, setModelAlias } = await import("@/lib/db/models"); +const localDb = { getModelAliases, setModelAlias }; const { importManagedModels } = await import("../../src/lib/providerModels/managedModelImport.ts"); const { mergeProviderModelListing } = await import("../../src/lib/providers/mergeProviderModelListing.ts"); diff --git a/tests/unit/model-alias-route.test.ts b/tests/unit/model-alias-route.test.ts index 6bf42b4db3..a82562eaaa 100644 --- a/tests/unit/model-alias-route.test.ts +++ b/tests/unit/model-alias-route.test.ts @@ -12,7 +12,8 @@ process.env.JWT_SECRET = process.env.JWT_SECRET || "model-alias-route-jwt"; const core = await import("../../src/lib/db/core.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const route = await import("../../src/app/api/models/alias/route.ts"); const catalogRoute = await import("../../src/app/api/models/catalog/route.ts"); const v1Catalog = await import("../../src/app/api/v1/models/catalog.ts"); diff --git a/tests/unit/model-aliases-settings-route-selfheal.test.ts b/tests/unit/model-aliases-settings-route-selfheal.test.ts index 2655a43ecd..da994f1487 100644 --- a/tests/unit/model-aliases-settings-route-selfheal.test.ts +++ b/tests/unit/model-aliases-settings-route-selfheal.test.ts @@ -22,7 +22,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.JWT_SECRET = process.env.JWT_SECRET || "model-aliases-selfheal-jwt"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const modelDeprecation = await import("../../open-sse/services/modelDeprecation.ts"); const route = await import("../../src/app/api/settings/model-aliases/route.ts"); diff --git a/tests/unit/model-sync-route.test.ts b/tests/unit/model-sync-route.test.ts index 73f199a1ec..d4a1445616 100644 --- a/tests/unit/model-sync-route.test.ts +++ b/tests/unit/model-sync-route.test.ts @@ -12,7 +12,9 @@ if (!process.env.API_KEY_SECRET) { } const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { setModelAlias, getModelAliases } = await import("@/lib/db/models"); +const localDb = { updateSettings, setModelAlias, getModelAliases }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); diff --git a/tests/unit/oidc-callback.test.ts b/tests/unit/oidc-callback.test.ts index 0659edf58c..bccf323694 100644 --- a/tests/unit/oidc-callback.test.ts +++ b/tests/unit/oidc-callback.test.ts @@ -15,7 +15,8 @@ process.env.JWT_SECRET = "test-jwt-secret-for-oidc-callback"; // @ts-ignore - intentional for test harness timing (see note at top) const core = await import("../../src/lib/db/core.ts"); // @ts-ignore - intentional for test harness timing -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // @ts-ignore - intentional for test harness timing const callbackRoute = await import("../../src/app/api/auth/oidc/callback/route.ts"); diff --git a/tests/unit/oidc-login-state.test.ts b/tests/unit/oidc-login-state.test.ts index 341ba53932..e06a26bfab 100644 --- a/tests/unit/oidc-login-state.test.ts +++ b/tests/unit/oidc-login-state.test.ts @@ -14,7 +14,8 @@ process.env.JWT_SECRET = "test-jwt-secret-for-oidc-login"; // @ts-ignore - intentional for test harness timing (see note at top) const core = await import("../../src/lib/db/core.ts"); // @ts-ignore - intentional for test harness timing -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; // @ts-ignore - intentional for test harness timing const loginRoute = await import("../../src/app/api/auth/oidc/login/route.ts"); diff --git a/tests/unit/persist-429-cooldown-account-fallback.test.ts b/tests/unit/persist-429-cooldown-account-fallback.test.ts index 82eeab2041..cc96643f6e 100644 --- a/tests/unit/persist-429-cooldown-account-fallback.test.ts +++ b/tests/unit/persist-429-cooldown-account-fallback.test.ts @@ -34,7 +34,8 @@ const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); import { applyErrorState, resetAccountState } from "../../open-sse/services/accountFallback.ts"; -import { markConnectionRateLimitedUntil, clearConnectionRateLimit } from "../../src/lib/localDb.ts"; +const { markConnectionRateLimitedUntil, clearConnectionRateLimit } = + await import("@/lib/db/providers"); test.after(() => { core.resetDbInstance(); diff --git a/tests/unit/provider-window-costs.test.ts b/tests/unit/provider-window-costs.test.ts index 46f42f5e7c..8abc7a5a56 100644 --- a/tests/unit/provider-window-costs.test.ts +++ b/tests/unit/provider-window-costs.test.ts @@ -11,7 +11,8 @@ process.env.API_KEY_SECRET = "provider-window-costs-test-secret"; const core = await import("../../src/lib/db/core.ts"); const apiKeys = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const providerLimits = await import("../../src/lib/db/providerLimits.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const costRules = await import("../../src/domain/costRules.ts"); diff --git a/tests/unit/proxy-egress-route-summary.test.ts b/tests/unit/proxy-egress-route-summary.test.ts index c96862fd6e..fb1003bc88 100644 --- a/tests/unit/proxy-egress-route-summary.test.ts +++ b/tests/unit/proxy-egress-route-summary.test.ts @@ -17,7 +17,8 @@ process.env.API_KEY_SECRET = "test-api-key-secret"; const core = await import("../../src/lib/db/core.ts"); const proxyLogger = await import("../../src/lib/proxyLogger.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const route = await import("../../src/app/api/settings/proxies/egress/route.ts"); diff --git a/tests/unit/quota-key-models-route.test.ts b/tests/unit/quota-key-models-route.test.ts index bb71a649ec..e1303de998 100644 --- a/tests/unit/quota-key-models-route.test.ts +++ b/tests/unit/quota-key-models-route.test.ts @@ -46,7 +46,7 @@ const src = readFileSync(ROUTE_PATH, "utf8"); test("quota/keys/[id]/models: imports requireManagementAuth", () => { assert.ok( src.includes("requireManagementAuth"), - "route must import and call requireManagementAuth", + "route must import and call requireManagementAuth" ); }); @@ -66,20 +66,17 @@ test("quota/keys/[id]/models: GET returns early when authError is truthy", () => const getBody = src.slice(getIdx); assert.ok( getBody.includes("if (authError) return authError"), - "GET must return authError immediately — 401 without auth", + "GET must return authError immediately — 401 without auth" ); }); // ── Error sanitization ──────────────────────────────────────────────────────── test("quota/keys/[id]/models: imports buildErrorBody from @omniroute/open-sse/utils/error", () => { - assert.ok( - src.includes("buildErrorBody"), - "route must use buildErrorBody — Hard Rule #12", - ); + assert.ok(src.includes("buildErrorBody"), "route must use buildErrorBody — Hard Rule #12"); assert.ok( src.includes("@omniroute/open-sse/utils/error"), - "route must import buildErrorBody from @omniroute/open-sse/utils/error", + "route must import buildErrorBody from @omniroute/open-sse/utils/error" ); }); @@ -99,43 +96,43 @@ test("quota/keys/[id]/models: does NOT leak raw err.message outside buildErrorBo test("quota/keys/[id]/models: imports resolveQuotaKeyScope from @/lib/quota/quotaKey", () => { assert.ok( src.includes("resolveQuotaKeyScope"), - "route must call resolveQuotaKeyScope to compute pool scope", + "route must call resolveQuotaKeyScope to compute pool scope" ); assert.ok( src.includes("@/lib/quota/quotaKey"), - "route must import resolveQuotaKeyScope from @/lib/quota/quotaKey", + "route must import resolveQuotaKeyScope from @/lib/quota/quotaKey" ); }); test("quota/keys/[id]/models: imports filterModelsToQuotaPools from @/lib/quota/quotaCombos", () => { assert.ok( src.includes("filterModelsToQuotaPools"), - "route must call filterModelsToQuotaPools to filter combo candidates", + "route must call filterModelsToQuotaPools to filter combo candidates" ); assert.ok( src.includes("@/lib/quota/quotaCombos"), - "route must import filterModelsToQuotaPools from @/lib/quota/quotaCombos", + "route must import filterModelsToQuotaPools from @/lib/quota/quotaCombos" ); }); test("quota/keys/[id]/models: passes scope.poolSlugs to filterModelsToQuotaPools", () => { assert.ok( src.includes("scope.poolSlugs"), - "route must pass scope.poolSlugs to filterModelsToQuotaPools (same as catalog.ts approach)", + "route must pass scope.poolSlugs to filterModelsToQuotaPools (same as catalog.ts approach)" ); }); // ── DB imports from localDb ─────────────────────────────────────────────────── -test("quota/keys/[id]/models: imports getApiKeyById from @/lib/localDb", () => { +test("quota/keys/[id]/models: imports getApiKeyById from @/lib/db/apiKeys", () => { assert.ok(src.includes("getApiKeyById"), "route must call getApiKeyById"); assert.ok( - src.includes("@/lib/localDb"), - "route must import from @/lib/localDb (not from @/lib/db/apiKeys directly)", + src.includes("@/lib/db/apiKeys"), + "route must import getApiKeyById from @/lib/db/apiKeys (not from the localDb barrel)" ); }); -test("quota/keys/[id]/models: imports getCombos from @/lib/localDb", () => { +test("quota/keys/[id]/models: imports getCombos from @/lib/db/combos", () => { assert.ok(src.includes("getCombos"), "route must call getCombos to build combo candidates"); }); @@ -144,14 +141,14 @@ test("quota/keys/[id]/models: imports getCombos from @/lib/localDb", () => { test("quota/keys/[id]/models: GET returns { models } shape", () => { assert.ok( src.includes("{ models }") || src.includes("{models}") || src.includes("models:"), - "GET must return { models } in the response body", + "GET must return { models } in the response body" ); }); test("quota/keys/[id]/models: maps filtered combos to model id strings (.map(m => m.id))", () => { assert.ok( src.includes(".map(") && src.includes("m.id"), - "route must map filtered combo entries to their id strings", + "route must map filtered combo entries to their id strings" ); }); @@ -160,7 +157,7 @@ test("quota/keys/[id]/models: maps filtered combos to model id strings (.map(m = test("quota/keys/[id]/models: returns buildErrorBody(404, ...) when key not found", () => { assert.ok( src.includes("buildErrorBody(404"), - "route must use buildErrorBody(404, ...) when the API key is not found", + "route must use buildErrorBody(404, ...) when the API key is not found" ); assert.ok(src.includes("404"), "route must return HTTP 404 on missing key"); }); @@ -170,7 +167,7 @@ test("quota/keys/[id]/models: returns buildErrorBody(404, ...) when key not foun test("quota/keys/[id]/models: has dynamic = 'force-dynamic' export", () => { assert.ok( src.includes('dynamic = "force-dynamic"') || src.includes("dynamic = 'force-dynamic'"), - "route must export dynamic = 'force-dynamic'", + "route must export dynamic = 'force-dynamic'" ); }); @@ -179,22 +176,19 @@ test("quota/keys/[id]/models: has dynamic = 'force-dynamic' export", () => { test("quota/keys/[id]/models: reads id via await params (Next 16 pattern)", () => { assert.ok( src.includes("await params"), - "route must use await params — Next 16 async params pattern", + "route must use await params — Next 16 async params pattern" ); }); test("quota/keys/[id]/models: params typed as Promise<{ id: string }>", () => { assert.ok( src.includes("Promise<") && src.includes("id: string"), - "params type must be Promise<{ id: string }> — matching groups/[id] pattern", + "params type must be Promise<{ id: string }> — matching groups/[id] pattern" ); }); // ── exports GET ─────────────────────────────────────────────────────────────── test("quota/keys/[id]/models: exports GET handler", () => { - assert.ok( - src.includes("export async function GET"), - "route must export the GET handler", - ); + assert.ok(src.includes("export async function GET"), "route must export the GET handler"); }); diff --git a/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts b/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts index 6ee7ff114a..9c8e3b31ae 100644 --- a/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts +++ b/tests/unit/replace-custom-models-preserve-hidden-5086.test.ts @@ -26,7 +26,7 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-replace-cus process.env.DATA_DIR = tmpDir; const { replaceCustomModels, mergeModelCompatOverride, getModelIsHidden, getModelCompatOverrides } = - await import("../../src/lib/localDb.ts"); + await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { diff --git a/tests/unit/route-edge-coverage.test.ts b/tests/unit/route-edge-coverage.test.ts index 80ab12de02..c03e2b0b8f 100644 --- a/tests/unit/route-edge-coverage.test.ts +++ b/tests/unit/route-edge-coverage.test.ts @@ -15,7 +15,10 @@ const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const compliance = await import("../../src/lib/compliance/index.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const modelsDb = await import("../../src/lib/db/models.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const { createProxy, assignProxyToScope } = await import("@/lib/db/proxies"); +const { invalidateDbCache } = await import("@/lib/db/readCache"); +const localDb = { updateSettings, createProxy, assignProxyToScope, invalidateDbCache }; const listKeysRoute = await import("../../src/app/api/keys/route.ts"); const settingsProxyRoute = await import("../../src/app/api/settings/proxy/route.ts"); const managementProxiesRoute = await import("../../src/app/api/v1/management/proxies/route.ts"); diff --git a/tests/unit/specialty-model-hidden-openrouter-9293.test.ts b/tests/unit/specialty-model-hidden-openrouter-9293.test.ts index 360dfe3758..fc4d8ff4ff 100644 --- a/tests/unit/specialty-model-hidden-openrouter-9293.test.ts +++ b/tests/unit/specialty-model-hidden-openrouter-9293.test.ts @@ -26,7 +26,7 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "9293-test-secret"; const core = await import("../../src/lib/db/core.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const { mergeModelCompatOverride, getModelIsHidden } = await import("../../src/lib/localDb.ts"); +const { mergeModelCompatOverride, getModelIsHidden } = await import("@/lib/db/models"); const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); async function resetStorage() { diff --git a/tests/unit/sync-routes.test.ts b/tests/unit/sync-routes.test.ts index f863c04f6b..acaf60ff6b 100644 --- a/tests/unit/sync-routes.test.ts +++ b/tests/unit/sync-routes.test.ts @@ -20,7 +20,8 @@ const compliance = await import("../../src/lib/compliance/index.ts"); const syncTokensRoute = await import("../../src/app/api/sync/tokens/route.ts"); const syncTokenByIdRoute = await import("../../src/app/api/sync/tokens/[id]/route.ts"); const syncBundleRoute = await import("../../src/app/api/sync/bundle/route.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; function resetStorage() { apiKeysDb.resetApiKeyState(); diff --git a/tests/unit/synced-model-delete-resync.test.ts b/tests/unit/synced-model-delete-resync.test.ts index 4aaf79b167..84da271a0e 100644 --- a/tests/unit/synced-model-delete-resync.test.ts +++ b/tests/unit/synced-model-delete-resync.test.ts @@ -14,7 +14,7 @@ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-test-synced-del- process.env.DATA_DIR = tmpDir; const { replaceSyncedAvailableModelsForConnection, getSyncedAvailableModels } = - await import("../../src/lib/localDb.ts"); + await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { @@ -38,7 +38,7 @@ test("a deleted synced model is restored when upstream advertises it again", asy let synced = (await getSyncedAvailableModels(provider)).map((m) => m.id); assert.ok(synced.includes("model-del"), "both models present after first sync"); - const { removeSyncedAvailableModel } = await import("../../src/lib/localDb.ts"); + const { removeSyncedAvailableModel } = await import("@/lib/db/models"); assert.equal(await removeSyncedAvailableModel(provider, "model-del"), true); synced = (await getSyncedAvailableModels(provider)).map((m) => m.id); assert.ok(!synced.includes("model-del"), "delete removes the current synced row"); diff --git a/tests/unit/synced-model-hide-persist-3782.test.ts b/tests/unit/synced-model-hide-persist-3782.test.ts index e85ef0973b..3232eb0615 100644 --- a/tests/unit/synced-model-hide-persist-3782.test.ts +++ b/tests/unit/synced-model-hide-persist-3782.test.ts @@ -24,7 +24,7 @@ const { getSyncedAvailableModels, mergeModelCompatOverride, getModelIsHidden, -} = await import("../../src/lib/localDb.ts"); +} = await import("@/lib/db/models"); const { resetDbInstance } = await import("../../src/lib/db/core.ts"); before(() => { diff --git a/tests/unit/system-prompt-runtime-hotreload.test.ts b/tests/unit/system-prompt-runtime-hotreload.test.ts new file mode 100644 index 0000000000..e0a8708922 --- /dev/null +++ b/tests/unit/system-prompt-runtime-hotreload.test.ts @@ -0,0 +1,46 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + applyRuntimeSettings, + resetRuntimeSettingsStateForTests, +} from "../../src/lib/config/runtimeSettings"; +import { getSystemPromptConfig } from "../../open-sse/services/systemPrompt.ts"; + +test.afterEach(() => { + resetRuntimeSettingsStateForTests(); +}); + +test("systemPrompt settings hot-reload updates globalThis in-memory config", async () => { + const newSettings = { + systemPrompt: { + enabled: true, + prefixPrompt: "Prefix rules for agent", + suffixPrompt: "Suffix rules for agent", + }, + }; + + const changes = await applyRuntimeSettings(newSettings, { force: true, source: "test" }); + assert.ok( + changes.some((c) => c.section === "systemPrompt"), + "systemPrompt section must be reported as reloaded" + ); + + const cfg = getSystemPromptConfig(); + assert.equal(cfg.enabled, true, "systemPrompt enabled flag must be updated"); + assert.equal( + cfg.prefixPrompt, + "Prefix rules for agent", + "prefixPrompt must match updated settings" + ); + assert.equal( + cfg.suffixPrompt, + "Suffix rules for agent", + "suffixPrompt must match updated settings" + ); + + // Hot-reload clearing systemPrompt + await applyRuntimeSettings({ systemPrompt: null }, { force: true, source: "test" }); + const clearedCfg = getSystemPromptConfig(); + assert.equal(clearedCfg.enabled, false, "systemPrompt must be disabled when set to null"); + assert.equal(clearedCfg.prefixPrompt, "", "prefixPrompt must be cleared when set to null"); +}); diff --git a/tests/unit/usage-account-analytics-route.test.ts b/tests/unit/usage-account-analytics-route.test.ts index f05a40342d..c8eef83dde 100644 --- a/tests/unit/usage-account-analytics-route.test.ts +++ b/tests/unit/usage-account-analytics-route.test.ts @@ -9,7 +9,8 @@ process.env.DATA_DIR = TEST_DATA_DIR; process.env.API_KEY_SECRET = "test-account-analytics-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const providersDb = await import("../../src/lib/db/providers.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); const { resolveUsageAccountIdentity } = await import("../../src/lib/usage/accountIdentity.ts"); diff --git a/tests/unit/usage-analytics-route.test.ts b/tests/unit/usage-analytics-route.test.ts index ed897b6733..d6e41df005 100644 --- a/tests/unit/usage-analytics-route.test.ts +++ b/tests/unit/usage-analytics-route.test.ts @@ -10,7 +10,8 @@ const ORIGINAL_API_KEY_SECRET = process.env.API_KEY_SECRET; process.env.API_KEY_SECRET = "test-usage-analytics-secret"; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); diff --git a/tests/unit/usage-analytics.test.ts b/tests/unit/usage-analytics.test.ts index e0c1d25092..89cc77ae8e 100644 --- a/tests/unit/usage-analytics.test.ts +++ b/tests/unit/usage-analytics.test.ts @@ -8,7 +8,8 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-usage-ana process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updatePricing } = await import("@/lib/db/settings"); +const localDb = { updatePricing }; const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); const providersDb = await import("../../src/lib/db/providers.ts"); const usageHistory = await import("../../src/lib/usage/usageHistory.ts"); diff --git a/tests/unit/v1-ws-route.test.ts b/tests/unit/v1-ws-route.test.ts index 55e68c165d..87179ccdb1 100644 --- a/tests/unit/v1-ws-route.test.ts +++ b/tests/unit/v1-ws-route.test.ts @@ -13,7 +13,8 @@ process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-v1-ws-route-sec const core = await import("../../src/lib/db/core.ts"); const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); -const localDb = await import("../../src/lib/localDb.ts"); +const { updateSettings } = await import("@/lib/db/settings"); +const localDb = { updateSettings }; const wsRoute = await import("../../src/app/api/v1/ws/route.ts"); function resetStorage() {