diff --git a/AGENTS.md b/AGENTS.md
index 1e90b6ceed..6e7ad18f2f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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 (168 migrations) |
+| Database | `src/lib/db/` | SQLite domain modules (169 migrations) |
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
| MCP Server | `open-sse/mcp-server/` | 110 tools (45 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 |
diff --git a/README.md b/README.md
index 119a590cb3..9814b0e62d 100644
--- a/README.md
+++ b/README.md
@@ -1244,7 +1244,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
| Runtime | Node.js 22.x / 24.x LTS — >=22.22.2 <23 || >=24.0.0 <27 |
| Language | TypeScript 6.0 — 100% TypeScript across src/ and open-sse/ (zero any in core since v2.0) |
| Framework | Next.js 16 + React 19 + Tailwind CSS 4 |
- | Database | better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 168 migrations |
+ | Database | better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 169 migrations |
| Memory | SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay |
| Schemas | Zod 4 — MCP tool I/O validation + API contracts |
| Protocols | MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE) |
diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json
index e5d0287a65..cfa43566b4 100644
--- a/config/quality/file-size-baseline.json
+++ b/config/quality/file-size-baseline.json
@@ -1,4 +1,5 @@
{
+ "_rebaseline_2026_09_03_moonshot_native_quota": "PR feat/moonshot-native-quota own growth on release/v3.8.51: src/lib/db/migrationRunner.ts 1201->1206 (+5, case 172 retroactive guard for daily_quota_reset_* columns); src/sse/handlers/chat.ts 2434->2450 (+16, registerMoonshotQuotaFetcher + startup node scan at the existing quota-fetcher registration chokepoint); src/sse/services/auth.ts 3427->3450 (+23, resolveDailyResetForProvider + dailyReset arg on checkFallbackError); open-sse/services/accountFallback.ts 2422->2461 (+39, compatible-node credits_exhausted carve-out + TPD node-clock lock); tests/unit/account-fallback-service.test.ts 2008->2056 (+48, TPD/empty-wallet cases). Wiring at existing chokepoints; Moonshot host predicates, daily reset clock, and the balance fetcher live in new leaves under cap. Covered by tests/unit/moonshot-*.test.ts + account-fallback-service.test.ts (135/135 focused).",
"_rebaseline_2026_09_02_11786_seekai_provider": "PR #11786 (feat/11786-seekai-provider, closes #11786) own growth: src/shared/constants/providers/apikey/gateways.ts 1438->1458 (check-file-size split-newline=1459; the seekai APIKEY_PROVIDERS_GATEWAYS catalog entry plus authHint, additive data at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #10987 logfare, #10531 freebuff). Covered by tests/unit/seekai-provider.test.ts.",
"_rebaseline_2026_09_02_12325_generic_429_invalidate": "PR #12325 own growth: open-sse/handlers/chatCore.ts 5946->5955 (+9 = the non-Codex 429 else-if that drops the generic quota wrapper and stamps force-refresh, plus a source-regex breadcrumb). Irreducible call-site wiring next to the existing Codex 429 invalidateCodexQuotaCache branch; not extractable without splitting handleChatCore mid-response. Covered by tests/unit/generic-quota-fetcher.test.ts (31/31) and tests/unit/antigravity-429-quota-cooldown.test.ts.",
"_rebaseline_2026_09_02_12429_wreq_migration_suite": "PR #12429 (wreq-js web-cookie transport): new test file tests/unit/tls-client-wreq-migration.test.ts at 1374 lines, above the 1200 new-file testCap. Frozen rather than split: it is the single cohesive regression suite for the transport migration (31 cases covering streaming, fragmented EOF sentinels, proxy isolation, first-byte and hard deadlines, binary responses and cancellation), and the cases share the native-transport harness the file sets up once. Splitting it during a merge would duplicate that harness across files for no coverage gain. Entered at the exact LOC, so it can only ratchet down from here.",
@@ -206,7 +207,7 @@
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1644,
- "tests/unit/account-fallback-service.test.ts": 2008,
+ "tests/unit/account-fallback-service.test.ts": 2056,
"tests/unit/batch_api.test.ts": 1345,
"tests/unit/cc-compatible-provider.test.ts": 1225,
"tests/unit/chatcore-translation-paths.test.ts": 3447,
@@ -419,7 +420,7 @@
"open-sse/handlers/search.ts": 1789,
"open-sse/mcp-server/schemas/tools.ts": 1621,
"open-sse/mcp-server/server.ts": 1572,
- "open-sse/services/accountFallback.ts": 2422,
+ "open-sse/services/accountFallback.ts": 2461,
"open-sse/services/adobeFireflyBrowserLogin.ts": 1401,
"open-sse/services/combo.ts": 4023,
"open-sse/translator/response/openai-responses.ts": 1466,
@@ -447,14 +448,14 @@
"src/app/docs/lib/openapi.generated.ts": 1347,
"src/lib/db/apiKeys.ts": 1610,
"src/lib/db/core.ts": 1745,
- "src/lib/db/migrationRunner.ts": 1201,
+ "src/lib/db/migrationRunner.ts": 1206,
"src/lib/tailscaleTunnel.ts": 1208,
"src/lib/tokenHealthCheck.ts": 1218,
"src/shared/components/RequestLoggerV2.tsx": 1718,
"src/shared/constants/providers/apikey/gateways.ts": 1459,
"src/shared/services/cliRuntime.ts": 1296,
- "src/sse/handlers/chat.ts": 2424,
- "src/sse/services/auth.ts": 3427,
+ "src/sse/handlers/chat.ts": 2450,
+ "src/sse/services/auth.ts": 3450,
"tests/unit/account-fallback-service.test.ts": 2453,
"tests/unit/provider-validation-specialty.test.ts": 4656
},
diff --git a/docs/i18n/ar/llm.txt b/docs/i18n/ar/llm.txt
index 603773977a..9f405eda91 100644
--- a/docs/i18n/ar/llm.txt
+++ b/docs/i18n/ar/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/az/llm.txt b/docs/i18n/az/llm.txt
index 3af7361352..74a600ba34 100644
--- a/docs/i18n/az/llm.txt
+++ b/docs/i18n/az/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/bg/llm.txt b/docs/i18n/bg/llm.txt
index c510c85d7e..0db6d8a4ce 100644
--- a/docs/i18n/bg/llm.txt
+++ b/docs/i18n/bg/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/bn/llm.txt b/docs/i18n/bn/llm.txt
index 54b9ca1d9a..a7417f642e 100644
--- a/docs/i18n/bn/llm.txt
+++ b/docs/i18n/bn/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/cs/llm.txt b/docs/i18n/cs/llm.txt
index 4dd9012d51..c8b5c31922 100644
--- a/docs/i18n/cs/llm.txt
+++ b/docs/i18n/cs/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/da/llm.txt b/docs/i18n/da/llm.txt
index d29af5f81c..3d9663c28f 100644
--- a/docs/i18n/da/llm.txt
+++ b/docs/i18n/da/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/de/llm.txt b/docs/i18n/de/llm.txt
index 0002d85510..0c8572e4f4 100644
--- a/docs/i18n/de/llm.txt
+++ b/docs/i18n/de/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/es/llm.txt b/docs/i18n/es/llm.txt
index c423fdd245..75a6488e37 100644
--- a/docs/i18n/es/llm.txt
+++ b/docs/i18n/es/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/fa/llm.txt b/docs/i18n/fa/llm.txt
index d8b83418ea..cdcccf2fc8 100644
--- a/docs/i18n/fa/llm.txt
+++ b/docs/i18n/fa/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/fi/llm.txt b/docs/i18n/fi/llm.txt
index b91777ac80..66d62309f4 100644
--- a/docs/i18n/fi/llm.txt
+++ b/docs/i18n/fi/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/fr/llm.txt b/docs/i18n/fr/llm.txt
index 7e396ec1e9..dabd29e87f 100644
--- a/docs/i18n/fr/llm.txt
+++ b/docs/i18n/fr/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/gu/llm.txt b/docs/i18n/gu/llm.txt
index 75561762bf..21dad6d8f9 100644
--- a/docs/i18n/gu/llm.txt
+++ b/docs/i18n/gu/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/he/llm.txt b/docs/i18n/he/llm.txt
index e8dab2b860..4174e2aa0f 100644
--- a/docs/i18n/he/llm.txt
+++ b/docs/i18n/he/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/hi/llm.txt b/docs/i18n/hi/llm.txt
index 3df2c700b6..b08baf5189 100644
--- a/docs/i18n/hi/llm.txt
+++ b/docs/i18n/hi/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/hu/llm.txt b/docs/i18n/hu/llm.txt
index bd0667630c..13109da6fc 100644
--- a/docs/i18n/hu/llm.txt
+++ b/docs/i18n/hu/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/id/llm.txt b/docs/i18n/id/llm.txt
index 0cce4bc272..77df7b4c16 100644
--- a/docs/i18n/id/llm.txt
+++ b/docs/i18n/id/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/it/llm.txt b/docs/i18n/it/llm.txt
index 0763f0b728..b479c853cd 100644
--- a/docs/i18n/it/llm.txt
+++ b/docs/i18n/it/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ja/llm.txt b/docs/i18n/ja/llm.txt
index 3dbb124940..a1528c646b 100644
--- a/docs/i18n/ja/llm.txt
+++ b/docs/i18n/ja/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ko/llm.txt b/docs/i18n/ko/llm.txt
index 0d44a5b222..4d8508fd12 100644
--- a/docs/i18n/ko/llm.txt
+++ b/docs/i18n/ko/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/mr/llm.txt b/docs/i18n/mr/llm.txt
index 9410d228f5..8ba71d98f5 100644
--- a/docs/i18n/mr/llm.txt
+++ b/docs/i18n/mr/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ms/llm.txt b/docs/i18n/ms/llm.txt
index e610a66e29..3903f94cf1 100644
--- a/docs/i18n/ms/llm.txt
+++ b/docs/i18n/ms/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/nl/llm.txt b/docs/i18n/nl/llm.txt
index a4364f5ac2..8db8eb985b 100644
--- a/docs/i18n/nl/llm.txt
+++ b/docs/i18n/nl/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/no/llm.txt b/docs/i18n/no/llm.txt
index 7daa5caa07..e635301c86 100644
--- a/docs/i18n/no/llm.txt
+++ b/docs/i18n/no/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/phi/llm.txt b/docs/i18n/phi/llm.txt
index 208e4a1398..b760a56857 100644
--- a/docs/i18n/phi/llm.txt
+++ b/docs/i18n/phi/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/pl/llm.txt b/docs/i18n/pl/llm.txt
index 79f3f32583..e683cdb76c 100644
--- a/docs/i18n/pl/llm.txt
+++ b/docs/i18n/pl/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/pt-BR/llm.txt b/docs/i18n/pt-BR/llm.txt
index e795080292..49a62961e7 100644
--- a/docs/i18n/pt-BR/llm.txt
+++ b/docs/i18n/pt-BR/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/pt/llm.txt b/docs/i18n/pt/llm.txt
index ffa62b14e6..9362022f86 100644
--- a/docs/i18n/pt/llm.txt
+++ b/docs/i18n/pt/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ro/llm.txt b/docs/i18n/ro/llm.txt
index 7a6f5067a9..fd21c2042b 100644
--- a/docs/i18n/ro/llm.txt
+++ b/docs/i18n/ro/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ru/llm.txt b/docs/i18n/ru/llm.txt
index ed8415886d..d68314887e 100644
--- a/docs/i18n/ru/llm.txt
+++ b/docs/i18n/ru/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/sk/llm.txt b/docs/i18n/sk/llm.txt
index 11385eade3..666d1a570e 100644
--- a/docs/i18n/sk/llm.txt
+++ b/docs/i18n/sk/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/sv/llm.txt b/docs/i18n/sv/llm.txt
index 5a0f5b5c80..74238247ae 100644
--- a/docs/i18n/sv/llm.txt
+++ b/docs/i18n/sv/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/sw/llm.txt b/docs/i18n/sw/llm.txt
index ef792bc7e7..5f129a8263 100644
--- a/docs/i18n/sw/llm.txt
+++ b/docs/i18n/sw/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ta/llm.txt b/docs/i18n/ta/llm.txt
index 2c0694b490..d42fa346a0 100644
--- a/docs/i18n/ta/llm.txt
+++ b/docs/i18n/ta/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/te/llm.txt b/docs/i18n/te/llm.txt
index 29ff10874a..6ca040018f 100644
--- a/docs/i18n/te/llm.txt
+++ b/docs/i18n/te/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/th/llm.txt b/docs/i18n/th/llm.txt
index 4096423b6d..9884ab3b21 100644
--- a/docs/i18n/th/llm.txt
+++ b/docs/i18n/th/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/tr/llm.txt b/docs/i18n/tr/llm.txt
index b83be6d908..77b9eadb1b 100644
--- a/docs/i18n/tr/llm.txt
+++ b/docs/i18n/tr/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/uk-UA/llm.txt b/docs/i18n/uk-UA/llm.txt
index f3cf580495..3a7bc01f36 100644
--- a/docs/i18n/uk-UA/llm.txt
+++ b/docs/i18n/uk-UA/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/ur/llm.txt b/docs/i18n/ur/llm.txt
index 9efbef2f56..1cdd0e6bb5 100644
--- a/docs/i18n/ur/llm.txt
+++ b/docs/i18n/ur/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/vi/llm.txt b/docs/i18n/vi/llm.txt
index eef12beee4..3e28435b0a 100644
--- a/docs/i18n/vi/llm.txt
+++ b/docs/i18n/vi/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/zh-CN/llm.txt b/docs/i18n/zh-CN/llm.txt
index f1c4a2b574..d11163a83a 100644
--- a/docs/i18n/zh-CN/llm.txt
+++ b/docs/i18n/zh-CN/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/docs/i18n/zh-TW/llm.txt b/docs/i18n/zh-TW/llm.txt
index b7b1342bf5..56256a53da 100644
--- a/docs/i18n/zh-TW/llm.txt
+++ b/docs/i18n/zh-TW/llm.txt
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -393,7 +393,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -437,7 +437,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/llm.txt b/llm.txt
index 13d3c28e78..3748b08432 100644
--- a/llm.txt
+++ b/llm.txt
@@ -14,7 +14,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
- **Runtime:** Node.js `>=22.22.2 <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, 168 migrations)
+- **Database:** SQLite via better-sqlite3 (local, zero-config, 169 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/ # 168 versioned SQL migration files
+│ │ │ └── migrations/ # 169 versioned SQL migration files
│ │ ├── evals/ # Eval runner and scheduler
│ │ ├── memory/ # Persistent conversational memory
│ │ │ ├── extraction.ts # Memory extraction from conversations
@@ -389,7 +389,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 122 `src/lib/db/` modules with 168 SQL migrations.
+9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 169 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`.
@@ -433,7 +433,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 (122 domain-specific files, 168 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 (122 domain-specific files, 169 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.
diff --git a/open-sse/services/accountFallback.ts b/open-sse/services/accountFallback.ts
index f5bd87c1ed..c6465321f9 100644
--- a/open-sse/services/accountFallback.ts
+++ b/open-sse/services/accountFallback.ts
@@ -64,6 +64,8 @@ import {
parseDelayString,
MAX_SHORT_RETRY_HINT_MS,
} from "./retryAfterJson.ts";
+import { isMoonshotAccountBalanceExhausted } from "./usage/moonshotOpenPlatform.ts";
+import { isTpdRateLimit, resolveTpdCooldownMs } from "./dailyQuotaReset.ts";
// Pre-compiled regex constants for hot-path retry parsing (avoid per-call compilation)
const RETRY_AFTER_RE = /retry\s+after\s+(\d+)\s*s/i;
@@ -1605,7 +1607,8 @@ export function isDailyQuotaExhausted(errorText: string): boolean {
return (
lower.includes("today's quota") ||
lower.includes("daily quota") ||
- lower.includes("try again tomorrow")
+ lower.includes("try again tomorrow") ||
+ lower.includes("tpd rate limit")
);
}
@@ -1651,7 +1654,12 @@ export function checkFallbackError(
headers: Headers | Record | null = null,
profileOverride: ProviderProfile | null = null,
structuredError?: { code?: string | null; type?: string | null } | null,
- rotation?: { account?: unknown } | null
+ rotation?: { account?: unknown } | null,
+ dailyReset?: {
+ timezone?: unknown;
+ hour?: unknown;
+ nowMs?: number;
+ } | null,
): {
shouldFallback: boolean;
cooldownMs: number;
@@ -1934,8 +1942,13 @@ export function checkFallbackError(
}
}
- // T10 (sub2api #1169) + #8247: credits/quota exhausted; *-compatible-* nicknames stay model-scoped.
- if (shouldUseQuotaSignal && isCreditsExhausted(errorStr) && !isCompatibleProvider(provider)) {
+ // T10 (sub2api #1169) + #8247: credits/quota exhausted; *-compatible-* nicknames stay model-scoped
+ // unless the body is an account-level Open Platform empty wallet.
+ if (
+ shouldUseQuotaSignal &&
+ isCreditsExhausted(errorStr) &&
+ (!isCompatibleProvider(provider) || isMoonshotAccountBalanceExhausted(errorStr))
+ ) {
return {
shouldFallback: true,
cooldownMs: COOLDOWN_MS.paymentRequired ?? 3600 * 1000, // 1h cooldown
@@ -1944,17 +1957,43 @@ export function checkFallbackError(
};
}
- // Daily quota exhausted — lock model until tomorrow
+ // Daily quota exhausted. TPD uses the node clock / header; other daily
+ // quota text still uses getMsUntilTomorrow. TPD without either is not a
+ // host-midnight lock — fall through to short 429.
if (shouldUseQuotaSignal && isDailyQuotaExhausted(errorStr)) {
- const msUntilTomorrow = getMsUntilTomorrow();
- // Cap at 24 hours to handle timezone edge cases
- const cooldownMs = Math.min(msUntilTomorrow, 24 * 60 * 60 * 1000);
- return {
- shouldFallback: true,
- cooldownMs,
- reason: RateLimitReason.QUOTA_EXHAUSTED,
- dailyQuotaExhausted: true,
- };
+ if (isTpdRateLimit(errorStr)) {
+ const headerResetAtMs = parseResetFromHeaders(headers);
+ const tpdMs = resolveTpdCooldownMs(errorStr, {
+ timezone: dailyReset?.timezone,
+ hour: dailyReset?.hour,
+ nowMs: dailyReset?.nowMs,
+ headerResetAtMs,
+ });
+ if (tpdMs == null) {
+ // no clock, no header — short 429, do not guess midnight
+ console.warn(
+ "[accountFallback] TPD 429 without node daily-reset clock or Reset header; using short cooldown",
+ { provider },
+ );
+ } else {
+ return {
+ shouldFallback: true,
+ cooldownMs: tpdMs,
+ reason: RateLimitReason.QUOTA_EXHAUSTED,
+ dailyQuotaExhausted: true,
+ };
+ }
+ } else {
+ const msUntilTomorrow = getMsUntilTomorrow();
+ // Cap at 24 hours to handle timezone edge cases
+ const cooldownMs = Math.min(msUntilTomorrow, 24 * 60 * 60 * 1000);
+ return {
+ shouldFallback: true,
+ cooldownMs,
+ reason: RateLimitReason.QUOTA_EXHAUSTED,
+ dailyQuotaExhausted: true,
+ };
+ }
}
// Issue #2321 (5h subscription quota) + Issue #3709 (ollama-cloud weekly
diff --git a/open-sse/services/dailyQuotaReset.ts b/open-sse/services/dailyQuotaReset.ts
new file mode 100644
index 0000000000..a5108d132f
--- /dev/null
+++ b/open-sse/services/dailyQuotaReset.ts
@@ -0,0 +1,145 @@
+/**
+ * Node-level daily quota reset clock.
+ *
+ * TPD cooldown endpoint: operator-configured IANA timezone + local hour.
+ * No default timezone. Do not call getMsUntilTomorrow() from here.
+ */
+
+export function isValidIanaTimeZone(tz: string): boolean {
+ if (typeof tz !== "string" || tz.trim() === "") return false;
+ try {
+ new Intl.DateTimeFormat("en-US", { timeZone: tz.trim() }).format();
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+export function isValidResetHour(hour: unknown): hour is number {
+ return typeof hour === "number" && Number.isInteger(hour) && hour >= 0 && hour <= 23;
+}
+
+export function nodeDailyResetConfigured(timezone: unknown, hour: unknown): boolean {
+ return typeof timezone === "string" && isValidIanaTimeZone(timezone) && isValidResetHour(hour);
+}
+
+type ZonedParts = {
+ year: number;
+ month: number;
+ day: number;
+ hour: number;
+ minute: number;
+ second: number;
+};
+
+function zonedParts(ms: number, timeZone: string): ZonedParts {
+ const fmt = new Intl.DateTimeFormat("en-US", {
+ timeZone,
+ hourCycle: "h23",
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ });
+ const bag: Record = {};
+ for (const part of fmt.formatToParts(new Date(ms))) {
+ if (part.type !== "literal") bag[part.type] = part.value;
+ }
+ return {
+ year: Number(bag.year),
+ month: Number(bag.month),
+ day: Number(bag.day),
+ hour: Number(bag.hour),
+ minute: Number(bag.minute),
+ second: Number(bag.second),
+ };
+}
+
+function addCalendarDay(year: number, month: number, day: number): {
+ year: number;
+ month: number;
+ day: number;
+} {
+ const utc = Date.UTC(year, month - 1, day + 1);
+ const dt = new Date(utc);
+ return { year: dt.getUTCFullYear(), month: dt.getUTCMonth() + 1, day: dt.getUTCDate() };
+}
+
+/** Convert wall-clock time in `timeZone` to epoch ms. */
+function zonedLocalToUtc(
+ year: number,
+ month: number,
+ day: number,
+ hour: number,
+ minute: number,
+ second: number,
+ timeZone: string,
+): number {
+ const wanted = Date.UTC(year, month - 1, day, hour, minute, second);
+ let guess = wanted;
+ for (let i = 0; i < 4; i++) {
+ const p = zonedParts(guess, timeZone);
+ const asIfUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
+ const delta = asIfUtc - wanted;
+ if (delta === 0) return guess;
+ guess -= delta;
+ }
+ return guess;
+}
+
+/**
+ * Next local `hour:00:00` in `timezone` strictly after `nowMs`.
+ * If now lands exactly on that instant, return the following cycle.
+ */
+export function nextDailyResetAtMs(timezone: string, hour: number, nowMs: number): number {
+ const now = zonedParts(nowMs, timezone);
+ let date = { year: now.year, month: now.month, day: now.day };
+ let next = zonedLocalToUtc(date.year, date.month, date.day, hour, 0, 0, timezone);
+ if (next <= nowMs) {
+ date = addCalendarDay(date.year, date.month, date.day);
+ next = zonedLocalToUtc(date.year, date.month, date.day, hour, 0, 0, timezone);
+ }
+ return next;
+}
+
+export function parseTpdLimitFromText(text: string): number | null {
+ const m = /limit:\s*(\d+)/i.exec(text);
+ if (!m) return null;
+ const n = Number(m[1]);
+ return Number.isFinite(n) ? n : null;
+}
+
+export function isTpdRateLimit(errorText: string | null | undefined): boolean {
+ return String(errorText || "")
+ .toLowerCase()
+ .includes("tpd rate limit");
+}
+
+export type TpdCooldownOptions = {
+ timezone?: unknown;
+ hour?: unknown;
+ nowMs?: number;
+ headerResetAtMs?: number | null;
+};
+
+/**
+ * Cooldown for a TPD 429. Header reset wins; else the node clock.
+ * Both missing → null (caller uses short 429, does not guess midnight).
+ */
+export function resolveTpdCooldownMs(
+ errorText: string | null | undefined,
+ options: TpdCooldownOptions = {},
+): number | null {
+ if (!isTpdRateLimit(errorText)) return null;
+ const now = options.nowMs ?? Date.now();
+ if (typeof options.headerResetAtMs === "number" && options.headerResetAtMs > now) {
+ return options.headerResetAtMs - now;
+ }
+ if (typeof options.timezone === "string" && isValidResetHour(options.hour)) {
+ if (!nodeDailyResetConfigured(options.timezone, options.hour)) return null;
+ return nextDailyResetAtMs(options.timezone, options.hour, now) - now;
+ }
+ return null;
+}
diff --git a/open-sse/services/moonshotQuotaFetcher.ts b/open-sse/services/moonshotQuotaFetcher.ts
new file mode 100644
index 0000000000..df80fe11b3
--- /dev/null
+++ b/open-sse/services/moonshotQuotaFetcher.ts
@@ -0,0 +1,229 @@
+/**
+ * moonshotQuotaFetcher.ts — Moonshot Open Platform balance quota fetcher
+ *
+ * GET {origin}/v1/users/me/balance
+ * { code: 0, data: { available_balance, voucher_balance, cash_balance } }
+ *
+ * Origin comes from the connection baseUrl (api.moonshot.cn or api.moonshot.ai).
+ * Do not hardcode .ai as a fallback for .cn keys.
+ *
+ * Cache: 60s in-memory. Registration: registerMoonshotQuotaFetcher() at startup.
+ */
+
+import { toNumber } from "@/shared/utils/numeric";
+import { registerQuotaFetcher, type QuotaInfo } from "./quotaPreflight.ts";
+import { registerMonitorFetcher } from "./quotaMonitor.ts";
+import { throttleQuotaFetch } from "./quotaFetchThrottle.ts";
+import {
+ isMoonshotOpenPlatformConnection,
+ moonshotBalanceUrl,
+ resolveMoonshotOrigin,
+} from "./usage/moonshotOpenPlatform.ts";
+import type { UsageQuota } from "./usage/quota.ts";
+
+const CACHE_TTL_MS = 60_000;
+
+export interface MoonshotQuota extends QuotaInfo {
+ availableBalance: number;
+ voucherBalance: number;
+ cashBalance: number;
+ origin: string;
+ limitReached: boolean;
+}
+
+interface CacheEntry {
+ quota: MoonshotQuota;
+ fetchedAt: number;
+}
+
+const quotaCache = new Map();
+
+const _cacheCleanup = setInterval(() => {
+ const now = Date.now();
+ for (const [key, entry] of quotaCache) {
+ if (now - entry.fetchedAt > CACHE_TTL_MS * 5) {
+ quotaCache.delete(key);
+ }
+ }
+}, 5 * 60_000);
+
+if (typeof _cacheCleanup === "object" && "unref" in _cacheCleanup) {
+ (_cacheCleanup as { unref?: () => void }).unref?.();
+}
+
+function toRecord(value: unknown): Record {
+ return value && typeof value === "object" && !Array.isArray(value)
+ ? (value as Record)
+ : {};
+}
+
+function parseMoonshotQuotaResponse(data: unknown, origin: string): MoonshotQuota | null {
+ const obj = toRecord(data);
+ const code = obj.code;
+ if (code !== 0 && code !== undefined) return null;
+ const payload = toRecord(obj.data);
+ if (!("available_balance" in payload) && !("availableBalance" in payload)) return null;
+ const availableBalance = toNumber(payload.available_balance ?? payload.availableBalance, 0);
+ const voucherBalance = toNumber(payload.voucher_balance ?? payload.voucherBalance, 0);
+ const cashBalance = toNumber(payload.cash_balance ?? payload.cashBalance, 0);
+ const limitReached = availableBalance <= 0;
+ const percentUsed = limitReached ? 1 : 0;
+ return {
+ used: percentUsed * 100,
+ total: 100,
+ percentUsed,
+ resetAt: null,
+ availableBalance,
+ voucherBalance,
+ cashBalance,
+ origin,
+ limitReached,
+ windows: { balance: { percentUsed, resetAt: null } },
+ };
+}
+
+function connectionApiKey(connection?: Record): string | null {
+ const apiKey = connection?.apiKey;
+ return typeof apiKey === "string" && apiKey.trim().length > 0 ? apiKey : null;
+}
+
+export async function fetchMoonshotQuota(
+ connectionId: string,
+ connection?: Record
+): Promise {
+ const cached = quotaCache.get(connectionId);
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
+ return cached.quota;
+ }
+
+ const apiKey = connectionApiKey(connection);
+ if (!apiKey) return null;
+
+ const origin = resolveMoonshotOrigin({
+ provider: typeof connection?.provider === "string" ? connection.provider : undefined,
+ providerSpecificData: connection?.providerSpecificData,
+ });
+ if (!origin) return null;
+
+ const url = moonshotBalanceUrl(origin);
+ const authHeader = ["Bearer", apiKey].join(" ");
+
+ try {
+ await throttleQuotaFetch();
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ Authorization: authHeader,
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ signal: AbortSignal.timeout(8_000),
+ });
+
+ if (response.status === 401 || response.status === 403) {
+ quotaCache.delete(connectionId);
+ return null;
+ }
+ if (!response.ok) return null;
+
+ const data = await response.json();
+ const quota = parseMoonshotQuotaResponse(data, origin);
+ if (!quota) return null;
+ quotaCache.set(connectionId, { quota, fetchedAt: Date.now() });
+ return quota;
+ } catch {
+ return null;
+ }
+}
+
+export function invalidateMoonshotQuotaCache(connectionId: string): void {
+ quotaCache.delete(connectionId);
+}
+
+export type MoonshotUsageConnection = {
+ id?: string;
+ provider?: string;
+ apiKey?: string;
+ providerSpecificData?: unknown;
+};
+
+export async function getMoonshotOpenPlatformUsage(
+ connection: MoonshotUsageConnection
+): Promise<{
+ plan?: string;
+ quotas?: Record;
+ message?: string;
+ limitReached?: boolean;
+}> {
+ const origin = resolveMoonshotOrigin(connection);
+ if (!origin) {
+ return { message: "Not a Moonshot Open Platform connection." };
+ }
+ const quota = (await fetchMoonshotQuota(connection.id || "moonshot", {
+ apiKey: connection.apiKey,
+ provider: connection.provider,
+ providerSpecificData: connection.providerSpecificData,
+ })) as MoonshotQuota | null;
+ if (!quota) {
+ return { message: "Moonshot API key not available. Add a key to view usage." };
+ }
+ const domestic = origin.includes("moonshot.cn");
+ return {
+ plan: domestic ? "Kimi 开放平台(国内)" : "Kimi Open Platform",
+ quotas: buildMoonshotBalanceQuotas(quota, domestic ? "CNY" : "USD"),
+ limitReached: quota.limitReached,
+ };
+}
+
+function balanceQuota(
+ remaining: number,
+ remainingPercentage: number,
+ currency: string
+): UsageQuota {
+ return {
+ used: 0,
+ total: 0,
+ remaining,
+ remainingPercentage,
+ resetAt: null,
+ unlimited: true,
+ currency,
+ };
+}
+
+function buildMoonshotBalanceQuotas(
+ quota: MoonshotQuota,
+ currency: string
+): Record {
+ return {
+ available: balanceQuota(quota.availableBalance, quota.limitReached ? 0 : 100, currency),
+ voucher: balanceQuota(quota.voucherBalance, 100, currency),
+ cash: balanceQuota(quota.cashBalance, 100, currency),
+ };
+}
+
+export function registerMoonshotQuotaFetcher(): void {
+ registerQuotaFetcher("moonshot", fetchMoonshotQuota);
+ registerQuotaFetcher("kimi", fetchMoonshotQuota);
+ registerMonitorFetcher("moonshot", fetchMoonshotQuota);
+ registerMonitorFetcher("kimi", fetchMoonshotQuota);
+}
+
+export function registerMoonshotFetchersForNodes(
+ nodes: Array<{ id?: string | null; prefix?: string | null; baseUrl?: string | null }>
+): void {
+ for (const node of nodes) {
+ const origin = resolveMoonshotOrigin({}, node.baseUrl);
+ if (!origin) continue;
+ if (typeof node.id === "string" && node.id) {
+ registerQuotaFetcher(node.id, fetchMoonshotQuota);
+ registerMonitorFetcher(node.id, fetchMoonshotQuota);
+ }
+ if (typeof node.prefix === "string" && node.prefix) {
+ registerQuotaFetcher(node.prefix, fetchMoonshotQuota);
+ registerMonitorFetcher(node.prefix, fetchMoonshotQuota);
+ }
+ }
+}
+
+export { isMoonshotOpenPlatformConnection };
diff --git a/open-sse/services/usage.ts b/open-sse/services/usage.ts
index ea240340ea..62e96ce1bc 100644
--- a/open-sse/services/usage.ts
+++ b/open-sse/services/usage.ts
@@ -61,6 +61,8 @@ import { getQoderUsage, parseQoderUserStatusUsage } from "./usage/qoder.ts";
export { parseQoderUserStatusUsage } from "./usage/qoder.ts";
import { getOpencodeUsage } from "./usage/opencode.ts";
import { getDeepseekUsage } from "./usage/deepseek.ts";
+import { getMoonshotOpenPlatformUsage } from "./moonshotQuotaFetcher.ts";
+import { isMoonshotOpenPlatformConnection } from "./usage/moonshotOpenPlatform.ts";
import { getDevinCliUsage } from "./usage/devinCli.ts";
import { getBailianCodingPlanUsage } from "./usage/bailian.ts";
import { getVertexUsage } from "./usage/vertex.ts";
@@ -111,6 +113,10 @@ export async function getUsageForProvider(
) {
const { id, provider, accessToken, apiKey, providerSpecificData, projectId, email } = connection;
+ if (isMoonshotOpenPlatformConnection(connection)) {
+ return await getMoonshotOpenPlatformUsage(connection);
+ }
+
switch (provider) {
case "github":
return await getGitHubUsage(accessToken, providerSpecificData);
@@ -168,6 +174,9 @@ export async function getUsageForProvider(
return await getNanoGptUsage(apiKey || "");
case "deepseek":
return await getDeepseekUsage(id || "", apiKey || "");
+ case "moonshot":
+ case "kimi":
+ return await getMoonshotOpenPlatformUsage(connection);
case "openrouter":
return await getOpenrouterUsage(id || "", apiKey || "", providerSpecificData);
case "opencode":
diff --git a/open-sse/services/usage/fetcherProviders.ts b/open-sse/services/usage/fetcherProviders.ts
index bfcc5da710..05a3b47225 100644
--- a/open-sse/services/usage/fetcherProviders.ts
+++ b/open-sse/services/usage/fetcherProviders.ts
@@ -45,6 +45,8 @@ export const USAGE_FETCHER_PROVIDERS = [
"qwen-cloud-token-plan",
"nanogpt",
"deepseek",
+ "moonshot",
+ "kimi",
"opencode",
"opencode-zen",
"xiaomi-mimo",
diff --git a/open-sse/services/usage/moonshotOpenPlatform.ts b/open-sse/services/usage/moonshotOpenPlatform.ts
new file mode 100644
index 0000000000..a652aacb36
--- /dev/null
+++ b/open-sse/services/usage/moonshotOpenPlatform.ts
@@ -0,0 +1,76 @@
+/**
+ * Moonshot Open Platform host recognition.
+ *
+ * Distinguishes prepaid Open Platform keys (api.moonshot.cn / api.moonshot.ai)
+ * from Kimi Coding Plan (api.kimi.com/coding). Custom compatible nodes are
+ * identified by baseUrl host, not by provider id (those ids are uuids).
+ */
+
+import { moonshotProvider } from "../../config/providers/registry/moonshot/index.ts";
+import { kimiProvider } from "../../config/providers/registry/kimi/index.ts";
+
+export const MOONSHOT_OPEN_PLATFORM_HOSTS: ReadonlySet = new Set([
+ "api.moonshot.cn",
+ "api.moonshot.ai",
+]);
+
+export type MoonshotOriginConnection = {
+ provider?: string;
+ providerSpecificData?: unknown;
+};
+
+function asRecord(value: unknown): Record {
+ return value && typeof value === "object" && !Array.isArray(value)
+ ? (value as Record)
+ : {};
+}
+
+export function parseMoonshotOrigin(baseUrl: string | null | undefined): string | null {
+ if (typeof baseUrl !== "string" || baseUrl.trim() === "") return null;
+ let url: URL;
+ try {
+ url = new URL(baseUrl.trim());
+ } catch {
+ return null;
+ }
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
+ const host = url.hostname.toLowerCase();
+ if (!MOONSHOT_OPEN_PLATFORM_HOSTS.has(host)) return null;
+ const port = url.port ? `:${url.port}` : "";
+ return `${url.protocol}//${host}${port}`;
+}
+
+export function moonshotBalanceUrl(origin: string): string {
+ return `${origin}/v1/users/me/balance`;
+}
+
+function registryDefaultOrigin(provider: string | undefined): string | null {
+ if (provider === "moonshot") return parseMoonshotOrigin(moonshotProvider.baseUrl);
+ if (provider === "kimi") return parseMoonshotOrigin(kimiProvider.baseUrl);
+ return null;
+}
+
+export function resolveMoonshotOrigin(
+ connection: MoonshotOriginConnection,
+ nodeBaseUrl?: string | null,
+): string | null {
+ const psd = asRecord(connection.providerSpecificData);
+ const fromPsd = typeof psd.baseUrl === "string" ? parseMoonshotOrigin(psd.baseUrl) : null;
+ if (fromPsd) return fromPsd;
+ const fromNode = parseMoonshotOrigin(nodeBaseUrl);
+ if (fromNode) return fromNode;
+ return registryDefaultOrigin(connection.provider);
+}
+
+export function isMoonshotOpenPlatformConnection(
+ connection: MoonshotOriginConnection,
+ nodeBaseUrl?: string | null,
+): boolean {
+ return resolveMoonshotOrigin(connection, nodeBaseUrl) !== null;
+}
+
+/** Account-level empty wallet on Open Platform. Narrower than any compatible 429. */
+export function isMoonshotAccountBalanceExhausted(errorText: string | null | undefined): boolean {
+ const lower = String(errorText || "").toLowerCase();
+ return lower.includes("insufficient balance") || lower.includes("exceeded_current_quota");
+}
diff --git a/open-sse/services/usage/supportedProviders.ts b/open-sse/services/usage/supportedProviders.ts
index dc088fa1c4..b8e0481b86 100644
--- a/open-sse/services/usage/supportedProviders.ts
+++ b/open-sse/services/usage/supportedProviders.ts
@@ -41,6 +41,8 @@ export const USAGE_SUPPORTED_PROVIDERS: readonly string[] = [
"crof",
"nanogpt",
"deepseek",
+ "moonshot",
+ "kimi",
"xiaomi-mimo",
"xiaomi-mimo-token-plan",
"vertex",
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditCompatibleNodeModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditCompatibleNodeModal.tsx
index 026aa324fa..dc1966677f 100644
--- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditCompatibleNodeModal.tsx
+++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditCompatibleNodeModal.tsx
@@ -15,6 +15,8 @@ interface EditCompatibleNodeModalNode {
chatPath?: string;
modelsPath?: string;
iconUrl?: string;
+ dailyQuotaResetTimezone?: string | null;
+ dailyQuotaResetHour?: number | null;
providerSpecificData?: Record;
}
@@ -48,6 +50,8 @@ export default function EditCompatibleNodeModal({
consoleApiKey: "",
newApiUserId: "",
quotaPerUnit: "",
+ dailyQuotaResetTimezone: "",
+ dailyQuotaResetHour: "",
});
const [saving, setSaving] = useState(false);
const [checkKey, setCheckKey] = useState("");
@@ -98,6 +102,11 @@ export default function EditCompatibleNodeModal({
consoleApiKey: typeof psd.consoleApiKey === "string" ? psd.consoleApiKey : "",
newApiUserId: typeof psd.newApiUserId === "string" ? psd.newApiUserId : "",
quotaPerUnit: typeof psd.quotaPerUnit === "number" ? String(psd.quotaPerUnit) : "",
+ dailyQuotaResetTimezone: node.dailyQuotaResetTimezone || "",
+ dailyQuotaResetHour:
+ node.dailyQuotaResetHour === 0 || node.dailyQuotaResetHour
+ ? String(node.dailyQuotaResetHour)
+ : "",
});
setSaveError(null);
setIconUrlError(null);
@@ -141,6 +150,10 @@ export default function EditCompatibleNodeModal({
modelsPath: isCcCompatible ? "" : formData.modelsPath,
iconUrl: formData.iconUrl.trim(),
};
+ const tz = formData.dailyQuotaResetTimezone.trim();
+ payload.dailyQuotaResetTimezone = tz || null;
+ const hourRaw = formData.dailyQuotaResetHour.trim();
+ payload.dailyQuotaResetHour = hourRaw === "" ? null : Number(hourRaw);
if (!isAnthropic) {
payload.apiType = formData.apiType;
}
@@ -345,6 +358,22 @@ export default function EditCompatibleNodeModal({
hint={t("modelsPathHint")}
/>
)}
+
+ setFormData({ ...formData, dailyQuotaResetTimezone: e.target.value })
+ }
+ placeholder="Asia/Shanghai"
+ hint={t("dailyQuotaResetTimezoneHint")}
+ />
+ setFormData({ ...formData, dailyQuotaResetHour: e.target.value })}
+ placeholder="0"
+ hint={t("dailyQuotaResetHourHint")}
+ />
)}
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx
index d5f7fa0de0..eda5308eb4 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx
@@ -16,8 +16,8 @@ import {
} from "./utils";
import Card from "@/shared/components/Card";
import { CardSkeleton } from "@/shared/components/Loading";
-import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
import { pickDisplayValue } from "@/shared/utils/maskEmail";
+import { supportsProviderQuota, isProviderQuotaVisible } from "@/shared/utils/providerQuotaVisibility";
import useEmailPrivacyStore from "@/store/emailPrivacyStore";
import { useNotificationStore } from "@/store/notificationStore";
@@ -32,7 +32,6 @@ import { formatAutoRefreshCountdown } from "./formatters";
import { translateUsageOrFallback, type UsageTranslationValues } from "./i18nFallback";
import { compareTr } from "@/shared/utils/turkishText";
import { fetchWithTimeout } from "@/shared/utils/fetchTimeout";
-import { isProviderQuotaVisible } from "@/shared/utils/providerQuotaVisibility";
// Bound the two first-paint requests so a stalled connection cannot wedge
// `initialLoading` on `true` and freeze the quota page on its skeleton forever
@@ -529,7 +528,7 @@ export default function ProviderLimits({
connections.filter(
(conn) =>
isProviderQuotaVisible(conn) &&
- USAGE_SUPPORTED_PROVIDERS.includes(conn.provider) &&
+ supportsProviderQuota(conn.provider, conn) &&
(conn.authType === "oauth" || conn.authType === "apikey")
),
[connections]
diff --git a/src/app/(dashboard)/home/ProviderQuotaWidget.tsx b/src/app/(dashboard)/home/ProviderQuotaWidget.tsx
index 37b11ff286..3e91bd3526 100644
--- a/src/app/(dashboard)/home/ProviderQuotaWidget.tsx
+++ b/src/app/(dashboard)/home/ProviderQuotaWidget.tsx
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslations } from "next-intl";
import Card from "@/shared/components/Card";
import ProviderIcon from "@/shared/components/ProviderIcon";
-import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
+import { supportsProviderQuota } from "@/shared/utils/providerQuotaVisibility";
import QuotaMiniBar from "../dashboard/usage/components/ProviderLimits/QuotaMiniBar";
import { PROVIDER_LABEL } from "../dashboard/usage/components/ProviderLimits/constants";
import { translateUsageOrFallback } from "../dashboard/usage/components/ProviderLimits/i18nFallback";
@@ -25,6 +25,7 @@ type Connection = {
name?: string;
displayName?: string;
email?: string;
+ providerSpecificData?: unknown;
};
type QuotaData = Record
;
@@ -178,7 +179,7 @@ export default function ProviderQuotaWidget({
const quotaResponseData = quotasResponse.ok ? await quotasResponse.json() : {};
const relevant = ((connectionData.connections || []) as Connection[]).filter(
(connection) =>
- USAGE_SUPPORTED_PROVIDERS.includes(connection.provider) &&
+ supportsProviderQuota(connection.provider, connection) &&
(connection.authType === "oauth" || connection.authType === "apikey")
);
setConnections(relevant);
diff --git a/src/app/api/provider-nodes/[id]/route.ts b/src/app/api/provider-nodes/[id]/route.ts
index eef4803372..19bd524ef8 100644
--- a/src/app/api/provider-nodes/[id]/route.ts
+++ b/src/app/api/provider-nodes/[id]/route.ts
@@ -56,7 +56,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
- const { name, prefix, apiType, baseUrl, chatPath, modelsPath, customHeaders, iconUrl } =
+ const { name, prefix, apiType, baseUrl, chatPath, modelsPath, customHeaders, iconUrl, dailyQuotaResetTimezone, dailyQuotaResetHour } =
validation.data;
const node: any = await getProviderNodeById(id);
@@ -98,6 +98,9 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
// previously stored custom icon.
iconUrl: iconUrl?.trim() || null,
customHeaders: customHeaders || null,
+ dailyQuotaResetTimezone: dailyQuotaResetTimezone?.trim() || null,
+ dailyQuotaResetHour:
+ dailyQuotaResetHour === 0 || dailyQuotaResetHour != null ? dailyQuotaResetHour : null,
};
if (node.type === "openai-compatible") {
@@ -106,6 +109,21 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
const updated = await updateProviderNode(id, updates);
+ try {
+ const { registerMoonshotFetchersForNodes } = await import(
+ "@omniroute/open-sse/services/moonshotQuotaFetcher.ts"
+ );
+ registerMoonshotFetchersForNodes([
+ {
+ id: typeof updated?.id === "string" ? updated.id : id,
+ prefix: prefix.trim(),
+ baseUrl: sanitizedBaseUrl,
+ },
+ ]);
+ } catch (error) {
+ console.warn("Moonshot fetcher re-register after node update skipped:", error);
+ }
+
const connections = await getProviderConnections({ provider: id });
await Promise.all(
connections.flatMap((connectionRaw) => {
diff --git a/src/app/api/provider-nodes/route.ts b/src/app/api/provider-nodes/route.ts
index a11180f505..d60e3b299a 100644
--- a/src/app/api/provider-nodes/route.ts
+++ b/src/app/api/provider-nodes/route.ts
@@ -48,6 +48,27 @@ function sanitizeVibeProxyBaseUrl(baseUrl: string) {
return `${base}/v1`;
}
+async function registerMoonshotFetchersForCreatedNode(node: {
+ id?: unknown;
+ prefix?: unknown;
+ baseUrl?: unknown;
+}): Promise {
+ try {
+ const { registerMoonshotFetchersForNodes } = await import(
+ "@omniroute/open-sse/services/moonshotQuotaFetcher.ts"
+ );
+ registerMoonshotFetchersForNodes([
+ {
+ id: typeof node.id === "string" ? node.id : null,
+ prefix: typeof node.prefix === "string" ? node.prefix : null,
+ baseUrl: typeof node.baseUrl === "string" ? node.baseUrl : null,
+ },
+ ]);
+ } catch (error) {
+ console.warn("Moonshot fetcher register after node create skipped:", error);
+ }
+}
+
function sanitizeAnthropicBaseUrl(baseUrl: string) {
return (baseUrl || "")
.trim()
@@ -126,6 +147,8 @@ export async function POST(request) {
modelsPath,
customHeaders,
iconUrl,
+ dailyQuotaResetTimezone,
+ dailyQuotaResetHour,
} = validation.data;
if (preset === "vibeproxy-openai") {
@@ -145,7 +168,11 @@ export async function POST(request) {
modelsPath: modelsPath || null,
iconUrl: iconUrl?.trim() || null,
customHeaders: customHeaders || null,
+ dailyQuotaResetTimezone: dailyQuotaResetTimezone?.trim() || null,
+ dailyQuotaResetHour:
+ dailyQuotaResetHour === 0 || dailyQuotaResetHour != null ? dailyQuotaResetHour : null,
});
+ await registerMoonshotFetchersForCreatedNode(node);
return NextResponse.json({ node }, { status: 201 });
}
@@ -170,7 +197,11 @@ export async function POST(request) {
modelsPath: modelsPath || null,
iconUrl: iconUrl?.trim() || null,
customHeaders: customHeaders || null,
+ dailyQuotaResetTimezone: dailyQuotaResetTimezone?.trim() || null,
+ dailyQuotaResetHour:
+ dailyQuotaResetHour === 0 || dailyQuotaResetHour != null ? dailyQuotaResetHour : null,
});
+ await registerMoonshotFetchersForCreatedNode(node);
return NextResponse.json({ node }, { status: 201 });
}
@@ -200,7 +231,11 @@ export async function POST(request) {
modelsPath: compatMode === "cc" ? null : modelsPath || null,
iconUrl: iconUrl?.trim() || null,
customHeaders: customHeaders || null,
+ dailyQuotaResetTimezone: dailyQuotaResetTimezone?.trim() || null,
+ dailyQuotaResetHour:
+ dailyQuotaResetHour === 0 || dailyQuotaResetHour != null ? dailyQuotaResetHour : null,
});
+ await registerMoonshotFetchersForCreatedNode(node);
return NextResponse.json({ node }, { status: 201 });
}
diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json
index a2d2c4acf9..bdf4b7a4c7 100644
--- a/src/i18n/messages/ar.json
+++ b/src/i18n/messages/ar.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "عنوان URL للأيقونة",
"iconUrlHint": "اختياري. عنوان URL للصورة المعروضة كأيقونة لهذا المزود.",
"iconUrlInvalid": "رابط الأيقونة غير صالح. استخدم http(s):// أو data:image/*;base64 رابط.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json
index 131a9b212b..543b6b60b0 100644
--- a/src/i18n/messages/az.json
+++ b/src/i18n/messages/az.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "İkon URL-i",
"iconUrlHint": "İstəyə bağlı. Bu provayderin ikonu kimi göstərilən şəkil URL-i.",
"iconUrlInvalid": "Yanlış ikon URL-si. http(s):// və ya data:image/*;base64 URL istifadə edin.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json
index f51cea18c1..3944642aff 100644
--- a/src/i18n/messages/bg.json
+++ b/src/i18n/messages/bg.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL адрес на икона",
"iconUrlHint": "По избор. URL адрес на изображение, показвано като икона на този доставчик.",
"iconUrlInvalid": "Невалиден URL на иконата. Използвайте http(s):// или data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json
index 4dacb5dd56..8f6afadff3 100644
--- a/src/i18n/messages/bn.json
+++ b/src/i18n/messages/bn.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "আইকন URL",
"iconUrlHint": "ঐচ্ছিক। এই প্রদানকারীর আইকন হিসেবে দেখানোর জন্য ছবির URL।",
"iconUrlInvalid": "অবৈধ আইকন URL। একটি http(s):// অথবা data:image/*;base64 URL ব্যবহার করুন।",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json
index 2450daa9ff..29af05425a 100644
--- a/src/i18n/messages/cs.json
+++ b/src/i18n/messages/cs.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL ikony",
"iconUrlHint": "Volitelné. URL obrázku zobrazeného jako ikona tohoto poskytovatele.",
"iconUrlInvalid": "Neplatná URL ikony. Použijte http(s):// nebo data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json
index 4dd4699f43..bea97fe340 100644
--- a/src/i18n/messages/da.json
+++ b/src/i18n/messages/da.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Ikon-URL",
"iconUrlHint": "Valgfrit. Billed-URL, der vises som denne udbyders ikon.",
"iconUrlInvalid": "Ugyldig ikon-URL. Brug en http(s):// eller data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json
index 0fa6991704..d0c0322ac1 100644
--- a/src/i18n/messages/de.json
+++ b/src/i18n/messages/de.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Icon-URL",
"iconUrlHint": "Optional. Bild-URL, die als Icon dieses Anbieters angezeigt wird.",
"iconUrlInvalid": "Ungültige Icon-URL. Verwenden Sie eine http(s):// oder data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "AC-Prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index e1b0adcb11..e9643f3511 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -5251,6 +5251,10 @@
"iconUrlLabel": "Icon URL",
"iconUrlHint": "Optional. Image URL shown as this provider's icon.",
"iconUrlInvalid": "Invalid icon URL. Use an http(s):// or data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json
index 99f66790a4..ba61b59daa 100644
--- a/src/i18n/messages/es.json
+++ b/src/i18n/messages/es.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Icon URL",
"iconUrlHint": "Optional. Image URL shown as this provider's icon.",
"iconUrlInvalid": "URL de icono no válida. Utilice una URL http(s):// o data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "producto ac",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json
index 176e89c4d2..79c0ec653f 100644
--- a/src/i18n/messages/fa.json
+++ b/src/i18n/messages/fa.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL آیکون",
"iconUrlHint": "اختیاری. URL تصویری که به عنوان آیکون این ارائهدهنده نمایش داده میشود.",
"iconUrlInvalid": "آدرس آیکون نامعتبر است. از آدرس http(s):// یا data:image/*;base64 استفاده کنید.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json
index 8c7c2c9c98..c83a56cb2c 100644
--- a/src/i18n/messages/fi.json
+++ b/src/i18n/messages/fi.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Kuvakkeen URL-osoite",
"iconUrlHint": "Valinnainen. Kuvan URL-osoite, joka näytetään tämän tarjoajan kuvakkeena.",
"iconUrlInvalid": "Virheellinen kuvakkeen URL. Käytä http(s):// tai data:image/*;base64 URL:ia.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json
index c17cb242a0..1bb74a6c87 100644
--- a/src/i18n/messages/fr.json
+++ b/src/i18n/messages/fr.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL de l'icône",
"iconUrlHint": "Facultatif. URL de l'image affichée comme icône de ce fournisseur.",
"iconUrlInvalid": "URL d'icône invalide. Utilisez une URL http(s):// ou data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json
index 00196e5ad8..7fbc8ac29b 100644
--- a/src/i18n/messages/gu.json
+++ b/src/i18n/messages/gu.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "આઇકન URL",
"iconUrlHint": "વૈકલ્પિક. આ પ્રદાતાના આઇકન તરીકે દર્શાવેલ છબી URL.",
"iconUrlInvalid": "અમાન્ય આઇકન URL. http(s):// અથવા data:image/*;base64 URL નો ઉપયોગ કરો.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json
index 19d7e96e65..66bea20bb8 100644
--- a/src/i18n/messages/he.json
+++ b/src/i18n/messages/he.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "כתובת URL של סמל",
"iconUrlHint": "אופציונלי. כתובת URL של תמונה שתוצג כסמל של ספק זה.",
"iconUrlInvalid": "כתובת ה-URL של האייקון אינה חוקית. השתמש ב-http(s):// או ב-data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json
index 2f9b11ace2..f45c0fad90 100644
--- a/src/i18n/messages/hi.json
+++ b/src/i18n/messages/hi.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "आइकन URL",
"iconUrlHint": "वैकल्पिक। इस प्रदाता के आइकन के रूप में दिखाया जाने वाला छवि URL।",
"iconUrlInvalid": "अमान्य आइकन URL। http(s):// या data:image/*;base64 URL का उपयोग करें।",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "एसी-उत्पाद",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json
index 339507ca3c..b1275355b9 100644
--- a/src/i18n/messages/hu.json
+++ b/src/i18n/messages/hu.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Ikon URL-címe",
"iconUrlHint": "Opcionális. A szolgáltató ikonjaként megjelenő kép URL-címe.",
"iconUrlInvalid": "Érvénytelen ikon URL. Használjon http(s):// vagy data:image/*;base64 URL-t.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json
index bf2f06a265..474a26973d 100644
--- a/src/i18n/messages/id.json
+++ b/src/i18n/messages/id.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL Ikon",
"iconUrlHint": "Opsional. URL gambar yang ditampilkan sebagai ikon penyedia ini.",
"iconUrlInvalid": "URL ikon tidak valid. Gunakan URL http(s):// atau data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json
index 855d5b3ecb..86e1226241 100644
--- a/src/i18n/messages/it.json
+++ b/src/i18n/messages/it.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL Icona",
"iconUrlHint": "Opzionale. URL dell'immagine mostrata come icona di questo provider.",
"iconUrlInvalid": "URL dell'icona non valida. Usa un URL http(s):// o data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json
index 6e734c226e..ab2f52c289 100644
--- a/src/i18n/messages/ja.json
+++ b/src/i18n/messages/ja.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "アイコンURL",
"iconUrlHint": "任意。このプロバイダーのアイコンとして表示される画像URL。",
"iconUrlInvalid": "無効なアイコンURLです。http(s)://またはdata:image/*;base64 URLを使用してください。",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json
index fb12ad0c99..ad26677619 100644
--- a/src/i18n/messages/ko.json
+++ b/src/i18n/messages/ko.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "아이콘 URL",
"iconUrlHint": "선택 사항. 이 제공자의 아이콘으로 표시될 이미지 URL입니다.",
"iconUrlInvalid": "잘못된 아이콘 URL입니다. http(s):// 또는 data:image/*;base64 URL을 사용하세요.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json
index 8817c1c87f..7f5ee425c9 100644
--- a/src/i18n/messages/mr.json
+++ b/src/i18n/messages/mr.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "आयकॉन URL",
"iconUrlHint": "पर्यायी. या प्रदात्याचा आयकॉन म्हणून दर्शविलेली इमेज URL.",
"iconUrlInvalid": "अवैध आयकॉन URL. http(s):// किंवा data:image/*;base64 URL वापरा.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json
index 186db22e3a..a780d09bcb 100644
--- a/src/i18n/messages/ms.json
+++ b/src/i18n/messages/ms.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL Ikon",
"iconUrlHint": "Pilihan. URL imej yang ditunjukkan sebagai ikon penyedia ini.",
"iconUrlInvalid": "URL ikon tidak sah. Gunakan URL http(s):// atau data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json
index edf66b80a1..706a5ccd35 100644
--- a/src/i18n/messages/nl.json
+++ b/src/i18n/messages/nl.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Pictogram-URL",
"iconUrlHint": "Optioneel. Afbeeldings-URL die wordt getoond als het pictogram van deze provider.",
"iconUrlInvalid": "Ongeldige pictogram-URL. Gebruik een http(s):// of data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json
index d4e45081b6..9576e4b6a6 100644
--- a/src/i18n/messages/no.json
+++ b/src/i18n/messages/no.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Ikon-URL",
"iconUrlHint": "Valgfritt. Bilde-URL som vises som denne leverandørens ikon.",
"iconUrlInvalid": "Ugyldig ikon-URL. Bruk en http(s):// eller data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json
index 6f9f973ec1..856b906cfb 100644
--- a/src/i18n/messages/phi.json
+++ b/src/i18n/messages/phi.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL ng Icon",
"iconUrlHint": "Opsyonal. URL ng larawan na ipinapakita bilang icon ng provider na ito.",
"iconUrlInvalid": "Hindi wastong URL ng icon. Gumamit ng http(s):// o data:image/*;base64 na URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json
index 002c11a98d..b9ecc290db 100644
--- a/src/i18n/messages/pl.json
+++ b/src/i18n/messages/pl.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL ikony",
"iconUrlHint": "Opcjonalnie. URL obrazu wyświetlany jako ikona tego provider.",
"iconUrlInvalid": "Nieprawidłowy adres URL ikony. Użyj adresu http(s):// lub data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json
index 5717bff5cc..e65916a54d 100644
--- a/src/i18n/messages/pt-BR.json
+++ b/src/i18n/messages/pt-BR.json
@@ -5252,6 +5252,10 @@
"iconUrlLabel": "URL do ícone",
"iconUrlHint": "Opcional. URL da imagem exibida como ícone deste provedor.",
"iconUrlInvalid": "URL do ícone inválido. Use um URL http(s):// ou data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json
index 4722209d02..8e518e2e0e 100644
--- a/src/i18n/messages/pt.json
+++ b/src/i18n/messages/pt.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL do ícone",
"iconUrlHint": "Opcional. URL da imagem mostrada como ícone deste provedor.",
"iconUrlInvalid": "URL do ícone inválido. Utilize uma URL http(s):// ou data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json
index a0d82ca4d5..0bfdea697e 100644
--- a/src/i18n/messages/ro.json
+++ b/src/i18n/messages/ro.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL pictogramă",
"iconUrlHint": "Opțional. URL-ul imaginii afișate ca pictogramă a acestui furnizor.",
"iconUrlInvalid": "URL-ul iconului este invalid. Folosiți un URL http(s):// sau data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json
index 4a7646aaef..c63a9fab99 100644
--- a/src/i18n/messages/ru.json
+++ b/src/i18n/messages/ru.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL-адрес иконки",
"iconUrlHint": "Необязательно. URL-адрес изображения, используемого в качестве иконки этого провайдера.",
"iconUrlInvalid": "Неверный URL значка. Используйте URL-адрес http(s):// или data:image/*;base64.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-прод",
"openaiPrefixPlaceholder": "oc-прод",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json
index 66a01e79e2..de5778a2cd 100644
--- a/src/i18n/messages/sk.json
+++ b/src/i18n/messages/sk.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL ikony",
"iconUrlHint": "Voliteľné. URL obrázka zobrazeného ako ikona tohto poskytovateľa.",
"iconUrlInvalid": "Neplatná URL ikony. Použite http(s):// alebo data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json
index d8ca20a04d..0408b0806b 100644
--- a/src/i18n/messages/sv.json
+++ b/src/i18n/messages/sv.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Ikon-URL",
"iconUrlHint": "Valfritt. Bild-URL som visas som denna leverantörs ikon.",
"iconUrlInvalid": "Ogiltig ikon-URL. Använd en http(s):// eller data:image/*;base64-URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json
index 5507c02543..88b9200348 100644
--- a/src/i18n/messages/sw.json
+++ b/src/i18n/messages/sw.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL ya Aikoni",
"iconUrlHint": "Si lazima. URL ya picha inayoonyeshwa kama aikoni ya mtoa huduma huyu.",
"iconUrlInvalid": "URL ya ikoni si sahihi. Tumia http(s):// au data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json
index a5e601ae93..95da2289ce 100644
--- a/src/i18n/messages/ta.json
+++ b/src/i18n/messages/ta.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "சின்னத்தின் URL",
"iconUrlHint": "விருப்பத்திற்குரியது. இந்த வழங்குநரின் சின்னமாக காட்டப்படும் பட URL.",
"iconUrlInvalid": "தவறான ஐகான் URL. http(s):// அல்லது data:image/*;base64 URL ஐப் பயன்படுத்தவும்.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json
index 69a51f4d6a..3526a50b41 100644
--- a/src/i18n/messages/te.json
+++ b/src/i18n/messages/te.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "ఐకాన్ URL",
"iconUrlHint": "ఐచ్ఛికం. ఈ ప్రొవైడర్ ఐకాన్గా చూపబడే చిత్రం URL.",
"iconUrlInvalid": "చెల్లని ఐకాన్ URL. http(s):// లేదా data:image/*;base64 URL ఉపయోగించండి.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json
index b43dda761c..e19eb88c20 100644
--- a/src/i18n/messages/th.json
+++ b/src/i18n/messages/th.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL ไอคอน",
"iconUrlHint": "ไม่บังคับ URL รูปภาพที่จะแสดงเป็นไอคอนของผู้ให้บริการรายนี้",
"iconUrlInvalid": "URL ไอคอนไม่ถูกต้อง ใช้ http(s):// หรือ data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-ผลิตภัณฑ์",
"openaiPrefixPlaceholder": "oc-ผลิตภัณฑ์",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json
index 31e48fb100..12b3973933 100644
--- a/src/i18n/messages/tr.json
+++ b/src/i18n/messages/tr.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "Simge URL'si",
"iconUrlHint": "İsteğe bağlı. Bu sağlayıcının simgesi olarak gösterilen görsel URL'si.",
"iconUrlInvalid": "Geçersiz simge URL'si. http(s):// veya data:image/*;base64 URL'si kullanın.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json
index 62d8db3226..fd9a0732d3 100644
--- a/src/i18n/messages/uk-UA.json
+++ b/src/i18n/messages/uk-UA.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "URL-адреса іконки",
"iconUrlHint": "Необов'язково. URL-адреса зображення, що відображається як іконка цього провайдера.",
"iconUrlInvalid": "Недійсне URL-адреса значка. Використовуйте http(s):// або data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json
index 1a4f1de826..02dba16c45 100644
--- a/src/i18n/messages/ur.json
+++ b/src/i18n/messages/ur.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "آئیکن URL",
"iconUrlHint": "اختیاری۔ تصویر کا URL جو اس فراہم کنندہ کے آئیکن کے طور پر دکھایا گیا ہے۔",
"iconUrlInvalid": "غلط آئیکن یو آر ایل۔ http(s):// یا data:image/*;base64 یو آر ایل استعمال کریں۔",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json
index 0a8428ecdf..cf7235f61b 100644
--- a/src/i18n/messages/vi.json
+++ b/src/i18n/messages/vi.json
@@ -5252,6 +5252,10 @@
"iconUrlLabel": "URL biểu tượng",
"iconUrlHint": "Tùy chọn. URL hình ảnh được hiển thị làm biểu tượng của nhà cung cấp này.",
"iconUrlInvalid": "URL biểu tượng không hợp lệ. Sử dụng http(s):// hoặc data:image/*;base64 URL.",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json
index facdccc8c2..e53f191cf9 100644
--- a/src/i18n/messages/zh-CN.json
+++ b/src/i18n/messages/zh-CN.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "图标 URL",
"iconUrlHint": "可选。显示为此服务商图标的图片 URL。",
"iconUrlInvalid": "无效的图标 URL。请使用 http(s):// 或 data:image/*;base64 URL。",
+ "dailyQuotaResetTimezoneLabel": "每日额度重置时区",
+ "dailyQuotaResetTimezoneHint": "可选。上游不返回 X-RateLimit-Reset 时使用的 IANA 时区。留空则只做短冷却。",
+ "dailyQuotaResetHourLabel": "每日额度重置小时",
+ "dailyQuotaResetHourHint": "上述时区的本地小时,0-23。留空表示不配置节点级时钟。",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json
index 3d10722987..2cddce94b2 100644
--- a/src/i18n/messages/zh-TW.json
+++ b/src/i18n/messages/zh-TW.json
@@ -5248,6 +5248,10 @@
"iconUrlLabel": "圖示網址",
"iconUrlHint": "選用。顯示為此提供者圖示的圖片網址。",
"iconUrlInvalid": "無效的圖示網址。請使用 http(s):// 或 data:image/*;base64 URL。",
+ "dailyQuotaResetTimezoneLabel": "Daily quota reset timezone",
+ "dailyQuotaResetTimezoneHint": "Optional IANA timezone used when the upstream omits X-RateLimit-Reset. Leave empty to keep a short cooldown.",
+ "dailyQuotaResetHourLabel": "Daily quota reset hour",
+ "dailyQuotaResetHourHint": "Local hour 0-23 in the timezone above. Empty means no node-level clock.",
"anthropicPrefixPlaceholder": "ac-prod",
"openaiPrefixPlaceholder": "oc-prod",
"anthropicBaseUrlPlaceholder": "https://api.anthropic.com/v1",
diff --git a/src/instrumentation-node.ts b/src/instrumentation-node.ts
index 069b8740b8..e3ae959118 100755
--- a/src/instrumentation-node.ts
+++ b/src/instrumentation-node.ts
@@ -282,6 +282,7 @@ export async function registerQuotaFetchers(): Promise {
{ registerQwenTokenPlanQuotaFetcher },
{ registerCrofUsageFetcher },
{ registerDeepseekQuotaFetcher },
+ { registerMoonshotQuotaFetcher, registerMoonshotFetchersForNodes },
{ registerOpenrouterQuotaFetcher },
{ registerOpencodeQuotaFetcher },
{ registerGrokWebQuotaFetcher },
@@ -292,6 +293,7 @@ export async function registerQuotaFetchers(): Promise {
import("@omniroute/open-sse/services/qwenTokenPlanQuotaFetcher"),
import("@omniroute/open-sse/services/crofUsageFetcher"),
import("@omniroute/open-sse/services/deepseekQuotaFetcher"),
+ import("@omniroute/open-sse/services/moonshotQuotaFetcher"),
import("@omniroute/open-sse/services/openrouterQuotaFetcher"),
import("@omniroute/open-sse/services/opencodeQuotaFetcher"),
import("@omniroute/open-sse/services/grokQuotaFetcher"),
@@ -303,6 +305,20 @@ export async function registerQuotaFetchers(): Promise {
registerQwenTokenPlanQuotaFetcher();
registerCrofUsageFetcher();
registerDeepseekQuotaFetcher();
+ registerMoonshotQuotaFetcher();
+ try {
+ const { getProviderNodes } = await import("@/lib/db/providers");
+ const nodes = await getProviderNodes();
+ registerMoonshotFetchersForNodes(
+ (Array.isArray(nodes) ? nodes : []).map((node) => ({
+ id: typeof node.id === "string" ? node.id : null,
+ prefix: typeof node.prefix === "string" ? node.prefix : null,
+ baseUrl: typeof node.baseUrl === "string" ? node.baseUrl : null,
+ })),
+ );
+ } catch (error) {
+ console.warn("[STARTUP] Moonshot custom-node fetcher scan skipped:", error);
+ }
registerOpenrouterQuotaFetcher();
registerOpencodeQuotaFetcher();
registerGrokWebQuotaFetcher();
diff --git a/src/lib/db/migrationRunner.ts b/src/lib/db/migrationRunner.ts
index 498be0f9bc..f53cd58070 100644
--- a/src/lib/db/migrationRunner.ts
+++ b/src/lib/db/migrationRunner.ts
@@ -516,6 +516,11 @@ function isSchemaAlreadyApplied(
db.prepare("SELECT 1 FROM provider_connections WHERE provider = 'freepik' LIMIT 1").get() ==
null
);
+ case "172":
+ return (
+ hasColumn(db, "provider_nodes", "daily_quota_reset_timezone") &&
+ hasColumn(db, "provider_nodes", "daily_quota_reset_hour")
+ );
default:
return false;
}
diff --git a/src/lib/db/migrations/172_provider_node_daily_quota_reset.sql b/src/lib/db/migrations/172_provider_node_daily_quota_reset.sql
new file mode 100644
index 0000000000..969df2e0fb
--- /dev/null
+++ b/src/lib/db/migrations/172_provider_node_daily_quota_reset.sql
@@ -0,0 +1,6 @@
+-- 172: per-node daily quota reset clock (IANA timezone + local hour).
+-- Used by TPD cooldown when upstream omits X-RateLimit-Reset.
+-- Both columns nullable: empty = operator has not configured a clock.
+
+ALTER TABLE provider_nodes ADD COLUMN daily_quota_reset_timezone TEXT;
+ALTER TABLE provider_nodes ADD COLUMN daily_quota_reset_hour INTEGER;
diff --git a/src/lib/db/providers/nodes.ts b/src/lib/db/providers/nodes.ts
index 7c9a251a78..3bc975db92 100644
--- a/src/lib/db/providers/nodes.ts
+++ b/src/lib/db/providers/nodes.ts
@@ -9,6 +9,25 @@ import { backupDbFile } from "../backup";
import { invalidateDbCache } from "../readCache";
import { toRecord, type JsonRecord } from "./columns";
+function normalizeDailyQuotaResetHour(value: unknown): number | null {
+ return value === 0 || value ? Number(value) : null;
+}
+
+function withParsedCustomHeaders(node: JsonRecord, storedJson: string | null): JsonRecord {
+ const result: JsonRecord = { ...node };
+ if (storedJson) {
+ try {
+ result.customHeaders = JSON.parse(storedJson);
+ } catch {
+ result.customHeaders = null;
+ }
+ } else {
+ result.customHeaders = null;
+ }
+ delete result.customHeadersJson;
+ return result;
+}
+
interface StatementLike {
all: (...params: unknown[]) => TRow[];
get: (...params: unknown[]) => TRow | undefined;
@@ -88,32 +107,23 @@ export async function createProviderNode(data: JsonRecord) {
// Optional operator-supplied remote icon URL (#2166) — plain TEXT, no JSON parsing needed.
iconUrl: data.iconUrl || null,
customHeadersJson,
+ dailyQuotaResetTimezone: data.dailyQuotaResetTimezone || null,
+ dailyQuotaResetHour: normalizeDailyQuotaResetHour(data.dailyQuotaResetHour),
createdAt: now,
updatedAt: now,
};
db.prepare(
`
- INSERT INTO provider_nodes (id, type, name, prefix, api_type, base_url, chat_path, models_path, icon_url, custom_headers_json, created_at, updated_at)
- VALUES (@id, @type, @name, @prefix, @apiType, @baseUrl, @chatPath, @modelsPath, @iconUrl, @customHeadersJson, @createdAt, @updatedAt)
+ INSERT INTO provider_nodes (id, type, name, prefix, api_type, base_url, chat_path, models_path, icon_url, custom_headers_json, daily_quota_reset_timezone, daily_quota_reset_hour, created_at, updated_at)
+ VALUES (@id, @type, @name, @prefix, @apiType, @baseUrl, @chatPath, @modelsPath, @iconUrl, @customHeadersJson, @dailyQuotaResetTimezone, @dailyQuotaResetHour, @createdAt, @updatedAt)
`
).run(node);
backupDbFile("pre-write");
invalidateDbCache("nodes");
- const result: JsonRecord = { ...node };
- if (customHeadersJson) {
- try {
- result.customHeaders = JSON.parse(customHeadersJson);
- } catch {
- result.customHeaders = null;
- }
- } else {
- result.customHeaders = null;
- }
- delete result.customHeadersJson;
- return result;
+ return withParsedCustomHeaders({ ...node }, customHeadersJson);
}
export async function updateProviderNode(id: string, data: JsonRecord) {
@@ -144,7 +154,10 @@ export async function updateProviderNode(id: string, data: JsonRecord) {
UPDATE provider_nodes SET type = @type, name = @name, prefix = @prefix,
api_type = @apiType, base_url = @baseUrl, chat_path = @chatPath,
models_path = @modelsPath, icon_url = @iconUrl,
- custom_headers_json = @customHeadersJson, updated_at = @updatedAt
+ custom_headers_json = @customHeadersJson,
+ daily_quota_reset_timezone = @dailyQuotaResetTimezone,
+ daily_quota_reset_hour = @dailyQuotaResetHour,
+ updated_at = @updatedAt
WHERE id = @id
`
).run({
@@ -160,25 +173,15 @@ export async function updateProviderNode(id: string, data: JsonRecord) {
// stored custom icon when the caller submits an empty value.
iconUrl: merged["iconUrl"] || null,
customHeadersJson: merged["customHeadersJson"] || null,
+ dailyQuotaResetTimezone: merged["dailyQuotaResetTimezone"] || null,
+ dailyQuotaResetHour: normalizeDailyQuotaResetHour(merged["dailyQuotaResetHour"]),
updatedAt: merged["updatedAt"],
});
backupDbFile("pre-write");
invalidateDbCache("nodes");
- const result: JsonRecord = { ...merged };
- const storedJson = merged["customHeadersJson"] as string | null;
- if (storedJson) {
- try {
- result.customHeaders = JSON.parse(storedJson);
- } catch {
- result.customHeaders = null;
- }
- } else {
- result.customHeaders = null;
- }
- delete result.customHeadersJson;
- return result;
+ return withParsedCustomHeaders(merged, (merged["customHeadersJson"] as string | null) ?? null);
}
export async function deleteProviderNode(id: string) {
diff --git a/src/lib/usage/apiKeySelfService.ts b/src/lib/usage/apiKeySelfService.ts
index 2e9dc9ffa7..7bc2d440ca 100644
--- a/src/lib/usage/apiKeySelfService.ts
+++ b/src/lib/usage/apiKeySelfService.ts
@@ -1,5 +1,5 @@
import { hasSelfAccountQuotaScope, hasSelfUsageScope } from "@/shared/constants/selfServiceScopes";
-import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
+import { supportsProviderQuota } from "@/shared/utils/providerQuotaVisibility";
type JsonRecord = Record;
type DateLike = number | string | Date | null | undefined;
@@ -64,6 +64,7 @@ interface AccountQuotaConnection {
id: string;
provider: string;
lookupFailed?: boolean;
+ providerSpecificData?: unknown;
}
function toNumber(value: unknown, fallback = 0): number {
@@ -208,11 +209,14 @@ function normalizePlan(value: unknown): unknown {
return undefined;
}
-function isSupportedProvider(provider: string): boolean {
- return USAGE_SUPPORTED_PROVIDERS.includes(provider as (typeof USAGE_SUPPORTED_PROVIDERS)[number]);
+function isSupportedProvider(
+ provider: string,
+ connection?: { provider?: string; providerSpecificData?: unknown },
+): boolean {
+ return supportsProviderQuota(provider, connection);
}
-function getConnectionIdentity(value: unknown): { id: string; provider: string } | null {
+function getConnectionIdentity(value: unknown): AccountQuotaConnection | null {
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const record = value as JsonRecord;
if (record.isActive === false) return null;
@@ -221,7 +225,11 @@ function getConnectionIdentity(value: unknown): { id: string; provider: string }
const provider = typeof record.provider === "string" ? record.provider : "";
if (!id || !provider) return null;
- return { id, provider };
+ return {
+ id,
+ provider,
+ providerSpecificData: record.providerSpecificData,
+ };
}
async function listAccountQuotaConnections(
@@ -297,7 +305,7 @@ async function resolveConnectionAccountQuota(
};
}
- if (!isSupportedProvider(connection.provider)) {
+ if (!isSupportedProvider(connection.provider, connection)) {
return {
provider: connection.provider,
connectionId: connection.id,
diff --git a/src/lib/usage/providerLimits.ts b/src/lib/usage/providerLimits.ts
index 9a36d5e491..2e0b8895da 100644
--- a/src/lib/usage/providerLimits.ts
+++ b/src/lib/usage/providerLimits.ts
@@ -16,7 +16,7 @@ import { setQuotaCache } from "@/domain/quotaCache";
import { buildClaudeExtraUsageConnectionUpdate } from "@/lib/providers/claudeExtraUsage";
import { clearRecoveredProviderState } from "@/sse/services/auth";
import { getMachineId } from "@/shared/utils/machine";
-import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
+import { supportsProviderQuota } from "@/shared/utils/providerQuotaVisibility";
import { mergeProviderLimitsCacheEntry, toProviderLimitsCacheEntry } from "./providerLimitsCache";
import { getCredentialRefreshExecutor } from "@omniroute/open-sse/executors/credential.ts";
import { getUsageForProvider } from "@omniroute/open-sse/services/usage.ts";
@@ -174,19 +174,14 @@ function shouldRefreshProviderLimitsCache(
}
export function isSupportedUsageConnection(connection: ProviderConnectionLike | null): boolean {
- if (
- !connection ||
- !connection.provider ||
- !USAGE_SUPPORTED_PROVIDERS.includes(connection.provider)
- ) {
- return false;
- }
+ if (!connection?.provider) return false;
- if (connection.authType === "oauth") return true;
- return (
- (connection.authType === "apikey" || connection.authType === "api_key") &&
- PROVIDER_LIMITS_APIKEY_PROVIDERS.has(connection.provider)
- );
+ if (connection.authType === "oauth") {
+ return supportsProviderQuota(connection.provider, connection);
+ }
+ if (connection.authType !== "apikey" && connection.authType !== "api_key") return false;
+ if (PROVIDER_LIMITS_APIKEY_PROVIDERS.has(connection.provider)) return true;
+ return supportsProviderQuota(connection.provider, connection);
}
function withStatus(error: Error, status: number): Error & { status: number } {
diff --git a/src/shared/utils/classify429.ts b/src/shared/utils/classify429.ts
index 03b6e41658..d482334778 100644
--- a/src/shared/utils/classify429.ts
+++ b/src/shared/utils/classify429.ts
@@ -91,6 +91,14 @@ const QUOTA_PATTERNS: ReadonlyArray = [
// Trailing punctuation/whitespace before the closing quote is tolerated
// because real API responses may include a period or trailing space.
/"error"\s*:\s*"usage limit reached[.\s]*"/i,
+
+ // Moonshot Open Platform organization TPD (tokens-per-day). Live body:
+ // "request reached organization TPD rate limit, current: N, limit: M".
+ // Do not use a bare /TPD/ — too wide. Limit is read from the body, never
+ // hardcoded (Tier0=1.5M, Tier1+=unlimited).
+ /organization TPD rate limit/i,
+ /\bTPD rate limit\b/i,
+ /insufficient balance/i,
];
/**
@@ -155,6 +163,9 @@ const TERMINAL_QUOTA_PATTERNS: ReadonlyArray = [
/individual quota reached/i,
/enable overages/i,
/daily free allocation/i,
+ /organization TPD rate limit/i,
+ /\bTPD rate limit\b/i,
+ /insufficient balance/i,
];
/**
diff --git a/src/shared/utils/providerQuotaVisibility.ts b/src/shared/utils/providerQuotaVisibility.ts
index 3968005204..81edcdc450 100644
--- a/src/shared/utils/providerQuotaVisibility.ts
+++ b/src/shared/utils/providerQuotaVisibility.ts
@@ -1,13 +1,23 @@
import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers";
+import { isMoonshotOpenPlatformConnection } from "@omniroute/open-sse/services/usage/moonshotOpenPlatform.ts";
export interface ProviderQuotaVisibilityConnection {
quotaVisible?: boolean;
+ provider?: string;
+ providerSpecificData?: unknown;
}
export function isProviderQuotaVisible(connection: ProviderQuotaVisibilityConnection): boolean {
return connection.quotaVisible !== false;
}
-export function supportsProviderQuota(providerId: string): boolean {
- return USAGE_SUPPORTED_PROVIDERS.includes(providerId);
+export function supportsProviderQuota(
+ providerId: string,
+ connection?: { provider?: string; providerSpecificData?: unknown },
+): boolean {
+ if (USAGE_SUPPORTED_PROVIDERS.includes(providerId)) return true;
+ return isMoonshotOpenPlatformConnection({
+ provider: providerId,
+ providerSpecificData: connection?.providerSpecificData,
+ });
}
diff --git a/src/shared/validation/schemas/provider.ts b/src/shared/validation/schemas/provider.ts
index fa6f0ef055..c93e563ab7 100644
--- a/src/shared/validation/schemas/provider.ts
+++ b/src/shared/validation/schemas/provider.ts
@@ -22,16 +22,36 @@ import {
isReservedProviderPrefix,
reservedProviderPrefixMessage,
} from "@/shared/constants/reservedProviderPrefixes";
-
+import {
+ isValidIanaTimeZone,
+ isValidResetHour,
+} from "@omniroute/open-sse/services/dailyQuotaReset.ts";
import {
upstreamHeadersRecordSchema,
modelCompatPerProtocolSchema,
customHeadersSchema,
} from "./misc.ts";
+import { isValidProviderIconUrl } from "@/shared/validation/iconUrl";
export { validateProviderSpecificData };
-import { isValidProviderIconUrl } from "@/shared/validation/iconUrl";
+const dailyQuotaResetTimezoneSchema = z
+ .string()
+ .trim()
+ .optional()
+ .or(z.literal(""))
+ .refine((value) => !value || isValidIanaTimeZone(value), {
+ message: "Unknown IANA timezone",
+ });
+
+const dailyQuotaResetHourSchema = z
+ .number()
+ .int()
+ .optional()
+ .nullable()
+ .refine((value) => value == null || isValidResetHour(value), {
+ message: "Hour must be 0-23",
+ });
// ──── Provider Schemas ────
@@ -337,6 +357,8 @@ export const createProviderNodeSchema = z
// isValidProviderIconUrl (2000 chars for http(s), 256 KiB for data:image).
iconUrl: providerNodeIconUrlSchema,
customHeaders: customHeadersSchema,
+ dailyQuotaResetTimezone: dailyQuotaResetTimezoneSchema,
+ dailyQuotaResetHour: dailyQuotaResetHourSchema,
})
.superRefine((value, ctx) => {
const nodeType = value.type || "openai-compatible";
@@ -419,6 +441,8 @@ export const updateProviderNodeSchema = z
// clears a previously stored custom icon.
iconUrl: providerNodeIconUrlSchema,
customHeaders: customHeadersSchema,
+ dailyQuotaResetTimezone: dailyQuotaResetTimezoneSchema,
+ dailyQuotaResetHour: dailyQuotaResetHourSchema,
})
.superRefine((value, ctx) => {
// Reserved-prefix guard (tokenrouter bug) — same rationale as the guard in
diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts
index 6c426bcfce..7ad2131076 100644
--- a/src/sse/handlers/chat.ts
+++ b/src/sse/handlers/chat.ts
@@ -174,6 +174,7 @@ import { registerBailianCodingPlanQuotaFetcher } from "@omniroute/open-sse/servi
import { registerQwenTokenPlanQuotaFetcher } from "@omniroute/open-sse/services/qwenTokenPlanQuotaFetcher.ts";
import { registerCrofUsageFetcher } from "@omniroute/open-sse/services/crofUsageFetcher.ts";
import { registerDeepseekQuotaFetcher } from "@omniroute/open-sse/services/deepseekQuotaFetcher.ts";
+import { registerMoonshotQuotaFetcher, registerMoonshotFetchersForNodes } from "@omniroute/open-sse/services/moonshotQuotaFetcher.ts";
import { registerOpenrouterQuotaFetcher } from "@omniroute/open-sse/services/openrouterQuotaFetcher.ts";
import { registerOpencodeQuotaFetcher } from "@omniroute/open-sse/services/opencodeQuotaFetcher.ts";
import { registerGrokWebQuotaFetcher } from "@omniroute/open-sse/services/grokQuotaFetcher.ts";
@@ -221,6 +222,21 @@ registerCrofUsageFetcher();
// Register DeepSeek balance quota fetcher.
// Hooks into quotaPreflight + quotaMonitor so combos can switch accounts before balance is exhausted.
registerDeepseekQuotaFetcher();
+registerMoonshotQuotaFetcher();
+void import("@/lib/db/providers")
+ .then(({ getProviderNodes }) => getProviderNodes())
+ .then((nodes) => {
+ registerMoonshotFetchersForNodes(
+ (Array.isArray(nodes) ? nodes : []).map((node) => ({
+ id: typeof node.id === "string" ? node.id : null,
+ prefix: typeof node.prefix === "string" ? node.prefix : null,
+ baseUrl: typeof node.baseUrl === "string" ? node.baseUrl : null,
+ })),
+ );
+ })
+ .catch((error) => {
+ console.warn("[STARTUP] Moonshot custom-node fetcher scan skipped:", error);
+ });
registerOpenrouterQuotaFetcher();
// Register OpenCode quota fetcher (opencode-go / opencode / opencode-zen).
diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts
index 697c8a036f..b121876376 100644
--- a/src/sse/services/auth.ts
+++ b/src/sse/services/auth.ts
@@ -2467,6 +2467,26 @@ export function isAgentrouterConnectionQuotaScope(
);
}
+async function resolveDailyResetForProvider(
+ provider: string | null,
+): Promise<{ timezone?: unknown; hour?: unknown } | null> {
+ if (!provider) return null;
+ try {
+ const nodes = await getCachedProviderNodes();
+ const node = nodes.find((candidate) => {
+ if (!candidate) return false;
+ return candidate.id === provider || candidate.prefix === provider;
+ });
+ if (!node) return null;
+ return {
+ timezone: node.dailyQuotaResetTimezone,
+ hour: node.dailyQuotaResetHour,
+ };
+ } catch {
+ return null;
+ }
+}
+
/**
* #10880 — cools down every connection sharing the failing connection's last
* known egress IP. Best-effort and side-effect-safe by design:
@@ -2683,7 +2703,10 @@ export async function markAccountUnavailable(
model,
provider,
options.headers ?? null,
- effectiveProviderProfile
+ effectiveProviderProfile,
+ null,
+ null,
+ await resolveDailyResetForProvider(provider),
);
// T-PROBE: probe-origin failures (model test-all) must never remove the
diff --git a/stryker.conf.json b/stryker.conf.json
index 58dd1f4d4f..96f421f624 100644
--- a/stryker.conf.json
+++ b/stryker.conf.json
@@ -58,6 +58,7 @@
"tests/unit/account-fallback-retry-after-json.test.ts",
"tests/unit/account-fallback-route-restriction-403.test.ts",
"tests/unit/account-fallback-service.test.ts",
+ "tests/unit/moonshot-quota-writeback.test.ts",
"tests/unit/accountfallback-ratelimit-400-4976.test.ts",
"tests/unit/adaptive-admission-route-matrix.test.ts",
"tests/unit/adaptive-admission-runtime.test.ts",
diff --git a/tests/unit/account-fallback-service.test.ts b/tests/unit/account-fallback-service.test.ts
index d28075545a..d2693a5acf 100644
--- a/tests/unit/account-fallback-service.test.ts
+++ b/tests/unit/account-fallback-service.test.ts
@@ -2005,3 +2005,51 @@ test("#10460 acceptance: unambiguous model-unsupported 400 makes exactly ONE ups
);
}
});
+
+const MOONSHOT_COMPAT = "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf";
+const MOONSHOT_TPD =
+ "[429]: Your account org-x / proj-y request reached organization TPD rate limit, current: 1537190, limit: 1500000";
+const MOONSHOT_BROKE =
+ "[429]: Your account org-x is suspended due to insufficient balance, please recharge your account or check your plan and billing details";
+
+test("checkFallbackError: compatible Moonshot insufficient balance is creditsExhausted", () => {
+ const result = checkFallbackError(429, MOONSHOT_BROKE, 0, null, MOONSHOT_COMPAT);
+ assert.equal(result.creditsExhausted, true);
+ assert.equal(result.reason, RateLimitReason.QUOTA_EXHAUSTED);
+});
+
+test("checkFallbackError: compatible node empty wallet without billing-suspend phrasing is creditsExhausted", () => {
+ const result = checkFallbackError(
+ 429,
+ "You have insufficient balance, please recharge your account",
+ 0,
+ null,
+ MOONSHOT_COMPAT,
+ );
+ assert.equal(result.creditsExhausted, true);
+ assert.equal(result.reason, RateLimitReason.QUOTA_EXHAUSTED);
+});
+
+test("isDailyQuotaExhausted detects organization TPD rate limit", () => {
+ const { isDailyQuotaExhausted } = accountFallback;
+ assert.equal(isDailyQuotaExhausted(MOONSHOT_TPD), true);
+ assert.equal(isDailyQuotaExhausted("The engine is currently overloaded"), false);
+});
+
+test("checkFallbackError: TPD with node clock uses that instant, not host midnight", () => {
+ const now = Date.parse("2026-09-02T07:30:00Z");
+ const result = checkFallbackError(429, MOONSHOT_TPD, 0, null, MOONSHOT_COMPAT, null, null, null, null, {
+ timezone: "Asia/Shanghai",
+ hour: 0,
+ nowMs: now,
+ });
+ assert.equal(result.dailyQuotaExhausted, true);
+ assert.equal(result.cooldownMs, Date.parse("2026-09-02T16:00:00Z") - now);
+ assert.equal(result.reason, RateLimitReason.QUOTA_EXHAUSTED);
+});
+
+test("checkFallbackError: TPD without clock or header is NOT host-midnight lock", () => {
+ const result = checkFallbackError(429, MOONSHOT_TPD, 0, null, MOONSHOT_COMPAT);
+ assert.notEqual(result.dailyQuotaExhausted, true);
+ assert.ok(result.cooldownMs < 2 * 60 * 60 * 1000);
+});
diff --git a/tests/unit/api-key-self-service.test.ts b/tests/unit/api-key-self-service.test.ts
index 6b768f7950..242c3b22b3 100644
--- a/tests/unit/api-key-self-service.test.ts
+++ b/tests/unit/api-key-self-service.test.ts
@@ -435,3 +435,39 @@ test("self-service status normalizes Codex account quota for one explicit connec
},
});
});
+
+test("self-service fetches Moonshot custom-node quota via providerSpecificData host", async () => {
+ const metadata = {
+ id: "key-mnative",
+ name: "moonshot native",
+ scopes: [SELF_USAGE_SCOPE, SELF_ACCOUNT_QUOTA_SCOPE],
+ allowedConnections: ["conn-mnative"],
+ };
+ const fetches: string[] = [];
+ const { deps } = makeDeps({
+ getProviderConnectionById: async (connectionId: string) => ({
+ id: connectionId,
+ provider: "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf",
+ isActive: true,
+ providerSpecificData: { baseUrl: "https://api.moonshot.cn/v1" },
+ }),
+ fetchAndPersistProviderLimits: async (connectionId: string) => {
+ fetches.push(connectionId);
+ return {
+ connection: { id: connectionId, provider: "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf" },
+ usage: {
+ plan: "Kimi 开放平台(国内)",
+ quotas: {
+ available: { remaining: 15, remainingPercentage: 100, unlimited: true, currency: "CNY" },
+ },
+ },
+ cache: { quotas: null, plan: null, message: null, fetchedAt: "" },
+ };
+ },
+ });
+
+ const status = await buildApiKeySelfServiceStatus(metadata, deps);
+ assert.deepEqual(fetches, ["conn-mnative"]);
+ assert.equal(status.accountQuotas[0].unavailable, undefined);
+ assert.equal(status.accountQuotas[0].plan, "Kimi 开放平台(国内)");
+});
diff --git a/tests/unit/classify429.test.ts b/tests/unit/classify429.test.ts
index d75d7f648f..6d42875afe 100644
--- a/tests/unit/classify429.test.ts
+++ b/tests/unit/classify429.test.ts
@@ -443,3 +443,18 @@ test("classify429: retryDelay outside a RetryInfo detail is ignored", () => {
};
assert.equal(classify429({ status: 429, body }), "quota_exhausted");
});
+
+const MOONSHOT_TPD =
+ "Your account org-73b383ab6d45484eb2ef72161074495c / proj-30863601b47548bdbbabc42ff4c72eee request reached organization TPD rate limit, current: 1537190, limit: 1500000";
+
+test("classify429: Moonshot organization TPD rate limit is quota_exhausted", () => {
+ assert.equal(classify429({ status: 429, body: MOONSHOT_TPD }), "quota_exhausted");
+ assert.equal(looksLikeQuotaExhausted(MOONSHOT_TPD), true);
+});
+
+test("classify429: Moonshot engine overloaded stays rate_limit", () => {
+ assert.equal(
+ classify429({ status: 429, body: "The engine is currently overloaded, please try again later" }),
+ "rate_limit",
+ );
+});
diff --git a/tests/unit/daily-quota-reset.test.ts b/tests/unit/daily-quota-reset.test.ts
new file mode 100644
index 0000000000..0d2c78cfaf
--- /dev/null
+++ b/tests/unit/daily-quota-reset.test.ts
@@ -0,0 +1,54 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import {
+ isValidIanaTimeZone,
+ isValidResetHour,
+ nodeDailyResetConfigured,
+ nextDailyResetAtMs,
+ parseTpdLimitFromText,
+} from "../../open-sse/services/dailyQuotaReset.ts";
+
+test("IANA: Asia/Shanghai ok, garbage rejected", () => {
+ assert.equal(isValidIanaTimeZone("Asia/Shanghai"), true);
+ assert.equal(isValidIanaTimeZone("America/New_York"), true);
+ assert.equal(isValidIanaTimeZone("Not/AZone"), false);
+ assert.equal(isValidIanaTimeZone(""), false);
+});
+
+test("isValidResetHour accepts 0-23 integers only", () => {
+ assert.equal(isValidResetHour(0), true);
+ assert.equal(isValidResetHour(23), true);
+ assert.equal(isValidResetHour(24), false);
+ assert.equal(isValidResetHour(-1), false);
+ assert.equal(isValidResetHour(1.5), false);
+ assert.equal(isValidResetHour(null), false);
+});
+
+test("nodeDailyResetConfigured requires both fields", () => {
+ assert.equal(nodeDailyResetConfigured("Asia/Shanghai", 0), true);
+ assert.equal(nodeDailyResetConfigured("Asia/Shanghai", null), false);
+ assert.equal(nodeDailyResetConfigured(null, 0), false);
+ assert.equal(nodeDailyResetConfigured("Not/AZone", 0), false);
+ assert.equal(nodeDailyResetConfigured("Asia/Shanghai", 24), false);
+});
+
+test("nextDailyResetAtMs locks to next local hour:00", () => {
+ // 2026-09-02 15:30 in Asia/Shanghai = 2026-09-02 07:30 UTC
+ const now = Date.parse("2026-09-02T07:30:00Z");
+ const next = nextDailyResetAtMs("Asia/Shanghai", 0, now);
+ // next calendar day 00:00 Shanghai = 2026-09-02 16:00 UTC
+ assert.equal(next, Date.parse("2026-09-02T16:00:00Z"));
+});
+
+test("nextDailyResetAtMs at exact reset instant returns the following cycle", () => {
+ const exactly = Date.parse("2026-09-02T16:00:00Z"); // 00:00 Shanghai
+ const next = nextDailyResetAtMs("Asia/Shanghai", 0, exactly);
+ assert.equal(next, Date.parse("2026-09-03T16:00:00Z"));
+});
+
+test("parseTpdLimitFromText reads limit: from live body", () => {
+ const body =
+ "request reached organization TPD rate limit, current: 1537190, limit: 1500000";
+ assert.equal(parseTpdLimitFromText(body), 1_500_000);
+ assert.equal(parseTpdLimitFromText("no numbers"), null);
+});
diff --git a/tests/unit/moonshot-open-platform.test.ts b/tests/unit/moonshot-open-platform.test.ts
new file mode 100644
index 0000000000..c44e1d4859
--- /dev/null
+++ b/tests/unit/moonshot-open-platform.test.ts
@@ -0,0 +1,72 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import {
+ parseMoonshotOrigin,
+ moonshotBalanceUrl,
+ resolveMoonshotOrigin,
+ isMoonshotOpenPlatformConnection,
+} from "../../open-sse/services/usage/moonshotOpenPlatform.ts";
+
+const CN = "https://api.moonshot.cn/v1";
+const AI = "https://api.moonshot.ai/v1";
+const COMPAT = "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf";
+
+test("parseMoonshotOrigin accepts cn and ai hosts only", () => {
+ assert.equal(parseMoonshotOrigin(CN), "https://api.moonshot.cn");
+ assert.equal(
+ parseMoonshotOrigin("https://api.moonshot.cn/v1/chat/completions"),
+ "https://api.moonshot.cn",
+ );
+ assert.equal(parseMoonshotOrigin(AI), "https://api.moonshot.ai");
+ assert.equal(parseMoonshotOrigin("https://api.openai.com/v1"), null);
+ assert.equal(parseMoonshotOrigin("https://api.kimi.com/coding/v1"), null);
+ assert.equal(parseMoonshotOrigin(""), null);
+ assert.equal(parseMoonshotOrigin(null), null);
+});
+
+test("moonshotBalanceUrl stays on the connection origin", () => {
+ assert.equal(
+ moonshotBalanceUrl("https://api.moonshot.cn"),
+ "https://api.moonshot.cn/v1/users/me/balance",
+ );
+ assert.equal(
+ moonshotBalanceUrl("https://api.moonshot.ai"),
+ "https://api.moonshot.ai/v1/users/me/balance",
+ );
+});
+
+test("resolveMoonshotOrigin prefers psd.baseUrl over node", () => {
+ const origin = resolveMoonshotOrigin(
+ {
+ provider: COMPAT,
+ providerSpecificData: { baseUrl: CN },
+ },
+ "https://api.moonshot.ai/v1",
+ );
+ assert.equal(origin, "https://api.moonshot.cn");
+});
+
+test("resolveMoonshotOrigin uses node baseUrl when psd has none", () => {
+ const origin = resolveMoonshotOrigin({ provider: COMPAT, providerSpecificData: {} }, CN);
+ assert.equal(origin, "https://api.moonshot.cn");
+});
+
+test("resolveMoonshotOrigin uses built-in moonshot/kimi registry host", () => {
+ assert.equal(resolveMoonshotOrigin({ provider: "moonshot" }), "https://api.moonshot.ai");
+ assert.equal(resolveMoonshotOrigin({ provider: "kimi" }), "https://api.moonshot.ai");
+});
+
+test("isMoonshotOpenPlatformConnection is true for mnative-shaped rows", () => {
+ assert.equal(
+ isMoonshotOpenPlatformConnection({
+ provider: COMPAT,
+ providerSpecificData: { baseUrl: CN, prefix: "mnative" },
+ }),
+ true,
+ );
+ assert.equal(
+ isMoonshotOpenPlatformConnection({ provider: "deepseek", providerSpecificData: {} }),
+ false,
+ );
+ assert.equal(isMoonshotOpenPlatformConnection({ provider: "moonshot" }), true);
+});
diff --git a/tests/unit/moonshot-quota-fetcher.test.ts b/tests/unit/moonshot-quota-fetcher.test.ts
new file mode 100644
index 0000000000..1c315632b9
--- /dev/null
+++ b/tests/unit/moonshot-quota-fetcher.test.ts
@@ -0,0 +1,158 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+
+import {
+ fetchMoonshotQuota,
+ invalidateMoonshotQuotaCache,
+ registerMoonshotQuotaFetcher,
+ getMoonshotOpenPlatformUsage,
+} from "../../open-sse/services/moonshotQuotaFetcher.ts";
+import { getUsageForProvider } from "../../open-sse/services/usage.ts";
+import { getQuotaFetcher } from "../../open-sse/services/quotaPreflight.ts";
+
+const originalFetch = globalThis.fetch;
+const CN = "https://api.moonshot.cn/v1";
+const COMPAT = "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf";
+
+test.afterEach(() => {
+ globalThis.fetch = originalFetch;
+});
+
+function jsonResponse(body: unknown, status = 200): Response {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "Content-Type": "application/json" },
+ });
+}
+
+test("fetchMoonshotQuota parses available_balance > 0 as not exhausted", async () => {
+ const connectionId = `ms-pos-${Date.now()}`;
+ globalThis.fetch = async (url) => {
+ assert.equal(String(url), "https://api.moonshot.cn/v1/users/me/balance");
+ return jsonResponse({
+ code: 0,
+ data: { available_balance: 2.5, voucher_balance: 0, cash_balance: 2.5 },
+ status: true,
+ });
+ };
+ const q = await fetchMoonshotQuota(connectionId, {
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: CN },
+ });
+ assert.equal(q?.limitReached, false);
+ assert.equal(q?.percentUsed, 0);
+ invalidateMoonshotQuotaCache(connectionId);
+});
+
+test("fetchMoonshotQuota treats available_balance 0 as exhausted", async () => {
+ const connectionId = `ms-zero-${Date.now()}`;
+ globalThis.fetch = async () =>
+ jsonResponse({
+ code: 0,
+ data: { available_balance: 0, voucher_balance: 0, cash_balance: 0 },
+ status: true,
+ });
+ const q = await fetchMoonshotQuota(connectionId, {
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: CN },
+ });
+ assert.equal(q?.limitReached, true);
+ invalidateMoonshotQuotaCache(connectionId);
+});
+
+test("fetchMoonshotQuota returns null on 401", async () => {
+ const connectionId = `ms-401-${Date.now()}`;
+ globalThis.fetch = async () => new Response(null, { status: 401 });
+ const q = await fetchMoonshotQuota(connectionId, {
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: CN },
+ });
+ assert.equal(q, null);
+});
+
+test("getUsageForProvider on custom uuid hits Moonshot path", async () => {
+ const connectionId = `ms-uuid-${Date.now()}`;
+ globalThis.fetch = async () =>
+ jsonResponse({
+ code: 0,
+ data: { available_balance: 15, voucher_balance: 15, cash_balance: 0 },
+ status: true,
+ });
+ const usage = await getUsageForProvider({
+ id: connectionId,
+ provider: COMPAT,
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: CN },
+ });
+ assert.equal(typeof usage === "object" && usage && "message" in usage, false);
+ assert.equal((usage as { plan?: string }).plan, "Kimi 开放平台(国内)");
+ invalidateMoonshotQuotaCache(connectionId);
+});
+
+test("getMoonshotOpenPlatformUsage uses Open Platform plan label for .ai host", async () => {
+ const connectionId = `ms-ai-${Date.now()}`;
+ globalThis.fetch = async (url) => {
+ assert.equal(String(url), "https://api.moonshot.ai/v1/users/me/balance");
+ return jsonResponse({
+ code: 0,
+ data: { available_balance: 1, voucher_balance: 0, cash_balance: 1 },
+ status: true,
+ });
+ };
+ const usage = await getMoonshotOpenPlatformUsage({
+ id: connectionId,
+ provider: "moonshot",
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: "https://api.moonshot.ai/v1" },
+ });
+ assert.equal(usage.plan, "Kimi Open Platform");
+ invalidateMoonshotQuotaCache(connectionId);
+});
+
+test("domestic Moonshot balance is labeled CNY, international USD", async () => {
+ const cnId = `ms-cny-${Date.now()}`;
+ globalThis.fetch = async () =>
+ jsonResponse({
+ code: 0,
+ data: { available_balance: 15, voucher_balance: 15, cash_balance: 0 },
+ status: true,
+ });
+ const cn = await getMoonshotOpenPlatformUsage({
+ id: cnId,
+ provider: COMPAT,
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: CN },
+ });
+ assert.equal(cn.quotas?.available?.currency, "CNY");
+ invalidateMoonshotQuotaCache(cnId);
+
+ const aiId = `ms-usd-${Date.now()}`;
+ const ai = await getMoonshotOpenPlatformUsage({
+ id: aiId,
+ provider: "moonshot",
+ apiKey: "sk-test",
+ providerSpecificData: { baseUrl: "https://api.moonshot.ai/v1" },
+ });
+ assert.equal(ai.quotas?.available?.currency, "USD");
+ invalidateMoonshotQuotaCache(aiId);
+});
+
+test("registerMoonshotQuotaFetcher wires moonshot and kimi ids", () => {
+ registerMoonshotQuotaFetcher();
+ assert.equal(typeof getQuotaFetcher("moonshot"), "function");
+ assert.equal(typeof getQuotaFetcher("kimi"), "function");
+});
+
+test("registerMoonshotFetchersForNodes registers custom node id and prefix", async () => {
+ const { registerMoonshotFetchersForNodes } = await import(
+ "../../open-sse/services/moonshotQuotaFetcher.ts"
+ );
+ const uuid = "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf";
+ registerMoonshotFetchersForNodes([
+ { id: uuid, prefix: "mnative", baseUrl: CN },
+ { id: "other", prefix: "oc-prod", baseUrl: "https://api.openai.com/v1" },
+ ]);
+ assert.equal(typeof getQuotaFetcher(uuid), "function");
+ assert.equal(typeof getQuotaFetcher("mnative"), "function");
+ assert.equal(getQuotaFetcher("oc-prod"), undefined);
+});
diff --git a/tests/unit/moonshot-quota-writeback.test.ts b/tests/unit/moonshot-quota-writeback.test.ts
new file mode 100644
index 0000000000..14c8e3168b
--- /dev/null
+++ b/tests/unit/moonshot-quota-writeback.test.ts
@@ -0,0 +1,50 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { classify429 } from "../../src/shared/utils/classify429.ts";
+import { checkFallbackError } from "../../open-sse/services/accountFallback.ts";
+import { registerMoonshotFetchersForNodes } from "../../open-sse/services/moonshotQuotaFetcher.ts";
+import { getQuotaFetcher } from "../../open-sse/services/quotaPreflight.ts";
+
+const MOONSHOT_TPD =
+ "Your account org-73b383ab6d45484eb2ef72161074495c / proj-30863601b47548bdbbabc42ff4c72eee request reached organization TPD rate limit, current: 1537190, limit: 1500000";
+const MOONSHOT_BROKE = "insufficient balance";
+const COMPAT = "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf";
+
+test("classify429 maps Moonshot TPD to quota_exhausted so combo persist stays on", () => {
+ assert.equal(classify429({ status: 429, body: MOONSHOT_TPD }), "quota_exhausted");
+});
+
+test("checkFallbackError TPD with node clock returns future cooldown, not host midnight", () => {
+ const now = Date.parse("2026-09-02T07:30:00Z");
+ const result = checkFallbackError(
+ 429,
+ MOONSHOT_TPD,
+ 0,
+ "kimi-k2.5",
+ COMPAT,
+ null,
+ null,
+ null,
+ null,
+ { timezone: "Asia/Shanghai", hour: 0, nowMs: now },
+ );
+ assert.equal(result.shouldFallback, true);
+ assert.equal(result.dailyQuotaExhausted, true);
+ assert.ok(result.cooldownMs > 8 * 3600_000);
+ const until = now + result.cooldownMs;
+ assert.ok(Math.abs(until - Date.parse("2026-09-02T16:00:00Z")) < 60_000);
+});
+
+test("checkFallbackError insufficient balance on compatible node is creditsExhausted", () => {
+ const result = checkFallbackError(429, MOONSHOT_BROKE, 0, "kimi-k2.5", COMPAT);
+ assert.equal(result.creditsExhausted, true);
+ assert.equal(result.shouldFallback, true);
+});
+
+test("registerMoonshotFetchersForNodes wires uuid and prefix after startup scan", () => {
+ registerMoonshotFetchersForNodes([
+ { id: COMPAT, prefix: "mnative", baseUrl: "https://api.moonshot.cn/v1" },
+ ]);
+ assert.equal(typeof getQuotaFetcher(COMPAT), "function");
+ assert.equal(typeof getQuotaFetcher("mnative"), "function");
+});
diff --git a/tests/unit/provider-node-daily-reset-schema.test.ts b/tests/unit/provider-node-daily-reset-schema.test.ts
new file mode 100644
index 0000000000..f20a918b68
--- /dev/null
+++ b/tests/unit/provider-node-daily-reset-schema.test.ts
@@ -0,0 +1,54 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+
+const { createProviderNodeSchema, updateProviderNodeSchema } =
+ await import("../../src/shared/validation/schemas/provider.ts");
+
+const BASE = {
+ name: "Moonshot native",
+ prefix: "mnative",
+ baseUrl: "https://api.moonshot.cn/v1",
+};
+
+test("updateProviderNodeSchema accepts IANA timezone + hour 0", () => {
+ const result = updateProviderNodeSchema.safeParse({
+ ...BASE,
+ dailyQuotaResetTimezone: "Asia/Shanghai",
+ dailyQuotaResetHour: 0,
+ });
+ assert.equal(result.success, true);
+});
+
+test("updateProviderNodeSchema rejects unknown timezone", () => {
+ const result = updateProviderNodeSchema.safeParse({
+ ...BASE,
+ dailyQuotaResetTimezone: "Shanghai",
+ dailyQuotaResetHour: 0,
+ });
+ assert.equal(result.success, false);
+});
+
+test("updateProviderNodeSchema rejects hour 24", () => {
+ const result = updateProviderNodeSchema.safeParse({
+ ...BASE,
+ dailyQuotaResetTimezone: "Asia/Shanghai",
+ dailyQuotaResetHour: 24,
+ });
+ assert.equal(result.success, false);
+});
+
+test("updateProviderNodeSchema accepts omitted reset clock", () => {
+ const result = updateProviderNodeSchema.safeParse(BASE);
+ assert.equal(result.success, true);
+});
+
+test("createProviderNodeSchema accepts IANA timezone + hour", () => {
+ const result = createProviderNodeSchema.safeParse({
+ ...BASE,
+ apiType: "chat",
+ type: "openai-compatible",
+ dailyQuotaResetTimezone: "UTC",
+ dailyQuotaResetHour: 0,
+ });
+ assert.equal(result.success, true);
+});
diff --git a/tests/unit/provider-quota-visibility.test.ts b/tests/unit/provider-quota-visibility.test.ts
index 665687feed..a8551e3ccb 100644
--- a/tests/unit/provider-quota-visibility.test.ts
+++ b/tests/unit/provider-quota-visibility.test.ts
@@ -16,3 +16,15 @@ test("quota visibility controls are limited to providers with quota support", ()
assert.equal(supportsProviderQuota("codex"), true);
assert.equal(supportsProviderQuota("openai"), false);
});
+
+test("supportsProviderQuota is true for moonshot-native shaped connection", () => {
+ assert.equal(
+ supportsProviderQuota("openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf", {
+ providerSpecificData: { baseUrl: "https://api.moonshot.cn/v1" },
+ }),
+ true,
+ );
+ assert.equal(supportsProviderQuota("moonshot"), true);
+ assert.equal(supportsProviderQuota("kimi"), true);
+ assert.equal(supportsProviderQuota("openai"), false);
+});
diff --git a/tests/unit/qoder-usage-quota.test.ts b/tests/unit/qoder-usage-quota.test.ts
index 14666abc11..8e69a8991e 100644
--- a/tests/unit/qoder-usage-quota.test.ts
+++ b/tests/unit/qoder-usage-quota.test.ts
@@ -148,6 +148,15 @@ test("a qoder PAT (apikey) connection is picked up by the provider-limits sync",
isSupportedUsageConnection({ id: "c2", provider: "some-random-provider", authType: "apikey" }),
false
);
+ assert.equal(
+ isSupportedUsageConnection({
+ id: "c3",
+ provider: "openai-compatible-chat-e2971611-bc02-4c37-8fc5-39b8e3906fdf",
+ authType: "apikey",
+ providerSpecificData: { baseUrl: "https://api.moonshot.cn/v1" },
+ }),
+ true,
+ );
});
// Guards the shared exchange contract the usage path relies on.