From 831ea040c3a45cc24ecffeba2bd3834f360b1f69 Mon Sep 17 00:00:00 2001 From: "Bob.Hou" Date: Thu, 3 Sep 2026 11:47:50 -0400 Subject: [PATCH] feat(quota): Moonshot Open Platform balance and TPD lock for custom nodes (#12590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca. O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva. --- AGENTS.md | 2 +- README.md | 2 +- config/quality/file-size-baseline.json | 11 +- docs/i18n/ar/llm.txt | 8 +- docs/i18n/az/llm.txt | 8 +- docs/i18n/bg/llm.txt | 8 +- docs/i18n/bn/llm.txt | 8 +- docs/i18n/cs/llm.txt | 8 +- docs/i18n/da/llm.txt | 8 +- docs/i18n/de/llm.txt | 8 +- docs/i18n/es/llm.txt | 8 +- docs/i18n/fa/llm.txt | 8 +- docs/i18n/fi/llm.txt | 8 +- docs/i18n/fr/llm.txt | 8 +- docs/i18n/gu/llm.txt | 8 +- docs/i18n/he/llm.txt | 8 +- docs/i18n/hi/llm.txt | 8 +- docs/i18n/hu/llm.txt | 8 +- docs/i18n/id/llm.txt | 8 +- docs/i18n/it/llm.txt | 8 +- docs/i18n/ja/llm.txt | 8 +- docs/i18n/ko/llm.txt | 8 +- docs/i18n/mr/llm.txt | 8 +- docs/i18n/ms/llm.txt | 8 +- docs/i18n/nl/llm.txt | 8 +- docs/i18n/no/llm.txt | 8 +- docs/i18n/phi/llm.txt | 8 +- docs/i18n/pl/llm.txt | 8 +- docs/i18n/pt-BR/llm.txt | 8 +- docs/i18n/pt/llm.txt | 8 +- docs/i18n/ro/llm.txt | 8 +- docs/i18n/ru/llm.txt | 8 +- docs/i18n/sk/llm.txt | 8 +- docs/i18n/sv/llm.txt | 8 +- docs/i18n/sw/llm.txt | 8 +- docs/i18n/ta/llm.txt | 8 +- docs/i18n/te/llm.txt | 8 +- docs/i18n/th/llm.txt | 8 +- docs/i18n/tr/llm.txt | 8 +- docs/i18n/uk-UA/llm.txt | 8 +- docs/i18n/ur/llm.txt | 8 +- docs/i18n/vi/llm.txt | 8 +- docs/i18n/zh-CN/llm.txt | 8 +- docs/i18n/zh-TW/llm.txt | 8 +- llm.txt | 8 +- open-sse/services/accountFallback.ts | 67 +++-- open-sse/services/dailyQuotaReset.ts | 145 +++++++++++ open-sse/services/moonshotQuotaFetcher.ts | 229 ++++++++++++++++++ open-sse/services/usage.ts | 9 + open-sse/services/usage/fetcherProviders.ts | 2 + .../services/usage/moonshotOpenPlatform.ts | 76 ++++++ open-sse/services/usage/supportedProviders.ts | 2 + .../modals/EditCompatibleNodeModal.tsx | 29 +++ .../usage/components/ProviderLimits/index.tsx | 5 +- .../(dashboard)/home/ProviderQuotaWidget.tsx | 5 +- src/app/api/provider-nodes/[id]/route.ts | 20 +- src/app/api/provider-nodes/route.ts | 35 +++ src/i18n/messages/ar.json | 4 + src/i18n/messages/az.json | 4 + src/i18n/messages/bg.json | 4 + src/i18n/messages/bn.json | 4 + src/i18n/messages/cs.json | 4 + src/i18n/messages/da.json | 4 + src/i18n/messages/de.json | 4 + src/i18n/messages/en.json | 4 + src/i18n/messages/es.json | 4 + src/i18n/messages/fa.json | 4 + src/i18n/messages/fi.json | 4 + src/i18n/messages/fr.json | 4 + src/i18n/messages/gu.json | 4 + src/i18n/messages/he.json | 4 + src/i18n/messages/hi.json | 4 + src/i18n/messages/hu.json | 4 + src/i18n/messages/id.json | 4 + src/i18n/messages/it.json | 4 + src/i18n/messages/ja.json | 4 + src/i18n/messages/ko.json | 4 + src/i18n/messages/mr.json | 4 + src/i18n/messages/ms.json | 4 + src/i18n/messages/nl.json | 4 + src/i18n/messages/no.json | 4 + src/i18n/messages/phi.json | 4 + src/i18n/messages/pl.json | 4 + src/i18n/messages/pt-BR.json | 4 + src/i18n/messages/pt.json | 4 + src/i18n/messages/ro.json | 4 + src/i18n/messages/ru.json | 4 + src/i18n/messages/sk.json | 4 + src/i18n/messages/sv.json | 4 + src/i18n/messages/sw.json | 4 + src/i18n/messages/ta.json | 4 + src/i18n/messages/te.json | 4 + src/i18n/messages/th.json | 4 + src/i18n/messages/tr.json | 4 + src/i18n/messages/uk-UA.json | 4 + src/i18n/messages/ur.json | 4 + src/i18n/messages/vi.json | 4 + src/i18n/messages/zh-CN.json | 4 + src/i18n/messages/zh-TW.json | 4 + src/instrumentation-node.ts | 16 ++ src/lib/db/migrationRunner.ts | 5 + .../172_provider_node_daily_quota_reset.sql | 6 + src/lib/db/providers/nodes.ts | 59 ++--- src/lib/usage/apiKeySelfService.ts | 20 +- src/lib/usage/providerLimits.ts | 21 +- src/shared/utils/classify429.ts | 11 + src/shared/utils/providerQuotaVisibility.ts | 14 +- src/shared/validation/schemas/provider.ts | 28 ++- src/sse/handlers/chat.ts | 16 ++ src/sse/services/auth.ts | 25 +- stryker.conf.json | 1 + tests/unit/account-fallback-service.test.ts | 48 ++++ tests/unit/api-key-self-service.test.ts | 36 +++ tests/unit/classify429.test.ts | 15 ++ tests/unit/daily-quota-reset.test.ts | 54 +++++ tests/unit/moonshot-open-platform.test.ts | 72 ++++++ tests/unit/moonshot-quota-fetcher.test.ts | 158 ++++++++++++ tests/unit/moonshot-quota-writeback.test.ts | 50 ++++ .../provider-node-daily-reset-schema.test.ts | 54 +++++ tests/unit/provider-quota-visibility.test.ts | 12 + tests/unit/qoder-usage-quota.test.ts | 9 + 121 files changed, 1626 insertions(+), 247 deletions(-) create mode 100644 open-sse/services/dailyQuotaReset.ts create mode 100644 open-sse/services/moonshotQuotaFetcher.ts create mode 100644 open-sse/services/usage/moonshotOpenPlatform.ts create mode 100644 src/lib/db/migrations/172_provider_node_daily_quota_reset.sql create mode 100644 tests/unit/daily-quota-reset.test.ts create mode 100644 tests/unit/moonshot-open-platform.test.ts create mode 100644 tests/unit/moonshot-quota-fetcher.test.ts create mode 100644 tests/unit/moonshot-quota-writeback.test.ts create mode 100644 tests/unit/provider-node-daily-reset-schema.test.ts 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 RuntimeNode.js 22.x / 24.x LTS — >=22.22.2 <23 || >=24.0.0 <27 LanguageTypeScript 6.0 — 100% TypeScript across src/ and open-sse/ (zero any in core since v2.0) FrameworkNext.js 16 + React 19 + Tailwind CSS 4 - Databasebetter-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 168 migrations + Databasebetter-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 169 migrations MemorySQLite FTS5 full-text + int8-quantized vector embeddings, typed decay SchemasZod 4 — MCP tool I/O validation + API contracts ProtocolsMCP (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.