Merge release/v3.8.50 (b6d2b4a4) into fix/qdrant-health-badge

This commit is contained in:
Rouzbeh
2026-08-17 11:24:50 +00:00
142 changed files with 2880 additions and 586 deletions

View File

@@ -1894,6 +1894,13 @@ APP_LOG_TO_FILE=true
# PROXY_AUTO_REMOVE=false
# Consecutive failures before an auto-remove fires. Default: 3.
# PROXY_AUTO_REMOVE_AFTER=3
# Set "true" to let the scheduler auto-disable (status "dead") proxies after
# repeated failures instead of deleting them. Non-destructive alternative to
# PROXY_AUTO_REMOVE — the row stays in the registry, drops out of pool/rotation
# resolution immediately, and is automatically re-activated once it starts
# answering probes again. Shares the PROXY_AUTO_REMOVE_AFTER threshold above.
# If both PROXY_AUTO_REMOVE and PROXY_AUTO_DISABLE are "true", auto-remove wins.
# PROXY_AUTO_DISABLE=false
# Let automated reachability probes (the scheduler + the "Test All" button) WRITE
# a proxy's status. Default "false": probes are read-only and never deactivate a
# proxy — only the operator sets active/inactive (a flaky probe must not strand an

View File

@@ -56,7 +56,7 @@ Repository map and Reference Documentation sections below.
| Translators | `open-sse/translator/` | Format conversion (OpenAI↔Claude↔Gemini) |
| Transformer | `open-sse/transformer/` | Responses API ↔ Chat Completions |
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
| Database | `src/lib/db/` | SQLite domain modules (148 migrations) |
| Database | `src/lib/db/` | SQLite domain modules (149 migrations) |
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
| MCP Server | `open-sse/mcp-server/` | 109 tools (44 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |

View File

@@ -167,6 +167,7 @@ _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `e
### 🐛 Bug Fixes
- **providers**: honor `PATCH /api/providers/[id]` so `omniroute providers rotate` stops 405ing (the OpenAPI spec and CLI already use PATCH) (PR #10366)
- **executors**: fix internal timeout misclassified as client disconnect (499) for 7 niche executors — pass TimeoutError reason to controller.abort() (#8197 side-finding)
- test(combo): guard auto/best-free never leaks the combo name as a model (#7754)
- fix(vision-bridge): describe-model no longer returns unreachable "openai/gpt-4o-mini" when every vision-capable provider is unreachable on the instance — returns null instead and surfaces a clear error (#8430)

View File

@@ -1108,7 +1108,7 @@ same process on one port, so there is no separate CLI-only package today.
<tr><td nowrap><b>Runtime</b></td><td>Node.js 22.x / 24.x LTS — <code>&gt;=22.22.2 &lt;23 || &gt;=24.0.0 &lt;27</code></td></tr>
<tr><td nowrap><b>Language</b></td><td>TypeScript 6.0 — <b>100% TypeScript</b> across <code>src/</code> and <code>open-sse/</code> (zero <code>any</code> in core since v2.0)</td></tr>
<tr><td nowrap><b>Framework</b></td><td>Next.js 16 + React 19 + Tailwind CSS 4</td></tr>
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 117 domain modules, 148 migrations</td></tr>
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 117 domain modules, 149 migrations</td></tr>
<tr><td nowrap><b>Memory</b></td><td>SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay</td></tr>
<tr><td nowrap><b>Schemas</b></td><td>Zod 4 — MCP tool I/O validation + API contracts</td></tr>
<tr><td nowrap><b>Protocols</b></td><td>MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)</td></tr>

View File

@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { platform, totalmem, hostname as osHostname } from "node:os";
import { platform, totalmem } from "node:os";
import { t } from "../i18n.mjs";
import { writePidFile, cleanupPidFile, waitForServer } from "../utils/pid.mjs";
import { ServerSupervisor, detectMitmCrash } from "../runtime/processSupervisor.mjs";
@@ -12,6 +12,7 @@ import {
isFatalInstrumentationHookFailure,
formatAndroidInstrumentationFailureHint,
} from "../utils/ensureAndroidCacheDir.mjs";
import { resolveServerHost } from "../utils/serverHost.mjs";
import {
resolveMaxOldSpaceMb,
calibrateHeapFallbackMb,
@@ -207,16 +208,10 @@ export async function runServe(opts = {}) {
PORT: String(dashboardPort),
DASHBOARD_PORT: String(dashboardPort),
API_PORT: String(apiPort),
// #6194: POSIX shells (bash/zsh) auto-set HOSTNAME to the machine name — the
// .env loader (first-wins) can never override it. Ignore HOSTNAME when it
// matches the OS-reported hostname (the auto-set signature). OMNIROUTE_SERVER_HOST
// takes precedence; legacy HOSTNAME values that don't match os.hostname() are
// still honoured for backward compatibility (e.g. Windows CMD/PowerShell users
// who set HOSTNAME in .env where it is NOT auto-set).
HOSTNAME:
process.env.OMNIROUTE_SERVER_HOST ||
(process.env.HOSTNAME !== osHostname() ? process.env.HOSTNAME : undefined) ||
"0.0.0.0",
// #10492: HOSTNAME is standard shell state on Unix-like systems, not an
// OmniRoute bind setting. The resolver only keeps its legacy meaning on
// Windows; OMNIROUTE_SERVER_HOST is the cross-platform explicit setting.
HOSTNAME: resolveServerHost(),
NODE_ENV: "production",
// #5238: preserve a user-set NODE_OPTIONS (incl. their own
// `--max-old-space-size=…`) instead of clobbering it with the calibrated

View File

@@ -0,0 +1,26 @@
import { hostname, platform } from "node:os";
/**
* Resolve the bind host passed to the standalone Next.js server.
*
* HOSTNAME is a standard shell variable on Unix-like systems, so only the
* dedicated OmniRoute variable is treated as configuration there. Windows
* keeps the legacy HOSTNAME fallback for compatibility with existing .env
* files, while still ignoring the OS-reported machine name.
*
* @param {NodeJS.ProcessEnv} [env]
* @param {NodeJS.Platform} [runtimePlatform]
* @param {string} [machineHostname]
* @returns {string}
*/
export function resolveServerHost(
env = process.env,
runtimePlatform = platform(),
machineHostname = hostname()
) {
if (env.OMNIROUTE_SERVER_HOST) return env.OMNIROUTE_SERVER_HOST;
if (runtimePlatform === "win32" && env.HOSTNAME && env.HOSTNAME !== machineHostname) {
return env.HOSTNAME;
}
return "0.0.0.0";
}

View File

@@ -0,0 +1 @@
- **fix(oauth):** Claude connections created via `claude-auth/import` now send required CLI headers on the bootstrap identity call and persist a `cliUserID` device identity, fixing intermittent "Third-party apps now draw from your extra usage" 400s on otherwise valid imported subscription tokens ([#10144](https://github.com/diegosouzapw/OmniRoute/pull/10144), fixes [#10143](https://github.com/diegosouzapw/OmniRoute/issues/10143))

View File

@@ -0,0 +1 @@
- **fix(cursor):** Stop truncating pending tool calls on non-composer models when a KV checkpoint arrives after text but before the `exec_mcp` frame — the KV short-circuit is now gated to the composer family where it was verified ([#10215](https://github.com/diegosouzapw/OmniRoute/issues/10215)).

View File

@@ -0,0 +1 @@
- **fix(responses):** repair corrupted SSE deltas for non-ASCII streams by keeping a single stream-aware `TextDecoder` (`{ stream: true }`) across `transform()` calls instead of recreating it per chunk and decoding without the `stream` flag. When a multi-byte UTF-8 character (CJK/emoji) was split across two TCP chunks — common in Chinese streaming text — the per-chunk decoder truncated it to `U+FFFD`, corrupting every delta while the rebuilt `*.done` snapshot stayed internally identical ([#10223](https://github.com/diegosouzapw/OmniRoute/issues/10223))

View File

@@ -0,0 +1 @@
- fix(sse): mark gemini-3.5-flash as thinking-capable so reasoning_effort is no longer rejected with a spurious 400 (#10286)

View File

@@ -0,0 +1 @@
- fix(sse): stop ZWJ-obfuscating the substring "hermes" in user messages and hostnames (#10484)

View File

@@ -0,0 +1 @@
- **fix(providers):** allow token-backed web sessions stored with `authType: "cookie"` to refresh their token through the provider update API ([#10518](https://github.com/diegosouzapw/OmniRoute/pull/10518)) — thanks @Zartharas

View File

@@ -0,0 +1 @@
- **fix(cli):** ignore the operating system `HOSTNAME` when choosing the server bind address on Linux and macOS, preventing startup failures when the shell hostname differs from `os.hostname()`; use `OMNIROUTE_SERVER_HOST` for explicit non-Windows configuration while preserving the legacy `HOSTNAME` fallback on Windows ([#10557](https://github.com/diegosouzapw/OmniRoute/pull/10557), closes [#10492](https://github.com/diegosouzapw/OmniRoute/issues/10492)) — thanks @redzrush101

View File

@@ -0,0 +1 @@
- fix(providers): strip uniqueItems from Gemini tool schemas (Gemini rejects it with 400 'Unknown name uniqueItems') (#9617)

View File

@@ -0,0 +1 @@
- fix(sse): exclude search providers from credential-health scheduler sweep to stop burning billed API queries (#9970)

View File

@@ -0,0 +1 @@
- **fix(api-manager):** Allowed Combos can now be restricted to zero entries: **All** is stored explicitly as `combo/*`, while **Restrict** with no selection saves an empty allowlist that denies Combo routes without blocking direct models. Existing keys are migrated to preserve their previous allow-all behavior.

View File

@@ -0,0 +1 @@
- **fix(db):** the `compression_run_telemetry` retention sweep now actually deletes expired rows. Its cutoff was computed in epoch seconds while the column stores epoch milliseconds, so `WHERE timestamp < cutoff` never matched and the table added by #6848 to bound `storage.sqlite` growth was unbounded in practice. Same unit mismatch as #9625, which corrected the sibling `domain_cost_history` sweep and missed this call site

View File

@@ -0,0 +1 @@
- **fix(db):** database settings API no longer returns HTTP 500 on SQLite builds compiled without the optional `dbstat` virtual table (sql.js/WASM); per-table sizes degrade to 0 instead of failing the whole stats call

View File

@@ -0,0 +1 @@
- **fix(models):** honor `MODELS_DEV_SYNC_ENABLED=0` as a hard kill switch over the dashboard setting so a wedged `/healthz` / UI can be recovered without HTTP (`src/lib/modelsDevSync.ts`)

View File

@@ -3319,4 +3319,4 @@
"count": 5
}
}
}
}

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -128,7 +128,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -394,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -817,6 +817,50 @@ The proxy is **not deleted** — it's marked unhealthy and won't be selected unt
---
## Automatic Failure Exclusion for Your Own Proxies
`failOneproxyProxy()` above only covers the 1proxy marketplace pool, which already
auto-degrades on failure (see [Proxy Quality Scores](#proxy-quality-scores)). For
proxies **you** added to the registry, the background health scheduler
(`src/lib/proxyHealth/scheduler.ts`) provides the same "exclude a dead member from
the chain automatically" behavior, without deleting anything:
```bash
# .env — soft-disable a proxy after 3 consecutive failed probes, re-enable it
# automatically once it starts answering probes again.
PROXY_AUTO_DISABLE=true
PROXY_AUTO_REMOVE_AFTER=3
```
How it fits into a multi-proxy chain:
1. The scheduler probes every registered proxy every `PROXY_HEALTH_INTERVAL_MS`
(default 10 min; minimum 1 min).
2. After `PROXY_AUTO_REMOVE_AFTER` consecutive **conclusive** failures (a real
connection failure — a timeout or the probe target's own 5xx never counts, see
[Proxy Health Checking](#proxy-health-checking-v3816)), the proxy's `status` is
set to `dead`.
3. `dead` is one of the statuses the alive-status filter used by pool/rotation
resolution excludes, so a scope's rotation (round-robin / random / sticky /
latency — see [Rotation Strategy Decision Tree](#rotation-strategy-decision-tree))
immediately stops handing that proxy to new requests. No other proxies in the
pool are affected, and the whole pool never silently falls back to a direct
connection — see the [4-Level Proxy System](#4-level-proxy-system) fail-closed
guard.
4. The scheduler keeps probing `dead` proxies on the same interval. The next
successful probe flips `status` back to `active` and it re-enters rotation —
no manual re-add required.
This is deliberately **opt-in and non-destructive**: by default the scheduler only
counts and logs failures (see policy C in `decision.ts`), and `PROXY_AUTO_DISABLE`
never deletes a row — that is what the separate, more aggressive
`PROXY_AUTO_REMOVE` flag is for. If both are set to `true`, `PROXY_AUTO_REMOVE`
wins (a proxy about to be deleted has no use for a soft-disable in between). See
the [Environment Config](../reference/ENVIRONMENT.md) reference for the full
variable list.
---
> 📖 **Related documentation:**
>
> - [User Guide](../guides/USER_GUIDE.md) — General setup and configuration

View File

@@ -151,7 +151,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
| `OMNIROUTE_SKIP_DB_HEALTHCHECK` | _(unset)_ | `src/lib/db/core.ts` / `src/lib/db/healthCheck.ts` | Set to `1` to skip the SQLite integrity health check on startup. Useful for faster boot on large databases. |
| `CREDENTIAL_HEALTH_CHECK_INTERVAL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/scheduler.ts` | Interval (ms) for the background credential health check scheduler. Minimum: 10000 (10s). |
| `CREDENTIAL_HEALTH_CACHE_TTL` | `300000` | `open-sse/config/constants.ts` / `src/lib/credentialHealth/cache.ts` | TTL (ms) for cached credential health status. |
| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | Set to `1` or `true` to disable background periodic testing of provider connections. |
| `OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK` | `false` | `src/lib/credentialHealth/scheduler.ts` | Set to `1` or `true` to disable background periodic testing of provider connections. Search providers (SEARCH_VALIDATOR_CONFIGS in `src/lib/providers/validation/searchProviders.ts`, e.g. `tavily-search`) are always excluded from the sweep — their "validation" is a real billed upstream query, so they are never health-checked on a timer (#9970). |
| `HOST` | `0.0.0.0` | `scripts/dev/run-next.mjs` | Bind address for the Next.js dev/start server. Overrides the default `0.0.0.0` when set. |
| `HOSTNAME` | `127.0.0.1` | `scripts/dev/run-next-playwright.mjs` | Bind address used by the Playwright runner when launching Next.js. Defaults to `127.0.0.1` for hermetic tests. **Do not use for `omniroute serve`** — use `OMNIROUTE_SERVER_HOST` instead (POSIX shells auto-set `HOSTNAME` to the machine name; `.env` cannot override it). |
| `OMNIROUTE_SERVER_HOST` | `0.0.0.0` | `bin/cli/commands/serve.mjs` | Bind address for `omniroute serve`. Avoids collision with the POSIX shell `HOSTNAME` variable (always set to the machine name by bash/zsh). Falls back to `0.0.0.0` when unset. (#6194) |
@@ -934,7 +934,7 @@ Chrome-driven session refresh (ARP) for the Adobe Firefly web provider (`open-ss
| Variable | Default | Source File | Description |
| ----------------------------------- | ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MODELS_DEV_SYNC_ENABLED` | `false` | `src/lib/modelsDevSync.ts` | Opt-in switch for the models.dev capability sync. Set to anything non-empty it wins over the `modelsDevSyncEnabled` setting (Dashboard > Settings > AI) in either direction, so a deployment can pin the sync on or off without depending on database state surviving a rebuild; unset, it defers to that setting. On for `1`, `true`, `yes` or `on` in any casing; any other value is off. |
| `MODELS_DEV_SYNC_ENABLED` | _(unset)_ | `src/lib/modelsDevSync.ts` | Hard override for models.dev pricing sync. Unset = honor Settings > AI (`modelsDevSyncEnabled`). `0`/`false`/`off`/`no` **wins over the DB** and skips both periodic sync and `getModelsDevPricing()` SQL/JSON scans (recovery when the dashboard is wedged on the same event loop). `1`/`true`/`on`/`yes` forces sync on. |
| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. |
| `CONTEXT_WINDOW_RECONCILE_INTERVAL` | `86400` (24h) | `src/lib/contextWindowResolver.ts` | Interval (seconds) for the self-correcting context-window reconciler (5004): pins provider-declared windows from `/models` discovery as `auto:discovery` overrides when they diverge from the catalog. Set to `0` to disable. Reuses already-synced data (no new fetch); never overwrites `manual` overrides. |
@@ -999,6 +999,7 @@ Anthropic-compatible provider instead.
| `PROXY_HEALTH_AUTO_DEACTIVATE` | `false` | `src/lib/proxyHealth/statusPolicy.ts` | When `false` (default), automated reachability probes (the scheduler + the `/api/settings/proxies/auto-test` "Test All" button) are **read-only** and never write a proxy's status — only the operator sets active/inactive, so a flaky probe can't strand an assigned proxy (#6246). Set `true` to restore the legacy test-and-set behaviour. |
| `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. |
| `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). |
| `PROXY_AUTO_DISABLE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler soft-disable (status `dead`, never deleted) a proxy after repeated consecutive failures, instead of removing it. Non-destructive alternative to `PROXY_AUTO_REMOVE`: the proxy drops out of pool/rotation resolution immediately (the alive-status filter used by scope-pool resolution already excludes it) and is automatically re-activated once it starts passing probes again. Shares the `PROXY_AUTO_REMOVE_AFTER` threshold. If both flags are `true`, `PROXY_AUTO_REMOVE` wins. |
| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | `false` | `src/shared/constants/featureFlagDefinitions.ts` | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Effective precedence is Feature Flags DB override > env var > default. |
| `RATE_LIMIT_MAX_WAIT_MS` | `15000` (15s) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
| `RATE_LIMIT_MAX_QUEUE_DEPTH` | `0` (disabled) | `open-sse/services/rateLimitManager.ts` | Queue admission cap: reject with a 429 `queue_full` once this many requests are already queued. `0` = unbounded (default). |

View File

@@ -14,7 +14,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
- **Framework:** Next.js 16 (App Router) with TypeScript 6
- **Database:** SQLite via better-sqlite3 (local, zero-config, 148 migrations)
- **Database:** SQLite via better-sqlite3 (local, zero-config, 149 migrations)
- **State management:** Zustand (client), SQLite (server persistence)
- **UI:** React 19, Tailwind CSS 4, Recharts for analytics, @lobehub/icons for 130+ provider SVG icons
- **Auth:** OAuth 2.0 (PKCE) for providers, bcrypt for local user auth
@@ -124,7 +124,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
│ │ │ ├── secrets.ts # Secrets management
│ │ │ ├── stateReset.ts # State reset utilities
│ │ │ ├── migrationRunner.ts # Schema migration runner
│ │ │ └── migrations/ # 148 versioned SQL migration files
│ │ │ └── migrations/ # 149 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -390,7 +390,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 148 SQL migrations.
9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 117 `src/lib/db/` modules with 149 SQL migrations.
10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`.
@@ -434,7 +434,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 148 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
5. **Database layer:** Operations go through `src/lib/db/` modules (117 domain-specific files, 149 migrations). `localDb.ts` is re-exports only — add new functions to the proper `db/*.ts` module.
6. **Tests** use Node.js built-in test runner + Vitest. Run `npm test`. Vitest for MCP/autoCombo (`npm run test:vitest`). Playwright for E2E (`npm run test:e2e`). Coverage gate: ratchet vs `quality-baseline.json`, absolute floor 60% statements/lines/functions/branches.

View File

@@ -173,14 +173,11 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string
// Accept either the public alias ("cmd") or the raw provider id ("command-code"),
// mirroring getProviderModels (same pattern as #2798/#3870).
const alias = PROVIDER_ID_TO_ALIAS[aliasOrId] || aliasOrId;
const models = PROVIDER_MODELS[alias];
// Strip provider prefix if present: "openai/gpt-5.6-luna" → "gpt-5.6-luna"
const prefix = alias + "/";
const bareModelId =
typeof modelId === "string" && modelId.startsWith(prefix)
? modelId.slice(prefix.length)
: modelId;
const found = models?.find((m) => m.id === bareModelId);
const prefixes = [`${aliasOrId}/`, `${alias}/`];
const prefix = prefixes.find((value) => modelId.startsWith(value));
const bareModelId = prefix ? modelId.slice(prefix.length) : modelId;
const found = PROVIDER_MODELS[alias]?.find((m) => m.id === bareModelId);
if (found?.targetFormat) return found.targetFormat;
// #5842: OpenAI "*-pro" reasoning models (o1-pro, gpt-5.x-pro) are only served by
// the native /v1/responses endpoint — /v1/chat/completions 404s ("only supported
@@ -188,16 +185,13 @@ export function getModelTargetFormat(aliasOrId: string, modelId: string): string
// covers dynamically-synced ids that post-date the catalog (same spirit as the gh
// executor's /codex/i routing, 9router#102). Scoped to the openai alias so other
// providers shipping *-pro ids keep their own endpoint semantics.
if (alias === "openai" && /-pro$/i.test(modelId)) return "openai-responses";
if (alias === "openai" && /-pro$/i.test(bareModelId)) return "openai-responses";
// Model-level targetFormat is provider-scoped: a catalog entry declares how THIS
// provider's endpoint serves the model. When the provider has its own catalog but
// the model is not in it, do NOT import the global entry's tag — it encodes the
// DECLARING provider's endpoint semantics (e.g. ghe-copilot tags gpt-5.6-* as
// openai-responses, which must not hijack command-code's chat-shaped
// /alpha/generate → 502 "Invalid prompt: messages must not be empty"). Providers
// with no catalog at all keep the global fallback as their only metadata source.
if (models) return null;
return getGlobalModel(bareModelId)?.targetFormat ?? null;
// provider's endpoint serves the model — do NOT import another provider's tag.
// #9994 scoped this for providers WITH a catalog; #10072 extends it to catalogless
// providers (openai-compatible-chat-*), which previously inherited the declaring
// provider's endpoint semantics via the global fallback.
return null;
}
export function getModelStripTypes(aliasOrId: string, modelId: string): string[] {
const models = PROVIDER_MODELS[aliasOrId];

View File

@@ -67,13 +67,6 @@ export const codebuddy_cnProvider: RegistryEntry = {
supportsReasoning: true,
supportsVision: true,
},
{
id: "glm-4.7",
name: "GLM-4.7",
contextLength: 200000,
maxOutputTokens: 48000,
supportsReasoning: true,
},
{
id: "minimax-m3",
name: "MiniMax-M3",
@@ -122,6 +115,14 @@ export const codebuddy_cnProvider: RegistryEntry = {
supportsReasoning: true,
supportsVision: true,
},
{
id: "hy3",
name: "Hy3",
contextLength: 192000,
maxOutputTokens: 64000,
supportsReasoning: true,
supportsVision: true,
},
{
id: "deepseek-v4-pro",
name: "DeepSeek-V4-Pro",

View File

@@ -30,6 +30,7 @@ export interface SearchProviderConfig {
* credentialed provider is available, or when requested explicitly by id.
*/
fallbackOnly?: boolean;
disabled?: boolean;
}
export const SEARCH_PROVIDERS: Record<string, SearchProviderConfig> = {

View File

@@ -681,13 +681,26 @@ export function processFrame(
// after text means the model finished and the server is saving the
// turn. Phase 8 keeps both signals as defense-in-depth.
//
// Safe vs tool calls: when the model invokes a tool, the exec_mcp event
// always arrives at or before this kv checkpoint (verified across many
// live composer-2.5 trials — a tool call never follows kv_after_text), so
// endReason is already "tool_calls" by the time we get here. Ending on
// kv_after_text therefore never truncates a pending tool call.
// Safe vs tool calls (composer family only): when the model invokes a
// tool, the exec_mcp event always arrives at or before this kv
// checkpoint (verified across many live composer-2.5 trials — a tool call
// never follows kv_after_text), so endReason is already "tool_calls" by
// the time we get here. Ending on kv_after_text therefore never truncates
// a pending tool call on composer.
//
// Non-composer models (cursor/grok-4.5-high, auto, ...) emit the KV
// checkpoint as a blob-store side-channel frame (envelope field 4,
// kv_get_blob/kv_set_blob) with NO turn-completion semantics, and it can
// arrive while the model is still streaming a long preamble BEFORE a
// pending exec_mcp. Ending the turn there drops that exec_mcp, leaving a
// narration-only finish_reason "stop" with zero tool_calls (#10215). On
// this family only the real terminal signals (turn_ended,
// tool_call_completed, server_end) decide — kvAfterTextSeen is kept purely
// as an observational flag, never as the turn terminator.
ctx.kvAfterTextSeen = true;
ctx.endReason = "kv_after_text";
if (isComposerModel(ctx.model)) {
ctx.endReason = "kv_after_text";
}
}
}
}

View File

@@ -35,6 +35,7 @@ import {
prepareStructuredEmbeddingRequest,
} from "./embeddingStructuredInput.ts";
import { MAX_EMBEDDING_INLINE_ITEM_BYTES } from "@/shared/validation/schemas/apiV1";
import { markAccountUnavailable } from "../../src/sse/services/auth.ts";
interface ClientRawRequest {
endpoint: string;
@@ -389,6 +390,28 @@ export async function handleEmbedding({
connectionId,
}).catch(() => {});
// #10347 — persist a connection-level failure marker on a hard upstream failure so
// the dead account is not re-selected and re-hit on the next embed request (chat
// parity). markAccountUnavailable classifies the status via checkFallbackError: a
// payment-required 402 becomes the TERMINAL state credits_exhausted (the terminal
// marker excludes the account from selection until an operator resets it), benign
// 4xx are a no-op, and terminal statuses are never overwritten. honors per-connection
// disableCooling. The write must never break the error response path, so it is
// best-effort.
if (connectionId) {
try {
await markAccountUnavailable(
connectionId,
response.status,
errorText,
provider,
model
);
} catch {
// swallow — the upstream error response takes priority
}
}
return {
success: false,
status: response.status,

View File

@@ -20,6 +20,7 @@ import { randomUUID } from "crypto";
import { getSearchProvider, type SearchProviderConfig } from "../config/searchRegistry.ts";
import { buildPerplexityRequest, parsePerplexitySearchOptions } from "./search/perplexitySearch.ts";
import * as fcSearch from "./search/firecrawlSearch.ts";
import { type FirecrawlSearchEnvelope } from "./search/firecrawlSearch.ts";
import { freeWebSearch } from "../services/freeWebSearch.ts";
import { saveCallLog } from "@/lib/usageDb";
import { safeOutboundFetch } from "@/shared/network/safeOutboundFetch";
@@ -304,7 +305,10 @@ function buildSerperRequest(
url: `${config.baseUrl}${endpoint}`,
init: {
method: "POST",
headers: { "Content-Type": "application/json", ...(params.token ? { "X-API-Key": params.token } : {}) },
headers: {
"Content-Type": "application/json",
...(params.token ? { "X-API-Key": params.token } : {}),
},
body: JSON.stringify(body),
},
};
@@ -322,7 +326,10 @@ function buildBraveRequest(
url: `${config.baseUrl}${endpoint}?${qp}`,
init: {
method: "GET",
headers: { Accept: "application/json", ...(params.token ? { "X-Subscription-Token": params.token } : {}) },
headers: {
Accept: "application/json",
...(params.token ? { "X-Subscription-Token": params.token } : {}),
},
},
};
}
@@ -348,7 +355,10 @@ function buildExaRequest(
url: config.baseUrl,
init: {
method: "POST",
headers: { "Content-Type": "application/json", ...(params.token ? { "x-api-key": params.token } : {}) },
headers: {
"Content-Type": "application/json",
...(params.token ? { "x-api-key": params.token } : {}),
},
body: JSON.stringify(body),
},
};
@@ -597,22 +607,33 @@ function buildOllamaRequest(
};
}
type SearchRequestBuilder = (
config: SearchProviderConfig,
params: SearchRequestParams
) => { url: string; init: RequestInit };
const requestBuilders: Record<string, SearchRequestBuilder> = {
"serper-search": buildSerperRequest,
"brave-search": buildBraveRequest,
"perplexity-search": buildPerplexityRequest,
"exa-search": buildExaRequest,
"tavily-search": buildTavilyRequest,
firecrawl: fcSearch.buildFirecrawlSearchRequest,
"google-pse-search": buildGooglePseRequest,
"linkup-search": buildLinkupRequest,
"searchapi-search": buildSearchApiRequest,
"youcom-search": buildYouComRequest,
"searxng-search": buildSearxngRequest,
"ollama-search": buildOllamaRequest,
};
function buildRequest(
config: SearchProviderConfig,
params: SearchRequestParams
): { url: string; init: RequestInit } {
if (config.id === "serper-search") return buildSerperRequest(config, params);
if (config.id === "brave-search") return buildBraveRequest(config, params);
if (config.id === "perplexity-search") return buildPerplexityRequest(config, params);
if (config.id === "exa-search") return buildExaRequest(config, params);
if (config.id === "tavily-search") return buildTavilyRequest(config, params);
if (config.id === "firecrawl") return fcSearch.buildFirecrawlSearchRequest(config, params);
if (config.id === "google-pse-search") return buildGooglePseRequest(config, params);
if (config.id === "linkup-search") return buildLinkupRequest(config, params);
if (config.id === "searchapi-search") return buildSearchApiRequest(config, params);
if (config.id === "youcom-search") return buildYouComRequest(config, params);
if (config.id === "searxng-search") return buildSearxngRequest(config, params);
if (config.id === "ollama-search") return buildOllamaRequest(config, params);
const builder = requestBuilders[config.id];
if (builder) return builder(config, params);
// Fallback for future providers: POST with bearer auth
return {
url: resolveSearchBaseUrl(config, params),
@@ -1161,29 +1182,40 @@ async function tryZaiMCPProvider(
}
}
type SearchResponseNormalizer = (
data: unknown,
query: string,
searchType: string
) => { results: SearchResult[]; totalResults: number | null };
const responseNormalizers: Record<string, SearchResponseNormalizer> = {
"serper-search": normalizeSerperResponse,
"brave-search": normalizeBraveResponse,
"perplexity-search": normalizePerplexityResponse,
"exa-search": normalizeExaResponse,
"tavily-search": normalizeTavilyResponse,
firecrawl: (data: FirecrawlSearchEnvelope, _query: string, searchType: string) =>
fcSearch.normalizeFirecrawlSearchResponse(data, searchType, makeResult),
"google-pse-search": normalizeGooglePseResponse,
"linkup-search": normalizeLinkupResponse,
"searchapi-search": normalizeSearchApiResponse,
"youcom-search": normalizeYouComResponse,
"searxng-search": normalizeSearxngResponse,
"ollama-search": normalizeOllamaResponse,
};
function normalizeResponse(
providerId: string,
data: any,
query: string,
searchType: string
): { results: SearchResult[]; totalResults: number | null } {
if (providerId === "serper-search") return normalizeSerperResponse(data, query, searchType);
if (providerId === "brave-search") return normalizeBraveResponse(data, query, searchType);
if (providerId === "perplexity-search")
return normalizePerplexityResponse(data, query, searchType);
if (providerId === "exa-search") return normalizeExaResponse(data, query, searchType);
if (providerId === "tavily-search") return normalizeTavilyResponse(data, query, searchType);
if (providerId === "firecrawl")
return fcSearch.normalizeFirecrawlSearchResponse(data, searchType, makeResult);
if (providerId === "google-pse-search")
return normalizeGooglePseResponse(data, query, searchType);
if (providerId === "linkup-search") return normalizeLinkupResponse(data, query, searchType);
if (providerId === "searchapi-search") return normalizeSearchApiResponse(data, query, searchType);
if (providerId === "youcom-search") return normalizeYouComResponse(data, query, searchType);
if (providerId === "searxng-search") return normalizeSearxngResponse(data, query, searchType);
if (providerId === "ollama-search") return normalizeOllamaResponse(data, query, searchType);
const normalizer = responseNormalizers[providerId];
if (normalizer) return normalizer(data, query, searchType);
return { results: [], totalResults: null };
}
export async function handleSearch(options: SearchHandlerOptions): Promise<SearchHandlerResult> {
const {
query,
@@ -1221,6 +1253,13 @@ export async function handleSearch(options: SearchHandlerOptions): Promise<Searc
error: `Unknown search provider: ${providerId}`,
};
}
if (primaryConfig.disabled) {
return {
success: false,
status: 403,
error: `Search provider '${providerId}' is currently disabled.`,
};
}
// 3. Get alternate config for failover (pre-resolved by route)
const alternateConfig = alternateProvider ? getSearchProvider(alternateProvider) : null;

View File

@@ -0,0 +1,17 @@
import { SEARCH_PROVIDERS } from "../../config/searchRegistry";
/**
* Dynamically generates a tuple of active search provider IDs for Zod enums.
* Filters out any providers marked as disabled in the registry.
*/
export function getActiveSearchProviders(): [string, ...string[]] {
const activeProviders = Object.values(SEARCH_PROVIDERS)
.filter((provider) => !provider.disabled)
.map((provider) => provider.id);
if (activeProviders.length === 0) {
return ["none_available"];
}
return activeProviders as [string, ...string[]];
}

View File

@@ -12,6 +12,7 @@
import { z } from "zod";
import { toolSearchTool } from "./toolSearch.ts";
import { pickFastestModelTool } from "./pickFastestModel.ts";
import { getActiveSearchProviders } from "./providerEnums";
import { CCR_MCP_TOOLS } from "./ccrTools.ts";
import { radarCatalogTool } from "./radarCatalog.ts";
import {
@@ -455,17 +456,7 @@ export const webSearchInput = z.object({
.describe("Maximum number of search results to return"),
search_type: z.enum(["web", "news"]).default("web").describe("Type of search to perform"),
provider: z
.enum([
"serper-search",
"brave-search",
"perplexity-search",
"exa-search",
"tavily-search",
"google-pse-search",
"linkup-search",
"searchapi-search",
"searxng-search",
])
.enum(getActiveSearchProviders())
.optional()
.describe("Specific search provider to use"),
});

View File

@@ -598,16 +598,7 @@ async function handleWebSearch(args: {
query: string;
max_results?: number;
search_type?: "web" | "news";
provider?:
| "serper-search"
| "brave-search"
| "perplexity-search"
| "exa-search"
| "tavily-search"
| "google-pse-search"
| "linkup-search"
| "searchapi-search"
| "searxng-search";
provider?: string;
}) {
const start = Date.now();
try {

View File

@@ -96,9 +96,10 @@ export const DEFAULT_OBFUSCATE_WORDS = [
// Open WebUI additions
"openwebui",
"open-webui",
// Hermes additions (#8350)
"hermes-agent",
"hermes",
// Do not add "hermes" / "hermes-agent" here. #8350 is handled by
// HERMES_PARAGRAPH_ANCHORS + HERMES_IDENTITY_PREFIXES (system-prompt
// drops only). ZWJ on the short substring "hermes" rewrites user
// messages and hostnames (#10484).
];
/**

View File

@@ -231,6 +231,11 @@ export function createResponsesApiTransformStream(
};
const encoder = new TextEncoder();
// #10223: a stream:false TextDecoder recreated per transform() chunk has no
// cross-call state, so a multi-byte UTF-8 character (CJK/emoji) split across
// two TCP chunks got truncated to U+FFFD, corrupting the deltas. A single
// persistent decoder with { stream: true } carries pending bytes between chunks.
const decoder = new TextDecoder();
const nextSeq = () => ++state.seq;
// Normalize output_index to a non-negative integer (replaces fragile parseInt calls)
@@ -577,7 +582,7 @@ export function createResponsesApiTransformStream(
(state.keepaliveTimer as { unref?: () => void })?.unref?.();
},
transform(chunk, controller) {
const text = new TextDecoder().decode(chunk);
const text = decoder.decode(chunk, { stream: true });
logger?.logInput(text.trim());
state.buffer += text;
@@ -887,6 +892,11 @@ export function createResponsesApiTransformStream(
},
flush(controller) {
// #10223: stream-end flush — drain any bytes the persistent decoder is
// still holding. With { stream:true } complete multi-byte chars are
// emitted within transform(), so normally there is nothing left; this
// only releases a terminating truncated byte and frees the decoder.
state.buffer += decoder.decode();
// Clear keepalive timer
if (state.keepaliveTimer) {
clearInterval(state.keepaliveTimer);

View File

@@ -58,6 +58,11 @@ export const GEMINI_UNSUPPORTED_SCHEMA_KEYS = new Set([
"contains",
"minContains",
"maxContains",
// #9617: array uniqueness keyword — agentic-CLI tool schemas (JSON-Schema
// generators) set this routinely and Gemini's schema parser has no field for
// it, rejecting the whole request with "Unknown name \"uniqueItems\"".
// Upstream 9router already strips it alongside `contains` for the same error.
"uniqueItems",
// Complex schema keywords (handled by flattenAnyOfOneOf/mergeAllOf)
"anyOf",
"oneOf",

View File

@@ -210,6 +210,14 @@ function hasClientTerminalSseMarker(text: string, clientResponseFormat?: string
);
}
// OpenAI chat completions: some providers omit `data: [DONE]` (already
// matched above) and terminate with a finish_reason chunk instead. A
// non-null finish_reason value is that terminal signal — a bare
// `finish_reason: null` delta chunk must NOT count (#10443).
if (clientResponseFormat === FORMATS.OPENAI) {
return /"finish_reason"\s*:\s*"[^"]+"/.test(text);
}
return false;
}
@@ -516,8 +524,22 @@ function resolveSilentCloseReason(input: {
}): string | null {
if (!input.bytesWereForwarded) return null;
if (!input.clientTerminalSeen && input.clientResponseFormat === FORMATS.CLAUDE) {
return "Upstream stream ended without a terminal marker";
if (!input.clientTerminalSeen) {
if (input.clientResponseFormat === FORMATS.CLAUDE) {
return "Upstream stream ended without a terminal marker";
}
// #10443: every known path that produces OpenAI chat chunks emits a
// terminal — the response translators (gemini/claude/kiro/cursor-to-openai)
// all emit a finish_reason chunk, the non-standard executors (kiro, cursor,
// nlpcloud, poe-web, copilot-m365-web, chatgpt-web, chipotle, gitlab)
// enqueue `data: [DONE]` themselves, and standard OpenAI-compatible
// upstreams end with finish_reason + [DONE] per spec. So a close that
// forwarded content but no terminal marker is an upstream drop, not a
// legitimate end. Guard on sawContent() so the #8649 empty-content
// verdict below keeps its more precise shape for content-free closes.
if (input.clientResponseFormat === FORMATS.OPENAI && input.contentWatcher.sawContent()) {
return "Upstream stream ended without a terminal marker";
}
}
const watcher = input.contentWatcher;

151
package-lock.json generated
View File

@@ -61,7 +61,7 @@
"next-themes": "^0.4.6",
"node-machine-id": "^1.1.12",
"omniglyph": "^1.0.2",
"onnxruntime-node": "~1.27.0",
"onnxruntime-node": "~1.24.3",
"open": "^11.0.0",
"ora": "^9.4.1",
"parse5": "^8.0.1",
@@ -88,7 +88,6 @@
"undici": "^8.10.0",
"update-notifier": "^7.3.1",
"uuid": "^14.0.0",
"wreq-js": "3.0.0",
"ws": "^8.21.3",
"xxhash-wasm": "^1.1.0",
"yazl": "^3.3.1",
@@ -110,7 +109,7 @@
"@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@types/better-sqlite3": "^9.6.0",
"@types/bun": "*",
"@types/bun": "latest",
"@types/node": "^26.2.0",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
@@ -3506,97 +3505,6 @@
"sharp": "^0.34.5"
}
},
"node_modules/@huggingface/transformers/node_modules/global-agent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"license": "BSD-3-Clause",
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
"matcher": "^3.0.0",
"roarr": "^2.15.3",
"semver": "^7.3.2",
"serialize-error": "^7.0.1"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/@huggingface/transformers/node_modules/matcher": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@huggingface/transformers/node_modules/onnxruntime-common": {
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
"license": "MIT"
},
"node_modules/@huggingface/transformers/node_modules/onnxruntime-node": {
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz",
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
"hasInstallScript": true,
"license": "MIT",
"os": [
"win32",
"darwin",
"linux"
],
"dependencies": {
"adm-zip": "^0.5.16",
"global-agent": "^3.0.0",
"onnxruntime-common": "1.24.3"
}
},
"node_modules/@huggingface/transformers/node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@huggingface/transformers/node_modules/serialize-error": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.13.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@huggingface/transformers/node_modules/type-fest": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -20557,15 +20465,17 @@
}
},
"node_modules/global-agent": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-4.1.3.tgz",
"integrity": "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"license": "BSD-3-Clause",
"dependencies": {
"globalthis": "^1.0.2",
"matcher": "^4.0.0",
"semver": "^7.3.5",
"serialize-error": "^8.1.0"
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
"matcher": "^3.0.0",
"roarr": "^2.15.3",
"semver": "^7.3.2",
"serialize-error": "^7.0.1"
},
"engines": {
"node": ">=10.0"
@@ -26020,18 +25930,15 @@
}
},
"node_modules/matcher": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-4.0.0.tgz",
"integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/material-symbols": {
@@ -29061,15 +28968,15 @@
}
},
"node_modules/onnxruntime-common": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.27.0.tgz",
"integrity": "sha512-3KxL5wIVqa8Ex08jxSzncm9CMgw8CjOFyOQ7SxvG9o0cVLlhTNKXyIQuTbtX4tGPJEf73OER2xrjt4HJSBL4ow==",
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
"license": "MIT"
},
"node_modules/onnxruntime-node": {
"version": "1.27.0",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.27.0.tgz",
"integrity": "sha512-QEzGwrvNBgv4uPVdnbHsOGG4G6T96mdlcFI8aAKPjMU8wOPpVocPXb6k3QGkaZagVTv2G9Bnnbo6Z3JdXr1fQw==",
"version": "1.24.3",
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz",
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
"hasInstallScript": true,
"license": "MIT",
"os": [
@@ -29079,8 +28986,8 @@
],
"dependencies": {
"adm-zip": "^0.5.16",
"global-agent": "^4.1.3",
"onnxruntime-common": "1.27.0"
"global-agent": "^3.0.0",
"onnxruntime-common": "1.24.3"
}
},
"node_modules/onnxruntime-web": {
@@ -33011,12 +32918,12 @@
}
},
"node_modules/serialize-error": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
"integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.20.2"
"type-fest": "^0.13.1"
},
"engines": {
"node": ">=10"
@@ -33026,9 +32933,9 @@
}
},
"node_modules/serialize-error/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"

View File

@@ -337,7 +337,7 @@
"zod": "^4.4.3",
"zustand": "^5.0.13",
"@huggingface/transformers": "^4.2.0",
"onnxruntime-node": "~1.27.0"
"onnxruntime-node": "~1.24.3"
},
"optionalDependencies": {
"@atjsh/llmlingua-2": "2.0.3",

View File

@@ -43,14 +43,19 @@ export const KNOWN_DUPLICATE_VERSIONS = new Set([
// ---------------------------------------------------------------------------
// ALLOWLIST 2 — gaps de sequência CONHECIDOS.
// Fonte: auditoria do disco (src/lib/db/migrations/). Além dos slots legados,
// As migrations Radar 144145 e a migration 143 já aterrissaram. O job registry
// foi promovido de 139 para 146 pela tabela RENAMED_MIGRATION_COMPATIBILITY. A
// 147149 estão reservadas por migrations atualmente em trânsito nos PRs #8228,
// #9313, #10047 e #10066; esta branch usa 150 para evitar essas colisões conhecidas.
// O stale-enforcement exige que cada reserva seja removida quando os arquivos
// correspondentes aterrissarem na release.
// As migrations Radar 144145, a migration 143 e a 147 já aterrissaram. O job
// registry foi promovido de 139 para 146 pela tabela
// RENAMED_MIGRATION_COMPATIBILITY. A 149 aterrissa junto com #10066
// (149_api_key_combo_access.sql). 148 permanece reservada por PRs #10001 e
// #10047 ainda em trânsito. O stale-enforcement exige que cada reserva seja
// removida quando os arquivos correspondentes aterrissarem na release.
// ---------------------------------------------------------------------------
export const KNOWN_GAPS = new Set(["026", "055", "121", "148", "149"]); // 121: número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12)
export const KNOWN_GAPS = new Set([
"026",
"055",
"121", // número queimado no ciclo v3.8.47 — 122 (#6909) mergeou antes e 121 nunca aterrissou (validação e2e 2026-07-12)
"148", // reserved by open PRs #10001 and #10047
]);
function pad3(n) {
return String(n).padStart(3, "0");

View File

@@ -33,6 +33,7 @@ import { BypassProviderQuotaToggle } from "./components/BypassProviderQuotaToggl
import { ApiKeyCompressionToggle } from "./components/ApiKeyCompressionToggle";
import ProviderModelPermissionList from "./components/ProviderModelPermissionList";
import ReasoningRoutingRules from "@/shared/components/ReasoningRoutingRules";
import { ALL_COMBOS_ACCESS_RULE } from "@/shared/constants/comboAccess";
// Constants for validation
const MAX_KEY_NAME_LENGTH = 200;
@@ -1056,7 +1057,8 @@ export default function ApiManagerPageClient() {
const providerCount = providerWildcards.length;
const modelCount = exactModels.length;
const hasComboRestrictions =
Array.isArray(key.allowedCombos) && key.allowedCombos.length > 0;
Array.isArray(key.allowedCombos) &&
!key.allowedCombos.includes(ALL_COMBOS_ACCESS_RULE);
const hasConnectionRestrictions =
Array.isArray(key.allowedConnections) && key.allowedConnections.length > 0;
const noLogEnabled = key.noLog === true;
@@ -1686,7 +1688,9 @@ const PermissionsModal = memo(function PermissionsModal({
() => (Array.isArray(apiKey?.blockedModels) ? apiKey.blockedModels : []),
[apiKey?.blockedModels]
);
const initialCombos = Array.isArray(apiKey?.allowedCombos) ? apiKey.allowedCombos : [];
const initialCombos = Array.isArray(apiKey?.allowedCombos)
? apiKey.allowedCombos.filter((combo) => combo !== ALL_COMBOS_ACCESS_RULE)
: [];
const initialConnections = Array.isArray(apiKey?.allowedConnections)
? apiKey.allowedConnections
: [];
@@ -1702,7 +1706,9 @@ const PermissionsModal = memo(function PermissionsModal({
const [allowAll, setAllowAll] = useState(
apiKey?.modelAccessMode === "restricted" ? false : initialModels.length === 0
);
const [allowAllCombos, setAllowAllCombos] = useState(initialCombos.length === 0);
const [allowAllCombos, setAllowAllCombos] = useState(
apiKey?.allowedCombos?.includes(ALL_COMBOS_ACCESS_RULE) === true
);
const [noLogEnabled, setNoLogEnabled] = useState(apiKey?.noLog === true);
const [autoResolveEnabled, setAutoResolveEnabled] = useState(apiKey?.autoResolve === true);
const [keyIsActive, setKeyIsActive] = useState(apiKey?.isActive !== false);
@@ -1938,7 +1944,7 @@ const PermissionsModal = memo(function PermissionsModal({
onSave(
keyName,
modelAccess.allowedModels,
allowAllCombos ? [] : selectedCombos,
allowAllCombos ? [ALL_COMBOS_ACCESS_RULE] : selectedCombos,
noLogEnabled,
allowAllConnections ? [] : selectedConnections,
autoResolveEnabled,

View File

@@ -1046,9 +1046,11 @@ import {
className="w-full px-3 py-2 rounded bg-bg-subtle border border-border"
value={form.status}
onChange={(e) => setForm((prev) => ({ ...prev, status: e.target.value }))}
data-testid="proxy-registry-status-select"
>
<option value="active">{t("statusActive")}</option>
<option value="inactive">{t("statusInactive")}</option>
{form.status === "dead" && <option value="dead">dead</option>}
</select>
</div>
</div>
@@ -1281,7 +1283,11 @@ import {
>
<option value="">{t("poolSelectProxy")}</option>
{items
.filter((item) => !poolMembers.includes(item.id))
.filter(
(item) =>
!poolMembers.includes(item.id) &&
(item.status ?? "").toLowerCase() !== "dead"
)
.map((item) => (
<option key={item.id} value={item.id}>
{item.name} ({item.type}://{item.host}:{item.port})

View File

@@ -6,9 +6,16 @@ interface ProxyStatusBadgeProps {
status?: string;
}
// Mirrors PROXY_ALIVE_PREDICATE (src/lib/db/proxies/guards.ts) — kept as a small
// client-side duplicate rather than importing the server DB module into a "use
// client" component (same pattern as RELAY_PROXY_TYPES in proxies/mappers.ts).
// Any status in this set (including "dead", written by PROXY_AUTO_DISABLE) is
// excluded from pool/rotation resolution, so it must not render as "Active".
const NOT_ALIVE_STATUSES = new Set(["inactive", "error", "disabled", "dead", "down"]);
export function ProxyStatusBadge({ status }: ProxyStatusBadgeProps) {
const t = useTranslations("settings");
const isInactive = status === "inactive";
const isInactive = NOT_ALIVE_STATUSES.has((status ?? "").toLowerCase());
return (
<span
className={`inline-flex items-center gap-1.5 text-xs px-2 py-1 rounded border ${

View File

@@ -81,8 +81,6 @@ const DEFAULT_OBFUSCATE_WORDS = [
"codecompanion",
"openwebui",
"open-webui",
"hermes-agent",
"hermes",
];
// Mirror of DEFAULT_SYSTEM_TRANSFORMS_CONFIG from open-sse/services/systemTransforms.ts.

View File

@@ -25,6 +25,7 @@ import {
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { isApiKeyRevealEnabled, maskStoredApiKey } from "@/lib/apiKeyExposure";
import { cleanupProviderModelsAfterConnectionDelete } from "@/lib/db/models";
import { canUpdateProviderApiKey } from "@/shared/providers/webSessionCredentials";
import {
refreshConnectionRateLimits,
enableRateLimitProtection,
@@ -161,7 +162,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
if (globalPriority !== undefined) updateData.globalPriority = globalPriority;
if (defaultModel !== undefined) updateData.defaultModel = defaultModel;
if (isActive !== undefined) updateData.isActive = isActive;
if (apiKey && existing.authType === "apikey") {
if (apiKey && canUpdateProviderApiKey(existing.authType, existing.provider)) {
if (existing.provider === "chatgpt-web-codex") {
const validationId =
incomingPsd && typeof incomingPsd.validationId === "string"
@@ -375,6 +376,15 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
}
}
// PATCH /api/providers/[id] - Update connection (partial)
// The OpenAPI spec and the CLI (`omniroute providers rotate`, generated
// api-commands) both use PATCH, but only PUT was implemented — PATCH requests
// 405'd. PATCH and PUT share the same update semantics here (the schema only
// applies provided fields), so delegate to the PUT handler.
export async function PATCH(request: Request, ctx: { params: Promise<{ id: string }> }) {
return PUT(request, ctx);
}
// DELETE /api/providers/[id] - Delete connection
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
const authError = await requireManagementAuth(request);

View File

@@ -368,6 +368,18 @@ async function buildUnifiedModelsResponseCore(
return collected;
};
// Health-check exclusions (provider_specific_data.excludedModels) are enforced
// at request time in getProviderCredentials(); mirror the same rule in the
// catalog so ghost models do not appear as available. A model is hidden when
// the provider HAS connections but NONE of them is eligible for it.
const isExcludedByProviderConnections = (providerKey: string, modelId: string) => {
const providerId = aliasToProviderId[providerKey] || providerKey;
const alias = providerIdToAlias[providerId] || providerKey;
const providerConnections = getConnectionsForProvider(providerId, alias, providerKey);
if (providerConnections.length === 0) return false; // noAuth / no DB row: keep
return !hasEligibleConnectionForModel(providerConnections, modelId);
};
const providerSupportsModel = (providerKey: string, modelId: string) => {
const providerId = aliasToProviderId[providerKey] || providerKey;
const alias = providerIdToAlias[providerId] || providerKey;
@@ -793,6 +805,7 @@ async function buildUnifiedModelsResponseCore(
if (!providerSupportsModel(canonicalProviderId, model.id)) continue;
const aliasId = `${alias}/${model.id}`;
if (getModelIsHidden(canonicalProviderId, model.id)) continue;
if (isExcludedByProviderConnections(canonicalProviderId, model.id)) continue;
if (shouldHidePaid(canonicalProviderId, model.id, (model as { pricing?: unknown }).pricing))
continue;
@@ -913,6 +926,7 @@ async function buildUnifiedModelsResponseCore(
continue;
}
if (getModelIsHidden(providerId, sm.id)) continue;
if (isExcludedByProviderConnections(canonicalProviderId, sm.id)) continue;
// #6457: some upstream discovery catalogs (e.g. HuggingFace's live
// `/v1/models`) return image/diffusion models with no modality info,
// so `endpoints` below would default to ["chat"] and misrepresent
@@ -1307,6 +1321,7 @@ async function buildUnifiedModelsResponseCore(
continue;
if (model.isHidden === true) continue;
if (getModelIsHidden(canonicalProviderId, modelId)) continue;
if (isExcludedByProviderConnections(canonicalProviderId, modelId)) continue;
// #6328: apply hidePaidModels to user-defined custom rows too.
// Custom entries do not carry pricing, so shouldHidePaid() decides
// via FREE_MODEL_IDS_BY_PROVIDER — matches synced/PROVIDER_MODELS.
@@ -1487,6 +1502,7 @@ async function buildUnifiedModelsResponseCore(
}
if (getModelIsHidden(canonicalProviderId, modelId)) continue;
if (isExcludedByProviderConnections(canonicalProviderId, modelId)) continue;
// #6328: apply hidePaidModels to alias-backed rows too. Alias mappings
// point at providerKey/modelId with no pricing, so shouldHidePaid()
// decides via the FREE_MODEL_IDS_BY_PROVIDER catalog tier.
@@ -1560,6 +1576,7 @@ async function buildUnifiedModelsResponseCore(
const modelId = typeof model.id === "string" ? model.id : null;
if (!modelId) continue;
if (getModelIsHidden(canonicalProviderId, modelId)) continue;
if (isExcludedByProviderConnections(canonicalProviderId, modelId)) continue;
// #6328: apply hidePaidModels to managed-fallback rows too. Compatible
// provider fallbacks lack pricing; shouldHidePaid() decides via the
// FREE_MODEL_IDS_BY_PROVIDER catalog tier.

View File

@@ -84,7 +84,14 @@ export async function POST(request, { params }) {
);
}
const result = await handleEmbedding({ body, credentials, log });
const result = await handleEmbedding({
body,
credentials,
log,
// #10347 — thread the selected connection id so a hard upstream failure cools
// the account instead of re-hitting it on every request.
connectionId: (credentials as { connectionId?: string } | null)?.connectionId ?? null,
});
if (result.success) {
await clearRecoveredProviderState(credentials);

View File

@@ -374,18 +374,24 @@ async function applyModelsDevSyncSection(
currentSnapshot: RuntimeSettingsSnapshot,
force: boolean
) {
const { startPeriodicSync, stopPeriodicSync } = await import("@/lib/modelsDevSync");
const {
startPeriodicSync,
stopPeriodicSync,
isModelsDevSyncEnvDisabled,
isModelsDevSyncEnvForcedOn,
} = await import("@/lib/modelsDevSync");
const skipBackgroundSyncInTests =
(isAutomatedTestProcess() && process.env.OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS !== "1") ||
isTruthyEnvFlag(process.env.OMNIROUTE_DISABLE_BACKGROUND_SERVICES);
if (skipBackgroundSyncInTests) {
if (skipBackgroundSyncInTests || isModelsDevSyncEnvDisabled()) {
stopPeriodicSync();
return;
}
const wasEnabled = previousSnapshot.modelsDevSyncEnabled === true;
const isEnabled = currentSnapshot.modelsDevSyncEnabled === true;
const isEnabled =
isModelsDevSyncEnvForcedOn() || currentSnapshot.modelsDevSyncEnabled === true;
const intervalChanged =
previousSnapshot.modelsDevSyncInterval !== currentSnapshot.modelsDevSyncInterval;

View File

@@ -25,6 +25,7 @@ import {
} from "@/lib/credentialHealth/cache";
import { emit } from "@/lib/events/eventBus";
import { isAutomatedTestProcess } from "@/shared/utils/testProcess";
import { SEARCH_VALIDATOR_CONFIGS } from "@/lib/providers/validation/searchProviders";
// ── Config ────────────────────────────────────────────────────────────────
@@ -230,7 +231,13 @@ export async function sweep(): Promise<void> {
try {
const raw = await getProviderConnections({ isActive: true });
connections = (Array.isArray(raw) ? raw : []).filter(
(conn: any) => conn && conn.id && (conn.authType === "apikey" || conn.authType === "oauth")
(conn: any) =>
conn &&
conn.id &&
(conn.authType === "apikey" || conn.authType === "oauth") &&
// #9970: search-provider "validation" fires a REAL billed upstream
// query (e.g. POST api.tavily.com/search) — never sweep these.
!(conn.provider in SEARCH_VALIDATOR_CONFIGS)
) as Array<{
id: string;
provider: string;

View File

@@ -30,6 +30,7 @@ import {
hasClaudeCodeWildcardPermission,
matchesWildcardPattern,
} from "./apiKeys/modelPermissions";
import { ALL_COMBOS_ACCESS_RULE } from "@/shared/constants/comboAccess";
import {
parseAllowedModels,
parseAllowedCombos,
@@ -422,7 +423,7 @@ function getPreparedStatements(db: ApiKeysDbLike): ApiKeysStatements {
"SELECT id, name, machine_id, model_access_mode, allowed_models, blocked_models, allowed_combos, allowed_connections, allowed_quotas, no_log, auto_resolve, is_active, access_schedule, max_requests_per_day, max_requests_per_minute, throttle_delay_ms, max_sessions, revoked_at, expires_at, ip_allowlist, scopes, rate_limits, is_banned, key_hash, allowed_endpoints, stream_default_mode, cache_default_mode, disable_non_public_models, allow_usage_command, usage_limit_enabled, daily_usage_limit_usd, weekly_usage_limit_usd, chaos_mode_enabled, compression_enabled, proxy_id FROM api_keys WHERE key = ? OR key_hash = ?",
);
_stmtInsertKey = db.prepare(
"INSERT INTO api_keys (id, name, key, machine_id, allowed_models, no_log, created_at, key_prefix, key_hash, scopes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
"INSERT INTO api_keys (id, name, key, machine_id, allowed_models, allowed_combos, no_log, created_at, key_prefix, key_hash, scopes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
_stmtDeleteKey = db.prepare("DELETE FROM api_keys WHERE id = ?");
}
@@ -642,7 +643,7 @@ export async function createApiKey(name: string, machineId: string, scopes: stri
machineId: machineId,
modelAccessMode: "all" as const,
allowedModels: [], // Empty array means all models allowed
allowedCombos: [], // Empty array means no explicit combo restriction
allowedCombos: [ALL_COMBOS_ACCESS_RULE], // Explicit wildcard means all combos allowed
allowedConnections: [], // Empty array means all connections allowed
noLog: false,
allowUsageCommand: false,
@@ -657,6 +658,7 @@ export async function createApiKey(name: string, machineId: string, scopes: stri
apiKey.key,
apiKey.machineId,
"[]",
JSON.stringify(apiKey.allowedCombos),
0,
apiKey.createdAt,
apiKey.key.slice(0, 12),
@@ -807,7 +809,7 @@ export async function updateApiKeyPermissions(
}
if (normalized.allowedCombos !== undefined) {
// Empty array means no explicit combo restriction; legacy allowed_models rules still apply.
// Empty array denies all combos; combo/* explicitly allows all combos.
updates.push("allowed_combos = @allowedCombos");
params.allowedCombos = JSON.stringify(normalized.allowedCombos || []);
}
@@ -1269,7 +1271,7 @@ export async function getApiKeyMetadata(
modelAccessMode: "all",
allowedModels: [],
blockedModels: [],
allowedCombos: [],
allowedCombos: [ALL_COMBOS_ACCESS_RULE],
allowedConnections: [],
allowedQuotas: [],
noLog: false,

View File

@@ -344,14 +344,16 @@ export async function cleanupXpAuditLog(): Promise<CleanupResult> {
/**
* Clean up old compression_run_telemetry based on retention settings. (#6848)
* Uses unix-epoch `timestamp` column (INTEGER).
* The `timestamp` column stores epoch milliseconds (recordCompressionRun stamps
* Date.now()), so the cutoff must be in milliseconds to match. Same unit bug as
* domain_cost_history (#9625), which this function was missed by.
*/
export async function cleanupCompressionRunTelemetry(): Promise<CleanupResult> {
const db = getDbInstance();
const retention = getRetentionSettings();
const retentionDays = retention.compressionRunTelemetry;
const cutoffEpoch = Math.floor(Date.now() / 1000) - retentionDays * 86_400;
const cutoffEpoch = Date.now() - retentionDays * 86_400_000;
const result: CleanupResult = { deleted: 0, errors: 0 };

View File

@@ -0,0 +1,17 @@
-- 149: Make API-key Combo access explicit: combo/* allows all; [] denies all.
-- Existing null/empty/malformed values meant allow-all before this migration.
UPDATE api_keys
SET allowed_combos = json_array('combo/*')
WHERE allowed_combos IS NULL
OR trim(allowed_combos) = ''
OR json_valid(allowed_combos) = 0
OR CASE
WHEN json_valid(allowed_combos) = 1 THEN json_type(allowed_combos) != 'array'
ELSE 0
END
OR CASE
WHEN json_valid(allowed_combos) = 1 AND json_type(allowed_combos) = 'array'
THEN json_array_length(allowed_combos) = 0
ELSE 0
END;

View File

@@ -35,7 +35,10 @@ export {
} from "./proxies/guards";
export { extractRelayAuth, redactProxySecrets } from "./proxies/mappers";
export { addProxiesToScopePool } from "./proxySubscriptions";
export { bumpProxyRegistryGeneration, getProxyRegistryGeneration } from "./proxies/registryGeneration";
export {
bumpProxyRegistryGeneration,
getProxyRegistryGeneration,
} from "./proxies/registryGeneration";
import {
normalizeRotationScopeId,
clearRotationState,
@@ -269,9 +272,7 @@ export async function listProxies(options?: {
params.push(limit, offset);
}
const rows = db.prepare(sql).all(...params) as unknown[];
const total = (
db.prepare("SELECT count(*) as cnt FROM proxy_registry").get() as CountResult
).cnt;
const total = (db.prepare("SELECT count(*) as cnt FROM proxy_registry").get() as CountResult).cnt;
const proxies = rows.map(mapProxyRow);
return { items: includeSecrets ? proxies : proxies.map(redactProxySecrets), total };
}
@@ -685,7 +686,6 @@ export async function deleteProxyById(id: string, options?: { force?: boolean })
return result.changes > 0;
}
export async function migrateLegacyProxyConfigToRegistry(options?: { force?: boolean }) {
const force = options?.force === true;
const db = getDbInstance();
@@ -770,6 +770,7 @@ export async function getProxyHealthStats(options?: { hours?: number }) {
p.type as proxy_type,
p.host as proxy_host,
p.port as proxy_port,
p.status as proxy_status,
COUNT(l.id) as total_requests,
SUM(CASE WHEN l.status = 'success' THEN 1 ELSE 0 END) as success_count,
SUM(CASE WHEN l.status = 'error' THEN 1 ELSE 0 END) as error_count,
@@ -800,6 +801,7 @@ export async function getProxyHealthStats(options?: { hours?: number }) {
type: String(row.proxy_type || "http"),
host: String(row.proxy_host || ""),
port: Number(row.proxy_port || 0),
status: String(row.proxy_status || "active"),
totalRequests: total,
successCount: success,
errorCount: error,

View File

@@ -24,6 +24,28 @@ export interface DatabaseStats {
cacheSize: number;
}
/**
* `dbstat` is a compile-time-optional SQLite virtual table (ENABLE_DBSTAT_VTAB).
* Builds without it — sql.js/WASM among them — reject the query with either
* "no such module: dbstat" or "no such table: dbstat" depending on the build,
* and drivers prefix their error class onto the message, so match loosely.
*
* Per-table byte sizes are a nice-to-have, so probe once and degrade to 0
* rather than failing the whole stats call — and with it every caller,
* including the database settings API.
*/
function isDbstatAvailable(db: SqliteAdapter): boolean {
try {
db.prepare(`SELECT SUM(pgsize) as size FROM dbstat WHERE name = ?`).get("sqlite_master");
return true;
} catch (error) {
if (error instanceof Error && /no such (module|table): dbstat/i.test(error.message)) {
return false;
}
throw error;
}
}
export function getDatabaseStats(db: SqliteAdapter = getDbInstance()): DatabaseStats {
const pageSize = db.pragma("page_size", { simple: true }) as number;
const pageCount = db.pragma("page_count", { simple: true }) as number;
@@ -36,12 +58,15 @@ export function getDatabaseStats(db: SqliteAdapter = getDbInstance()): DatabaseS
)
.all() as Array<{ name: string }>;
const dbstatAvailable = isDbstatAvailable(db);
const tableStats = tables.map((table) => {
let rowCount = 0;
try {
const quotedName = `"${table.name.replaceAll('"', '""')}"`;
const row = db.prepare(`SELECT COUNT(*) as count FROM ${quotedName}`).get() as
{ count: number } | undefined;
| { count: number }
| undefined;
rowCount = row?.count ?? 0;
} catch (error) {
if (!(error instanceof Error) || !error.message.startsWith("no such module:")) {
@@ -50,14 +75,18 @@ export function getDatabaseStats(db: SqliteAdapter = getDbInstance()): DatabaseS
// Optional virtual-table modules may be unavailable on this connection.
}
const tableSize = db
.prepare(`SELECT SUM(pgsize) as size FROM dbstat WHERE name = ?`)
.get(table.name) as { size: number | null };
let size = 0;
if (dbstatAvailable) {
const tableSize = db
.prepare(`SELECT SUM(pgsize) as size FROM dbstat WHERE name = ?`)
.get(table.name) as { size: number | null } | undefined;
size = tableSize?.size || 0;
}
return {
name: table.name,
rowCount,
size: tableSize?.size || 0,
size,
};
});

View File

@@ -302,7 +302,12 @@ export async function createEmbeddingResponse(
clientRawRequest: options.clientRawRequest || null,
apiKeyId: options.apiKeyId || null,
apiKeyName: options.apiKeyName || null,
connectionId: options.connectionId || null,
// #10347 — thread the selected connection id so handleEmbedding can cool the
// account on a hard upstream failure (previously always null on /v1/embeddings).
connectionId:
((credentials as { connectionId?: string } | null)?.connectionId) ||
options.connectionId ||
null,
});
const result = connectionIdForProxy

View File

@@ -47,13 +47,6 @@ const TOOL_CALLING_UNSUPPORTED_PATTERNS: string[] = [
"stable-diffusion",
];
const REASONING_UNSUPPORTED_PATTERNS = [
"antigravity/claude-sonnet-4-6",
"antigravity/claude-sonnet-4-5",
"antigravity/claude-sonnet-4",
// Non-Claude antigravity models don't support thinking params (#1361)
"antigravity/gemini-",
"antigravity/gpt-oss-",
"antigravity/gemini-3",
"antigravity/tab_",
// Specialty / non-chat surfaces (#8016)
"whisper",

View File

@@ -14,12 +14,11 @@
* 3. LiteLLM sync (`pricing_synced` namespace)
* 4. Hardcoded defaults (`pricing.ts`)
*
* Opt-in, default off. Enabled either from Dashboard > Settings > AI or with
* MODELS_DEV_SYNC_ENABLED, which wins over that setting whenever it is set to
* anything non-empty, in either direction, so a deployment can pin the sync on
* or off regardless of what is stored. Unset or empty, it defers to the
* setting. On for "1", "true", "yes" or "on" in any casing; every other value
* is off.
* Settings UI (`modelsDevSyncEnabled`) controls the periodic sync by default.
* `MODELS_DEV_SYNC_ENABLED=0|false|off|no` is a hard kill switch: it wins over
* the DB setting so an operator can recover a wedged process (dashboard /
* /healthz frozen on the same event loop — #10052) without the UI. Unset =
* honor settings. `1|true|on|yes` forces sync on even if the setting is off.
*/
import { getDbInstance } from "./db/core";
@@ -76,12 +75,34 @@ interface SyncResult {
const MODELS_DEV_API_URL = "https://models.dev/api.json";
const TRUE_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
const parsedInterval = parseInt(process.env.MODELS_DEV_SYNC_INTERVAL || "86400", 10);
const SYNC_INTERVAL_MS =
Number.isFinite(parsedInterval) && parsedInterval > 0 ? parsedInterval * 1000 : 86400 * 1000;
/** Parse MODELS_DEV_SYNC_ENABLED. Invalid / empty → unset (honor DB settings). */
export function readModelsDevSyncEnvFlag(
value: string | undefined = process.env.MODELS_DEV_SYNC_ENABLED
): "true" | "false" | "unset" {
if (value == null) return "unset";
const normalized = value.trim().toLowerCase();
if (normalized === "") return "unset";
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
return "true";
}
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
return "false";
}
return "unset";
}
export function isModelsDevSyncEnvDisabled(): boolean {
return readModelsDevSyncEnvFlag() === "false";
}
export function isModelsDevSyncEnvForcedOn(): boolean {
return readModelsDevSyncEnvFlag() === "true";
}
// ─── Periodic sync state ─────────────────────────────────
let syncTimer: ReturnType<typeof setInterval> | null = null;
@@ -212,8 +233,15 @@ let pricingMemoVersion = -1; // -1: never equals a real cacheVersion (starts at
/**
* Read synced pricing from `models_dev_pricing` namespace.
* Results are memoized until `saveModelsDevPricing` / `clearModelsDevPricing`.
*/
export function getModelsDevPricing(): PricingByProvider {
// Kill switch: skip the SQL + JSON.parse scan entirely so a leftover
// models_dev_pricing namespace cannot pin the event loop (#9685 / #10052).
if (isModelsDevSyncEnvDisabled()) {
return {};
}
const currentVersion = getModelCatalogCacheVersion();
if (pricingMemo !== null && pricingMemoVersion === currentVersion) {
return pricingMemo;
@@ -737,35 +765,16 @@ export function getSyncStatus(): SyncStatus {
* Initialize models.dev sync if enabled.
*/
export async function initModelsDevSync(): Promise<void> {
if (isModelsDevSyncEnvDisabled()) {
console.log("[MODELS_DEV] Disabled (MODELS_DEV_SYNC_ENABLED=0)");
return;
}
const { getSettings } = await import("./localDb");
const settings = await getSettings();
// Until now the docblock above advertised MODELS_DEV_SYNC_ENABLED and nothing
// read it: the only control was the stored setting, so an operator following
// that line got silence whichever value they set. This makes the variable real.
//
// An explicit env value decides, in either direction, and only an unset or
// empty one defers to the setting. That means a deployment can pin the sync
// off from its compose file or unit even when a previous operator left the
// dashboard toggle on, which is the case a force-on-only variable cannot
// express and the reason for choosing this shape.
//
// It is worth being plain that this is a third resolution pattern rather than
// a reuse of an existing one, because the two in the tree solve different
// problems: shared/utils/featureFlags.ts::resolveFeatureFlag puts the DB
// override ABOVE the env var, so a deployment cannot override an operator's
// stored choice at all; db/ccDiscoveryAliases.ts::getCcAliasGlobalState reads
// only "1" and "true" and can force a flag ON, letting every other value
// including "false" fall through to the DB. Neither can turn a
// dashboard-enabled switch off from the environment. Following either one
// here would leave the variable unable to do the thing it is being added for.
const envValue = process.env.MODELS_DEV_SYNC_ENABLED?.trim();
const enabled = envValue
? TRUE_ENV_VALUES.has(envValue.toLowerCase())
: settings.modelsDevSyncEnabled === true;
if (!enabled) {
console.log("[MODELS_DEV] Disabled (enable via Settings > AI or MODELS_DEV_SYNC_ENABLED=true)");
if (!isModelsDevSyncEnvForcedOn() && settings.modelsDevSyncEnabled !== true) {
console.log("[MODELS_DEV] Disabled (enable via Settings > AI or MODELS_DEV_SYNC_ENABLED=1)");
return;
}

View File

@@ -1,8 +1,10 @@
import crypto from "node:crypto";
import {
getProviderConnections,
createProviderConnection,
updateProviderConnection,
} from "@/lib/localDb";
import { getClaudeCodeUserAgent } from "@/shared/constants/claudeCodeClient";
import { ClaudeAuthFileError } from "@/lib/oauth/utils/claudeAuthFile";
type JsonRecord = Record<string, unknown>;
@@ -119,6 +121,8 @@ export async function enrichWithBootstrap(
Authorization: `Bearer ${parsed.accessToken}`,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
"User-Agent": getClaudeCodeUserAgent("cli"),
"anthropic-beta": "oauth-2025-04-20",
},
signal: controller.signal,
});
@@ -212,6 +216,12 @@ export async function createConnectionFromAuthFile(
subscriptionType: enriched.subscriptionType,
bootstrapEmail: enriched.email,
importedAt: new Date().toISOString(),
// #10143: preserve an already-persisted device identity across
// re-imports so the connection doesn't present as a new device to
// Anthropic on every process restart; only mint one if absent.
cliUserID:
toNonEmptyString(toRecord(existing.providerSpecificData).cliUserID) ||
crypto.randomBytes(32).toString("hex"),
},
});
@@ -252,6 +262,10 @@ export async function createConnectionFromAuthFile(
subscriptionType: enriched.subscriptionType,
bootstrapEmail: enriched.email,
importedAt: new Date().toISOString(),
// #10143: mint a persistent device identity so this imported
// connection doesn't fall back to a lazy-random device id that
// regenerates on every process restart (see resolveCliUserID).
cliUserID: crypto.randomBytes(32).toString("hex"),
},
});

View File

@@ -5,15 +5,28 @@
* exhaustively without any I/O. The sweep classifies each probe into a tri-state
* {@link ProxyProbeOutcome} and applies the returned {@link ProxyHealthDecision}.
*
* Policy (agreed for #6246):
* Policy (agreed for #6246, extended for the auto-disable mode below):
* A — downgrade only after `removeAfter` CONSECUTIVE conclusive failures.
* B — an `inconclusive` probe (our own timeout/abort, or the probe TARGET
* erroring) never penalizes: it neither counts nor changes status.
* C — by DEFAULT (auto-remove off) the health check NEVER mutates a proxy's
* status. It only counts failures for logging. A proxy is downgraded to
* `inactive` (and removed) only when the operator opts in via
* PROXY_AUTO_REMOVE=true. This mirrors how accounts are only auto-disabled
* when the operator allows it — the operator owns their (often paid) proxies.
* C — by DEFAULT (both auto-remove and auto-disable off) the health check
* NEVER mutates a proxy's status. It only counts failures for logging.
* A proxy's status is only touched once the operator opts in via
* PROXY_AUTO_REMOVE=true or PROXY_AUTO_DISABLE=true. This mirrors how
* accounts are only auto-disabled when the operator allows it — the
* operator owns their (often paid) proxies.
* D — PROXY_AUTO_DISABLE=true is the non-destructive sibling of
* PROXY_AUTO_REMOVE: at the same consecutive-failure threshold it writes
* `status: "dead"` instead of deleting the row. `"dead"` is already one
* of the statuses PROXY_ALIVE_PREDICATE excludes (src/lib/db/proxies/guards.ts),
* so a disabled proxy drops out of pool/rotation resolution immediately
* with no other code changes. Because the sweep keeps probing every
* registered proxy regardless of status, a "dead" proxy that starts
* answering again is picked back up by the same `outcome === "ok"`
* branch that already re-activates proxies for auto-remove — recovery
* is free once autoDisable participates in `managesStatus` below. If
* both flags are set, auto-remove (destructive) wins: a proxy that is
* about to be deleted has no use for a soft-disable in between.
*/
export type ProxyProbeOutcome = "ok" | "fail" | "inconclusive";
@@ -23,8 +36,14 @@ export interface ProxyHealthDecisionInput {
outcome: ProxyProbeOutcome;
/** Consecutive failure count recorded BEFORE this probe. */
priorFailures: number;
/** PROXY_AUTO_REMOVE === "true" — operator opted into status management. */
/** PROXY_AUTO_REMOVE === "true" — operator opted into delete-on-death. */
autoRemove: boolean;
/**
* PROXY_AUTO_DISABLE === "true" — operator opted into soft-disable-on-death
* (status "dead", never deleted). Optional/defaults to `false` so existing
* callers that predate this flag keep their exact prior behavior.
*/
autoDisable?: boolean;
/** Consecutive conclusive failures required before a downgrade/removal. */
removeAfter: number;
}
@@ -35,14 +54,16 @@ export interface ProxyHealthDecision {
/** Whether to drop this proxy from the consecutive-failure map. */
clearFailures: boolean;
/** Status to write, or `null` to leave the operator-controlled status untouched. */
setStatus: "active" | "inactive" | null;
setStatus: "active" | "inactive" | "dead" | null;
/** Whether to auto-remove the proxy (only ever true when autoRemove is on). */
remove: boolean;
}
export function decideProxyHealthAction(input: ProxyHealthDecisionInput): ProxyHealthDecision {
const { outcome, priorFailures, autoRemove, removeAfter } = input;
const { outcome, priorFailures, autoRemove, autoDisable = false, removeAfter } = input;
const threshold = Number.isFinite(removeAfter) && removeAfter > 0 ? removeAfter : 3;
// Either opt-in flag hands status control from the operator to the sweep.
const managesStatus = autoRemove || autoDisable;
// B: inconclusive probes are neutral — do not touch count or status.
if (outcome === "inconclusive") {
@@ -55,7 +76,7 @@ export function decideProxyHealthAction(input: ProxyHealthDecisionInput): ProxyH
return {
failures: 0,
clearFailures: true,
setStatus: autoRemove ? "active" : null,
setStatus: managesStatus ? "active" : null,
remove: false,
};
}
@@ -64,13 +85,17 @@ export function decideProxyHealthAction(input: ProxyHealthDecisionInput): ProxyH
const failures = priorFailures + 1;
// C: default mode only counts/logs — never downgrades.
if (!autoRemove) {
if (!managesStatus) {
return { failures, clearFailures: false, setStatus: null, remove: false };
}
// A: downgrade + remove only once the consecutive threshold is reached.
// A/D: act only once the consecutive threshold is reached. Auto-remove
// (destructive) takes precedence over auto-disable when both are enabled.
if (failures >= threshold) {
return { failures, clearFailures: false, setStatus: "inactive", remove: true };
if (autoRemove) {
return { failures, clearFailures: false, setStatus: "inactive", remove: true };
}
return { failures, clearFailures: false, setStatus: "dead", remove: false };
}
return { failures, clearFailures: false, setStatus: null, remove: false };

Some files were not shown because too many files have changed in this diff Show More