diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9317e3102..9937d3512d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -533,6 +533,15 @@ jobs: env: BASE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || '' }} run: node scripts/i18n/check-new-key-coverage.mjs + # Absolute complement of the two gates above: every locale must carry exactly the key + # set of en.json, whatever the age of the key. A locale batch is generated from the + # en.json of the day the branch is cut and translates for days while the base keeps + # adding keys — the batch PR adds no key itself, so the new-key gate stays silent and + # 43 absent keys out of ~13,000 still read 99.7 % coverage. Incident 2026-09-15: + # batch 1 (#13044) landed 43 keys short in nine locales, batch 2 (#13660) 10 keys short + # in eight. Fix is `sync-ui-keys --locale= --translate-markers`. + - name: i18n key completeness (every locale carries every en.json key) + run: node scripts/i18n/check-key-completeness.mjs # #8038: cheap glossary/protected-terms consistency gate — # complements i18n-ui-coverage (key parity) and the ICU `i18n` job below diff --git a/.github/workflows/release-acceptance.yml b/.github/workflows/release-acceptance.yml new file mode 100644 index 0000000000..75527ad2dd --- /dev/null +++ b/.github/workflows/release-acceptance.yml @@ -0,0 +1,42 @@ +name: Release acceptance + +on: + push: + branches: ["release/v*"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-acceptance-${{ github.ref }} + cancel-in-progress: false + +jobs: + acceptance: + name: Release acceptance + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: actions/setup-node@v5 + with: + node-version: "22" + cache: npm + - run: npm ci + - name: Emit shadow acceptance report + run: | + node scripts/quality/validate-release-acceptance.mjs \ + --plan tests/fixtures/release-acceptance/plan-lint.json \ + --manifests tests/fixtures/release-acceptance/shadow-manifests \ + --out release-acceptance-report.json + continue-on-error: true + - uses: actions/upload-artifact@v4 + if: always() + with: + name: release-acceptance-report + path: release-acceptance-report.json + if-no-files-found: ignore + retention-days: 30 diff --git a/AGENTS.md b/AGENTS.md index 69e81b7ab3..749badbfd3 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 (176 migrations) | +| Database | `src/lib/db/` | SQLite domain modules (177 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 edca9ae592..f83c6eccfc 100644 --- a/README.md +++ b/README.md @@ -1268,7 +1268,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, 176 migrations + Databasebetter-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 122 domain modules, 177 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/changelog.d/features/13827-i18n-key-completeness-gate.md b/changelog.d/features/13827-i18n-key-completeness-gate.md new file mode 100644 index 0000000000..f7a405faac --- /dev/null +++ b/changelog.d/features/13827-i18n-key-completeness-gate.md @@ -0,0 +1 @@ +- **feat(i18n):** new blocking gate `i18n:check-keys` (`scripts/i18n/check-key-completeness.mjs`) — every locale catalog must carry exactly the key set of `en.json`, whatever the age of the key; the percentage and new-key gates let batch 1 (#13044) ship 43 keys short and batch 2 (#13660) 10 keys short. The i18n guide now documents the post-merge re-sync and the retranslation flow. (#13827) diff --git a/changelog.d/fixes/12861-direct-fetch-timeout-unhandled-rejection.md b/changelog.d/fixes/12861-direct-fetch-timeout-unhandled-rejection.md new file mode 100644 index 0000000000..3821c75945 --- /dev/null +++ b/changelog.d/fixes/12861-direct-fetch-timeout-unhandled-rejection.md @@ -0,0 +1 @@ +- **fix(resilience):** a recoverable direct-fetch response-start timeout (`DIRECT_RESPONSE_START_TIMEOUT`) could, in a narrow timer/promise-settlement race, escape as an `unhandledRejection` → `uncaughtException` and kill the server process — even though `proxyFetch` already retries this exact condition on a fresh socket. Guarded the timer callback so it can no longer fire against an already-settled attempt, and extended the process-level crash guard (already used by the WS/API-bridge servers) to recognize and swallow this code if it ever escapes anyway. Also installs that same guard in the production server entrypoint (`dist/server-ws.mjs`), which never had it even though the dev server already did ([#12861](https://github.com/diegosouzapw/OmniRoute/issues/12861)) — thanks @insoln diff --git a/changelog.d/fixes/13234-embed-lan-keyed-auth.md b/changelog.d/fixes/13234-embed-lan-keyed-auth.md new file mode 100644 index 0000000000..8228549624 --- /dev/null +++ b/changelog.d/fixes/13234-embed-lan-keyed-auth.md @@ -0,0 +1 @@ +- **fix(embeddings):** LAN/CGNAT OpenAI-compatible embeddings nodes with a stored API key now send `Authorization: Bearer` on the outbound request, matching dashboard Check. Keyless LAN nodes stay no-auth ([#6925](https://github.com/diegosouzapw/OmniRoute/issues/6925)) ([#13234](https://github.com/diegosouzapw/OmniRoute/issues/13234)) diff --git a/changelog.d/fixes/13326-memory-fts-skip-access-updates.md b/changelog.d/fixes/13326-memory-fts-skip-access-updates.md new file mode 100644 index 0000000000..d1564a7c1c --- /dev/null +++ b/changelog.d/fixes/13326-memory-fts-skip-access-updates.md @@ -0,0 +1 @@ +- **fix(memory):** stop FTS5 rewrite on access-count updates; rebuild the index on cleanup so leftover tombstones shrink (#13326). diff --git a/changelog.d/fixes/13445-groq-tls-fingerprint.md b/changelog.d/fixes/13445-groq-tls-fingerprint.md new file mode 100644 index 0000000000..8fc169fedb --- /dev/null +++ b/changelog.d/fixes/13445-groq-tls-fingerprint.md @@ -0,0 +1 @@ +- **fix(network):** skip Chrome TLS impersonation for Groq (`api.groq.com`); Cloudflare 1010s that JA3 while native undici reaches the API ([#13445](https://github.com/diegosouzapw/OmniRoute/pull/13445)) (#13225) diff --git a/changelog.d/fixes/13795-opencode-429-proxy-dedup.md b/changelog.d/fixes/13795-opencode-429-proxy-dedup.md new file mode 100644 index 0000000000..244830df58 --- /dev/null +++ b/changelog.d/fixes/13795-opencode-429-proxy-dedup.md @@ -0,0 +1 @@ +- **fix(sse):** stop re-sending a request to an already-refused route after a 429 — each refused route is tried once per request ([#13795](https://github.com/diegosouzapw/OmniRoute/pull/13795)) — thanks @maxmad64bis diff --git a/changelog.d/fixes/agnes-thinking-effort-tiers.md b/changelog.d/fixes/agnes-thinking-effort-tiers.md new file mode 100644 index 0000000000..3fc652bc1b --- /dev/null +++ b/changelog.d/fixes/agnes-thinking-effort-tiers.md @@ -0,0 +1 @@ +- fix(providers): **declare Agnes chat models' live `reasoning_effort` vocabulary so catalog/builder/sanitizer stop inventing aliases the API 400s.** 2.0/2.5 accept `none/low/medium/high/max`; 3.0 also accepts `minimal` and `xhigh`. `off`/`ultra` still clamp off the wire. diff --git a/changelog.d/fixes/claude-assistant-prefill.md b/changelog.d/fixes/claude-assistant-prefill.md new file mode 100644 index 0000000000..8b01de5170 --- /dev/null +++ b/changelog.d/fixes/claude-assistant-prefill.md @@ -0,0 +1 @@ +- Strip a trailing text-only assistant turn before official Claude OAuth dispatch. Claude returns 400 `This model does not support assistant message prefill` for that shape; the shared strip only covered Mistral. diff --git a/changelog.d/fixes/codex-reasoning-object-whitelist.md b/changelog.d/fixes/codex-reasoning-object-whitelist.md new file mode 100644 index 0000000000..139d470bae --- /dev/null +++ b/changelog.d/fixes/codex-reasoning-object-whitelist.md @@ -0,0 +1 @@ +- Fixed Codex executor forwarding client `reasoning` sub-fields (`enabled`, `max_tokens`, `exclude`) that the Codex Responses API rejects with HTTP 400, taking down every combo target with a deterministic client error. The reasoning object is now whitelisted to `effort`/`summary`, and `enabled: false` maps to effort `none` when no more specific effort was requested. diff --git a/changelog.d/fixes/combo-probe-no-thinking.md b/changelog.d/fixes/combo-probe-no-thinking.md new file mode 100644 index 0000000000..8246088861 --- /dev/null +++ b/changelog.d/fixes/combo-probe-no-thinking.md @@ -0,0 +1 @@ +- **fix(combos):** Gemini combo probes send `reasoning_effort: none` so thinking does not eat the health-check budget; truncated `finish_reason: length` responses are no longer rewritten as empty-content 502s diff --git a/changelog.d/fixes/gemini-38-think-level.md b/changelog.d/fixes/gemini-38-think-level.md new file mode 100644 index 0000000000..7dcef7435a --- /dev/null +++ b/changelog.d/fixes/gemini-38-think-level.md @@ -0,0 +1 @@ +- **fix(gemini):** send Gemini 3.8 `thinkingLevel` instead of a numeric `thinkingBudget`, and omit `includeThoughts` unless the client asked, so hidden thoughts stop eating `maxOutputTokens` diff --git a/changelog.d/fixes/release-acceptance-shadow.md b/changelog.d/fixes/release-acceptance-shadow.md new file mode 100644 index 0000000000..aeae72889c --- /dev/null +++ b/changelog.d/fixes/release-acceptance-shadow.md @@ -0,0 +1 @@ +- Add a shadow release-acceptance report next to release-green.json. It does not close #12732 and is not a Mergify required check. diff --git a/changelog.d/fixes/sensenova-deepseek-v4-effort-clamp.md b/changelog.d/fixes/sensenova-deepseek-v4-effort-clamp.md new file mode 100644 index 0000000000..b333d26e6d --- /dev/null +++ b/changelog.d/fixes/sensenova-deepseek-v4-effort-clamp.md @@ -0,0 +1 @@ +- **fix(providers):** clamp SenseNova DeepSeek V4 Flash `reasoning_effort` `xhigh`/`max` to `high` (upstream lists `xhigh` then 400s it) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 79d974eab8..78a27ebb4c 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -1,4 +1,7 @@ { + "_rebaseline_2026_09_15_13572_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/executors/base.ts->1754. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", + "_rebaseline_2026_09_15_13445_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/utils/proxyFetch.ts->1296. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", + "_rebaseline_2026_09_15_13643_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/executors/codex.ts->1528. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13609_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/accountFallback.ts->2507. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13602_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1214. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", "_rebaseline_2026_09_15_13580_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1202. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.", @@ -448,9 +451,9 @@ "_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).", "_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.", "open-sse/executors/antigravity.ts": 1665, - "open-sse/executors/base.ts": 1753, + "open-sse/executors/base.ts": 1754, "open-sse/executors/chatgpt-web.ts": 5056, - "open-sse/executors/codex.ts": 1505, + "open-sse/executors/codex.ts": 1528, "open-sse/executors/cursor.ts": 1759, "open-sse/executors/muse-spark-web.ts": 1405, "open-sse/handlers/chatCore.ts": 6146, @@ -464,7 +467,7 @@ "open-sse/services/combo/executeTargetAttempt.ts": 1228, "open-sse/translator/response/openai-responses.ts": 1466, "open-sse/utils/cursorAgentProtobuf.ts": 1547, - "open-sse/utils/proxyFetch.ts": 1275, + "open-sse/utils/proxyFetch.ts": 1296, "open-sse/utils/stream.ts": 3098, "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/browser-worker.ts": 4398, "open-sse/vendor/codex-chatgpt-web/bridge.ts": 1335, diff --git a/config/quality/release-acceptance.schema.json b/config/quality/release-acceptance.schema.json new file mode 100644 index 0000000000..28c024ca5d --- /dev/null +++ b/config/quality/release-acceptance.schema.json @@ -0,0 +1,183 @@ +{ + "$id": "https://omniroute.local/quality/release-acceptance.schema.json", + "title": "Release acceptance report", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "identity", + "required_gates", + "gates", + "evidence_errors", + "verdict", + "artifact" + ], + "properties": { + "schema_version": { "type": "integer", "const": 1 }, + "identity": { "$ref": "#/$defs/identity" }, + "required_gates": { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/$defs/gateInstanceKey" } + }, + "gates": { + "type": "array", + "items": { "$ref": "#/$defs/gateResult" } + }, + "evidence_errors": { + "type": "array", + "items": { "$ref": "#/$defs/evidenceError" } + }, + "verdict": { "enum": ["VERIFIED", "FAILED", "UNVERIFIED"] }, + "artifact": { + "anyOf": [ + { "type": "null" }, + { "$ref": "#/$defs/artifact" } + ] + } + }, + "allOf": [ + { + "if": { "properties": { "verdict": { "const": "VERIFIED" } }, "required": ["verdict"] }, + "then": { "properties": { "required_gates": { "minItems": 1 } } } + } + ], + "$defs": { + "sha": { + "type": "string", + "pattern": "^[0-9a-f]{40}$" + }, + "digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "gateInstanceKey": { + "type": "object", + "additionalProperties": false, + "required": ["gate_id", "suite_id", "shard_index", "shard_total"], + "properties": { + "gate_id": { "type": "string", "minLength": 1 }, + "suite_id": { "type": ["string", "null"] }, + "shard_index": { "type": ["integer", "null"], "minimum": 0 }, + "shard_total": { "type": ["integer", "null"], "minimum": 1 } + } + }, + "identity": { + "type": "object", + "additionalProperties": false, + "required": [ + "repository", + "run_id", + "run_attempt", + "workflow", + "trigger", + "scope", + "requested_ref", + "base_sha", + "candidate_sha", + "tested_sha" + ], + "properties": { + "repository": { "type": "string", "minLength": 1 }, + "run_id": { "type": "string", "minLength": 1 }, + "run_attempt": { "type": "integer", "minimum": 1 }, + "workflow": { "type": "string", "minLength": 1 }, + "trigger": { "type": "string", "minLength": 1 }, + "scope": { "enum": ["pr", "release", "scheduled"] }, + "requested_ref": { "type": "string", "minLength": 1 }, + "base_sha": { "$ref": "#/$defs/sha" }, + "candidate_sha": { "$ref": "#/$defs/sha" }, + "tested_sha": { "$ref": "#/$defs/sha" } + } + }, + "evidenceRef": { + "type": "object", + "additionalProperties": false, + "required": ["artifact_id", "member", "algorithm", "digest"], + "properties": { + "artifact_id": { "type": "string", "minLength": 1 }, + "member": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$))[^\\s]+$" + }, + "algorithm": { "const": "sha256" }, + "digest": { "$ref": "#/$defs/digest" } + } + }, + "artifact": { + "type": "object", + "additionalProperties": false, + "required": ["algorithm", "digest", "identity"], + "properties": { + "algorithm": { "const": "sha256" }, + "digest": { "$ref": "#/$defs/digest" }, + "identity": { "type": "string", "minLength": 1 } + } + }, + "evidenceError": { + "type": "object", + "additionalProperties": false, + "required": ["code", "gate", "detail"], + "properties": { + "code": { "type": "string", "minLength": 1 }, + "gate": { "$ref": "#/$defs/gateInstanceKey" }, + "detail": { "type": "string" } + } + }, + "gateResult": { + "type": "object", + "additionalProperties": false, + "required": [ + "gate_id", + "suite_id", + "shard_index", + "shard_total", + "tested_sha", + "run_id", + "run_attempt", + "command_id", + "gate_type", + "status", + "cause", + "exit_code", + "duration_ms", + "evidence" + ], + "properties": { + "gate_id": { "type": "string", "minLength": 1 }, + "suite_id": { "type": ["string", "null"] }, + "shard_index": { "type": ["integer", "null"], "minimum": 0 }, + "shard_total": { "type": ["integer", "null"], "minimum": 1 }, + "tested_sha": { "$ref": "#/$defs/sha" }, + "run_id": { "type": "string", "minLength": 1 }, + "run_attempt": { "type": "integer", "minimum": 1 }, + "command_id": { "type": "string", "minLength": 1 }, + "gate_type": { "enum": ["static", "test", "artifact"] }, + "status": { "enum": ["PASS", "FAIL", "INFRA_ERROR", "SKIPPED"] }, + "reason": { "type": "string", "minLength": 1 }, + "cause": { + "anyOf": [ + { "type": "null" }, + { "$ref": "#/$defs/gateInstanceKey" } + ] + }, + "exit_code": { "type": ["integer", "null"] }, + "duration_ms": { "type": "integer", "minimum": 0 }, + "evidence": { + "type": "array", + "items": { "$ref": "#/$defs/evidenceRef" } + } + }, + "allOf": [ + { + "if": { + "properties": { "status": { "const": "SKIPPED" } }, + "required": ["status"] + }, + "then": { "required": ["reason"] } + } + ] + } + } +} diff --git a/docs/architecture/QUALITY_GATES.md b/docs/architecture/QUALITY_GATES.md index 2038c437a7..d3f053e28d 100644 --- a/docs/architecture/QUALITY_GATES.md +++ b/docs/architecture/QUALITY_GATES.md @@ -145,6 +145,7 @@ Runs on every PR to `main`. Blocks merge on failure. | `check-ui-keys-coverage` (inline) | UI i18n key coverage is ≥ 65% | Yes | | `check-ui-value-drift` (inline) | A rewritten English **value** leaves no stale translation behind | Yes | | `check-new-key-coverage` (inline) | A **new** English key reaches every locale | Yes | +| `check-key-completeness` (inline) | Every locale carries exactly the key set of `en.json` (absent key = defect, whatever its age; `__MISSING__` counts as present) | Yes | | `check-translation-ratio` | Real-translation ratio per locale (identical-to-English / placeholder / missing leaves outside the allowlist) must not exceed `config/quality/i18n-translation-baseline.json` + slack | **Advisory** | Needs `fetch-depth: 0` — the value-drift gate diffs `en.json` against the merge base. @@ -536,6 +537,21 @@ several "obvious" merges turned out to hide debt and are **not** clean drop-ins. - Supply-chain (provenance, SBOM, Trivy, Scorecard): [`docs/security/SUPPLY_CHAIN.md`](../security/SUPPLY_CHAIN.md) +#### `check-key-completeness` — key-set parity gate + +`scripts/i18n/check-key-completeness.mjs` (`npm run i18n:check-keys`, job `i18n-ui-coverage`). +Compares the leaf key set of every `src/i18n/messages/.json` with `en.json` and fails +on any absent or extra leaf, regardless of when the key was added. `__MISSING__:` placeholders +count as present (their content is the ratio gate's business). It is the absolute complement +of the two diff-based/percentage gates: `check-ui-keys-coverage` enforces an 80 % floor per +locale (43 absent keys out of ~13,000 still read 99.7 %) and `check-new-key-coverage` judges +only the keys a PR adds to `en.json`. A locale batch is generated from the `en.json` of the day +its branch is cut and translates for days while the base keeps adding keys; the batch PR adds no +key itself, so both siblings stayed silent when batch 1 (#13044) landed 43 keys short in nine +locales and batch 2 (#13660) 10 keys short in eight (2026-09-15). Fix a red with +`node scripts/i18n/sync-ui-keys.mjs --locale= --translate-markers`; an `extra` leaf +means the source dropped it — delete it from the locale. `--warn` reports without failing. + #### `check-new-key-coverage` — new-key i18n gate Sibling of `check-ui-value-drift`. That one catches an English value that was **rewritten** diff --git a/docs/guides/I18N.md b/docs/guides/I18N.md index d518238449..627ff57162 100644 --- a/docs/guides/I18N.md +++ b/docs/guides/I18N.md @@ -211,6 +211,38 @@ npm run i18n:check-ui-coverage && npm run i18n:check-ratio && npm run check:docs adapter and must not be edited by hand. The Google-Translate generator (`generate-multilang.mjs`) is deprecated and is not part of this flow. +## Keeping catalogs complete and retranslating English copies + +Three gates guard the catalogs, and they see different things: + +| Gate | Sees | +| -------------------------------- | ----------------------------------------------------------------------- | +| `npm run i18n:check-ui-coverage` | ≥ 80 % of leaves translated per locale | +| `npm run i18n:check-new-keys` | a key the PR adds to `en.json` reached every locale | +| `npm run i18n:check-keys` | every locale carries exactly the key set of `en.json`, whatever the age | +| `npm run i18n:check-ratio` | share of leaves still identical to English may only fall (ratchet) | + +**After every merge of the base into a locale branch**, re-sync the locales the branch owns — +the base keeps adding keys while a batch translates: + +```bash +node scripts/i18n/sync-ui-keys.mjs --locale=km,kn,ml --translate-markers --batch-size=40 +npm run i18n:check-keys +``` + +**Retranslating verbatim-English leaves** (`--retranslate-identical`) turns every leaf that is +still byte-identical to `en.json` — outside `scripts/i18n/untranslatable-keys.json` — into a +`__MISSING__:` placeholder and translates it in the same run. Before a bulk run, put every key +that a test pins to its English value (product, engine and flag names — e.g. the Vietnamese +sidebar engines in `dashboard-localization-contract.test.ts`, the pt-BR label in +`server-owned-tool-loop-flag.test.ts`) into the allowlist first, then: + +```bash +node scripts/i18n/sync-ui-keys.mjs --locale=es --retranslate-identical --translate-markers --batch-size=40 +npm run i18n:check-ratio:update # tighten the baseline once the locale improved +npm run i18n:check-glossary # zh-CN / zh-TW / ko protected terms +``` + ## Auto-Translation Pipeline ### generate-multilang.mjs (Google Translate) diff --git a/docs/i18n/am/llm.txt b/docs/i18n/am/llm.txt index 39a61f6de3..3fd30fd6e8 100644 --- a/docs/i18n/am/llm.txt +++ b/docs/i18n/am/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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/ar/llm.txt b/docs/i18n/ar/llm.txt index 9134543a98..85ec10e863 100644 --- a/docs/i18n/ar/llm.txt +++ b/docs/i18n/ar/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 0ed9ba4afe..46a9e5c5ef 100644 --- a/docs/i18n/az/llm.txt +++ b/docs/i18n/az/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 6ec72e9b07..74b15b8ef7 100644 --- a/docs/i18n/bg/llm.txt +++ b/docs/i18n/bg/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 3f682bf3b7..26bbd35aff 100644 --- a/docs/i18n/bn/llm.txt +++ b/docs/i18n/bn/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 0846e63050..21835322da 100644 --- a/docs/i18n/cs/llm.txt +++ b/docs/i18n/cs/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 28c6c9004b..dc0ce39fe4 100644 --- a/docs/i18n/da/llm.txt +++ b/docs/i18n/da/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 b7c8f543cb..c6a5c62820 100644 --- a/docs/i18n/de/llm.txt +++ b/docs/i18n/de/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/el/llm.txt b/docs/i18n/el/llm.txt index bb42596d24..36c9d0b6eb 100644 --- a/docs/i18n/el/llm.txt +++ b/docs/i18n/el/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 35277266c1..fbd051da85 100644 --- a/docs/i18n/es/llm.txt +++ b/docs/i18n/es/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/et/llm.txt b/docs/i18n/et/llm.txt index 05ea61696e..6dd26c2d27 100644 --- a/docs/i18n/et/llm.txt +++ b/docs/i18n/et/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 10c736125b..822d7f4dbf 100644 --- a/docs/i18n/fa/llm.txt +++ b/docs/i18n/fa/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 b90155fe90..a160e93e4f 100644 --- a/docs/i18n/fi/llm.txt +++ b/docs/i18n/fi/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 b4575e8b23..f63d52762b 100644 --- a/docs/i18n/fr/llm.txt +++ b/docs/i18n/fr/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/ga/llm.txt b/docs/i18n/ga/llm.txt index 000ec04984..ba09cd23da 100644 --- a/docs/i18n/ga/llm.txt +++ b/docs/i18n/ga/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 625e9d85e3..12cdc76106 100644 --- a/docs/i18n/gu/llm.txt +++ b/docs/i18n/gu/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/ha/llm.txt b/docs/i18n/ha/llm.txt index c1c8f69cb1..437b43b9e2 100644 --- a/docs/i18n/ha/llm.txt +++ b/docs/i18n/ha/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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 e3bbdcd90d..07b2e37432 100644 --- a/docs/i18n/he/llm.txt +++ b/docs/i18n/he/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 e38bc37c27..b6e5ed7454 100644 --- a/docs/i18n/hi/llm.txt +++ b/docs/i18n/hi/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/hr/llm.txt b/docs/i18n/hr/llm.txt index 8ef00fa5c7..b37316fcfd 100644 --- a/docs/i18n/hr/llm.txt +++ b/docs/i18n/hr/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 106c2b25a7..24a5f01979 100644 --- a/docs/i18n/hu/llm.txt +++ b/docs/i18n/hu/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/hy/llm.txt b/docs/i18n/hy/llm.txt index 4de463ecd4..28c7554783 100644 --- a/docs/i18n/hy/llm.txt +++ b/docs/i18n/hy/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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 ccfb8f192d..55d8014b2f 100644 --- a/docs/i18n/id/llm.txt +++ b/docs/i18n/id/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/ig/llm.txt b/docs/i18n/ig/llm.txt index 27c0f08db8..f64fc05aa7 100644 --- a/docs/i18n/ig/llm.txt +++ b/docs/i18n/ig/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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 586c70bf9e..48d6ea9ad0 100644 --- a/docs/i18n/it/llm.txt +++ b/docs/i18n/it/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 42aa3211aa..2347208bed 100644 --- a/docs/i18n/ja/llm.txt +++ b/docs/i18n/ja/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/ka/llm.txt b/docs/i18n/ka/llm.txt index b87709baa1..9a3e2d42c3 100644 --- a/docs/i18n/ka/llm.txt +++ b/docs/i18n/ka/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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/km/llm.txt b/docs/i18n/km/llm.txt index c559c1c069..5526e08ab5 100644 --- a/docs/i18n/km/llm.txt +++ b/docs/i18n/km/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/kn/llm.txt b/docs/i18n/kn/llm.txt index 8b90b5bd75..a64dbb1e5c 100644 --- a/docs/i18n/kn/llm.txt +++ b/docs/i18n/kn/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 e2c88a3bc8..2b5f63a15e 100644 --- a/docs/i18n/ko/llm.txt +++ b/docs/i18n/ko/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/lt/llm.txt b/docs/i18n/lt/llm.txt index 30c84480ef..6e78b81fa4 100644 --- a/docs/i18n/lt/llm.txt +++ b/docs/i18n/lt/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/lv/llm.txt b/docs/i18n/lv/llm.txt index a10e5cfb44..b386b2daf2 100644 --- a/docs/i18n/lv/llm.txt +++ b/docs/i18n/lv/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/ml/llm.txt b/docs/i18n/ml/llm.txt index c35b9e0c3f..5a3318e29c 100644 --- a/docs/i18n/ml/llm.txt +++ b/docs/i18n/ml/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 267d20e389..a796da9ea4 100644 --- a/docs/i18n/mr/llm.txt +++ b/docs/i18n/mr/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 1cecd2685f..08b7450997 100644 --- a/docs/i18n/ms/llm.txt +++ b/docs/i18n/ms/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/mt/llm.txt b/docs/i18n/mt/llm.txt index 06b08636c9..8825f716c6 100644 --- a/docs/i18n/mt/llm.txt +++ b/docs/i18n/mt/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/my/llm.txt b/docs/i18n/my/llm.txt index 7f5052c484..5668c46786 100644 --- a/docs/i18n/my/llm.txt +++ b/docs/i18n/my/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/ne/llm.txt b/docs/i18n/ne/llm.txt index 7e3c7b9afe..d8b183a344 100644 --- a/docs/i18n/ne/llm.txt +++ b/docs/i18n/ne/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 0f60572235..814333c97e 100644 --- a/docs/i18n/nl/llm.txt +++ b/docs/i18n/nl/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 d800a7dddc..2818051cd1 100644 --- a/docs/i18n/no/llm.txt +++ b/docs/i18n/no/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/or/llm.txt b/docs/i18n/or/llm.txt index ec6ac7fce7..affee668c4 100644 --- a/docs/i18n/or/llm.txt +++ b/docs/i18n/or/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/pa/llm.txt b/docs/i18n/pa/llm.txt index b55f4ae03d..cfe7831247 100644 --- a/docs/i18n/pa/llm.txt +++ b/docs/i18n/pa/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 9fc54131b0..d63139decc 100644 --- a/docs/i18n/phi/llm.txt +++ b/docs/i18n/phi/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 e78a340602..9feed57c18 100644 --- a/docs/i18n/pl/llm.txt +++ b/docs/i18n/pl/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 1bec9af6e6..f2b6bd7f58 100644 --- a/docs/i18n/pt-BR/llm.txt +++ b/docs/i18n/pt-BR/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 31a8ae5a12..f8ecf8e074 100644 --- a/docs/i18n/pt/llm.txt +++ b/docs/i18n/pt/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 d723831165..eee1267ccc 100644 --- a/docs/i18n/ro/llm.txt +++ b/docs/i18n/ro/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 ada7cab271..978b5ca65f 100644 --- a/docs/i18n/ru/llm.txt +++ b/docs/i18n/ru/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/si/llm.txt b/docs/i18n/si/llm.txt index 663f79b27f..b87a8e2696 100644 --- a/docs/i18n/si/llm.txt +++ b/docs/i18n/si/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 d6c5098f0a..9ee58a3dbb 100644 --- a/docs/i18n/sk/llm.txt +++ b/docs/i18n/sk/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/sl/llm.txt b/docs/i18n/sl/llm.txt index 7a6e4b2a6d..5d24b2a309 100644 --- a/docs/i18n/sl/llm.txt +++ b/docs/i18n/sl/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/sr/llm.txt b/docs/i18n/sr/llm.txt index 56bc46640c..02b29498ec 100644 --- a/docs/i18n/sr/llm.txt +++ b/docs/i18n/sr/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 7d4698e370..98ece5b543 100644 --- a/docs/i18n/sv/llm.txt +++ b/docs/i18n/sv/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 7d041ae8e9..b7b4c36bc5 100644 --- a/docs/i18n/sw/llm.txt +++ b/docs/i18n/sw/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 a6c2c2852d..01f3e7eca4 100644 --- a/docs/i18n/ta/llm.txt +++ b/docs/i18n/ta/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 4b7315bc31..1cd62114ac 100644 --- a/docs/i18n/te/llm.txt +++ b/docs/i18n/te/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 c363c854bb..277ba182dd 100644 --- a/docs/i18n/th/llm.txt +++ b/docs/i18n/th/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 2cf5ea45dc..8a9667d58a 100644 --- a/docs/i18n/tr/llm.txt +++ b/docs/i18n/tr/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 253b94e9f5..39f39db145 100644 --- a/docs/i18n/uk-UA/llm.txt +++ b/docs/i18n/uk-UA/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 304f6d87fa..c865b71a75 100644 --- a/docs/i18n/ur/llm.txt +++ b/docs/i18n/ur/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/uz/llm.txt b/docs/i18n/uz/llm.txt index e9217f5d03..9958d6f2c0 100644 --- a/docs/i18n/uz/llm.txt +++ b/docs/i18n/uz/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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 6a7c3c25c7..f44866ee7c 100644 --- a/docs/i18n/vi/llm.txt +++ b/docs/i18n/vi/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/yo/llm.txt b/docs/i18n/yo/llm.txt index 9956519f16..92dfeb92c3 100644 --- a/docs/i18n/yo/llm.txt +++ b/docs/i18n/yo/llm.txt @@ -5,6 +5,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -19,7 +20,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 @@ -129,7 +130,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -394,7 +395,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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 SQL migrations. 10. **Upstream headers:** Custom headers merged in executors after default auth; same header name replaces executor value. Forbidden header names in `src/shared/constants/upstreamHeaders.ts`. @@ -438,7 +439,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, 176 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, 177 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 9f130ca59b..a034e8823e 100644 --- a/docs/i18n/zh-CN/llm.txt +++ b/docs/i18n/zh-CN/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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 be78d6b4a5..3bb76542f9 100644 --- a/docs/i18n/zh-TW/llm.txt +++ b/docs/i18n/zh-TW/llm.txt @@ -4,6 +4,7 @@ --- + > OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 358 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -18,7 +19,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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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 +129,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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 versioned SQL migration files │ │ ├── evals/ # Eval runner and scheduler │ │ ├── memory/ # Persistent conversational memory │ │ │ ├── extraction.ts # Memory extraction from conversations @@ -393,7 +394,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 8. **ProviderIcon component:** Unified icon system using `@lobehub/icons` (130+ SVG) with PNG fallback and generic icon fallback chain. Used on providers, dashboard, and agents pages. -9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool 4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`. -5. **Database layer:** Operations go through `src/lib/db/` modules (122 domain-specific files, 176 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, 177 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/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index c4052e9748..e25c1c6ca6 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -373,7 +373,7 @@ Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress con | `SOCKS_HANDSHAKE_TIMEOUT_MS` | `10000` | `open-sse/utils/socksConnectorWithFamily.ts` | SOCKS5 handshake (connect) timeout in ms. Raise it when a single residential gateway host is hit by high concurrency (e.g. 100 simultaneous requests) — the real handshake can exceed 10s under a saturated pool even though the proxy is reachable, which otherwise surfaces as a false `[Proxy Fast-Fail] Proxy unreachable`. Capped at `120000`. | | `PROXY_FAIL_OPEN` | `false` | `src/sse/handlers/chatHelpers.ts` | When `false` (default), a request whose assigned proxy fails to resolve is **refused (fail-closed)** rather than falling back to a direct connection — prevents real-IP leaks. Set `true` to restore the legacy DIRECT fallback. | | `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | Spoof TLS fingerprint using wreq-js (mimics Chrome 124). Counters JA3/JA4 blocking. | -| `TLS_FINGERPRINT_PROVIDERS` | _(unset)_ | `open-sse/utils/proxyFetch.ts` | Comma-separated provider allowlist for the new proxied TLS routing (`open-sse/utils/proxyFetch.ts`). Direct TLS keeps its legacy behavior when unset; only these providers route through the Chrome-124 fingerprint bridge. | +| `TLS_FINGERPRINT_PROVIDERS` | _(unset)_ | `open-sse/utils/proxyFetch.ts` | Comma-separated provider allowlist for the new proxied TLS routing (`open-sse/utils/proxyFetch.ts`). Direct TLS keeps its legacy behavior when unset; only these providers route through the Chrome-124 fingerprint bridge. Groq (`api.groq.com`) is excluded even when the allowlist is unset, because Chrome JA3 triggers Cloudflare 1010 while native undici reaches the API. | | `OMNIROUTE_TURNSTILE_IGNORE_TLS_ERRORS` | `false` | `open-sse/services/claudeTurnstileSolver.ts` | Allow the Claude Turnstile Playwright browser context to ignore HTTPS certificate errors. | ### Scenarios diff --git a/llm.txt b/llm.txt index 4d7f1d1f63..ad0d97232c 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, 176 migrations) +- **Database:** SQLite via better-sqlite3 (local, zero-config, 177 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/ # 176 versioned SQL migration files +│ │ │ └── migrations/ # 177 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 176 SQL migrations. +9. **DB architecture:** `localDb.ts` is a re-export layer only — real logic lives in 122 `src/lib/db/` modules with 177 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, 176 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, 177 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/config/providers/registry/agnes/index.ts b/open-sse/config/providers/registry/agnes/index.ts index 8e3a5cbdf5..cd8bf89bcb 100644 --- a/open-sse/config/providers/registry/agnes/index.ts +++ b/open-sse/config/providers/registry/agnes/index.ts @@ -1,5 +1,22 @@ import type { RegistryEntry } from "../../shared.ts"; +// Official Agnes chat contract from live /v1/chat/completions probes +// (2026-09-14, apihub.agnes-ai.com). 2.0/2.5 accept none/low/medium/high/max +// and 400 on xhigh/off/ultra/minimal. 3.0 additionally accepts minimal and +// xhigh. HuggingFace's Agnes-3.0-Flash card lists four of these +// (none/low/medium/high); live 3.0 also takes minimal and xhigh, so the +// registry follows the live API rather than the shorter card. +export const AGNES_FLASH_THINKING_EFFORTS = ["none", "low", "medium", "high", "max"] as const; +export const AGNES_30_THINKING_EFFORTS = [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max", +] as const; + export const agnesProvider: RegistryEntry = { id: "agnes", format: "openai", @@ -15,6 +32,7 @@ export const agnesProvider: RegistryEntry = { contextLength: 262144, maxOutputTokens: 65536, supportsReasoning: true, + supportedThinkingEfforts: [...AGNES_FLASH_THINKING_EFFORTS], supportsVision: true, toolCalling: true, }, @@ -24,18 +42,20 @@ export const agnesProvider: RegistryEntry = { contextLength: 524288, maxOutputTokens: 65536, supportsReasoning: true, + supportedThinkingEfforts: [...AGNES_FLASH_THINKING_EFFORTS], supportsVision: true, toolCalling: true, interleavedField: "reasoning_content", }, + // Wiki (2026-09-10): agnes-3.0-flash is 512k context / 65,536 + // output, same window as 2.5-flash. Live /v1/models lists it. { - // Wiki (2026-09-10) lists agnes-3.0-flash at 512K context / 65,536 max - // output, same window as 2.5 Flash. Live GET /v1/models includes it. id: "agnes-3.0-flash", name: "Agnes 3.0 Flash", contextLength: 524288, maxOutputTokens: 65536, supportsReasoning: true, + supportedThinkingEfforts: [...AGNES_30_THINKING_EFFORTS], supportsVision: true, toolCalling: true, interleavedField: "reasoning_content", diff --git a/open-sse/config/providers/registry/sensenova/index.ts b/open-sse/config/providers/registry/sensenova/index.ts index 2e8b5822f9..fb7d2435d1 100644 --- a/open-sse/config/providers/registry/sensenova/index.ts +++ b/open-sse/config/providers/registry/sensenova/index.ts @@ -27,8 +27,8 @@ export const sensenovaProvider: RegistryEntry = { contextLength: 1048576, maxOutputTokens: 65536, supportsReasoning: true, - supportedThinkingEfforts: ["none", "low", "medium", "high", "xhigh"], - supportsXHighEffort: true, + supportedThinkingEfforts: ["none", "low", "medium", "high"], + supportsXHighEffort: false, interleavedField: "reasoning_content", }, { diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 5d07ef73c3..4b559d396e 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -1354,8 +1354,9 @@ export class BaseExecutor { // drop any tool_result orphaned by that strip (discussion #2410). const adjacent = isClaude ? fixToolPairs(fixToolAdjacency(fixed)) : fixed; const stripped = stripTrailingAssistantOrphanToolUse(adjacent); - // Some providers (e.g. Mistral) require the last message to be user - // or tool and reject trailing assistant text messages with 400 (#3396). + // Some providers (Mistral #3396, official Claude OAuth) reject a + // trailing text-only assistant turn with 400. Strip here so combo + // failover does not burn the next account on the same body. tb.messages = stripTrailingAssistantForProvider(stripped, this.provider); } } diff --git a/open-sse/executors/base/reasoningEffort.ts b/open-sse/executors/base/reasoningEffort.ts index 55e893e7d6..5965838e00 100644 --- a/open-sse/executors/base/reasoningEffort.ts +++ b/open-sse/executors/base/reasoningEffort.ts @@ -70,19 +70,11 @@ export const GLM_53_FAMILY_PATTERN = /(?:^|\/|\b)glm-5\.3(?:$|-)/i; export const GLM_52_FAMILY_PATTERN = /(?:^|\/|\b)glm-5\.2(?:$|-)/i; export function isCommandCodeProvider(provider: string): boolean { - return ( - provider === "command-code" || - provider === "cmd" || - provider === "command_code" - ); + return provider === "command-code" || provider === "cmd" || provider === "command_code"; } export function isOllamaCloudProvider(provider: string): boolean { - return ( - provider === "ollama-cloud" || - provider === "ollamacloud" || - provider === "ollama_cloud" - ); + return provider === "ollama-cloud" || provider === "ollamacloud" || provider === "ollama_cloud"; } export function isOpencodeGoProvider(provider: string): boolean { @@ -94,6 +86,15 @@ export function isOpencodeGoProvider(provider: string): boolean { ); } +export function isSenseNovaDeepSeekV4Flash(provider: string, model: string | undefined): boolean { + const modelStr = (model || "").toLowerCase(); + const isDeepSeekV4Flash = + /(?:^|\/)deepseek-v4-flash(?:$|-)/.test(modelStr) && !modelStr.includes("vision"); + if (!isDeepSeekV4Flash) return false; + if (provider === "sensenova" || provider === "snova") return true; + return /(?:^|\/)snova(?:\/|$)/.test(modelStr); +} + type ReasoningSanitizeLog = { info?: (tag: string, msg: string) => void; }; @@ -206,12 +207,7 @@ export function supportsMaxEffortForProvider(provider: string, model: string): b MAX_TIER_REASONING_MODEL_PATTERN.test(resolvedModelId) || MAX_TIER_REASONING_MODEL_PATTERN.test(model); return ( - isClaude || - isOpencodeGo || - isOllamaCloud || - isMoonshotK3 || - isCommandCode || - isMaxTierModel + isClaude || isOpencodeGo || isOllamaCloud || isMoonshotK3 || isCommandCode || isMaxTierModel ); } @@ -485,6 +481,17 @@ export function sanitizeReasoningEffortForProvider( // - DeepSeek V4+ (Flash, Pro, Vision, ...) // - Kimi K3+ (Moonshot AI K3, K4, ...) // OpenRouter (pi#4055) is excluded because OpenRouter's normalized API expects xhigh. + if ( + isSenseNovaDeepSeekV4Flash(provider, modelStr) && + (effortStr === "xhigh" || effortStr === "max") + ) { + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: clamped reasoning_effort ${effortStr} to high (SenseNova DeepSeek V4 Flash ceiling)` + ); + return writeEffortValue(b, "high", c); + } + const isMaxTierTarget = provider !== "openrouter" && (isCommandCodeProvider(provider) || @@ -549,11 +556,10 @@ export function sanitizeReasoningEffortForProvider( ? modelStr.slice(provider.length + 1) : modelStr; const declaredEfforts = getProviderModels(provider).find( - (entry) => entry.id === providerModelIdForClamp || entry.aliases?.includes(providerModelIdForClamp) + (entry) => + entry.id === providerModelIdForClamp || entry.aliases?.includes(providerModelIdForClamp) )?.supportedThinkingEfforts; - const declaredRanked = ( - Array.isArray(declaredEfforts) ? declaredEfforts : [] - ) + const declaredRanked = (Array.isArray(declaredEfforts) ? declaredEfforts : []) .map((tier) => ({ tier, rank: REASONING_EFFORT_ORDER.indexOf(tier) })) .filter((x) => x.rank >= 0) .sort((a, b) => a.rank - b.rank); diff --git a/open-sse/executors/codex.ts b/open-sse/executors/codex.ts index 25b316df7e..8e478cb833 100644 --- a/open-sse/executors/codex.ts +++ b/open-sse/executors/codex.ts @@ -1379,8 +1379,16 @@ export class CodexExecutor extends BaseExecutor { // Issue #2331: model suffix aliases (for example gpt-5.5-xhigh) represent an // explicit model selection, so they must override client-injected defaults such // as OpenCode's automatic reasoning.effort=medium for GPT-5-family requests. + // OpenRouter-style `enabled: false` asks for reasoning to be off. It + // wins over the connection default but still loses to any per-request + // effort selection (model suffix, reasoning.effort, or flat + // reasoning_effort). + const clientDisabledReasoning = reasoningRecord?.enabled === false; const rawEffort = - modelEffort || explicitReasoning || requestReasoningEffort || fallbackReasoningEffort; + modelEffort || + explicitReasoning || + requestReasoningEffort || + (clientDisabledReasoning ? "none" : fallbackReasoningEffort); if (rawEffort) { const clampedEffort = clampEffort(cleanModel, rawEffort); @@ -1390,6 +1398,24 @@ export class CodexExecutor extends BaseExecutor { effort: clampedEffort === "ultra" ? "max" : clampedEffort, }; } + + // The Codex Responses API accepts only `effort` and `summary` inside + // `reasoning`. Client ecosystems send OpenRouter-style keys (`enabled`, + // `max_tokens`, `exclude`, ...) that the upstream rejects with HTTP 400 + // "Unknown parameter: 'reasoning.'", so whitelist the object before + // it reaches the wire. This must run even when no effort was resolved, + // because the client's original object is forwarded unchanged in that + // case. + const wireReasoning = + body.reasoning && typeof body.reasoning === "object" && !Array.isArray(body.reasoning) + ? (body.reasoning as Record) + : null; + if (wireReasoning) { + for (const key of Object.keys(wireReasoning)) { + if (key !== "effort" && key !== "summary") delete wireReasoning[key]; + } + if (Object.keys(wireReasoning).length === 0) delete body.reasoning; + } ensureCodexReasoningSummary(body); if (isCompactRequest) { delete body.include; diff --git a/open-sse/executors/opencode.ts b/open-sse/executors/opencode.ts index 9c18e7945a..0859957a3d 100644 --- a/open-sse/executors/opencode.ts +++ b/open-sse/executors/opencode.ts @@ -621,8 +621,9 @@ export class OpencodeExecutor extends BaseExecutor { // persistently malformed upstream. const emptyRejectionBudget = this.accounts.length === 1 ? 1 : 0; // Tried set: proxy keys already proven unusable for this request's - // model (geo-blocked, or transient 5xx). Request-local only — nothing - // persists past execute(). + // model (geo-blocked, transient 5xx, or already-429 this request). + // Request-local only — nothing persists past execute(). Cross-request + // set-aside (noteProxyRefusal) applies on top when enabled. const geoTriedProxyKeys = new Set(); // Opt-in (PROXY_SKIP_RECENTLY_FAILED, default off): members the provider just refused // (received refusal or refused TCP probe) are skipped. Off = plain rotation. @@ -788,6 +789,8 @@ export class OpencodeExecutor extends BaseExecutor { const status = result.response.status; if (status === 429) { this.markCooldown(account); + const key = proxyKeyOf(account.proxy); + if (key !== null) geoTriedProxyKeys.add(key); // The provider refused through this member: set it aside beyond the account // cooldown. A direct account has a null key and is never set aside. const setAsideMs = skipRecentlyFailed @@ -808,7 +811,7 @@ export class OpencodeExecutor extends BaseExecutor { } log?.warn?.( "OPENCODE", - `${cid}Rate limited (429) on account ${masked}` + + `${cid}Rate limited (429) on account ${masked} (proxy ${key ?? "direct"})` + (setAsideMs ? `, member set aside for ${Math.round(setAsideMs / 1000)}s` : "") + ", rotating to next…" ); diff --git a/open-sse/services/contextManager.ts b/open-sse/services/contextManager.ts index 3198a14905..3d4dddec1a 100644 --- a/open-sse/services/contextManager.ts +++ b/open-sse/services/contextManager.ts @@ -937,12 +937,13 @@ export function stripTrailingAssistantOrphanToolUse( } /** - * Providers that strictly require the last message to be `user` or `tool`. - * A trailing `assistant` message with plain text content (no tool_use) is - * valid for Anthropic/OpenAI (signals "continue from here") but rejected by - * Mistral with: "Expected last role User or Tool … but got assistant" (#3396). + * Some providers reject a trailing text-only assistant turn. + * Mistral: "Expected last role User or Tool but got assistant" (#3396). + * Official Claude OAuth: "This model does not support assistant message + * prefill. The conversation must end with a user message." (live 2026-09-13 + * on claude/claude-opus-5). */ -const PROVIDERS_REQUIRING_USER_LAST_MESSAGE = new Set(["mistral"]); +const PROVIDERS_REQUIRING_USER_LAST_MESSAGE = new Set(["mistral", "claude"]); /** * Strip a trailing `assistant` message that contains ONLY plain text (no diff --git a/open-sse/services/thinkingBudget.ts b/open-sse/services/thinkingBudget.ts index d9926cbb71..003dd82e19 100644 --- a/open-sse/services/thinkingBudget.ts +++ b/open-sse/services/thinkingBudget.ts @@ -62,6 +62,62 @@ export const THINKING_LEVEL_MAP: Record = { xhigh: 131072, // T11: explicit xhigh alias }; +export type Gemini38ThinkingLevel = "low" | "medium" | "high"; + +/** Gemini 3.8 Flash (and prefixed ids like agy/gemini-3.8-flash-high). */ +export function isGemini38Model(model: string): boolean { + return /(?:^|[\/])gemini-3\.8(?:$|-)/i.test(model); +} + +export function gemini38ThinkingLevelFromBudget( + model: string, + budget: number +): Gemini38ThinkingLevel { + const resolved = getResolvedModelCapabilities(model); + const cap = resolved.thinkingBudgetCap ?? 24576; + const medium = resolved.defaultThinkingBudget || 8192; + if (budget <= 0) { + throw new RangeError( + "gemini38ThinkingLevelFromBudget: budget must be > 0; use gemini38ThinkingConfig for the off-switch" + ); + } + if (budget >= cap) return "high"; + if (budget <= 1024) return "low"; + if (budget <= medium) return "medium"; + return "high"; +} + +function clientAskedForThoughts(body: Record): boolean { + return body.includeThoughts === true || body.include_thoughts === true; +} + +/** + * Gemini 3.8 honors thinkingLevel, not a 3.7 numeric thinkingBudget. + * Omit includeThoughts unless the client asked - thoughts share maxOutputTokens. + */ +export function gemini38ThinkingConfig( + model: string, + budget: number, + body: Record +): + | { thinkingLevel: Gemini38ThinkingLevel; includeThoughts?: boolean } + | { thinkingBudget: number; includeThoughts: boolean } { + if (budget <= 0) { + return { thinkingBudget: 0, includeThoughts: false }; + } + const thinkingConfig: { + thinkingLevel: Gemini38ThinkingLevel; + includeThoughts?: boolean; + } = { + thinkingLevel: gemini38ThinkingLevelFromBudget(model, budget), + }; + if (clientAskedForThoughts(body)) { + thinkingConfig.includeThoughts = true; + } + return thinkingConfig; +} + + // Default config (passthrough = backward compatible) export const DEFAULT_THINKING_CONFIG = { mode: ThinkingMode.PASSTHROUGH, diff --git a/open-sse/translator/request/claude-to-gemini.ts b/open-sse/translator/request/claude-to-gemini.ts index 9137aec0f0..b41fc9d88c 100644 --- a/open-sse/translator/request/claude-to-gemini.ts +++ b/open-sse/translator/request/claude-to-gemini.ts @@ -11,6 +11,8 @@ import { } from "../../services/geminiThoughtSignatureStore.ts"; import { capMaxOutputTokens, capThinkingBudget } from "../../../src/lib/modelCapabilities.ts"; import { getModelSpec } from "../../../src/shared/constants/modelSpecs.ts"; +import { gemini38ThinkingConfig, isGemini38Model } from "../../services/thinkingBudget.ts"; + import { buildChangedToolNameMap, buildHistoricalToolResultContext, @@ -259,14 +261,16 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) { // but thinkingBudgetCap:24576, meaning it supports thinking via budget). // Models not in MODEL_SPECS (thinkingBudgetCap=undefined) default to allowed. if (cappedBudget > 0 || getModelSpec(model)?.thinkingBudgetCap !== 0) { - result.generationConfig.thinkingConfig = { - thinkingBudget: cappedBudget, - // #6813: `budget_tokens: 0` on this explicit path is the client's dynamic-thinking - // sentinel, not an off-switch — includeThoughts stays true regardless of the - // (possibly cap-clamped) budget value. Only the reasoning_effort/output_config.effort - // paths below treat a resulting budget of 0 as "thinking disabled". - includeThoughts: true, - }; + result.generationConfig.thinkingConfig = isGemini38Model(model) + ? gemini38ThinkingConfig(model, cappedBudget, body) + : { + thinkingBudget: cappedBudget, + // #6813: `budget_tokens: 0` is the explicit path's client's dynamic-thinking + // sentinel, not an off-switch — includeThoughts stays true regardless of the + // (possibly cap-clamped) budget value. Only the reasoning_effort/output_config.effort + // paths below treat a resulting budget of 0 as "thinking disabled". + includeThoughts: true, + }; } } else if (typeof body.output_config?.effort === "string") { const effort = body.output_config.effort.toLowerCase(); @@ -290,10 +294,12 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) { // Models with thinkingBudgetCap:0 (e.g. gemini-3-flash) reject // thinkingConfig even for effort-based paths. if (getModelSpec(model)?.thinkingBudgetCap !== 0) { - result.generationConfig.thinkingConfig = { - thinkingBudget: budget, - includeThoughts: true, - }; + result.generationConfig.thinkingConfig = isGemini38Model(model) + ? gemini38ThinkingConfig(model, budget, body) + : { + thinkingBudget: budget, + includeThoughts: true, + }; } } } diff --git a/open-sse/translator/request/openai-to-gemini.ts b/open-sse/translator/request/openai-to-gemini.ts index d68946369b..23a0a767ba 100644 --- a/open-sse/translator/request/openai-to-gemini.ts +++ b/open-sse/translator/request/openai-to-gemini.ts @@ -17,6 +17,7 @@ import { getDefaultThinkingBudget, } from "../../../src/lib/modelCapabilities.ts"; import { getModelSpec } from "../../../src/shared/constants/modelSpecs.ts"; +import { gemini38ThinkingConfig, isGemini38Model } from "../../services/thinkingBudget.ts"; import { DEFAULT_SAFETY_SETTINGS, @@ -241,10 +242,12 @@ function openaiToGeminiBase( // the pre-#6943 native-defaults contract (thinkingBudget 0 / includeThoughts // false must still be present) and crashed callers that read // .thinkingConfig.thinkingBudget unconditionally. - result.generationConfig.thinkingConfig = { - thinkingBudget: budget, - includeThoughts: budget !== 0, - }; + result.generationConfig.thinkingConfig = isGemini38Model(model) + ? gemini38ThinkingConfig(model, budget, body) + : { + thinkingBudget: budget, + includeThoughts: budget !== 0, + }; } // 2. Claude format: thinking (type: enabled, budget_tokens) // Use an explicit numeric check (not truthy) so an explicit `budget_tokens: 0` — the @@ -264,10 +267,12 @@ function openaiToGeminiBase( // but thinkingBudgetCap:24576, meaning it supports thinking via budget). // Models not in MODEL_SPECS (thinkingBudgetCap=undefined) default to allowed. if (cappedBudget > 0 || getModelSpec(model)?.thinkingBudgetCap !== 0) { - result.generationConfig.thinkingConfig = { - thinkingBudget: cappedBudget, - includeThoughts: cappedBudget !== 0, - }; + result.generationConfig.thinkingConfig = isGemini38Model(model) + ? gemini38ThinkingConfig(model, cappedBudget, body) + : { + thinkingBudget: cappedBudget, + includeThoughts: cappedBudget !== 0, + }; } } } @@ -294,10 +299,14 @@ function openaiToGeminiBase( // Models not in MODEL_SPECS (thinkingBudgetCap=undefined) default to allowed. getModelSpec(model)?.thinkingBudgetCap !== 0 ) { - result.generationConfig.thinkingConfig = { - thinkingBudget: getDefaultThinkingBudget(model) || capThinkingBudget(model, 24576), - includeThoughts: true, - }; + const defaultBudget = + getDefaultThinkingBudget(model) || capThinkingBudget(model, 24576); + result.generationConfig.thinkingConfig = isGemini38Model(model) + ? gemini38ThinkingConfig(model, defaultBudget, body) + : { + thinkingBudget: defaultBudget, + includeThoughts: true, + }; } } diff --git a/open-sse/translator/request/openai-to-gemini/helpers.ts b/open-sse/translator/request/openai-to-gemini/helpers.ts index 092a857a2c..23c9cd03d4 100644 --- a/open-sse/translator/request/openai-to-gemini/helpers.ts +++ b/open-sse/translator/request/openai-to-gemini/helpers.ts @@ -11,8 +11,9 @@ export type GeminiGenerationConfig = { topK?: unknown; maxOutputTokens?: unknown; thinkingConfig?: { - thinkingBudget: number; - includeThoughts: boolean; + thinkingBudget?: number; + thinkingLevel?: "low" | "medium" | "high"; + includeThoughts?: boolean; }; responseMimeType?: string; responseSchema?: unknown; diff --git a/open-sse/utils/diagnostics.ts b/open-sse/utils/diagnostics.ts index ebee72e1e8..bb84d02461 100644 --- a/open-sse/utils/diagnostics.ts +++ b/open-sse/utils/diagnostics.ts @@ -313,7 +313,23 @@ export function detectMalformedNonStream(resp: unknown): MalformedReason | null return false; }); - if (!anyHasOutput) return "empty_choices"; + if (!anyHasOutput) { + // Same terminal stops isEmptyContentResponse already accepts as + // successful truncation, not a silent fake-success. Gemini 3.8 + // health probes that spend max_tokens on thinking come back as + // content:"" + finish_reason:"length". Treating that as empty_choices + // rewrites a valid 200 into 502 and fails dashboard Test all. + const truncatedAtLimit = choices.some((choice) => { + const c = choice as Record; + return ( + c?.finish_reason === "length" || + c?.finish_reason === "tool_calls" || + c?.finish_reason === "content_filter" + ); + }); + if (truncatedAtLimit) return null; + return "empty_choices"; + } return null; } @@ -323,7 +339,8 @@ export function describeMalformedNonStream( ): { message: string; code: string; type: string } { const body = resp && typeof resp === "object" ? (resp as Record) : null; if (body?.object === "response" && body.status === "failed") { - const err = body.error && typeof body.error === "object" ? (body.error as Record) : null; + const err = + body.error && typeof body.error === "object" ? (body.error as Record) : null; const rawMessage = typeof err?.message === "string" && err.message.trim().length > 0 ? err.message.trim() : null; return { diff --git a/open-sse/utils/directResponseStartTimeout.ts b/open-sse/utils/directResponseStartTimeout.ts index 90e7b6a04a..2476413805 100644 --- a/open-sse/utils/directResponseStartTimeout.ts +++ b/open-sse/utils/directResponseStartTimeout.ts @@ -61,16 +61,29 @@ export async function directFetchWithBoundedResponseStart( ): Promise { if (!timeoutMs || timeoutMs <= 0) return fetchImpl(input, options); const attemptController = new AbortController(); - const timer = setTimeout( - () => attemptController.abort(createDirectResponseStartTimeout(timeoutMs)), - timeoutMs - ); + // #12861: guards a narrow but real race between the timer macrotask and the + // fetch promise settling. If `fetchImpl` has already resolved/rejected by + // the time this timer fires, aborting now delivers the abort reason to a + // promise nobody is awaiting anymore — Node promotes that to an + // unhandledRejection -> uncaughtException and kills the process. Once the + // attempt has settled, the timer becomes a no-op instead: the caller + // already has its answer, and there's nothing left to abort for. + let settled = false; + const timer = setTimeout(() => { + if (settled) return; + attemptController.abort(createDirectResponseStartTimeout(timeoutMs)); + }, timeoutMs); timer.unref?.(); try { - return await fetchImpl(input, { + const response = await fetchImpl(input, { ...options, signal: mergeAbortSignals(options.signal, attemptController.signal), }); + settled = true; + return response; + } catch (err) { + settled = true; + throw err; } finally { clearTimeout(timer); } diff --git a/open-sse/utils/proxyFetch.ts b/open-sse/utils/proxyFetch.ts index f060573f98..bef527cdae 100644 --- a/open-sse/utils/proxyFetch.ts +++ b/open-sse/utils/proxyFetch.ts @@ -85,12 +85,33 @@ function isTlsFingerprintEnabled() { return process.env.ENABLE_TLS_FINGERPRINT === "true"; } +function isGroqTlsFingerprintHost(url: string | null | undefined): boolean { + if (!url) return false; + try { + const host = new URL(url).hostname.toLowerCase(); + return host === "api.groq.com" || host.endsWith(".groq.com"); + } catch { + return /(?:^|[./])api\.groq\.com(?:[:/?]|$)/i.test(String(url)); + } +} + +function isGroqTlsFingerprintProvider( + provider: string | null | undefined +): boolean { + const normalized = provider?.trim().toLowerCase(); + return normalized === "groq"; +} + function tlsFingerprintProviderAllowed( provider: string | null | undefined, - proxied: boolean + proxied: boolean, + url?: string | null ): boolean { + if (isGroqTlsFingerprintProvider(provider) || isGroqTlsFingerprintHost(url)) { + return false; + } const configured = process.env.TLS_FINGERPRINT_PROVIDERS?.trim(); - // Preserve the legacy direct-only opt-in. The new proxied transport requires + // Preserve legacy direct-only opt-in. The new proxied transport requires // an explicit allowlist so enabling TLS cannot silently change proxy traffic. if (!configured) return !proxied; if (!provider) return false; @@ -793,7 +814,7 @@ async function patchedFetchUnrecorded( if ( isTlsFingerprintEnabled() && activeTlsClient.available && - tlsFingerprintProviderAllowed(tlsStore?.provider, false) && + tlsFingerprintProviderAllowed(tlsStore?.provider, false, targetUrl) && isTlsRequestEligible(input, options) ) { try { @@ -1085,7 +1106,7 @@ async function patchedFetchUnrecorded( typeof tlsStore?.sessionScope === "string" && tlsStore.sessionScope.trim().length > 0 && activeTlsClient.available && - tlsFingerprintProviderAllowed(tlsStore?.provider, true) && + tlsFingerprintProviderAllowed(tlsStore?.provider, true, targetUrl) && isTlsRequestEligible(input, options) && isWreqProxySupported(proxyUrl) ) { diff --git a/package.json b/package.json index a42bf3a0cf..acc9cd9b4b 100644 --- a/package.json +++ b/package.json @@ -286,7 +286,8 @@ "test:unit:serial": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/unit/serial/**/*.test.ts\"", "alibaba:sync-allowlist": "node --import tsx/esm scripts/ops/sync-alibaba-allowlist.mjs", "check:vitest-exclusions": "node scripts/check/check-vitest-exclusions.mjs", - "i18n:check-new-keys": "node scripts/i18n/check-new-key-coverage.mjs" + "i18n:check-new-keys": "node scripts/i18n/check-new-key-coverage.mjs", + "i18n:check-keys": "node scripts/i18n/check-key-completeness.mjs" }, "dependencies": { "@aws-sdk/client-bedrock-runtime": "^3.1120.0", diff --git a/scripts/build/assembleStandalone.mjs b/scripts/build/assembleStandalone.mjs index d6e2010ef2..16ee2426c2 100644 --- a/scripts/build/assembleStandalone.mjs +++ b/scripts/build/assembleStandalone.mjs @@ -229,6 +229,15 @@ const EXTRA_MODULE_ENTRIES = [ src: ["scripts", "dev", "responses-ws-proxy.mjs"], dest: ["responses-ws-proxy.mjs"], }, + { + // server-ws.mjs imports ./httpClientAbortGuard.mjs. In the repo that path is + // the scripts/dev shim re-exporting the shared implementation, but the + // assembled bundle has no src/ tree, so ship the real self-contained + // implementation (no relative imports of its own) under the same file name. + label: "http client abort guard (server-ws.mjs dependency)", + src: ["src", "shared", "utils", "httpClientAbortGuard.mjs"], + dest: ["httpClientAbortGuard.mjs"], + }, { label: "ChatGPT Web Codex MCP tunnel entrypoint", src: ["bin", "chatgpt-web-codex-mcp.mjs"], diff --git a/scripts/dev/httpClientAbortGuard.mjs b/scripts/dev/httpClientAbortGuard.mjs index 9fdabf7a61..8c032372d9 100644 --- a/scripts/dev/httpClientAbortGuard.mjs +++ b/scripts/dev/httpClientAbortGuard.mjs @@ -11,6 +11,7 @@ export { isClientAbortError, + isRecoverableUpstreamTimeoutError, shouldSwallowUncaught, attachRequestStreamGuards, installProcessCrashGuard, diff --git a/scripts/dev/standalone-server-ws.mjs b/scripts/dev/standalone-server-ws.mjs index 65fb3ab65a..ec4508f295 100644 --- a/scripts/dev/standalone-server-ws.mjs +++ b/scripts/dev/standalone-server-ws.mjs @@ -9,6 +9,17 @@ import headResponseGuard from "./head-response-guard.cjs"; import { resolveTlsOptions, createServerListener } from "./tls-options.mjs"; import { getMainServerTimeoutConfig } from "./main-server-timeouts.mjs"; import { createSystemdNotifier } from "./systemd-notify.mjs"; +import { installProcessCrashGuard } from "./httpClientAbortGuard.mjs"; + +// Safety net (#12861): this is the actual production entry point (see the +// keepAliveTimeout comment below for why `run-next.mjs`-only fixes don't +// reach real installs). Without this, a client abort OR a recoverable +// upstream-fetch timeout that a retry path already handles (see +// open-sse/utils/directResponseStartTimeout.ts) can surface as an +// unhandledRejection -> uncaughtException and take the whole server down — +// exactly the asymmetry `run-next.mjs` already closed for dev. Benign errors +// are swallowed and logged; genuine bugs still crash loudly. +installProcessCrashGuard(); // systemd sd_notify (Type=notify / WatchdogSec=): this process is the one // whose event loop can freeze (cold /v1/models rebuild), so it must own the diff --git a/scripts/i18n/check-key-completeness.mjs b/scripts/i18n/check-key-completeness.mjs new file mode 100644 index 0000000000..bd9e006c3e --- /dev/null +++ b/scripts/i18n/check-key-completeness.mjs @@ -0,0 +1,132 @@ +#!/usr/bin/env node +/** + * OmniRoute — i18n key COMPLETENESS gate (CI gate, blocking). + * + * Every `src/i18n/messages/.json` must carry exactly the key set of `en.json`: + * no leaf absent, no leaf the source no longer has. A `__MISSING__:` placeholder counts as + * present (the ratio gate judges its content); an ABSENT key is the defect this gate names. + * + * Why the two sibling gates cannot see it (the incident it encodes, 2026-09-15): + * - `check-ui-keys-coverage.mjs` enforces an 80 % floor per locale — 43 absent keys out of + * ~13,000 still reads 99.7 %. + * - `check-new-key-coverage.mjs` judges only the keys a PR ADDS to en.json. A locale batch + * is generated from the en.json of the moment the branch is cut; while its translation + * runs for days the base keeps adding keys, and the batch PR adds none itself — so the + * nine batch-1 catalogs (#13044) landed 43 keys short and the eight batch-2 catalogs + * (#13660) 10 keys short. The home widget test was the first thing that noticed. + * + * This gate is absolute, not diff-based: it compares the tree as it is. + * + * Usage: + * node scripts/i18n/check-key-completeness.mjs # blocking + * node scripts/i18n/check-key-completeness.mjs --warn # report only, exit 0 + * npm run i18n:check-keys + */ + +import { promises as fs } from "node:fs"; +import path from "node:path"; +import process from "node:process"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(SCRIPT_DIR, "..", ".."); +const MESSAGES_DIR = path.join(ROOT, "src", "i18n", "messages"); +const SOURCE_LOCALE = "en"; + +function isPlainObject(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +/** Dotted leaf paths of a catalog tree (objects recurse, everything else is a leaf). */ +export function leafPaths(node, prefix = "", out = new Set()) { + if (!isPlainObject(node)) return out; + for (const [key, value] of Object.entries(node)) { + const dotted = prefix ? `${prefix}.${key}` : key; + if (isPlainObject(value)) leafPaths(value, dotted, out); + else out.add(dotted); + } + return out; +} + +/** + * Pure core. `en` is the source catalog, `locales` maps locale code → catalog. Returns one + * entry per locale whose key set differs from the source, sorted by locale, with sorted + * `missing` (in en, absent in the locale) and `extra` (in the locale, gone from en) lists. + * Locales with an identical key set are not listed. + */ +export function findIncompleteLocales({ en, locales }) { + const source = leafPaths(en); + const gaps = []; + for (const locale of Object.keys(locales).sort()) { + const target = leafPaths(locales[locale]); + const missing = [...source].filter((k) => !target.has(k)).sort(); + const extra = [...target].filter((k) => !source.has(k)).sort(); + if (missing.length || extra.length) gaps.push({ locale, missing, extra }); + } + return gaps; +} + +async function readCatalogs() { + const files = (await fs.readdir(MESSAGES_DIR)).filter((f) => f.endsWith(".json")).sort(); + const locales = {}; + let en = null; + for (const file of files) { + const code = file.slice(0, -".json".length); + const parsed = JSON.parse(await fs.readFile(path.join(MESSAGES_DIR, file), "utf8")); + if (code === SOURCE_LOCALE) en = parsed; + else locales[code] = parsed; + } + if (!en) throw new Error(`[i18n-keys] ${SOURCE_LOCALE}.json not found in ${MESSAGES_DIR}`); + return { en, locales }; +} + +function formatReport(gaps, sample = 5) { + const lines = []; + for (const { locale, missing, extra } of gaps) { + const parts = []; + if (missing.length) { + parts.push( + `${missing.length} missing (${missing.slice(0, sample).join(", ")}${missing.length > sample ? ", …" : ""})` + ); + } + if (extra.length) { + parts.push( + `${extra.length} extra (${extra.slice(0, sample).join(", ")}${extra.length > sample ? ", …" : ""})` + ); + } + lines.push(` - ${locale}: ${parts.join("; ")}`); + } + return lines.join("\n"); +} + +async function main() { + const warnOnly = process.argv.includes("--warn"); + const { en, locales } = await readCatalogs(); + const gaps = findIncompleteLocales({ en, locales }); + const total = leafPaths(en).size; + const count = Object.keys(locales).length; + if (gaps.length === 0) { + console.log( + `[i18n-keys] OK — ${count} locales carry all ${total} keys of en.json, none extra.` + ); + return; + } + const missingTotal = gaps.reduce((s, g) => s + g.missing.length, 0); + const extraTotal = gaps.reduce((s, g) => s + g.extra.length, 0); + console.error( + `[i18n-keys] ${warnOnly ? "WARN" : "FAIL"} — ${gaps.length}/${count} locales differ from en.json (${missingTotal} missing, ${extraTotal} extra leaves):` + ); + console.error(formatReport(gaps)); + console.error( + "[i18n-keys] Fix: node scripts/i18n/sync-ui-keys.mjs --locale= --translate-markers (adds the missing keys and translates them); extra keys mean the source dropped them — remove them from the locale." + ); + if (!warnOnly) process.exitCode = 1; +} + +const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; +if (isDirectRun) { + main().catch((err) => { + console.error(`[i18n-keys] ${err.message}`); + process.exitCode = 1; + }); +} diff --git a/scripts/quality/release-acceptance/closeOracle.mjs b/scripts/quality/release-acceptance/closeOracle.mjs new file mode 100644 index 0000000000..0417c955aa --- /dev/null +++ b/scripts/quality/release-acceptance/closeOracle.mjs @@ -0,0 +1,30 @@ +const CLOSE_RE = + /gh issue close\b|issues\.update\b|state=closed/g; + +const KEYWORD_RE = new RegExp( + String.raw`\b(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#(\d+)\b`, + "i" +); + +export function findTrackerCloses(workflowText) { + const hits = []; + const lines = String(workflowText ?? "").split(/\n/); + for (let i = 0; i < lines.length; i++) { + CLOSE_RE.lastIndex = 0; + if (CLOSE_RE.test(lines[i])) { + hits.push({ line: i + 1, text: lines[i].trim() }); + } + CLOSE_RE.lastIndex = 0; + } + return hits; +} + +export function closingKeywordInBody(body, tracker = 12732) { + const re = new RegExp(KEYWORD_RE.source, KEYWORD_RE.flags.includes("g") ? KEYWORD_RE.flags : `${KEYWORD_RE.flags}g`); + const text = String(body ?? ""); + let m; + while ((m = re.exec(text)) !== null) { + if (Number(m[1]) === Number(tracker)) return true; + } + return false; +} diff --git a/scripts/quality/release-acceptance/inventory.mjs b/scripts/quality/release-acceptance/inventory.mjs new file mode 100644 index 0000000000..6145b32164 --- /dev/null +++ b/scripts/quality/release-acceptance/inventory.mjs @@ -0,0 +1,102 @@ +import fs from "node:fs"; +import path from "node:path"; +import { + COLLECTORS, + globToRegExp, +} from "../../check/check-test-discovery.mjs"; + +const UNIT_CI_GLOBS = new Set([ + "tests/unit/*.test.ts", + "tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,translator,ui,usage}/**/*.test.ts", + "tests/unit/dashboard/**/*.test.ts", + "tests/unit/serial/**/*.test.ts", + "tests/unit/**/*.test.mjs", +]); + +const INTEGRATION_GLOBS = new Set([ + "tests/integration/*.test.ts", + "tests/integration/combo-matrix/*.test.ts", +]); + +function inScope(collector, scopeSuites) { + const suites = new Set(scopeSuites); + if (suites.has("test:unit:ci") && UNIT_CI_GLOBS.has(collector.glob)) return true; + if (suites.has("test:integration") && INTEGRATION_GLOBS.has(collector.glob)) return true; + if (suites.has("test:vitest") && collector.sources?.includes("vitest.mcp.config.ts")) { + return true; + } + return false; +} + +function walkTestFiles(root = process.cwd()) { + const out = []; + function walk(dir) { + if (!fs.existsSync(dir)) return; + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + if (e.name === "node_modules" || e.name === ".git") continue; + const p = path.join(dir, e.name); + if (e.isDirectory()) walk(p); + else if (/\.(test|spec)\.(ts|tsx|mjs|js)$/.test(e.name)) { + out.push(path.relative(root, p).split(path.sep).join("/")); + } + } + } + walk(path.join(root, "tests")); + walk(path.join(root, "open-sse")); + walk(path.join(root, "src")); + return out; +} + +export function canonicalSet(scopeSuites, collectors = COLLECTORS, files) { + const scoped = collectors.filter((c) => inScope(c, scopeSuites)); + const regexes = scoped.map((c) => globToRegExp(c.glob)); + const discovered = files ?? walkTestFiles(); + return discovered.filter((f) => regexes.some((re) => re.test(f))); +} + +export function knownUnexecuted(scopeSuites, collectors = COLLECTORS, baseline, files) { + const discovered = files ?? walkTestFiles(); + const orphans = baseline?.orphans ?? []; + const outOfScope = collectors.filter((c) => !inScope(c, scopeSuites)); + const collectorsOut = outOfScope.map((c) => { + const re = globToRegExp(c.glob); + const count = discovered.filter((f) => re.test(f)).length; + return { + glob: c.glob, + count, + reason: "collector runner is not a suite of this scope", + }; + }); + return { + orphans: { count: orphans.length, paths: orphans }, + collectors: collectorsOut, + }; +} + +export function inventoryErrors(scopeSuites, collectors, baseline, discoveredFiles) { + const errors = []; + const full = COLLECTORS; + const givenGlobs = new Set(collectors.map((c) => c.glob)); + for (const c of full) { + if (!givenGlobs.has(c.glob)) { + errors.push({ + code: "collector_omitted", + glob: c.glob, + detail: `collector ${c.glob} omitted without known_unexecuted listing`, + }); + } + } + const ku = knownUnexecuted(scopeSuites, collectors, baseline, discoveredFiles); + const knownGlobs = new Set(ku.collectors.map((c) => c.glob)); + const knownOrphans = new Set(ku.orphans.paths); + const scoped = collectors.filter((c) => inScope(c, scopeSuites)); + const regexes = scoped.map((c) => globToRegExp(c.glob)); + for (const f of discoveredFiles ?? []) { + const inCanonical = regexes.some((re) => re.test(f)); + const inKnown = knownOrphans.has(f) || [...knownGlobs].some((g) => globToRegExp(g).test(f)); + if (!inCanonical && !inKnown) { + errors.push({ code: "unmapped_file", path: f, detail: "discovered file belongs to no set" }); + } + } + return errors; +} diff --git a/scripts/quality/release-acceptance/nodeReporter.mjs b/scripts/quality/release-acceptance/nodeReporter.mjs new file mode 100644 index 0000000000..105f16ba38 --- /dev/null +++ b/scripts/quality/release-acceptance/nodeReporter.mjs @@ -0,0 +1,41 @@ +const SUBTEST = /^# Subtest:\s+(\S+)/; +const RESULT = /^(ok|not ok)\s+\d+\s+-\s+(\S+)/; + +export function fromNodeTestTap(tapText, argvFiles) { + const completed = []; + const failed = []; + const seen = new Set(); + const lines = String(tapText ?? "").split(/\r?\n/); + let pending = null; + for (const line of lines) { + const sub = line.match(SUBTEST); + if (sub) { + pending = sub[1]; + continue; + } + const res = line.match(RESULT); + if (res) { + const file = pending; + const ok = res[1] === "ok"; + if (file) { + seen.add(file); + if (!ok) { + if (!failed.includes(file)) failed.push(file); + const i = completed.indexOf(file); + if (i >= 0) completed.splice(i, 1); + } else if (!failed.includes(file) && !completed.includes(file)) { + completed.push(file); + } + } + } + } + const attempted = [...argvFiles]; + const missing = attempted.filter((f) => !seen.has(f)); + return { + completed, + attempted, + missing, + failed, + pass: completed.length > 0 && missing.length === 0 && failed.length === 0, + }; +} diff --git a/scripts/quality/release-acceptance/reduce.mjs b/scripts/quality/release-acceptance/reduce.mjs new file mode 100644 index 0000000000..6e3ddd6157 --- /dev/null +++ b/scripts/quality/release-acceptance/reduce.mjs @@ -0,0 +1,243 @@ +import { gateKey, sameKey } from "./types.mjs"; + +export function classifyDependent(prereqStatus, dependentKey, prereqKey) { + if (prereqStatus === "FAIL") { + return { status: "FAIL", cause: prereqKey }; + } + if (prereqStatus === "INFRA_ERROR") { + return { status: "INFRA_ERROR", cause: prereqKey }; + } + if (prereqStatus == null) { + return { + status: "INFRA_ERROR", + cause: prereqKey, + evidence_error: { + code: "prerequisite_missing", + gate: dependentKey, + detail: `missing prerequisite ${prereqKey.gate_id}`, + }, + }; + } + if (prereqStatus === "SKIPPED") { + return { status: "SKIPPED", cause: prereqKey }; + } + return { status: "RUN", cause: null }; +} + +function requiredSet(plan) { + return plan.required_gates ?? []; +} + +function optionalSet(plan) { + return plan.optional_gates ?? []; +} + +function isRequired(plan, k) { + return requiredSet(plan).some((r) => sameKey(r, k)); +} + +function copies(gates, k) { + return gates.filter((g) => sameKey(gateKey(g), k)); +} + +function copiesByGateId(gates, gateId) { + return gates.filter((g) => g.gate_id === gateId); +} + +function uniqueKeys(keys) { + const out = []; + for (const k of keys) { + if (!out.some((existing) => sameKey(existing, k))) out.push(k); + } + return out; +} + +function keysForGateId(plan, gates, gateId) { + return uniqueKeys([ + ...copiesByGateId(gates, gateId).map((g) => gateKey(g)), + ...requiredSet(plan).filter((k) => k.gate_id === gateId), + ...optionalSet(plan).filter((k) => k.gate_id === gateId), + ]); +} + +function statusOf(gates, k) { + const list = copies(gates, k); + if (list.length === 0) return null; + if (list.some((g) => g.status === "INFRA_ERROR")) return "INFRA_ERROR"; + if (list.some((g) => g.status === "FAIL")) return "FAIL"; + if (list.some((g) => g.status === "SKIPPED")) return "SKIPPED"; + return list[0].status; +} + +function statusOfGateId(gates, gateId) { + const list = copiesByGateId(gates, gateId); + if (list.length === 0) return null; + if (list.some((g) => g.status === "INFRA_ERROR")) return "INFRA_ERROR"; + if (list.some((g) => g.status === "FAIL")) return "FAIL"; + if (list.some((g) => g.status === "SKIPPED")) return "SKIPPED"; + return list[0].status; +} + +function pushEvidenceError(evidence_errors, err) { + if (!err) return; + const already = evidence_errors.some( + (e) => + e.code === err.code && + e.detail === err.detail && + e.gate?.gate_id === err.gate?.gate_id + ); + if (!already) evidence_errors.push(err); +} + +function patchDependent(gates, depKey, classified, prereqKey, evidence_errors, identity) { + const matches = copies(gates, depKey); + const reason = + classified.status === "SKIPPED" ? `classified from ${prereqKey.gate_id}` : undefined; + const exit_code = classified.status === "FAIL" ? 1 : 2; + if (matches.length === 0) { + gates.push({ + gate_id: depKey.gate_id, + suite_id: depKey.suite_id, + shard_index: depKey.shard_index, + shard_total: depKey.shard_total, + tested_sha: identity.tested_sha || "0".repeat(40), + run_id: identity.run_id ?? "0", + run_attempt: identity.run_attempt ?? 1, + command_id: depKey.gate_id, + gate_type: "artifact", + status: classified.status, + cause: classified.cause, + reason, + exit_code, + duration_ms: 0, + evidence: [], + }); + if (classified.evidence_error) pushEvidenceError(evidence_errors, classified.evidence_error); + return true; + } + let changed = false; + for (const existing of matches) { + if ( + existing.status === classified.status && + ((existing.cause == null && classified.cause == null) || + (existing.cause && classified.cause && sameKey(existing.cause, classified.cause))) + ) { + continue; + } + existing.status = classified.status; + existing.cause = classified.cause; + existing.exit_code = exit_code; + if (classified.status === "SKIPPED" && !existing.reason) existing.reason = reason; + changed = true; + } + if (changed && classified.evidence_error) { + pushEvidenceError(evidence_errors, classified.evidence_error); + } + return changed; +} + +function assertAcyclic(deps) { + const visiting = new Set(); + const done = new Set(); + function walk(id) { + if (done.has(id)) return; + if (visiting.has(id)) throw new Error("cyclic prerequisite"); + visiting.add(id); + if (Object.hasOwn(deps, id)) walk(deps[id]); + visiting.delete(id); + done.add(id); + } + for (const id of Object.keys(deps)) walk(id); +} + +export function reduce(plan, records) { + const deps = plan.dependencies ?? {}; + assertAcyclic(deps); + for (const [depId, prereqId] of Object.entries(deps)) { + const requiredDep = requiredSet(plan).some((k) => k.gate_id === depId); + const optionalPrereq = optionalSet(plan).some((k) => k.gate_id === prereqId); + if (requiredDep && optionalPrereq) { + throw new Error("optional prerequisite"); + } + } + + const gates = []; + const evidence_errors = []; + + for (const rec of records) { + const k = gateKey(rec); + const copy = { ...rec, cause: rec.cause ?? null }; + if (copy.status === "SKIPPED" && isRequired(plan, k) && !copy.reason) { + copy.reason = "required skipped"; + } + gates.push(copy); + } + + const identity = plan.identity ?? {}; + const edges = Object.entries(deps); + let changed = true; + let guard = edges.length + 1; + while (changed && guard-- > 0) { + changed = false; + for (const [depId, prereqId] of edges) { + const prereqKey = { gate_id: prereqId, suite_id: null, shard_index: null, shard_total: null }; + let depKeys = keysForGateId(plan, gates, depId); + if (depKeys.length === 0) { + depKeys = [{ gate_id: depId, suite_id: null, shard_index: null, shard_total: null }]; + } + const prereqStatus = statusOfGateId(gates, prereqId); + for (const depKey of depKeys) { + const classified = classifyDependent(prereqStatus, depKey, prereqKey); + if (classified.status === "RUN") continue; + if (patchDependent(gates, depKey, classified, prereqKey, evidence_errors, identity)) { + changed = true; + } + } + } + } + + for (const k of requiredSet(plan)) { + const rec = gates.find((g) => sameKey(gateKey(g), k)); + if (!rec) { + evidence_errors.push({ + code: "missing_record", + gate: k, + detail: `required gate ${k.gate_id} has no record`, + }); + } else if (rec.status === "SKIPPED") { + evidence_errors.push({ + code: "required_skipped", + gate: k, + detail: rec.reason ?? "required gate SKIPPED", + }); + } + } + + const required = requiredSet(plan); + if (required.length === 0) { + evidence_errors.push({ + code: "empty_required_set", + gate: { gate_id: "schema", suite_id: null, shard_index: null, shard_total: null }, + detail: "required_gates is empty", + }); + } + + let verdict = "VERIFIED"; + const hasFail = gates.some( + (g) => g.status === "FAIL" && isRequired(plan, gateKey(g)) && statusOf(gates, gateKey(g)) === "FAIL" + ); + const hasUnverified = + evidence_errors.length > 0 || + gates.some( + (g) => + isRequired(plan, gateKey(g)) && + (g.status === "SKIPPED" || g.status === "INFRA_ERROR") + ); + if (hasFail) verdict = "FAILED"; + else if (hasUnverified) verdict = "UNVERIFIED"; + else if (required.some((k) => !gates.some((g) => sameKey(gateKey(g), k)))) { + verdict = "UNVERIFIED"; + } + + return { verdict, evidence_errors, gates }; +} diff --git a/scripts/quality/release-acceptance/staticAdapter.mjs b/scripts/quality/release-acceptance/staticAdapter.mjs new file mode 100644 index 0000000000..f00a55f44d --- /dev/null +++ b/scripts/quality/release-acceptance/staticAdapter.mjs @@ -0,0 +1,50 @@ +export function adaptCompiler({ commandId, inputDigest, exitCode, diagnostics }) { + const diags = Array.isArray(diagnostics) ? diagnostics : []; + const digest = typeof inputDigest === "string" ? inputDigest : ""; + if (exitCode === 0 && digest.length > 0) { + return { + command_id: commandId, + input_digest: digest, + exit_code: 0, + diagnostics: diags, + status: "PASS", + }; + } + if (exitCode === 0 && digest.length === 0) { + return { + command_id: commandId, + input_digest: digest, + exit_code: 0, + diagnostics: diags, + status: "INFRA_ERROR", + }; + } + return { + command_id: commandId, + input_digest: digest, + exit_code: exitCode, + diagnostics: diags, + status: "FAIL", + }; +} + +export function adaptScript({ commandId, inputDigest, exitCode, stdout }) { + const digest = typeof inputDigest === "string" ? inputDigest : ""; + let parsed = null; + if (typeof stdout === "string" && stdout.trim()) { + try { + parsed = JSON.parse(stdout); + } catch { + parsed = null; + } + } + const diagnostics = parsed ?? { input_digest: digest, exit_code: exitCode, diagnostics: stdout ?? "" }; + const status = exitCode === 0 ? (digest ? "PASS" : "INFRA_ERROR") : "FAIL"; + return { + command_id: commandId, + input_digest: digest, + exit_code: exitCode, + diagnostics, + status, + }; +} diff --git a/scripts/quality/release-acceptance/types.mjs b/scripts/quality/release-acceptance/types.mjs new file mode 100644 index 0000000000..8090da5424 --- /dev/null +++ b/scripts/quality/release-acceptance/types.mjs @@ -0,0 +1,19 @@ +export const STATUSES = Object.freeze(["PASS", "FAIL", "INFRA_ERROR", "SKIPPED"]); +export const VERDICTS = Object.freeze(["VERIFIED", "FAILED", "UNVERIFIED"]); + +export function gateKey(rec) { + return { + gate_id: rec.gate_id, + suite_id: rec.suite_id ?? null, + shard_index: rec.shard_index ?? null, + shard_total: rec.shard_total ?? null, + }; +} + +export function keyId(k) { + return `${k.gate_id}\0${k.suite_id ?? ""}\0${k.shard_index ?? ""}\0${k.shard_total ?? ""}`; +} + +export function sameKey(a, b) { + return keyId(a) === keyId(b); +} diff --git a/scripts/quality/validate-release-acceptance.mjs b/scripts/quality/validate-release-acceptance.mjs new file mode 100644 index 0000000000..9a45ac95e5 --- /dev/null +++ b/scripts/quality/validate-release-acceptance.mjs @@ -0,0 +1,81 @@ +#!/usr/bin/env node +import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import Ajv from "ajv"; +import { reduce } from "./release-acceptance/reduce.mjs"; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); + +function loadJson(p) { + return JSON.parse(readFileSync(p, "utf8")); +} + +export function exitFor(verdict) { + if (verdict === "VERIFIED") return 0; + if (verdict === "FAILED") return 1; + return 2; +} + +export function reduceManifests(plan, manifests) { + const records = []; + for (const m of manifests) { + if (Array.isArray(m.gates)) records.push(...m.gates); + else records.push(m); + } + return reduce(plan, records); +} + +export function validateReport(report, schema) { + const ajv = new Ajv({ allErrors: true, strict: false }); + const validate = ajv.compile(schema); + return { ok: validate(report), errors: validate.errors }; +} + +function parseArgs(argv) { + const out = { plan: null, manifests: null, out: join(ROOT, "release-acceptance-report.json") }; + for (let i = 2; i < argv.length; i++) { + if (argv[i] === "--plan") out.plan = argv[++i]; + else if (argv[i] === "--manifests") out.manifests = argv[++i]; + else if (argv[i] === "--out") out.out = argv[++i]; + } + return out; +} + +export async function main(argv = process.argv) { + const args = parseArgs(argv); + const plan = loadJson(args.plan); + const schema = loadJson(join(ROOT, "config/quality/release-acceptance.schema.json")); + const files = readdirSync(args.manifests) + .filter((f) => f.endsWith(".json")) + .map((f) => loadJson(join(args.manifests, f))); + const reduced = reduceManifests(plan, files); + const report = { + schema_version: 1, + identity: plan.identity, + required_gates: plan.required_gates ?? [], + gates: reduced.gates, + evidence_errors: reduced.evidence_errors, + verdict: reduced.verdict, + artifact: plan.artifact ?? null, + }; + const { ok, errors } = validateReport(report, schema); + if (!ok) { + if (report.verdict !== "FAILED") report.verdict = "UNVERIFIED"; + const gate = + Array.isArray(plan.required_gates) && plan.required_gates.length > 0 + ? plan.required_gates[0] + : { gate_id: "schema", suite_id: null, shard_index: null, shard_total: null }; + report.evidence_errors = [ + ...(report.evidence_errors ?? []), + { code: "schema_invalid", gate, detail: JSON.stringify(errors) }, + ]; + } + mkdirSync(dirname(args.out), { recursive: true }); + writeFileSync(args.out, JSON.stringify(report, null, 2) + "\n"); + return exitFor(report.verdict); +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + main().then((code) => process.exit(code)); +} diff --git a/src/lib/combos/testHealth.ts b/src/lib/combos/testHealth.ts index 140c4fa33e..9a9a793a6e 100644 --- a/src/lib/combos/testHealth.ts +++ b/src/lib/combos/testHealth.ts @@ -111,6 +111,10 @@ export function buildComboTestPrompt() { return COMBO_TEST_PROMPT; } +function isGeminiComboProbe(modelStr: string) { + return /(?:^|\/)gemini(?:-|$)/i.test(modelStr); +} + export function buildComboTestRequestBody( modelStr: string, isEmbedding: boolean = false, @@ -123,7 +127,13 @@ export function buildComboTestRequestBody( }; } - return { + const body: { + model: string; + messages: { role: string; content: string }[]; + max_tokens: number; + stream: boolean; + reasoning_effort?: "none"; + } = { model: modelStr, messages: [{ role: "user", content: buildComboTestPrompt() }], // Keep the smoke probe short so reasoning-heavy models do not burn the @@ -133,6 +143,13 @@ export function buildComboTestRequestBody( (options.stream ? STREAMING_MODEL_TEST_MAX_TOKENS : COMBO_TEST_MAX_TOKENS), stream: options.stream ?? false, }; + // Gemini 3.8 flash-high injects thinkingLevel=high unless the documented + // off-switch is set. Other providers must not see this field: some + // OpenAI-compatible endpoints 400 unknown parameters. + if (isGeminiComboProbe(modelStr)) { + body.reasoning_effort = "none"; + } + return body; } export type ComboTestStreamResult = { diff --git a/src/lib/db/cleanup.ts b/src/lib/db/cleanup.ts index 2edec55234..7d92e72f53 100644 --- a/src/lib/db/cleanup.ts +++ b/src/lib/db/cleanup.ts @@ -283,6 +283,19 @@ export async function cleanupMemoryEntries(): Promise { } } + // optimize only merges segments; it does not drop tombstones from + // access-count UPDATEs that already reindexed. rebuild from the + // content table on every pass so a bloated index cannot wait for + // a memory-row delete that may never happen. + if (tableExists("memory_fts")) { + try { + db.exec("INSERT INTO memory_fts(memory_fts) VALUES('rebuild')"); + } catch (err: unknown) { + console.error("[Cleanup] FTS5 rebuild after memory retention failed:", err); + result.errors++; + } + } + console.log( `[Cleanup] Deleted ${result.deleted} memory_entries older than ${retentionDays} days` ); diff --git a/src/lib/db/migrationRunner/constants.ts b/src/lib/db/migrationRunner/constants.ts index 089837f93f..73d1ea3da4 100644 --- a/src/lib/db/migrationRunner/constants.ts +++ b/src/lib/db/migrationRunner/constants.ts @@ -211,6 +211,12 @@ export const RENAMED_MIGRATION_COMPATIBILITY = [ toVersion: "101", toName: "api_key_usage_limits", }, + { + fromVersion: "176", + fromName: "memory_fts_skip_access_updates", + toVersion: "180", + toName: "memory_fts_au_conditional_memory_id", + }, ] as const; export const LEGACY_VERSION_SLOT_MIGRATIONS = [ @@ -257,4 +263,4 @@ export const PHYSICAL_SCHEMA_SENTINELS = [ ] as const; export const INITIAL_SCHEMA_SENTINELS = ["provider_connections", "combos", "call_logs"] as const; -export const OPTIONAL_FTS5_MIGRATION_VERSIONS = new Set(["022", "023"]); +export const OPTIONAL_FTS5_MIGRATION_VERSIONS = new Set(["022", "023", "180"]); diff --git a/src/lib/db/migrations/180_memory_fts_au_conditional_memory_id.sql b/src/lib/db/migrations/180_memory_fts_au_conditional_memory_id.sql new file mode 100644 index 0000000000..25db52b8bb --- /dev/null +++ b/src/lib/db/migrations/180_memory_fts_au_conditional_memory_id.sql @@ -0,0 +1,23 @@ +-- 180_memory_fts_au_conditional_memory_id.sql +-- recordMemoryAccess() updates access_count / last_accessed_at on every +-- retrieval. The AFTER UPDATE trigger from 023 rewrote the FTS5 row for +-- those telemetry columns too, so memory_fts_data / memory_fts_docsize +-- grew without bound (live: 962 memories -> 175k FTS data rows). +-- +-- Recreate memory_fts_au so it only reindexes when content, key, or +-- memory_id actually change. memory_id still belongs here: createMemory +-- inserts then backfills memory_id, and that UPDATE must stay in FTS. + +DROP TRIGGER IF EXISTS memory_fts_au; + +CREATE TRIGGER IF NOT EXISTS memory_fts_au AFTER UPDATE ON memories +WHEN + NEW.content IS NOT OLD.content OR + NEW.key IS NOT OLD.key OR + NEW.memory_id IS NOT OLD.memory_id +BEGIN + INSERT INTO memory_fts(memory_fts, rowid, content, key) + VALUES('delete', old.memory_id, old.content, old.key); + INSERT INTO memory_fts(rowid, content, key) + VALUES (new.memory_id, new.content, new.key); +END; diff --git a/src/lib/embeddings/service.ts b/src/lib/embeddings/service.ts index 7bf961f2af..cead8383a0 100644 --- a/src/lib/embeddings/service.ts +++ b/src/lib/embeddings/service.ts @@ -344,6 +344,31 @@ export async function createEmbeddingResponse( ) { credentials = localCredentials; } + } else if (!credentials && providerConfig.authType === "none") { + // #13234: private-host nodes are classified no-auth so a keyless + // LAN Ollama still works (#6925). A stored API key on that same + // node must still ride outbound, matching dashboard Check. + const keyedCredentials = await getProviderCredentials(credentialsProviderId); + if ( + keyedCredentials && + !("allRateLimited" in keyedCredentials) && + !("allExpired" in keyedCredentials) + ) { + const token = + (typeof (keyedCredentials as { apiKey?: unknown }).apiKey === "string" && + (keyedCredentials as { apiKey?: string }).apiKey) || + (typeof (keyedCredentials as { accessToken?: unknown }).accessToken === "string" && + (keyedCredentials as { accessToken?: string }).accessToken) || + ""; + if (token) { + credentials = keyedCredentials; + providerConfig = { + ...providerConfig, + authType: "apikey", + authHeader: "bearer", + }; + } + } } // #474: when the request used a bare model name (no "/" — e.g. an alias that diff --git a/src/shared/utils/httpClientAbortGuard.mjs b/src/shared/utils/httpClientAbortGuard.mjs index 41d5b07cb5..9465ff3208 100644 --- a/src/shared/utils/httpClientAbortGuard.mjs +++ b/src/shared/utils/httpClientAbortGuard.mjs @@ -1,7 +1,8 @@ "use strict"; /** - * HTTP client-abort crash guard (#fix-dev-server-aborted). + * HTTP client-abort / recoverable-upstream-timeout crash guard + * (#fix-dev-server-aborted, #12861). * * Node's http.Server turns an 'error' event on an IncomingMessage/ServerResponse * into an uncaughtException (and therefore a process exit) WHENEVER the emitter @@ -16,14 +17,29 @@ * connections + a live WebSocket; stray client-side socket closes during * navigation/HMR were taking the dev server down. * + * Two more categories were added after the 2026-09-14 agnes-cn upstream storm + * produced two sibling escapes in production: an intentional combo hedge + * cancellation (`AbortError: hedge-cancelled` — the sibling leg already won, + * so the cancellation is expected, not a fault) and undici fetch failures + * (`TypeError: fetch failed` with a socket-level code) against a flapping + * upstream. Both are runtime/environmental conditions the request layer + * already handles; neither is a process-fatal logic bug. + * + * A further, unrelated category covers #12861: `directFetchWithBoundedResponseStart`'s + * response-start timeout (`DIRECT_RESPONSE_START_TIMEOUT`) is a *recoverable* + * signal `proxyFetch.ts` already retries on a fresh socket — but a narrow + * timer/promise-settlement race can still deliver its abort reason to a + * promise nobody is awaiting anymore, which otherwise kills the whole process + * over a single upstream stall that the retry path was built to handle. + * * Two layers: * 1. `attachRequestStreamGuards(req, res)` — per-request listeners that absorb * client-abort errors so they never bubble to the process level. Call it * inside every `http.createServer((req, res) => …)` request listener. * 2. `installProcessCrashGuard()` — a last-resort safety net on * `process.on('uncaughtException' | 'unhandledRejection')` that swallows - * the same benign client-abort errors but otherwise preserves the existing - * crash semantics (so genuine bugs still surface). Idempotent. + * the same benign errors but otherwise preserves the existing crash + * semantics (so genuine bugs still surface). Idempotent. * * Kept as a `.mjs` module (no build step) so it is importable both from the * Node-only dev server (`scripts/dev/run-next.mjs`) and from the TypeScript @@ -63,9 +79,100 @@ export function isClientAbortError(err) { } } +/** + * #12861: a recoverable upstream-fetch timeout that `proxyFetch.ts` already + * retries on a fresh socket (see `open-sse/utils/directResponseStartTimeout.ts`). + * A narrow timer/promise-settlement race can still deliver its abort reason to + * a promise nobody is awaiting anymore, which otherwise surfaces here as an + * unhandledRejection/uncaughtException — even though the retry path already + * handles this exact condition and normally logs it as a plain 504. + * + * Kept as a bare string-code check (no import of the `.ts` source of truth) + * because this file has to stay build-free/plain-JS-loadable — see the module + * docstring. `DIRECT_RESPONSE_START_TIMEOUT_CODE` in + * `open-sse/utils/directResponseStartTimeout.ts` is the canonical definition; + * keep this string literal in sync with it. + * + * @param {unknown} err + * @returns {boolean} + */ +export function isRecoverableUpstreamTimeoutError(err) { + // Same reason-shape tolerance as isIntentionalComboAbort: a bare string + // reason rejects waiters with the string itself, not an Error object. + if (err === "DIRECT_RESPONSE_START_TIMEOUT") return true; + if (!err || typeof err !== "object") return false; + return /** @type {NodeJS.ErrnoException} */ (err).code === "DIRECT_RESPONSE_START_TIMEOUT"; +} + +/** + * Intentional combo-leg cancellation. When a combo dispatches hedged targets, + * the losing legs are aborted with a distinctive reason once a sibling wins + * (`hedge-cancelled`) or exceeds its per-model budget (`combo-per-model-timeout`) + * — see `COMBO_HEDGE_CANCELLED_REASON` / `COMBO_PER_MODEL_TIMEOUT_REASON` in + * `open-sse/services/combo/comboAbortReasons.ts` (bare literals duplicated here + * because this file must stay build-free; keep in sync). On 2026-09-14 such a + * cancellation escaped its promise chain and killed production with + * `Error [AbortError]: hedge-cancelled` — the request it belonged to had + * already completed 200 via the winning leg. + * + * Distinct from a *client* abort: only these exact reasons qualify, so an + * AbortError from an unknown subsystem still crashes loudly. + * + * @param {unknown} err + * @returns {boolean} + */ +export function isIntentionalComboAbort(err) { + const reasons = new Set(["hedge-cancelled", "combo-per-model-timeout"]); + // AbortSignal.reason is whatever was handed to abort(): a raw string + // reason rejects waiters with the string itself, not an Error object. + if (typeof err === "string") return reasons.has(err); + if (!err || typeof err !== "object") return false; + const e = /** @type {NodeJS.ErrnoException} */ (err); + if (e.name !== "AbortError") return false; + if (reasons.has(String(e.message))) return true; + const cause = /** @type {{ cause?: unknown }} */ (err).cause; + return typeof cause === "string" && reasons.has(cause); +} + +/** + * A network/IO failure against an upstream or its proxy — undici surfaces it + * as `TypeError: fetch failed` (fixed message; the syscall code rides on + * `cause`) or as an error carrying a `PROXY_UNREACHABLE` / `UND_ERR_*` code. + * On 2026-09-14 one of these (`PROXY_UNREACHABLE` / ECONNRESET to + * api.agnes-ai.cn) escaped as an uncaughtException and killed production. + * The request that triggered the fetch already fails through the normal + * error path; the stray copy delivered to nobody must not be process-fatal. + * + * The "fetch failed" message match is exact on purpose: it is undici's fixed + * wrapping message, so arbitrary TypeErrors still crash loudly. + * + * @param {unknown} err + * @returns {boolean} + */ +export function isUpstreamNetworkError(err) { + if (!err || typeof err !== "object") return false; + const e = /** @type {NodeJS.ErrnoException} */ (err); + if (e.name === "TypeError" && e.message === "fetch failed") return true; + switch (e.code) { + case "PROXY_UNREACHABLE": + case "UND_ERR_SOCKET": + case "UND_ERR_CONNECT_TIMEOUT": + case "UND_ERR_HEADERS_TIMEOUT": + case "UND_ERR_BODY_TIMEOUT": + case "ECONNREFUSED": + case "EHOSTUNREACH": + case "ENETUNREACH": + case "EAI_AGAIN": + return true; + default: + return false; + } +} + /** * Decide whether a process-level uncaughtException/unhandledRejection should be - * swallowed (benign client-abort) or allowed to surface (genuine bug). + * swallowed (benign client-abort, or a recoverable upstream timeout that a + * retry path already handles — #12861) or allowed to surface (genuine bug). * * Pure + exported so it can be unit-tested without poking process listeners. * @@ -75,7 +182,14 @@ export function isClientAbortError(err) { * @returns {boolean} true => swallow (log only), false => re-throw / let crash. */ export function shouldSwallowUncaught(err, origin) { - if (!isClientAbortError(err)) return false; + if ( + !isClientAbortError(err) && + !isRecoverableUpstreamTimeoutError(err) && + !isIntentionalComboAbort(err) && + !isUpstreamNetworkError(err) + ) { + return false; + } // Only swallow when the origin matches what the guard installed for. If some // other subsystem raised it (e.g. a deliberate `throw` in a domain), keep the // existing crash semantics. @@ -131,7 +245,9 @@ export function installProcessCrashGuard(log) { process.on("uncaughtException", (err, origin) => { if (shouldSwallowUncaught(err, origin)) { - logger("warn", "[server] swallowed client-abort uncaughtException:", err?.message ?? err); + // The warn line is the only evidence a swallowed error ever happened; + // pass the full error object so the stack survives. + logger("warn", "[server] swallowed benign uncaughtException:", err); return; } throw err; @@ -139,11 +255,7 @@ export function installProcessCrashGuard(log) { process.on("unhandledRejection", (reason) => { if (shouldSwallowUncaught(reason, "unhandledRejection")) { - logger( - "warn", - "[server] swallowed client-abort unhandledRejection:", - reason?.message ?? reason - ); + logger("warn", "[server] swallowed benign unhandledRejection:", reason); return; } throw reason; diff --git a/tests/fixtures/release-acceptance/failed-pack-boot.json b/tests/fixtures/release-acceptance/failed-pack-boot.json new file mode 100644 index 0000000000..0f3fdefe11 --- /dev/null +++ b/tests/fixtures/release-acceptance/failed-pack-boot.json @@ -0,0 +1,89 @@ +{ + "schema_version": 1, + "identity": { + "repository": "diegosouzapw/OmniRoute", + "run_id": "1", + "run_attempt": 1, + "workflow": "release-acceptance.yml", + "trigger": "push", + "scope": "release", + "requested_ref": "refs/heads/release/v3.8.51", + "base_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "candidate_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e" + }, + "required_gates": [ + { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null + }, + { + "gate_id": "pack-boot", + "suite_id": null, + "shard_index": null, + "shard_total": null + } + ], + "gates": [ + { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "check:pack-artifact", + "gate_type": "artifact", + "status": "FAIL", + "cause": null, + "exit_code": 1, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ] + }, + { + "gate_id": "pack-boot", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "check:pack-boot", + "gate_type": "artifact", + "status": "FAIL", + "cause": { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null + }, + "exit_code": 1, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ] + } + ], + "evidence_errors": [], + "verdict": "FAILED", + "artifact": { + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e", + "identity": "omniroute.tgz" + } +} diff --git a/tests/fixtures/release-acceptance/infra-pack-boot.json b/tests/fixtures/release-acceptance/infra-pack-boot.json new file mode 100644 index 0000000000..d7b721b021 --- /dev/null +++ b/tests/fixtures/release-acceptance/infra-pack-boot.json @@ -0,0 +1,100 @@ +{ + "schema_version": 1, + "identity": { + "repository": "diegosouzapw/OmniRoute", + "run_id": "1", + "run_attempt": 1, + "workflow": "release-acceptance.yml", + "trigger": "push", + "scope": "release", + "requested_ref": "refs/heads/release/v3.8.51", + "base_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "candidate_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e" + }, + "required_gates": [ + { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null + }, + { + "gate_id": "pack-boot", + "suite_id": null, + "shard_index": null, + "shard_total": null + } + ], + "gates": [ + { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "check:pack-artifact", + "gate_type": "artifact", + "status": "INFRA_ERROR", + "cause": null, + "exit_code": 2, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ] + }, + { + "gate_id": "pack-boot", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "check:pack-boot", + "gate_type": "artifact", + "status": "INFRA_ERROR", + "cause": { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null + }, + "exit_code": 2, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ] + } + ], + "evidence_errors": [ + { + "code": "infra", + "gate": { + "gate_id": "pack-artifact", + "suite_id": null, + "shard_index": null, + "shard_total": null + }, + "detail": "evidence cap" + } + ], + "verdict": "UNVERIFIED", + "artifact": { + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e", + "identity": "omniroute.tgz" + } +} diff --git a/tests/fixtures/release-acceptance/legacy-close-steps.yml b/tests/fixtures/release-acceptance/legacy-close-steps.yml new file mode 100644 index 0000000000..ffaa57b679 --- /dev/null +++ b/tests/fixtures/release-acceptance/legacy-close-steps.yml @@ -0,0 +1,39 @@ + - name: Close tracking issue when the branch is green again + if: steps.validate.outputs.exit == '0' + env: + GH_TOKEN: ${{ github.token }} + TARGET: ${{ steps.branch.outputs.target }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + # The open/update step above is the UPWARD half of the loop; without this + # step a stale "not green" issue outlives the fix and every base-green check + # (`AGENTS.md` → "Base-green check") keeps stamping new PRs as base-red inherited. + TITLE="🔴 Release branch not green: ${TARGET}" + EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \ + --search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "") + if [ -n "$EXISTING" ]; then + gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" --reason completed \ + --comment "✅ \`${TARGET}\` is release-green again at \`${GITHUB_SHA:0:9}\` — ${RUN_URL}. Auto-closed by Release-Green (continuous)." + echo "Closed issue #$EXISTING" + fi + + - name: Close tracking issue when the branch is green again + if: steps.validate.outputs.exit == '0' + env: + GH_TOKEN: ${{ github.token }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + # The open/update step above is the UPWARD half of the loop; without this + # step a stale "not green" issue outlives the fix and every base-green check + # (`AGENTS.md` → "Base-green check") keeps stamping new PRs as base-red inherited. + TITLE="🔴 main branch not green" + EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \ + --search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "") + if [ -n "$EXISTING" ]; then + gh issue close "$EXISTING" --repo "$GITHUB_REPOSITORY" --reason completed \ + --comment "✅ \`main\` is main-green again at \`${GITHUB_SHA:0:9}\` — ${RUN_URL}. Auto-closed by Release-Green (continuous)." + echo "Closed issue #$EXISTING" + fi + diff --git a/tests/fixtures/release-acceptance/plan-lint.json b/tests/fixtures/release-acceptance/plan-lint.json new file mode 100644 index 0000000000..6def6c2dc1 --- /dev/null +++ b/tests/fixtures/release-acceptance/plan-lint.json @@ -0,0 +1,24 @@ +{ + "schema_version": 1, + "identity": { + "repository": "diegosouzapw/OmniRoute", + "run_id": "1", + "run_attempt": 1, + "workflow": "release-acceptance.yml", + "trigger": "push", + "scope": "release", + "requested_ref": "refs/heads/release/v3.8.51", + "base_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "candidate_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e" + }, + "required_gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null + } + ], + "artifact": null +} diff --git a/tests/fixtures/release-acceptance/shadow-manifests/lint.json b/tests/fixtures/release-acceptance/shadow-manifests/lint.json new file mode 100644 index 0000000000..e4c632df67 --- /dev/null +++ b/tests/fixtures/release-acceptance/shadow-manifests/lint.json @@ -0,0 +1,51 @@ +{ + "schema_version": 1, + "identity": { + "repository": "diegosouzapw/OmniRoute", + "run_id": "1", + "run_attempt": 1, + "workflow": "release-acceptance.yml", + "trigger": "push", + "scope": "release", + "requested_ref": "refs/heads/release/v3.8.51", + "base_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "candidate_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e" + }, + "required_gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null + } + ], + "gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "lint", + "gate_type": "static", + "status": "PASS", + "cause": null, + "exit_code": 0, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ] + } + ], + "evidence_errors": [], + "verdict": "VERIFIED", + "artifact": null +} diff --git a/tests/fixtures/release-acceptance/unverified-required-skipped.json b/tests/fixtures/release-acceptance/unverified-required-skipped.json new file mode 100644 index 0000000000..b99c18123c --- /dev/null +++ b/tests/fixtures/release-acceptance/unverified-required-skipped.json @@ -0,0 +1,63 @@ +{ + "schema_version": 1, + "identity": { + "repository": "diegosouzapw/OmniRoute", + "run_id": "1", + "run_attempt": 1, + "workflow": "release-acceptance.yml", + "trigger": "push", + "scope": "release", + "requested_ref": "refs/heads/release/v3.8.51", + "base_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "candidate_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e" + }, + "required_gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null + } + ], + "gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "lint", + "gate_type": "static", + "status": "SKIPPED", + "cause": null, + "exit_code": null, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ], + "reason": "plan-optional-looking" + } + ], + "evidence_errors": [ + { + "code": "required_skipped", + "gate": { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null + }, + "detail": "required gate SKIPPED" + } + ], + "verdict": "UNVERIFIED", + "artifact": null +} diff --git a/tests/fixtures/release-acceptance/verified.json b/tests/fixtures/release-acceptance/verified.json new file mode 100644 index 0000000000..e4c632df67 --- /dev/null +++ b/tests/fixtures/release-acceptance/verified.json @@ -0,0 +1,51 @@ +{ + "schema_version": 1, + "identity": { + "repository": "diegosouzapw/OmniRoute", + "run_id": "1", + "run_attempt": 1, + "workflow": "release-acceptance.yml", + "trigger": "push", + "scope": "release", + "requested_ref": "refs/heads/release/v3.8.51", + "base_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "candidate_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e" + }, + "required_gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null + } + ], + "gates": [ + { + "gate_id": "lint", + "suite_id": null, + "shard_index": null, + "shard_total": null, + "tested_sha": "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e", + "run_id": "1", + "run_attempt": 1, + "command_id": "lint", + "gate_type": "static", + "status": "PASS", + "cause": null, + "exit_code": 0, + "duration_ms": 10, + "evidence": [ + { + "artifact_id": "logs", + "member": "lint.log", + "algorithm": "sha256", + "digest": "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e" + } + ] + } + ], + "evidence_errors": [], + "verdict": "VERIFIED", + "artifact": null +} diff --git a/tests/unit/agnes-provider.test.ts b/tests/unit/agnes-provider.test.ts index f1353c6e1a..a8bc7a5d50 100644 --- a/tests/unit/agnes-provider.test.ts +++ b/tests/unit/agnes-provider.test.ts @@ -9,13 +9,18 @@ process.env.DATA_DIR = TEST_DATA_DIR; const { APIKEY_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); const { VIDEO_PROVIDER_IDS } = await import("../../src/shared/constants/providers.ts"); -const { REGISTRY: providerRegistry } = await import("../../open-sse/config/providerRegistry.ts"); +const { REGISTRY: providerRegistry, getRegistryModelThinkingEfforts } = + await import("../../open-sse/config/providerRegistry.ts"); const { IMAGE_PROVIDERS, getAllImageModels } = await import("../../open-sse/config/imageRegistry.ts"); const { VIDEO_PROVIDERS, getAllVideoModels } = await import("../../open-sse/config/videoRegistry.ts"); const { FREE_MODEL_BUDGETS } = await import("../../open-sse/config/freeModelCatalog.ts"); const { DefaultExecutor } = await import("../../open-sse/executors/default.ts"); +const { sanitizeReasoningEffortForProvider } = + await import("../../open-sse/executors/base/reasoningEffort.ts"); +const { getThinkingCapabilityFields } = + await import("../../src/app/api/v1/models/catalogHelpers.ts"); const { handleImageGeneration } = await import("../../open-sse/handlers/imageGeneration.ts"); const { handleVideoGeneration } = await import("../../open-sse/handlers/videoGeneration.ts"); const { resolveChatCoreTargetFormat } = @@ -87,6 +92,7 @@ test("agnes ships the current public chat models with the correct capabilities", assert.equal(flash20.contextLength, 262144); assert.equal(flash20.maxOutputTokens, 65536); assert.equal(flash20.supportsReasoning, true); + assert.deepEqual(flash20.supportedThinkingEfforts, ["none", "low", "medium", "high", "max"]); assert.equal(flash20.supportsVision, true); assert.equal(flash20.toolCalling, true); @@ -94,26 +100,97 @@ test("agnes ships the current public chat models with the correct capabilities", assert.ok(flash25, "agnes-2.5-flash must be defined"); assert.equal(flash25.contextLength, 524288); assert.equal(flash25.maxOutputTokens, 65536); + assert.equal(flash25.supportsReasoning, true); + assert.deepEqual(flash25.supportedThinkingEfforts, ["none", "low", "medium", "high", "max"]); const flash30 = entry.models.find((m) => m.id === "agnes-3.0-flash"); assert.ok(flash30, "agnes-3.0-flash must be defined"); assert.equal(flash30.contextLength, 524288); assert.equal(flash30.maxOutputTokens, 65536); assert.equal(flash30.supportsReasoning, true); + assert.deepEqual(flash30.supportedThinkingEfforts, [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max", + ]); assert.equal(flash30.supportsVision, true); assert.equal(flash30.toolCalling, true); assert.equal(flash30.interleavedField, "reasoning_content"); }); +test("agnes chat models advertise official thinking vocabulary", () => { + for (const id of ["agnes-2.0-flash", "agnes-2.5-flash"]) { + assert.deepEqual(getRegistryModelThinkingEfforts("agnes", id), [ + "none", + "low", + "medium", + "high", + "max", + ]); + } + assert.deepEqual(getRegistryModelThinkingEfforts("agnes", "agnes-3.0-flash"), [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max", + ]); +}); + +test("agnes catalog effort_tiers match declared vocabulary, not six-tier fallback", () => { + const efforts = getRegistryModelThinkingEfforts("agnes", "agnes-3.0-flash"); + assert.ok(efforts && efforts.length > 0); + assert.deepEqual( + getThinkingCapabilityFields("agnes", "agnes-3.0-flash", true, efforts, !efforts.length), + { + thinking: true, + supportsThinking: true, + effort_tiers: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], + } + ); +}); + +test("agnes sanitizer keeps official tiers and clamps undocumented ones", () => { + const clamp = (model: string, effort: string) => + ( + sanitizeReasoningEffortForProvider({ reasoning_effort: effort }, "agnes", model) as { + reasoning_effort?: string; + } + ).reasoning_effort; + + assert.equal(clamp("agnes-3.0-flash", "none"), "none"); + assert.equal(clamp("agnes-3.0-flash", "minimal"), "minimal"); + assert.equal(clamp("agnes-3.0-flash", "low"), "low"); + assert.equal(clamp("agnes-3.0-flash", "high"), "high"); + assert.equal(clamp("agnes-3.0-flash", "xhigh"), "xhigh"); + assert.equal(clamp("agnes-3.0-flash", "max"), "max"); + assert.equal(clamp("agnes-3.0-flash", "ultra"), "max"); + assert.equal(clamp("agnes-3.0-flash", "off"), "none"); + + // 2.0/2.5 reject xhigh (HTTP 400); clamp up to the next accepted tier (max). + assert.equal(clamp("agnes-2.0-flash", "xhigh"), "max"); + assert.equal(clamp("agnes-2.5-flash", "xhigh"), "max"); + assert.equal(clamp("agnes-2.0-flash", "max"), "max"); + assert.equal(clamp("agnes-2.0-flash", "off"), "none"); + assert.equal(clamp("agnes-2.0-flash", "minimal"), "low"); + assert.equal(clamp("agnes-2.5-flash", "minimal"), "low"); + assert.equal(clamp("agnes-2.5-flash", "off"), "none"); +}); + test("agnes registry advertises the live OpenAI-style /models endpoint", () => { const entry = providerRegistry.agnes; assert.equal(entry.modelsUrl, AGNES_MODELS_URL); }); test("agnes is classified for live OpenAI-style /models discovery", async () => { - const { isNamedOpenAIStyleProvider } = await import( - "../../src/app/api/providers/[id]/models/discovery/providerSets.ts" - ); + const { isNamedOpenAIStyleProvider } = + await import("../../src/app/api/providers/[id]/models/discovery/providerSets.ts"); assert.equal(isNamedOpenAIStyleProvider("agnes"), true); }); @@ -125,9 +202,8 @@ test("agnes honors per-connection CN base URL override", () => { }); test("agnes base-URL field is always-on so CN keys can point at api.agnes-ai.cn", async () => { - const helpers = await import( - "../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts" - ); + const helpers = + await import("../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts"); assert.equal(helpers.isBaseUrlConfigurableProvider("agnes"), true); assert.equal(helpers.getProviderBaseUrlDefault("agnes"), "https://apihub.agnes-ai.com/v1"); assert.equal(helpers.getProviderBaseUrlPlaceholder("agnes"), AGNES_CN_BASE_URL); diff --git a/tests/unit/codex-reasoning-wire-whitelist.test.ts b/tests/unit/codex-reasoning-wire-whitelist.test.ts new file mode 100644 index 0000000000..6b6cb84eaa --- /dev/null +++ b/tests/unit/codex-reasoning-wire-whitelist.test.ts @@ -0,0 +1,96 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { CodexExecutor } from "../../open-sse/executors/codex.ts"; +import { setThinkingBudgetConfig, ThinkingMode } from "../../open-sse/services/thinkingBudget.ts"; + +// The Codex Responses API accepts only `effort` and `summary` inside +// `reasoning`. Client ecosystems send OpenRouter-style keys (`enabled`, +// `max_tokens`, `exclude`, ...) that the upstream rejects with HTTP 400 +// "Unknown parameter: 'reasoning.'", taking down every combo target +// with the same deterministic client error. The executor must whitelist the +// object before it reaches the wire; `enabled: false` maps to effort "none" +// when no more specific effort was requested. + +const CTX = { requestEndpointPath: "/responses" }; + +function transform(body: Record, model = "gpt-6-astra") { + const executor = new CodexExecutor(); + return executor.transformRequest(model, body, false, CTX) as Record; +} + +function reasoningOf(result: Record): Record | null { + const r = result.reasoning; + if (r && typeof r === "object" && !Array.isArray(r)) return r as Record; + return null; +} + +test("reasoning.enabled is stripped; explicit effort survives", () => { + const r = reasoningOf(transform({ reasoning: { enabled: true, effort: "high" } })); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.effort, "high"); + assert.equal("enabled" in r, false); +}); + +test("reasoning.enabled:false maps to effort none when nothing more specific is set", () => { + const r = reasoningOf(transform({ reasoning: { enabled: false } })); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.effort, "none"); + assert.equal("enabled" in r, false); + assert.equal("summary" in r, false, "no summary for disabled reasoning"); +}); + +test("OpenRouter-style reasoning.max_tokens never reaches the wire", () => { + const r = reasoningOf(transform({ reasoning: { max_tokens: 2048 } })); + assert.ok(!r || !("max_tokens" in r), "max_tokens must be stripped"); +}); + +test("reasoning.exclude is stripped; sibling effort survives", () => { + const r = reasoningOf(transform({ reasoning: { exclude: true, effort: "low" } })); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.effort, "low"); + assert.equal("exclude" in r, false); +}); + +test("client-provided summary is preserved", () => { + const r = reasoningOf(transform({ reasoning: { summary: "detailed", effort: "medium" } })); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.summary, "detailed"); + assert.equal(r.effort, "medium"); +}); + +test("model suffix effort still wins over enabled:false", () => { + const r = reasoningOf(transform({ reasoning: { enabled: false } }, "gpt-6-astra-high")); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.effort, "high"); +}); + +test("enabled:false wins over an explicit connection reasoning default", () => { + setThinkingBudgetConfig({ mode: ThinkingMode.PASSTHROUGH }); + try { + const executor = new CodexExecutor(); + const result = executor.transformRequest( + "gpt-6-astra", + { reasoning: { enabled: false } }, + false, + { + requestEndpointPath: "/responses", + providerSpecificData: { requestDefaults: { reasoningEffort: "high" } }, + } + ) as Record; + const r = reasoningOf(result); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.effort, "none", "client disable must beat the connection default"); + } finally { + setThinkingBudgetConfig({}); + } +}); + +test("flat reasoning_effort path stays clean of extra keys", () => { + const result = transform({ reasoning_effort: "low", reasoning: { enabled: true } }); + assert.equal("reasoning_effort" in result, false, "flat key must never reach the wire"); + const r = reasoningOf(result); + assert.ok(r, "reasoning object should be present"); + assert.equal(r.effort, "low"); + assert.equal("enabled" in r, false); +}); diff --git a/tests/unit/combo-test-health.test.ts b/tests/unit/combo-test-health.test.ts index c0e9ecf4c8..85dd4311c4 100644 --- a/tests/unit/combo-test-health.test.ts +++ b/tests/unit/combo-test-health.test.ts @@ -8,7 +8,7 @@ const { extractComboTestStreamText, } = await import("../../src/lib/combos/testHealth.ts"); -test("combo test helper builds a short smoke payload", () => { +test("combo test helper builds short smoke payload", () => { const body = buildComboTestRequestBody("openrouter/openai/gpt-5.4"); assert.equal(body.model, "openrouter/openai/gpt-5.4"); @@ -16,6 +16,15 @@ test("combo test helper builds a short smoke payload", () => { assert.equal(body.max_tokens, 64); assert.equal("temperature" in body, false); assert.equal(body.stream, false); + assert.equal("reasoning_effort" in body, false); +}); + +test("combo test helper turns off thinking for Gemini 3.8 flash-high probes", () => { + const body = buildComboTestRequestBody("agy/gemini-3.8-flash-high"); + + assert.equal(body.messages[0].content, "Reply with exactly: pong"); + assert.equal(body.max_tokens, 64); + assert.equal(body.reasoning_effort, "none"); }); test("combo test helper builds a small streaming model probe", () => { diff --git a/tests/unit/combo-test-route.test.ts b/tests/unit/combo-test-route.test.ts index 0053330f23..661b5a3066 100644 --- a/tests/unit/combo-test-route.test.ts +++ b/tests/unit/combo-test-route.test.ts @@ -148,6 +148,7 @@ test("combo test route marks a model healthy only when it returns assistant text assert.equal(forwardedBody.model, "openrouter/openai/gpt-5.4"); assert.equal(forwardedBody.messages[0].content, "Reply with exactly: pong"); assert.equal(forwardedBody.max_tokens, 64); + assert.equal("reasoning_effort" in forwardedBody, false); assert.equal("temperature" in forwardedBody, false); assert.equal(body.resolvedBy, "openrouter/openai/gpt-5.4"); assert.equal(body.results[0].status, "ok"); diff --git a/tests/unit/db-migrationrunner-constants-split.test.ts b/tests/unit/db-migrationrunner-constants-split.test.ts index eb932cbb13..c225a9518b 100644 --- a/tests/unit/db-migrationrunner-constants-split.test.ts +++ b/tests/unit/db-migrationrunner-constants-split.test.ts @@ -63,7 +63,7 @@ describe("migrationRunner/constants — exact small-table snapshots", () => { it("OPTIONAL_FTS5_MIGRATION_VERSIONS is exactly {022, 023}", () => { assert.ok(OPTIONAL_FTS5_MIGRATION_VERSIONS instanceof Set); - assert.deepEqual([...OPTIONAL_FTS5_MIGRATION_VERSIONS].sort(), ["022", "023"]); + assert.deepEqual([...OPTIONAL_FTS5_MIGRATION_VERSIONS].sort(), ["022", "023", "180"]); }); }); @@ -71,7 +71,7 @@ describe("migrationRunner/constants — exact small-table snapshots", () => { describe("migrationRunner/constants — large-table integrity", () => { it("RENAMED_MIGRATION_COMPATIBILITY has 32 well-formed entries", () => { - assert.equal(RENAMED_MIGRATION_COMPATIBILITY.length, 32); + assert.equal(RENAMED_MIGRATION_COMPATIBILITY.length, 33); for (const e of RENAMED_MIGRATION_COMPATIBILITY) { assert.equal(typeof e.fromVersion, "string"); assert.equal(typeof e.fromName, "string"); @@ -124,49 +124,55 @@ describe("migrationRunner/constants — large-table integrity", () => { toName: "inspector_custom_hosts", } ); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-7), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-8), { fromVersion: "134", fromName: "ccr_blocks", toVersion: "139", toName: "ccr_blocks", }); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-6), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-7), { fromVersion: "139", fromName: "job_registry", toVersion: "146", toName: "job_registry", }); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-5), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-6), { fromVersion: "143", fromName: "radar_local_model_state", toVersion: "153", toName: "radar_local_model_state", }); // #12036: renamed migrations 056/073/077/101 appended as compatibility renames - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-4), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-5), { fromVersion: "056", fromName: "provider_default", toVersion: "056", toName: "mcp_accessibility_compression", }); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-3), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-4), { fromVersion: "073", fromName: "discovery_results", toVersion: "073", toName: "per_model_token_limits", }); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-2), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-3), { fromVersion: "077", fromName: "plugin_metrics", toVersion: "077", toName: "api_key_stream_default_mode", }); - assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-1), { + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-2), { fromVersion: "101", fromName: "proxy_pool_rotation", toVersion: "101", toName: "api_key_usage_limits", }); + assert.deepEqual(RENAMED_MIGRATION_COMPATIBILITY.at(-1), { + fromVersion: "176", + fromName: "memory_fts_skip_access_updates", + toVersion: "180", + toName: "memory_fts_au_conditional_memory_id", + }); }); it("PHYSICAL_SCHEMA_SENTINELS has 15 well-formed entries incl. the newest 064", () => { diff --git a/tests/unit/diagnostics.test.ts b/tests/unit/diagnostics.test.ts index 62da56fc0c..f4fd6438f9 100644 --- a/tests/unit/diagnostics.test.ts +++ b/tests/unit/diagnostics.test.ts @@ -128,6 +128,14 @@ test("detectMalformedNonStream returns 'empty_choices' when choice message has n assert.equal(detectMalformedNonStream(body), "empty_choices"); }); +test("detectMalformedNonStream returns null when empty content stopped at token limit", () => { + const body = { + choices: [{ index: 0, message: { role: "assistant", content: "" }, finish_reason: "length" }], + usage: { reasoning_tokens: 28 }, + }; + assert.equal(detectMalformedNonStream(body), null); +}); + test("detectMalformedNonStream returns null for valid chat completion", () => { const body = { choices: [{ message: { content: "Hello!", tool_calls: null }, finish_reason: "stop" }], diff --git a/tests/unit/direct-response-start-timeout-settled-guard-12861.test.ts b/tests/unit/direct-response-start-timeout-settled-guard-12861.test.ts new file mode 100644 index 0000000000..24053128b1 --- /dev/null +++ b/tests/unit/direct-response-start-timeout-settled-guard-12861.test.ts @@ -0,0 +1,178 @@ +// #12861 — proxyFetch: DIRECT_RESPONSE_START_TIMEOUT escapes as +// unhandledRejection -> uncaughtException, server process exits. +// +// A narrow race: if the timer fires AFTER the wrapped fetch has already +// settled (resolved or rejected) — e.g. the awaiting frame was already torn +// down — aborting the (by-then-irrelevant) AbortController can deliver its +// abort reason to a promise nobody is awaiting anymore, which Node promotes +// to an unhandledRejection -> uncaughtException. These tests use node:test's +// mock timer API to deterministically force exactly that ordering, rather +// than relying on real wall-clock timing (which cannot reliably reproduce a +// race this narrow). +import test, { mock } from "node:test"; +import assert from "node:assert/strict"; +import { + directFetchWithBoundedResponseStart, + isDirectResponseStartTimeout, + resolveDirectHeadersTimeoutMs, +} from "../../open-sse/utils/directResponseStartTimeout.ts"; + +test.afterEach(() => { + mock.timers.reset(); +}); + +test("resolves normally when the fetch settles well before the timeout", async () => { + const response = new Response("ok"); + const result = await directFetchWithBoundedResponseStart( + "http://example.test", + {}, + async () => response, + 30_000 + ); + assert.equal(result, response); +}); + +test("rejects with DIRECT_RESPONSE_START_TIMEOUT when the fetch never settles before the timeout", async () => { + mock.timers.enable({ apis: ["setTimeout"] }); + try { + const fetchImpl = (_input: RequestInfo | URL, options: RequestInit) => + new Promise((_resolve, reject) => { + options.signal?.addEventListener("abort", () => { + reject((options.signal as AbortSignal).reason); + }); + }); + + const pending = directFetchWithBoundedResponseStart( + "http://example.test", + {}, + fetchImpl, + 5_000 + ); + const assertion = assert.rejects(pending, (err: unknown) => { + assert.equal(isDirectResponseStartTimeout(err), true); + return true; + }); + + await Promise.resolve(); + mock.timers.tick(5_000); + await assertion; + } finally { + mock.timers.reset(); + } +}); + +test("#12861: a timer firing AFTER the fetch already settled does not escape as an unhandled rejection", async () => { + // This is the actual race the report describes: `clearTimeout()` runs in + // the `finally` block, but the timer callback has already been dequeued by + // the time it runs, so clearing it has no effect. Node's real timer/ + // microtask scheduler can't be forced into that exact interleaving + // deterministically from a test, so the observable consequence is forced + // directly instead: neuter clearTimeout so the timer fires regardless of + // whether the code "tried" to cancel it, exactly as it would if clearTimeout + // had lost that race. + const realClearTimeout = globalThis.clearTimeout; + const realSetTimeout = globalThis.setTimeout; + globalThis.clearTimeout = (() => {}) as typeof clearTimeout; + + let unhandled: unknown = null; + const onUnhandledRejection = (reason: unknown) => { + unhandled = reason; + }; + process.on("unhandledRejection", onUnhandledRejection); + + try { + const response = new Response("ok"); + // Simulates what a real fetch/undici implementation does internally: some + // async chain tied to the same abort signal that the OUTER caller never + // awaits or attaches a .catch() to (e.g. background body-stream cleanup). + // This is the actual mechanism the issue traces the escaped rejection + // back to — not the outer `await fetchImpl(...)` itself, which normal + // control flow already handles fine. + const fetchImpl = async (_input: RequestInfo | URL, options: RequestInit) => { + const detachedInternalChain = new Promise((_resolve, reject) => { + options.signal?.addEventListener( + "abort", + () => reject((options.signal as AbortSignal).reason), + { once: true } + ); + }); + void detachedInternalChain; + return response; + }; + + const result = await directFetchWithBoundedResponseStart( + "http://example.test", + {}, + fetchImpl, + 10 + ); + assert.equal(result, response); + + // Real timer, real (short) wait — clearTimeout was neutered above, so the + // 10ms timer WILL fire regardless of the `finally` block having "tried" + // to clear it, exactly reproducing the reported race's end state. + await new Promise((resolve) => realSetTimeout(resolve, 50)); + + assert.equal(unhandled, null, "post-settlement timer fire must not produce a rejection"); + } finally { + globalThis.clearTimeout = realClearTimeout; + process.off("unhandledRejection", onUnhandledRejection); + } +}); + +test("#12861: a timer firing AFTER the fetch already rejected (for an unrelated reason) does not escape either", async () => { + const realClearTimeout = globalThis.clearTimeout; + const realSetTimeout = globalThis.setTimeout; + globalThis.clearTimeout = (() => {}) as typeof clearTimeout; + + let unhandled: unknown = null; + const onUnhandledRejection = (reason: unknown) => { + unhandled = reason; + }; + process.on("unhandledRejection", onUnhandledRejection); + + try { + const clientAbortError = Object.assign(new Error("aborted"), { code: "ECONNRESET" }); + const fetchImpl = async (_input: RequestInfo | URL, options: RequestInit) => { + const detachedInternalChain = new Promise((_resolve, reject) => { + options.signal?.addEventListener( + "abort", + () => reject((options.signal as AbortSignal).reason), + { once: true } + ); + }); + void detachedInternalChain; + throw clientAbortError; + }; + + await assert.rejects( + directFetchWithBoundedResponseStart("http://example.test", {}, fetchImpl, 10), + clientAbortError + ); + + await new Promise((resolve) => realSetTimeout(resolve, 50)); + + assert.equal(unhandled, null, "post-settlement timer fire must not produce a rejection"); + } finally { + globalThis.clearTimeout = realClearTimeout; + process.off("unhandledRejection", onUnhandledRejection); + } +}); + +test("passes through immediately with no timer when timeoutMs is 0 or negative", async () => { + const response = new Response("ok"); + const result = await directFetchWithBoundedResponseStart( + "http://example.test", + {}, + async () => response, + 0 + ); + assert.equal(result, response); +}); + +test("resolveDirectHeadersTimeoutMs defaults to 30000 and respects OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS", () => { + assert.equal(resolveDirectHeadersTimeoutMs({}), 30_000); + assert.equal(resolveDirectHeadersTimeoutMs({ OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS: "5000" }), 5_000); + assert.equal(resolveDirectHeadersTimeoutMs({ OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS: "" }), 30_000); + assert.equal(resolveDirectHeadersTimeoutMs({ OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS: "not-a-number" }), 0); +}); diff --git a/tests/unit/embeddings-lan-keyed-auth-13234.test.ts b/tests/unit/embeddings-lan-keyed-auth-13234.test.ts new file mode 100644 index 0000000000..fdba2165ca --- /dev/null +++ b/tests/unit/embeddings-lan-keyed-auth-13234.test.ts @@ -0,0 +1,165 @@ +/** + * #13234: a LAN/CGNAT OpenAI-compatible embeddings node with a stored API key + * must send Authorization: Bearer on the outbound proxy request. + * + * Dashboard Check already does this via buildBearerHeaders. The embeddings + * proxy did not: #6925 classified every private-host node as authType "none" + * before credentials were loaded, so buildAuth dropped the key and the + * upstream returned 401. Chat/completions against the same node/key worked. + * + * Keyless LAN nodes stay no-auth (#6925). Cloud-metadata hosts stay blocked. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-embed-lan-key-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const { createProviderNode } = await import("../../src/lib/db/providers/nodes.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const { createEmbeddingResponse } = await import("../../src/lib/embeddings/service.ts"); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); +}); + +function stubEmbeddingFetch() { + const originalFetch = globalThis.fetch; + let captured: { url: string; headers: Record } | null = null; + globalThis.fetch = async (url: RequestInfo | URL, options: RequestInit = {}) => { + captured = { + url: String(url), + headers: (options.headers as Record) || {}, + }; + return new Response( + JSON.stringify({ + data: [{ object: "embedding", embedding: [0.1, 0.2], index: 0 }], + usage: { prompt_tokens: 3, total_tokens: 3 }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + }; + return { + get captured() { + return captured; + }, + restore() { + globalThis.fetch = originalFetch; + }, + }; +} + +test("#13234: 100.64 CGNAT embeddings node with stored key sends Authorization", async () => { + const node = await createProviderNode({ + type: "openai-compatible-embeddings", + name: "CGNAT Embed", + prefix: "cgnatembed13234", + apiType: "embeddings", + baseUrl: "http://100.64.1.10:8080/v1", + }); + + await providersDb.createProviderConnection({ + provider: node.id, + authType: "apikey", + name: "CGNAT Embed Key", + apiKey: "sk-embed-13234", + isActive: true, + testStatus: "active", + providerSpecificData: { + prefix: "cgnatembed13234", + baseUrl: "http://100.64.1.10:8080/v1", + }, + }); + + const fetchStub = stubEmbeddingFetch(); + try { + const res = await createEmbeddingResponse({ + model: "cgnatembed13234/nomic-embed-text", + input: "hello world", + }); + assert.equal(res.status, 200); + } finally { + fetchStub.restore(); + } + + assert.ok(fetchStub.captured); + assert.equal( + fetchStub.captured!.url, + "http://100.64.1.10:8080/v1/embeddings", + "should hit the node's own embeddings endpoint" + ); + assert.equal( + fetchStub.captured!.headers.Authorization, + "Bearer sk-embed-13234", + "stored key must ride on the outbound embeddings request, matching Check" + ); +}); + +test("#13234: keyless 10.x LAN embeddings node still sends no Authorization", async () => { + await createProviderNode({ + type: "openai-compatible-embeddings", + name: "LAN Ollama Keyless", + prefix: "lanollama13234", + apiType: "embeddings", + baseUrl: "http://10.10.0.181:11434/v1", + }); + + const fetchStub = stubEmbeddingFetch(); + try { + const res = await createEmbeddingResponse({ + model: "lanollama13234/nomic-embed-text", + input: "hello world", + }); + assert.equal(res.status, 200); + } finally { + fetchStub.restore(); + } + + assert.ok(fetchStub.captured); + assert.equal( + fetchStub.captured!.headers.Authorization, + undefined, + "a keyless LAN provider must not receive a fabricated Authorization header" + ); +}); + +test("#13234: LAN node with a keyless connection record still sends no Authorization", async () => { + const node = await createProviderNode({ + type: "openai-compatible-embeddings", + name: "LAN empty key", + prefix: "lanempty13234", + apiType: "embeddings", + baseUrl: "http://10.20.0.5:11434/v1", + }); + + await providersDb.createProviderConnection({ + provider: node.id, + authType: "apikey", + name: "LAN empty key conn", + apiKey: "", + isActive: true, + }); + + const fetchStub = stubEmbeddingFetch(); + try { + const res = await createEmbeddingResponse({ + model: "lanempty13234/nomic-embed-text", + input: "hello world", + }); + assert.equal(res.status, 200); + } finally { + fetchStub.restore(); + } + + assert.ok(fetchStub.captured); + assert.equal( + fetchStub.captured!.headers.Authorization, + undefined, + "empty stored key must not fabricate Authorization", + ); +}); diff --git a/tests/unit/gemini-38-thinking-level-output.test.ts b/tests/unit/gemini-38-thinking-level-output.test.ts new file mode 100644 index 0000000000..adf823e264 --- /dev/null +++ b/tests/unit/gemini-38-thinking-level-output.test.ts @@ -0,0 +1,229 @@ +/** + * Gemini 3.8 Flash talks thinking_level (low|medium|high), not the 3.7 numeric + * thinkingBudget Omni still emits. includeThoughts:true also shares + * maxOutputTokens with hidden thoughts, so a review-sized max_tokens=65536 + * request starves visible completion (finish=length, ~2.6k text). + * + * Tests hit openaiToGeminiRequest / openaiToAntigravityRequest / claudeToGeminiRequest + * at the write sites so a helper cannot stay green with the call gone. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { openaiToGeminiRequest, openaiToAntigravityRequest } = await import( + "../../open-sse/translator/request/openai-to-gemini.ts" +); +const { claudeToGeminiRequest } = await import( + "../../open-sse/translator/request/claude-to-gemini.ts" +); +const { gemini38ThinkingLevelFromBudget } = await import( + "../../open-sse/services/thinkingBudget.ts" +); + +type ThinkingConfig = { + thinkingBudget?: number; + thinkingLevel?: string; + includeThoughts?: boolean; +}; + +type GeminiReq = { + generationConfig?: { + maxOutputTokens?: number; + thinkingConfig?: ThinkingConfig; + }; +}; + +type EnvelopeReq = { + request?: { + generationConfig?: { + maxOutputTokens?: number; + thinkingConfig?: ThinkingConfig; + }; + }; +}; + +const reviewBody = (extra: Record = {}) => ({ + messages: [{ role: "user", content: "review diff" }], + max_tokens: 65536, + ...extra, +}); + +function thinkingConfigOf(model: string, extra: Record = {}) { + const result = openaiToGeminiRequest(model, reviewBody(extra), false) as GeminiReq; + return result.generationConfig?.thinkingConfig; +} + +test("gemini-3.8-flash-high emits thinkingLevel high, not numeric thinkingBudget", () => { + const tc = thinkingConfigOf("gemini-3.8-flash-high", { reasoning_effort: "high" }); + assert.equal(tc?.thinkingLevel, "high"); + assert.equal(tc?.thinkingBudget, undefined); +}); + +test("gemini-3.8-flash-medium emits thinkingLevel medium", () => { + const tc = thinkingConfigOf("gemini-3.8-flash-medium", { reasoning_effort: "medium" }); + assert.equal(tc?.thinkingLevel, "medium"); + assert.equal(tc?.thinkingBudget, undefined); +}); + +test("gemini-3.8-flash-low emits thinkingLevel low", () => { + const tc = thinkingConfigOf("gemini-3.8-flash-low", { reasoning_effort: "low" }); + assert.equal(tc?.thinkingLevel, "low"); + assert.equal(tc?.thinkingBudget, undefined); +}); + +test("gemini-3.8-flash bare default maps thinkingLevel medium", () => { + const tc = thinkingConfigOf("gemini-3.8-flash"); + assert.equal(tc?.thinkingLevel, "medium"); + assert.equal(tc?.thinkingBudget, undefined); +}); + +test("gemini-3.8-flash-tiered default maps thinkingLevel medium", () => { + const tc = thinkingConfigOf("gemini-3.8-flash-tiered"); + assert.equal(tc?.thinkingLevel, "medium"); + assert.equal(tc?.thinkingBudget, undefined); +}); + +test("agy/gemini-3.8-flash-high prefix emits thinkingLevel high", () => { + const tc = thinkingConfigOf("agy/gemini-3.8-flash-high", { reasoning_effort: "high" }); + assert.equal(tc?.thinkingLevel, "high"); + assert.equal(tc?.thinkingBudget, undefined); +}); + +test("gemini-3.8 review-shaped request does not default-inject includeThoughts", () => { + const result = openaiToGeminiRequest( + "gemini-3.8-flash-high", + reviewBody(), + false + ) as GeminiReq; + assert.equal(result.generationConfig?.maxOutputTokens, 65536); + assert.equal(result.generationConfig?.thinkingConfig?.thinkingLevel, "high"); + assert.equal(result.generationConfig?.thinkingConfig?.includeThoughts, undefined); +}); + +test("gemini-3.8 includeThoughts is set only when the client asked for thoughts", () => { + const silent = thinkingConfigOf("gemini-3.8-flash-high", { reasoning_effort: "high" }); + assert.equal(silent?.includeThoughts, undefined); + + const asked = thinkingConfigOf("gemini-3.8-flash-high", { + reasoning_effort: "high", + includeThoughts: true, + }); + assert.equal(asked?.includeThoughts, true); + assert.equal(asked?.thinkingLevel, "high"); +}); + +test("gemini-3.8 reasoning_effort none stays an explicit off-switch", () => { + const tc = thinkingConfigOf("gemini-3.8-flash-high", { reasoning_effort: "none" }); + assert.equal(tc?.thinkingBudget, 0); + assert.equal(tc?.includeThoughts, false); + assert.equal(tc?.thinkingLevel, undefined); +}); + +test("gemini-2.5-flash still emits numeric thinkingBudget (3.8 gate)", () => { + const result = openaiToGeminiRequest( + "gemini-2.5-flash", + reviewBody({ reasoning_effort: "high" }), + false + ) as GeminiReq; + assert.equal(result.generationConfig?.thinkingConfig?.thinkingBudget, 24576); + assert.equal(result.generationConfig?.thinkingConfig?.includeThoughts, true); + assert.equal(result.generationConfig?.thinkingConfig?.thinkingLevel, undefined); +}); + +test("Antigravity envelope keeps maxOutputTokens 65536 and thinkingLevel for 3.8", () => { + const result = openaiToAntigravityRequest( + "gemini-3.8-flash-high", + reviewBody({ reasoning_effort: "high" }), + false, + { projectId: "proj-gemini38" } + ) as EnvelopeReq; + const gc = result.request?.generationConfig; + assert.equal(gc?.maxOutputTokens, 65536); + assert.equal(gc?.thinkingConfig?.thinkingLevel, "high"); + assert.equal(gc?.thinkingConfig?.thinkingBudget, undefined); + assert.equal(gc?.thinkingConfig?.includeThoughts, undefined); +}); + +test("gemini-3.8 thinking.budget_tokens maps thinkingLevel, not thinkingBudget", () => { + const tc = thinkingConfigOf("gemini-3.8-flash-high", { + thinking: { type: "enabled", budget_tokens: 24576 }, + }); + assert.equal(tc?.thinkingLevel, "high"); + assert.equal(tc?.thinkingBudget, undefined); + assert.equal(tc?.includeThoughts, undefined); +}); + +test("claude-to-gemini gemini-3.8 budget_tokens maps thinkingLevel", () => { + const result = claudeToGeminiRequest( + "gemini-3.8-flash-high", + { + messages: [{ role: "user", content: [{ type: "text", text: "review diff" }] }], + max_tokens: 65536, + thinking: { type: "enabled", budget_tokens: 24576 }, + }, + false + ) as GeminiReq; + assert.equal(result.generationConfig?.thinkingConfig?.thinkingLevel, "high"); + assert.equal(result.generationConfig?.thinkingConfig?.thinkingBudget, undefined); + assert.equal(result.generationConfig?.thinkingConfig?.includeThoughts, undefined); +}); + +test("claude-to-gemini gemini-3.8-flash-high emits thinkingLevel, not includeThoughts", () => { + const result = claudeToGeminiRequest( + "gemini-3.8-flash-high", + { + messages: [{ role: "user", content: [{ type: "text", text: "review diff" }] }], + max_tokens: 65536, + output_config: { effort: "high" }, + }, + false + ) as GeminiReq; + assert.equal(result.generationConfig?.maxOutputTokens, 65536); + assert.equal(result.generationConfig?.thinkingConfig?.thinkingLevel, "high"); + assert.equal(result.generationConfig?.thinkingConfig?.thinkingBudget, undefined); + assert.equal(result.generationConfig?.thinkingConfig?.includeThoughts, undefined); +}); + +test("gemini38ThinkingLevelFromBudget rejects budget <= 0", () => { + assert.throws( + () => gemini38ThinkingLevelFromBudget("gemini-3.8-flash-high", 0), + RangeError + ); + assert.throws( + () => gemini38ThinkingLevelFromBudget("gemini-3.8-flash-high", -1), + RangeError + ); +}); + +test("gemini38ThinkingLevelFromBudget maps 1 and 1024 to low", () => { + assert.equal( + gemini38ThinkingLevelFromBudget("gemini-3.8-flash-high", 1), + "low" + ); + assert.equal( + gemini38ThinkingLevelFromBudget("gemini-3.8-flash-high", 1024), + "low" + ); +}); + +test("gemini38ThinkingLevelFromBudget maps 1025 through medium cap to medium", () => { + assert.equal( + gemini38ThinkingLevelFromBudget("gemini-3.8-flash-medium", 1025), + "medium" + ); + assert.equal( + gemini38ThinkingLevelFromBudget("gemini-3.8-flash-medium", 8192), + "medium" + ); +}); + +test("gemini38ThinkingLevelFromBudget maps above medium cap to high", () => { + assert.equal( + gemini38ThinkingLevelFromBudget("gemini-3.8-flash-medium", 8193), + "high" + ); + assert.equal( + gemini38ThinkingLevelFromBudget("gemini-3.8-flash-high", 24576), + "high" + ); +}); diff --git a/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts b/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts new file mode 100644 index 0000000000..b50e6fa965 --- /dev/null +++ b/tests/unit/http-client-abort-guard-direct-timeout-12861.test.ts @@ -0,0 +1,144 @@ +// #12861 — the shared process-crash guard (already installed for the +// dev server and the WS/API-bridge servers) needs to also recognize the +// recoverable DIRECT_RESPONSE_START_TIMEOUT code so a stray escaped +// rejection from that path is swallowed and logged instead of taking the +// process down, exactly like a benign client-abort already is. +import test from "node:test"; +import assert from "node:assert/strict"; +import { + isClientAbortError, + isIntentionalComboAbort, + isRecoverableUpstreamTimeoutError, + isUpstreamNetworkError, + shouldSwallowUncaught, +} from "../../src/shared/utils/httpClientAbortGuard.mjs"; + +test("isRecoverableUpstreamTimeoutError recognizes DIRECT_RESPONSE_START_TIMEOUT", () => { + const err = Object.assign(new Error("Direct response did not start within 30000ms"), { + code: "DIRECT_RESPONSE_START_TIMEOUT", + name: "TimeoutError", + }); + assert.equal(isRecoverableUpstreamTimeoutError(err), true); + // A raw string abort reason rejects waiters with the string itself. + assert.equal(isRecoverableUpstreamTimeoutError("DIRECT_RESPONSE_START_TIMEOUT"), true); +}); + +test("isRecoverableUpstreamTimeoutError rejects unrelated error codes", () => { + assert.equal(isRecoverableUpstreamTimeoutError(new Error("boom")), false); + assert.equal( + isRecoverableUpstreamTimeoutError(Object.assign(new Error("x"), { code: "ECONNRESET" })), + false + ); + assert.equal(isRecoverableUpstreamTimeoutError(null), false); + assert.equal(isRecoverableUpstreamTimeoutError(undefined), false); + assert.equal(isRecoverableUpstreamTimeoutError("a string, not an object"), false); +}); + +test("isRecoverableUpstreamTimeoutError does not overlap with isClientAbortError's own codes", () => { + // These two predicates should classify disjoint sets of codes; a + // DIRECT_RESPONSE_START_TIMEOUT is not a client abort and vice versa. + const timeoutErr = { code: "DIRECT_RESPONSE_START_TIMEOUT" }; + assert.equal(isClientAbortError(timeoutErr), false); + assert.equal(isRecoverableUpstreamTimeoutError(timeoutErr), true); + + const abortErr = { code: "ECONNRESET" }; + assert.equal(isClientAbortError(abortErr), true); + assert.equal(isRecoverableUpstreamTimeoutError(abortErr), false); +}); + +test("shouldSwallowUncaught swallows DIRECT_RESPONSE_START_TIMEOUT for uncaughtException and unhandledRejection origins", () => { + const err = Object.assign(new Error("timeout"), { code: "DIRECT_RESPONSE_START_TIMEOUT" }); + assert.equal(shouldSwallowUncaught(err, "uncaughtException"), true); + assert.equal(shouldSwallowUncaught(err, "unhandledRejection"), true); + assert.equal(shouldSwallowUncaught(err, undefined), true); +}); + +test("shouldSwallowUncaught still surfaces genuine errors (no code, no client-abort message)", () => { + const genuineBug = new TypeError("Cannot read properties of undefined"); + assert.equal(shouldSwallowUncaught(genuineBug, "uncaughtException"), false); + assert.equal(shouldSwallowUncaught(genuineBug, "unhandledRejection"), false); +}); + +test("shouldSwallowUncaught still swallows the original client-abort cases (no regression)", () => { + const aborted = new Error("aborted"); + assert.equal(shouldSwallowUncaught(aborted, "uncaughtException"), true); + + const econnreset = Object.assign(new Error("socket hang up"), { code: "ECONNRESET" }); + assert.equal(shouldSwallowUncaught(econnreset, "unhandledRejection"), true); +}); + +test("isIntentionalComboAbort recognizes hedge-cancelled aborts (message and cause variants)", () => { + const byMessage = Object.assign(new Error("hedge-cancelled"), { name: "AbortError" }); + assert.equal(isIntentionalComboAbort(byMessage), true); + + const byCause = Object.assign(new Error("This operation was aborted"), { + name: "AbortError", + cause: "hedge-cancelled", + }); + assert.equal(isIntentionalComboAbort(byCause), true); + + const perModelTimeout = Object.assign(new Error("combo-per-model-timeout"), { + name: "AbortError", + }); + assert.equal(isIntentionalComboAbort(perModelTimeout), true); +}); + +test("isIntentionalComboAbort rejects client aborts with unknown reasons", () => { + const clientGone = Object.assign(new Error("request_signal_aborted"), { name: "AbortError" }); + assert.equal(isIntentionalComboAbort(clientGone), false); + assert.equal(isIntentionalComboAbort(new Error("hedge-cancelled")), false); + assert.equal(isIntentionalComboAbort(null), false); +}); + +test("isIntentionalComboAbort accepts a bare string abort reason", () => { + // AbortSignal.reason is whatever was handed to abort(); a raw string reason + // rejects waiters with the string itself, not an Error object. + assert.equal(isIntentionalComboAbort("hedge-cancelled"), true); + assert.equal(isIntentionalComboAbort("combo-per-model-timeout"), true); + assert.equal(isIntentionalComboAbort("client-gone"), false); + assert.equal(isIntentionalComboAbort(""), false); +}); + +test("isUpstreamNetworkError recognizes fetch failures and proxy unreachable", () => { + const fetchFailed = Object.assign(new TypeError("fetch failed"), { + cause: Object.assign(new Error("socket disconnected"), { code: "ECONNRESET" }), + }); + assert.equal(isUpstreamNetworkError(fetchFailed), true); + + const proxyUnreachable = Object.assign(new TypeError("fetch failed"), { + code: "PROXY_UNREACHABLE", + }); + assert.equal(isUpstreamNetworkError(proxyUnreachable), true); + + const undiciSocket = Object.assign(new Error("other side closed"), { code: "UND_ERR_SOCKET" }); + assert.equal(isUpstreamNetworkError(undiciSocket), true); +}); + +test("isUpstreamNetworkError rejects genuine errors", () => { + assert.equal(isUpstreamNetworkError(new TypeError("Cannot read properties of undefined")), false); + assert.equal(isUpstreamNetworkError(new Error("fetch failedish")), false); + assert.equal(isUpstreamNetworkError(null), false); + assert.equal(isUpstreamNetworkError("a string"), false); +}); + +test("shouldSwallowUncaught swallows the 2026-09-14 agnes-storm crash shapes", () => { + // 06:11:04 exit 7: hedge cancellation escaped while the sibling leg won. + const hedge = Object.assign(new Error("hedge-cancelled"), { name: "AbortError" }); + assert.equal(shouldSwallowUncaught(hedge, "uncaughtException"), true); + assert.equal(shouldSwallowUncaught(hedge, "unhandledRejection"), true); + + // 06:27:38 exit 7: undici fetch failure against a flapping upstream. + const fetchFailed = Object.assign(new TypeError("fetch failed"), { + code: "PROXY_UNREACHABLE", + }); + assert.equal(shouldSwallowUncaught(fetchFailed, "uncaughtException"), true); + assert.equal(shouldSwallowUncaught(fetchFailed, "unhandledRejection"), true); +}); + +test("shouldSwallowUncaught still surfaces genuine bugs after the extension", () => { + const genuineBug = new TypeError("Cannot read properties of undefined"); + assert.equal(shouldSwallowUncaught(genuineBug, "uncaughtException"), false); + + const unknownAbort = Object.assign(new Error("mystery"), { name: "AbortError" }); + assert.equal(shouldSwallowUncaught(unknownAbort, "unhandledRejection"), false); +}); diff --git a/tests/unit/httpClientAbortGuard-default-logger.test.mjs b/tests/unit/httpClientAbortGuard-default-logger.test.mjs index 8154e4d001..f1eac91990 100644 --- a/tests/unit/httpClientAbortGuard-default-logger.test.mjs +++ b/tests/unit/httpClientAbortGuard-default-logger.test.mjs @@ -19,14 +19,14 @@ test("installProcessCrashGuard() with no argument swallows a client abort withou installProcessCrashGuard(); const handlers = process .listeners("uncaughtException") - .filter((fn) => fn.toString().includes("swallowed client-abort")); + .filter((fn) => fn.toString().includes("swallowed benign uncaughtException")); assert.ok(handlers.length > 0, "guard handler must be registered"); const abortErr = Object.assign(new Error("aborted"), { code: "ECONNRESET" }); // A broken default logger (console is an object, not a function) throws // TypeError here — that is what took the production process down. assert.doesNotThrow(() => handlers[0](abortErr, "uncaughtException")); assert.equal(warnings.length, 1, "the swallowed abort must be logged once"); - assert.ok(String(warnings[0][1]).includes("swallowed client-abort")); + assert.ok(String(warnings[0][1]).includes("swallowed benign uncaughtException")); } finally { console.warn = originalWarn; } diff --git a/tests/unit/httpClientAbortGuard.test.mjs b/tests/unit/httpClientAbortGuard.test.mjs index 293e8ae0ca..9345ccef22 100644 --- a/tests/unit/httpClientAbortGuard.test.mjs +++ b/tests/unit/httpClientAbortGuard.test.mjs @@ -202,3 +202,38 @@ test("installProcessCrashGuard still crashes on genuine errors (no over-swallowi assert.notEqual(status, 0, "genuine errors must keep crash semantics"); assert.doesNotMatch(stdout, /SHOULD_NOT_REACH/); }); + +// A swallowed error is the ONLY evidence it ever happened; logging just +// code/message throws away the stack. The logger must receive the full +// error object so the origin stays diagnosable. +test("installProcessCrashGuard logs the full error object for swallowed errors", async () => { + const guardPath = fileURLToPath( + new URL("../../src/shared/utils/httpClientAbortGuard.mjs", import.meta.url) + ); + const script = ` + const { installProcessCrashGuard } = await import(process.argv[1]); + installProcessCrashGuard((level, ...args) => { + console.log( + "LOGARGS", + level, + args.map((a) => (a instanceof Error ? "Error" : typeof a)).join(",") + ); + }); + process.emit( + "unhandledRejection", + Object.assign(new Error("hedge-cancelled"), { name: "AbortError" }), + Promise.resolve() + ); + `; + const { status, stdout } = await new Promise((resolve, reject) => { + const child = spawn(process.execPath, ["--input-type=module", "-e", script, guardPath], { + stdio: ["ignore", "pipe", "pipe"], + }); + let out = ""; + child.stdout.on("data", (d) => (out += d)); + child.on("close", (status) => resolve({ status, stdout: out })); + child.on("error", reject); + }); + assert.equal(status, 0); + assert.match(stdout, /LOGARGS warn string,Error/); +}); diff --git a/tests/unit/i18n-key-completeness.test.ts b/tests/unit/i18n-key-completeness.test.ts new file mode 100644 index 0000000000..bda8b71c12 --- /dev/null +++ b/tests/unit/i18n-key-completeness.test.ts @@ -0,0 +1,63 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { findIncompleteLocales, leafPaths } from "../../scripts/i18n/check-key-completeness.mjs"; + +// Absolute key-set parity between en.json and every locale catalog. Unlike the new-key gate +// (diff-based) and the coverage gate (80 % floor), this one names an ABSENT key regardless of +// when it was added — the defect the batch-1/batch-2 locale PRs (#13044, #13660) shipped. + +const en = { home: { title: "Home", legend: { active: "Active" } }, common: { save: "Save" } }; + +test("leafPaths flattens nested objects into dotted leaves and ignores non-objects", () => { + assert.deepEqual([...leafPaths(en)].sort(), ["common.save", "home.legend.active", "home.title"]); + assert.deepEqual([...leafPaths("not a tree")], []); +}); + +test("a locale with exactly the source key set is not listed", () => { + const gaps = findIncompleteLocales({ + en, + locales: { + pt: { home: { title: "Início", legend: { active: "Ativo" } }, common: { save: "Salvar" } }, + }, + }); + assert.deepEqual(gaps, []); +}); + +test("a __MISSING__ placeholder counts as present — the ratio gate judges its content", () => { + const gaps = findIncompleteLocales({ + en, + locales: { + pt: { + home: { title: "__MISSING__:Home", legend: { active: "Ativo" } }, + common: { save: "Salvar" }, + }, + }, + }); + assert.deepEqual(gaps, []); +}); + +test("absent leaves are reported per locale, sorted, whatever their age", () => { + const gaps = findIncompleteLocales({ + en, + locales: { + km: { home: { title: "ទំព័រដើម" }, common: { save: "រក្សាទុក" } }, + de: { home: { title: "Start", legend: { active: "Aktiv" } }, common: { save: "Speichern" } }, + }, + }); + assert.deepEqual(gaps, [{ locale: "km", missing: ["home.legend.active"], extra: [] }]); +}); + +test("leaves the source dropped are reported as extra, and a wrong shape counts as missing", () => { + const gaps = findIncompleteLocales({ + en, + locales: { + fr: { + home: { title: "Accueil", legend: "Légende" }, + common: { save: "Enregistrer", cancel: "Annuler" }, + }, + }, + }); + assert.deepEqual(gaps, [ + { locale: "fr", missing: ["home.legend.active"], extra: ["common.cancel", "home.legend"] }, + ]); +}); diff --git a/tests/unit/memory-fts-access-update.test.ts b/tests/unit/memory-fts-access-update.test.ts new file mode 100644 index 0000000000..1c6ef607d0 --- /dev/null +++ b/tests/unit/memory-fts-access-update.test.ts @@ -0,0 +1,152 @@ +import { after, test } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +// DATA_DIR must be frozen before the first db import. Run this file alone. + +const dataDir = mkdtempSync(join(tmpdir(), "omniroute-memory-fts-au-")); +process.env.DATA_DIR = dataDir; +process.env.APP_LOG_TO_FILE = "false"; + +const { MemoryType } = await import("../../src/lib/memory/types.ts"); +const { createMemory, recordMemoryAccess, getMemory } = + await import("../../src/lib/memory/store.ts"); +const { cleanupMemoryEntries } = await import("../../src/lib/db/cleanup.ts"); +const { getDbInstance, resetDbInstance } = await import("../../src/lib/db/core.ts"); + +function ftsCounts(db: ReturnType): { + data: number; + docsize: number; +} { + const data = db.prepare("SELECT count(*) AS n FROM memory_fts_data").get() as { n: number }; + const docsize = db.prepare("SELECT count(*) AS n FROM memory_fts_docsize").get() as { + n: number; + }; + return { data: data.n, docsize: docsize.n }; +} + +after(() => { + try { + resetDbInstance(); + } catch { + /* ignore */ + } + rmSync(dataDir, { recursive: true, force: true }); +}); + +test("recordMemoryAccess does not grow FTS5 posting lists", async () => { + const mem = await createMemory({ + apiKeyId: "k-fts-au", + sessionId: "s1", + type: MemoryType.FACTUAL, + key: "stable-key", + content: "needle-alpha unique phrase", + metadata: {}, + expiresAt: null, + }); + const db = getDbInstance(); + const before = ftsCounts(db); + + for (let i = 0; i < 20; i++) { + recordMemoryAccess([mem.id]); + } + + const after = ftsCounts(db); + assert.equal(after.data, before.data, "access_count updates must not append FTS5 segments"); + assert.equal( + after.docsize, + before.docsize, + "access_count updates must not append FTS5 docsize rows" + ); + const reloaded = await getMemory(mem.id); + assert.equal(reloaded?.accessCount, 20); +}); + +test("content edits still reindex FTS5", async () => { + const mem = await createMemory({ + apiKeyId: "k-fts-au-edit", + sessionId: "s1", + type: MemoryType.FACTUAL, + key: "edit-key", + content: "needle-before unique phrase", + metadata: {}, + expiresAt: null, + }); + + await createMemory({ + apiKeyId: "k-fts-au-edit", + sessionId: "s1", + type: MemoryType.FACTUAL, + key: "edit-key", + content: "needle-after unique phrase", + metadata: {}, + expiresAt: null, + }); + + const db = getDbInstance(); + const oldHits = db + .prepare("SELECT count(*) AS n FROM memory_fts WHERE memory_fts MATCH ?") + .get('"needle-before"') as { n: number }; + const newHits = db + .prepare("SELECT count(*) AS n FROM memory_fts WHERE memory_fts MATCH ?") + .get('"needle-after"') as { n: number }; + assert.equal(oldHits.n, 0, "old content must leave the index"); + assert.ok(newHits.n >= 1, "new content must be searchable"); + const reloaded = await getMemory(mem.id); + assert.equal(reloaded?.content, "needle-after unique phrase"); +}); + +test("new inserts remain searchable after memory_id sync", async () => { + await createMemory({ + apiKeyId: "k-fts-au-insert", + sessionId: "s1", + type: MemoryType.FACTUAL, + key: "insert-key", + content: "needle-insert unique phrase", + metadata: {}, + expiresAt: null, + }); + const db = getDbInstance(); + const hits = db + .prepare("SELECT count(*) AS n FROM memory_fts WHERE memory_fts MATCH ?") + .get('"needle-insert"') as { n: number }; + assert.ok(hits.n >= 1, "fresh inserts must be in FTS5 after memory_id sync"); +}); + +test("cleanupMemoryEntries issues FTS5 rebuild even when no rows expire", async () => { + await createMemory({ + apiKeyId: "k-fts-rebuild", + sessionId: "s1", + type: MemoryType.FACTUAL, + key: "rebuild-key", + content: "needle-rebuild unique phrase", + metadata: {}, + expiresAt: null, + }); + const db = getDbInstance(); + const calls: string[] = []; + const orig = db.exec.bind(db); + db.exec = ((sql: string) => { + calls.push(sql); + return orig(sql); + }) as typeof db.exec; + + const result = await cleanupMemoryEntries(); + assert.equal(result.deleted, 0, "fresh memories must survive default retention"); + assert.equal(result.errors, 0); + assert.ok( + calls.some((sql) => sql.includes("VALUES('rebuild')")), + `cleanup must rebuild FTS5, got ${JSON.stringify(calls)}` + ); + assert.equal( + calls.some((sql) => sql.includes("VALUES('optimize')")), + false, + "optimize must not substitute for rebuild" + ); + const hits = db + .prepare("SELECT count(*) AS n FROM memory_fts WHERE memory_fts MATCH ?") + .get('"needle-rebuild"') as { n: number }; + assert.ok(hits.n >= 1, "content must stay searchable after rebuild"); +}); diff --git a/tests/unit/mistral-trailing-assistant.test.ts b/tests/unit/mistral-trailing-assistant.test.ts index 1ec5769d0d..8d625a7f05 100644 --- a/tests/unit/mistral-trailing-assistant.test.ts +++ b/tests/unit/mistral-trailing-assistant.test.ts @@ -1,16 +1,13 @@ /** - * Regression test for #3396: Mistral returns 400 when the last message is - * `role: "assistant"` with plain text content. + * Regression test for stripTrailingAssistantForProvider. * - * `stripTrailingAssistantOrphanToolUse` only removed tool_use blocks — it left - * trailing text-only assistant messages intact. Mistral (and providers sharing - * the same constraint) reject such requests with: - * "400: Expected last role User or Tool (or Assistant with prefix True) - * for serving but got assistant" + * Mistral (#3396) returns 400 when the last message is role assistant with + * plain text. Official Claude OAuth (claude-opus-5 live 2026-09-13) returns + * 400 "This model does not support assistant message prefill" for the same + * shape. stripTrailingAssistantOrphanToolUse only removes tool_use blocks. * - * The fix adds `stripTrailingAssistantForProvider(messages, provider)` which - * also drops a trailing text-only assistant message for providers that require - * user-last format (e.g. "mistral"). + * stripTrailingAssistantForProvider drops a trailing text-only assistant + * message for providers in PROVIDERS_REQUIRING_USER_LAST_MESSAGE. */ import { describe, it } from "node:test"; import assert from "node:assert/strict"; @@ -56,10 +53,21 @@ describe("stripTrailingAssistantForProvider (#3396)", () => { assert.strictEqual(result.length, 2); }); - it("does NOT strip trailing text assistant for anthropic/claude", () => { + it("strips trailing text-only assistant message for claude", () => { const msgs = [user("hi"), assistant("continue from here")]; const result = stripTrailingAssistantForProvider(msgs, "claude"); - assert.strictEqual(result.length, 2); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].role, "user"); + }); + + it("strips trailing assistant with array-string content for claude", () => { + const msgs = [ + user("hi"), + { role: "assistant", content: [{ type: "text", text: "continue from here" }] }, + ]; + const result = stripTrailingAssistantForProvider(msgs, "claude"); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].role, "user"); }); it("returns messages unchanged when last message is user", () => { diff --git a/tests/unit/opencode-429-proxy-dedup.test.ts b/tests/unit/opencode-429-proxy-dedup.test.ts new file mode 100644 index 0000000000..972cba8403 --- /dev/null +++ b/tests/unit/opencode-429-proxy-dedup.test.ts @@ -0,0 +1,91 @@ +/** + * Per-request refused-route skip on 429 — a request never re-sends to a route + * the upstream just refused with 429 (keyed by host:port of the refused route). + * + * Goes through `execute()` with a stubbed fetch: the candidate predicate is + * a non-exported closure and the geo-block helper is out of scope — no test + * level without a new interface. Fixture entries injected via + * `providerSpecificData` (`syncAccountsFromCredentials`), non-premium model + * (avoids the 402 guard), stubbed fetch (the fire-and-forget reachability + * probe never gates the first dispatch — `proxyFetch.ts:686-697`). + * Sequential runs, no parallelism. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +const { OpencodeExecutor } = await import("../../open-sse/executors/opencode.ts"); + +const log = { debug() {}, info() {}, warn() {}, error() {} }; + +function proxy(host: string, port: number) { + return { type: "http", host, port }; +} + +function entriesFor(entries: Array<{ fingerprint: string; proxy: unknown }>) { + return { + providerSpecificData: { + fingerprints: entries.map((e) => e.fingerprint), + accountProxies: entries.map((e) => ({ fingerprint: e.fingerprint, proxy: e.proxy })), + }, + } as never; +} + +async function runWith429Stub(credentials: never): Promise<{ calls: number; status: number }> { + const exec = new OpencodeExecutor("opencode"); + let calls = 0; + const originalFetch = globalThis.fetch; + globalThis.fetch = (async () => { + calls++; + return new Response(JSON.stringify({ error: { message: "rate limited" } }), { + status: 429, + headers: { "Content-Type": "application/json" }, + }); + }) as typeof globalThis.fetch; + try { + const result = await exec.execute({ + model: "deepseek-v4-flash-free", + body: { messages: [{ role: "user", content: "hi" }], stream: false }, + stream: false, + signal: null, + credentials, + log, + }); + return { calls, status: (result as { response: Response }).response.status }; + } finally { + globalThis.fetch = originalFetch; + } +} + +test("refused route skipped: refused route tried once per request", async () => { + const shared = proxy("127.0.0.1", 18091); + const { calls } = await runWith429Stub( + entriesFor([ + { fingerprint: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", proxy: shared }, + { fingerprint: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", proxy: shared }, + ]) + ); + assert.strictEqual(calls, 1, `same route dialed twice: ${calls} calls, expected 1`); +}); + +test("refused routes fully excluded drain lastResult 429 with no new error", async () => { + const shared = proxy("127.0.0.1", 18092); + const { calls, status } = await runWith429Stub( + entriesFor([ + { fingerprint: "cccccccccccccccccccccccccccccccc", proxy: shared }, + { fingerprint: "dddddddddddddddddddddddddddddddd", proxy: shared }, + ]) + ); + assert.strictEqual(status, 429); + assert.ok(calls <= 2, `calls beyond budget: ${calls}`); +}); + +test("refused route skip keeps distinct routes: two distinct routes produce two calls", async () => { + const { calls, status } = await runWith429Stub( + entriesFor([ + { fingerprint: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", proxy: proxy("127.0.0.1", 18093) }, + { fingerprint: "ffffffffffffffffffffffffffffffff", proxy: proxy("127.0.0.1", 18094) }, + ]) + ); + assert.strictEqual(calls, 2, `over-exclusion: ${calls} calls, expected 2`); + assert.strictEqual(status, 429); +}); diff --git a/tests/unit/release-acceptance-cli.test.ts b/tests/unit/release-acceptance-cli.test.ts new file mode 100644 index 0000000000..952399065d --- /dev/null +++ b/tests/unit/release-acceptance-cli.test.ts @@ -0,0 +1,174 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, writeFileSync, readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + exitFor, + reduceManifests, + main, +} from "../../scripts/quality/validate-release-acceptance.mjs"; + +const SHA = "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e"; +function key(id) { + return { gate_id: id, suite_id: null, shard_index: null, shard_total: null }; +} +function gate(id, status) { + return { + gate_id: id, + suite_id: null, + shard_index: null, + shard_total: null, + tested_sha: SHA, + run_id: "1", + run_attempt: 1, + command_id: id, + gate_type: "static", + status, + cause: null, + exit_code: status === "PASS" ? 0 : 1, + duration_ms: 1, + evidence: [ + { + artifact_id: "logs", + member: "lint.log", + algorithm: "sha256", + digest: "7f227db1653b6b723b07c8f2f6eb488f1f09e2f083ca7a3f5e02bbb274f5ff2e", + }, + ], + }; +} + +test("exit mapping", () => { + assert.equal(exitFor("VERIFIED"), 0); + assert.equal(exitFor("FAILED"), 1); + assert.equal(exitFor("UNVERIFIED"), 2); +}); + +test("three PASS manifests yield VERIFIED", () => { + const plan = { + required_gates: [key("a"), key("b"), key("c")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + }; + const out = reduceManifests(plan, [ + { gates: [gate("a", "PASS")] }, + { gates: [gate("b", "PASS")] }, + { gates: [gate("c", "PASS")] }, + ]); + assert.equal(out.verdict, "VERIFIED"); + assert.equal(exitFor(out.verdict), 0); +}); + +test("one FAIL yields FAILED", () => { + const plan = { + required_gates: [key("a")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + }; + const out = reduceManifests(plan, [{ gates: [gate("a", "FAIL")] }]); + assert.equal(out.verdict, "FAILED"); + assert.equal(exitFor(out.verdict), 1); +}); + +test("required missing yields UNVERIFIED", () => { + const plan = { + required_gates: [key("a"), key("b")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + }; + const out = reduceManifests(plan, [{ gates: [gate("a", "PASS")] }]); + assert.equal(out.verdict, "UNVERIFIED"); + assert.equal(exitFor(out.verdict), 2); +}); + +test("workflow source-guard", () => { + const text = readFileSync(".github/workflows/release-acceptance.yml", "utf8"); + assert.match(text, /name: Release acceptance/); + assert.match(text, /cancel-in-progress: false/); + assert.equal(text.includes("gh issue close"), false); + assert.match(text, /if: github.event_name != 'pull_request'/); +}); + +test("schema_invalid does not throw when required_gates is missing", async () => { + const dir = mkdtempSync(join(tmpdir(), "acc-")); + writeFileSync( + join(dir, "plan.json"), + JSON.stringify({ + identity: { + repository: "diegosouzapw/OmniRoute", + run_id: "1", + run_attempt: 1, + workflow: "release-acceptance.yml", + trigger: "push", + scope: "release", + requested_ref: "refs/heads/release/v3.8.51", + base_sha: SHA, + candidate_sha: SHA, + tested_sha: SHA, + }, + artifact: null, + }) + ); + const man = join(dir, "m"); + mkdirSync(man); + writeFileSync(join(man, "a.json"), JSON.stringify({ gates: [gate("a", "PASS")] })); + const out = join(dir, "report.json"); + const code = await main([ + "node", + "cli", + "--plan", + join(dir, "plan.json"), + "--manifests", + man, + "--out", + out, + ]); + assert.equal(code, 2); + const report = JSON.parse(readFileSync(out, "utf8")); + assert.equal(report.verdict, "UNVERIFIED"); + assert.ok(Array.isArray(report.required_gates)); + assert.ok(report.evidence_errors.some((e) => e.code === "empty_required_set")); + assert.equal( + report.evidence_errors.some((e) => e.code === "schema_invalid"), + false + ); +}); + +test("schema_invalid keeps FAILED when reduce already failed", async () => { + const dir = mkdtempSync(join(tmpdir(), "acc-fail-")); + const plan = { + required_gates: [key("a")], + identity: { + repository: "diegosouzapw/OmniRoute", + run_id: "1", + run_attempt: 1, + workflow: "release-acceptance.yml", + trigger: "push", + scope: "release", + requested_ref: "refs/heads/release/v3.8.51", + base_sha: SHA, + candidate_sha: SHA, + tested_sha: SHA, + }, + artifact: null, + }; + writeFileSync(join(dir, "plan.json"), JSON.stringify(plan)); + const man = join(dir, "m"); + mkdirSync(man); + const g = gate("a", "FAIL"); + g.unexpected = true; + writeFileSync(join(man, "a.json"), JSON.stringify({ gates: [g] })); + const out = join(dir, "report.json"); + const code = await main([ + "node", + "cli", + "--plan", + join(dir, "plan.json"), + "--manifests", + man, + "--out", + out, + ]); + assert.equal(code, 1); + const report = JSON.parse(readFileSync(out, "utf8")); + assert.equal(report.verdict, "FAILED"); + assert.ok(report.evidence_errors.some((e) => e.code === "schema_invalid")); +}); diff --git a/tests/unit/release-acceptance-close-oracle.test.ts b/tests/unit/release-acceptance-close-oracle.test.ts new file mode 100644 index 0000000000..96ae3f8659 --- /dev/null +++ b/tests/unit/release-acceptance-close-oracle.test.ts @@ -0,0 +1,23 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { + findTrackerCloses, + closingKeywordInBody, +} from "../../scripts/quality/release-acceptance/closeOracle.mjs"; + +test("nightly still auto-closes the tracker via two steps (deliberate, #12085)", () => { + const text = readFileSync(".github/workflows/nightly-release-green.yml", "utf8"); + assert.equal(findTrackerCloses(text).length, 2); + const legacy = readFileSync( + new URL("../fixtures/release-acceptance/legacy-close-steps.yml", import.meta.url), + "utf8" + ); + assert.equal(findTrackerCloses(legacy).length, 2); +}); + +test("Fixes #12732 is a closing keyword; Related to #12732 is not", () => { + assert.equal(closingKeywordInBody("Fixes #12732.\n"), true); + assert.equal(closingKeywordInBody("Related to #12732.\n"), false); + assert.equal(closingKeywordInBody("Fixes #1. Closes #12732\n"), true); +}); diff --git a/tests/unit/release-acceptance-inventory.test.ts b/tests/unit/release-acceptance-inventory.test.ts new file mode 100644 index 0000000000..dc5e2e496c --- /dev/null +++ b/tests/unit/release-acceptance-inventory.test.ts @@ -0,0 +1,40 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { COLLECTORS } from "../../scripts/check/check-test-discovery.mjs"; +import { + knownUnexecuted, + inventoryErrors, +} from "../../scripts/quality/release-acceptance/inventory.mjs"; + +const RELEASE_SUITES = ["test:unit:ci", "test:vitest", "test:integration"]; +const baseline = JSON.parse( + readFileSync(new URL("../../config/quality/test-discovery-baseline.json", import.meta.url), "utf8") +); + +test("tsx files under tests/unit are known_unexecuted for release scope, not inventory errors", () => { + const ku = knownUnexecuted(RELEASE_SUITES, COLLECTORS, baseline); + const tsx = ku.collectors.find((c) => c.glob === "tests/unit/**/*.test.tsx"); + assert.ok(tsx, "tsx collector must be listed as known_unexecuted"); + assert.equal(typeof tsx.count, "number"); + assert.ok(tsx.count > 0); +}); + +test("omitting a collector without listing it is an inventory error", () => { + const collectors = COLLECTORS.filter((c) => c.glob !== "tests/unit/**/*.test.tsx"); + const discoveredFiles = ["tests/unit/AutoComboCatalog.test.tsx"]; + const errors = inventoryErrors(RELEASE_SUITES, collectors, baseline, discoveredFiles); + assert.ok(errors.some((e) => e.code === "collector_omitted")); +}); + +test("combo-matrix glob is in release integration scope, not known_unexecuted", () => { + const ku = knownUnexecuted(RELEASE_SUITES, COLLECTORS, baseline); + assert.equal( + ku.collectors.some((c) => c.glob === "tests/integration/combo-matrix/*.test.ts"), + false + ); + const combo = COLLECTORS.find( + (c) => c.glob === "tests/integration/combo-matrix/*.test.ts" + ); + assert.ok(combo); +}); diff --git a/tests/unit/release-acceptance-node-reporter.test.ts b/tests/unit/release-acceptance-node-reporter.test.ts new file mode 100644 index 0000000000..4e8c47cdfc --- /dev/null +++ b/tests/unit/release-acceptance-node-reporter.test.ts @@ -0,0 +1,86 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { fromNodeTestTap } from "../../scripts/quality/release-acceptance/nodeReporter.mjs"; + +const TAP = `TAP version 13 +# Subtest: tests/unit/a.test.ts +ok 1 - tests/unit/a.test.ts +# Subtest: tests/unit/b.test.ts +ok 2 - tests/unit/b.test.ts +# Subtest: tests/unit/c.test.ts +not ok 3 - tests/unit/c.test.ts +`; + +test("argv file without TAP completion is missing", () => { + const out = fromNodeTestTap(TAP, [ + "tests/unit/a.test.ts", + "tests/unit/b.test.ts", + "tests/unit/c.test.ts", + "tests/unit/d.test.ts", + ]); + assert.equal(out.completed.length, 2); + assert.deepEqual(out.failed, ["tests/unit/c.test.ts"]); + assert.equal(out.missing.length, 1); + assert.equal(out.missing[0], "tests/unit/d.test.ts"); + assert.equal(out.pass, false); +}); + +test("zero completed files is not PASS", () => { + const out = fromNodeTestTap("TAP version 13\n", ["tests/unit/a.test.ts"]); + assert.equal(out.completed.length, 0); + assert.equal(out.pass, false); +}); + +test("Subtest path wins when the result line has a short name", () => { + const tap = `TAP version 13 +# Subtest: tests/unit/a.test.ts +ok 1 - some name +`; + const out = fromNodeTestTap(tap, ["tests/unit/a.test.ts"]); + assert.equal(out.completed[0], "tests/unit/a.test.ts"); + assert.equal(out.missing.length, 0); +}); + +test("not ok is not pass", () => { + const tap = `TAP version 13 +# Subtest: tests/unit/a.test.ts +not ok 1 - tests/unit/a.test.ts +`; + const out = fromNodeTestTap(tap, ["tests/unit/a.test.ts"]); + assert.equal(out.pass, false); + assert.deepEqual(out.failed, ["tests/unit/a.test.ts"]); +}); + +test("ok line without Subtest does not complete an argv file", () => { + const tap = `# a malicious test printed: +ok 99 - tests/unit/missing.test.ts +`; + const out = fromNodeTestTap(tap, ["tests/unit/missing.test.ts"]); + assert.equal(out.pass, false); + assert.deepEqual(out.missing, ["tests/unit/missing.test.ts"]); +}); + +test("later not ok retracts an earlier ok for the same Subtest", () => { + const tap = `TAP version 13 +# Subtest: tests/unit/a.test.ts +ok 1 - tests/unit/a.test.ts +# Subtest: tests/unit/a.test.ts +not ok 2 - tests/unit/a.test.ts +`; + const out = fromNodeTestTap(tap, ["tests/unit/a.test.ts"]); + assert.equal(out.pass, false); + assert.deepEqual(out.failed, ["tests/unit/a.test.ts"]); + assert.equal(out.completed.includes("tests/unit/a.test.ts"), false); +}); + +test("later not ok on the same pending Subtest retracts ok", () => { + const tap = `TAP version 13 +# Subtest: tests/unit/a.test.ts +ok 1 - tests/unit/a.test.ts +not ok 2 - tests/unit/a.test.ts +`; + const out = fromNodeTestTap(tap, ["tests/unit/a.test.ts"]); + assert.equal(out.pass, false); + assert.deepEqual(out.failed, ["tests/unit/a.test.ts"]); + assert.equal(out.completed.includes("tests/unit/a.test.ts"), false); +}); diff --git a/tests/unit/release-acceptance-pack-boot.test.ts b/tests/unit/release-acceptance-pack-boot.test.ts new file mode 100644 index 0000000000..a594941cfb --- /dev/null +++ b/tests/unit/release-acceptance-pack-boot.test.ts @@ -0,0 +1,68 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { reduce } from "../../scripts/quality/release-acceptance/reduce.mjs"; + +const SHA = "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e"; +const planPack = { + required_gates: [ + { gate_id: "pack-artifact", suite_id: null, shard_index: null, shard_total: null }, + { gate_id: "pack-boot", suite_id: null, shard_index: null, shard_total: null }, + ], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { "pack-boot": "pack-artifact" }, +}; + +function record(partial) { + return { + gate_id: "pack-artifact", + suite_id: null, + shard_index: null, + shard_total: null, + tested_sha: SHA, + run_id: "1", + run_attempt: 1, + command_id: "check:pack-artifact", + gate_type: "artifact", + status: "PASS", + cause: null, + exit_code: 0, + duration_ms: 10, + evidence: [], + ...partial, + }; +} + +test("legacy computeVerdict still hard-fails pack-boot when pack-artifact times out", async () => { + const { computeVerdict } = await import("../../scripts/quality/validate-release-green.mjs"); + const v = computeVerdict([ + { id: "pack-artifact", kind: "hard", ok: false, detail: "timeout" }, + { + id: "pack-boot", + kind: "hard", + ok: false, + detail: "skipped because package-artifact did not produce a valid dist/ build", + }, + ]); + assert.equal(v.releaseGreen, false); +}); + +test("new reducer maps the same timeout to UNVERIFIED", () => { + const out = reduce(planPack, [ + record({ gate_id: "pack-artifact", status: "INFRA_ERROR" }), + ]); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("synthesized pack-boot without identity.tested_sha keeps a 40-hex sha and FAILED", () => { + const plan = { + required_gates: planPack.required_gates, + identity: { run_id: "1", run_attempt: 1 }, + dependencies: { "pack-boot": "pack-artifact" }, + }; + const out = reduce(plan, [record({ gate_id: "pack-artifact", status: "FAIL" })]); + const boot = out.gates.find((g) => g.gate_id === "pack-boot"); + assert.ok(boot); + assert.notEqual(boot.tested_sha, null); + assert.match(String(boot.tested_sha), /^[0-9a-f]{40}$/); + assert.equal(out.verdict, "FAILED"); +}); diff --git a/tests/unit/release-acceptance-reduce.test.ts b/tests/unit/release-acceptance-reduce.test.ts new file mode 100644 index 0000000000..02152362fd --- /dev/null +++ b/tests/unit/release-acceptance-reduce.test.ts @@ -0,0 +1,287 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { reduce } from "../../scripts/quality/release-acceptance/reduce.mjs"; + +const SHA = "30b5bf18fbe827a0283ce17e91bda22cc8b4c13e"; + +function key(gate_id) { + return { gate_id, suite_id: null, shard_index: null, shard_total: null }; +} + +function record(partial) { + return { + gate_id: "lint", + suite_id: null, + shard_index: null, + shard_total: null, + tested_sha: SHA, + run_id: "1", + run_attempt: 1, + command_id: partial.gate_id ?? "lint", + gate_type: "static", + status: "PASS", + cause: null, + exit_code: 0, + duration_ms: 10, + evidence: [], + ...partial, + }; +} + +function planWithRequired(gateId, extra = {}) { + return { + required_gates: [key(gateId)], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + ...extra, + }; +} + +const planPack = { + required_gates: [key("pack-artifact"), key("pack-boot")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { "pack-boot": "pack-artifact" }, +}; + +test("required SKIPPED never yields VERIFIED", () => { + const out = reduce(planWithRequired("lint"), [ + record({ gate_id: "lint", status: "SKIPPED", reason: "optional-looking" }), + ]); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("pack-artifact FAIL classifies pack-boot as FAIL with cause", () => { + const out = reduce(planPack, [ + record({ gate_id: "pack-artifact", status: "FAIL", gate_type: "artifact" }), + ]); + const boot = out.gates.find((g) => g.gate_id === "pack-boot"); + assert.equal(boot.status, "FAIL"); + assert.equal(boot.cause.gate_id, "pack-artifact"); + assert.equal(out.verdict, "FAILED"); +}); + +test("pack-artifact INFRA_ERROR classifies pack-boot as INFRA_ERROR", () => { + const out = reduce(planPack, [ + record({ + gate_id: "pack-artifact", + status: "INFRA_ERROR", + gate_type: "artifact", + }), + ]); + const boot = out.gates.find((g) => g.gate_id === "pack-boot"); + assert.equal(boot.status, "INFRA_ERROR"); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("plan that marks a required gate's prerequisite optional is rejected", () => { + const illegalPlan = { + required_gates: [key("pack-boot")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { "pack-boot": "pack-artifact" }, + optional_gates: [key("pack-artifact")], + }; + assert.throws(() => reduce(illegalPlan, []), /optional prerequisite/); +}); + +test("required SKIPPED prerequisite classifies dependent as SKIPPED, does not throw", () => { + const out = reduce(planPack, [ + record({ + gate_id: "pack-artifact", + status: "SKIPPED", + reason: "runner skipped", + gate_type: "artifact", + }), + ]); + const boot = out.gates.find((g) => g.gate_id === "pack-boot"); + assert.equal(boot.status, "SKIPPED"); + assert.equal(boot.cause.gate_id, "pack-artifact"); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("INFRA_ERROR artifact reclassifies an already-emitted FAIL boot to INFRA_ERROR", () => { + const out = reduce(planPack, [ + record({ + gate_id: "pack-artifact", + status: "INFRA_ERROR", + gate_type: "artifact", + }), + record({ + gate_id: "pack-boot", + status: "FAIL", + gate_type: "artifact", + }), + ]); + const boot = out.gates.find((g) => g.gate_id === "pack-boot"); + assert.equal(boot.status, "INFRA_ERROR"); + assert.equal(boot.cause.gate_id, "pack-artifact"); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("empty required_gates is UNVERIFIED", () => { + const out = reduce( + { required_gates: [], identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 } }, + [record({ gate_id: "lint", status: "PASS" })] + ); + assert.equal(out.verdict, "UNVERIFIED"); + assert.ok(out.evidence_errors.some((e) => e.code === "empty_required_set")); +}); + +test("INFRA_ERROR artifact reclassifies every FAIL boot copy", () => { + const out = reduce(planPack, [ + record({ gate_id: "pack-artifact", status: "INFRA_ERROR", gate_type: "artifact" }), + record({ gate_id: "pack-boot", status: "FAIL", gate_type: "artifact" }), + record({ gate_id: "pack-boot", status: "FAIL", gate_type: "artifact" }), + ]); + const boots = out.gates.filter((g) => g.gate_id === "pack-boot"); + assert.ok(boots.length >= 1); + assert.ok(boots.every((g) => g.status === "INFRA_ERROR")); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("transitive INFRA on a three-gate chain is UNVERIFIED, not leaked FAIL", () => { + const plan = { + required_gates: [key("a"), key("b"), key("c")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { b: "a", c: "b" }, + }; + const out = reduce(plan, [ + record({ gate_id: "a", status: "INFRA_ERROR", gate_type: "artifact" }), + record({ gate_id: "b", status: "FAIL", gate_type: "artifact" }), + record({ gate_id: "c", status: "PASS", gate_type: "artifact" }), + ]); + assert.equal(out.gates.find((g) => g.gate_id === "a").status, "INFRA_ERROR"); + assert.equal(out.gates.find((g) => g.gate_id === "b").status, "INFRA_ERROR"); + assert.equal(out.gates.find((g) => g.gate_id === "c").status, "INFRA_ERROR"); + assert.equal(out.verdict, "UNVERIFIED"); +}); + +test("transitive FAIL on a three-gate chain classifies every dependent", () => { + const plan = { + required_gates: [key("a"), key("b"), key("c")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { b: "a", c: "b" }, + }; + const out = reduce(plan, [ + record({ gate_id: "a", status: "FAIL", gate_type: "artifact" }), + record({ gate_id: "b", status: "PASS", gate_type: "artifact" }), + record({ gate_id: "c", status: "PASS", gate_type: "artifact" }), + ]); + assert.equal(out.gates.find((g) => g.gate_id === "b").status, "FAIL"); + assert.equal(out.gates.find((g) => g.gate_id === "c").status, "FAIL"); + assert.equal(out.verdict, "FAILED"); +}); + +test("INFRA copy of a required gate dominates a FAIL copy of the same key", () => { + const out = reduce(planPack, [ + record({ gate_id: "pack-artifact", status: "INFRA_ERROR", gate_type: "artifact" }), + record({ gate_id: "pack-artifact", status: "FAIL", gate_type: "artifact" }), + record({ gate_id: "pack-boot", status: "PASS", gate_type: "artifact" }), + ]); + assert.equal(out.verdict, "UNVERIFIED"); + const boot = out.gates.find((g) => g.gate_id === "pack-boot"); + assert.equal(boot.status, "INFRA_ERROR"); +}); + +test("cyclic dependencies are rejected", () => { + const cyclic = { + required_gates: [key("a"), key("b")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { a: "b", b: "a" }, + }; + assert.throws( + () => reduce(cyclic, [record({ gate_id: "a", status: "INFRA_ERROR" }), record({ gate_id: "b", status: "FAIL" })]), + /cyclic prerequisite/ + ); +}); + +test("missing prerequisite records one evidence error, not one per loop", () => { + const out = reduce( + { + required_gates: [key("boot")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { boot: "art" }, + }, + [] + ); + assert.equal(out.verdict, "UNVERIFIED"); + assert.equal( + out.evidence_errors.filter((e) => e.code === "prerequisite_missing").length, + 1 + ); +}); + +test("missing prerequisite records one evidence error for all shards of a gate_id", () => { + const shard0 = { gate_id: "u", suite_id: "s", shard_index: 0, shard_total: 2 }; + const shard1 = { gate_id: "u", suite_id: "s", shard_index: 1, shard_total: 2 }; + const out = reduce( + { + required_gates: [shard0, shard1], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { u: "art" }, + }, + [] + ); + assert.equal(out.verdict, "UNVERIFIED"); + assert.equal( + out.evidence_errors.filter((e) => e.code === "prerequisite_missing").length, + 1 + ); +}); + +test("two dependents of the same missing prerequisite keep one error per edge", () => { + const out = reduce( + { + required_gates: [key("boot"), key("pack")], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { boot: "art", pack: "art" }, + }, + [] + ); + assert.equal(out.verdict, "UNVERIFIED"); + const missing = out.evidence_errors.filter((e) => e.code === "prerequisite_missing"); + assert.equal(missing.length, 2); + const gates = missing.map((e) => e.gate?.gate_id).sort(); + assert.deepEqual(gates, ["boot", "pack"]); + assert.equal(out.gates.find((g) => g.gate_id === "boot")?.status, "INFRA_ERROR"); + assert.equal(out.gates.find((g) => g.gate_id === "pack")?.status, "INFRA_ERROR"); +}); + +test("sharded required dependents inherit a FAIL prerequisite of the same gate_id", () => { + const shard0 = { gate_id: "u", suite_id: "s", shard_index: 0, shard_total: 2 }; + const shard1 = { gate_id: "u", suite_id: "s", shard_index: 1, shard_total: 2 }; + const out = reduce( + { + required_gates: [shard0, shard1], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { u: "art" }, + }, + [ + record({ gate_id: "art", status: "FAIL", gate_type: "artifact" }), + record({ ...shard0, status: "PASS", gate_type: "artifact" }), + record({ ...shard1, status: "PASS", gate_type: "artifact" }), + ] + ); + const shards = out.gates.filter((g) => g.gate_id === "u" && g.suite_id === "s"); + assert.equal(shards.length, 2); + assert.ok(shards.every((g) => g.status === "FAIL")); + assert.equal(out.verdict, "FAILED"); +}); + +test("sharded required dependents inherit an INFRA prerequisite of the same gate_id", () => { + const shard0 = { gate_id: "u", suite_id: "s", shard_index: 0, shard_total: 2 }; + const shard1 = { gate_id: "u", suite_id: "s", shard_index: 1, shard_total: 2 }; + const out = reduce( + { + required_gates: [shard0, shard1], + identity: { tested_sha: SHA, run_id: "1", run_attempt: 1 }, + dependencies: { u: "art" }, + }, + [ + record({ gate_id: "art", status: "INFRA_ERROR", gate_type: "artifact" }), + record({ ...shard0, status: "PASS", gate_type: "artifact" }), + record({ ...shard1, status: "PASS", gate_type: "artifact" }), + ] + ); + const shards = out.gates.filter((g) => g.gate_id === "u" && g.suite_id === "s"); + assert.ok(shards.every((g) => g.status === "INFRA_ERROR")); + assert.equal(out.verdict, "UNVERIFIED"); +}); diff --git a/tests/unit/release-acceptance-schema.test.ts b/tests/unit/release-acceptance-schema.test.ts new file mode 100644 index 0000000000..461667be6b --- /dev/null +++ b/tests/unit/release-acceptance-schema.test.ts @@ -0,0 +1,112 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import Ajv from "ajv"; + +const schema = JSON.parse( + readFileSync( + new URL("../../config/quality/release-acceptance.schema.json", import.meta.url), + "utf8" + ) +); + +function compile() { + const ajv = new Ajv({ allErrors: true, strict: false }); + return ajv.compile(schema); +} + +test("version 1 requires cause when status is classified by a prerequisite", () => { + const validate = compile(); + const missingCause = JSON.parse( + readFileSync( + new URL("../fixtures/release-acceptance/failed-pack-boot.json", import.meta.url), + "utf8" + ) + ); + delete missingCause.gates[1].cause; + assert.equal(validate(missingCause), false); +}); + +test("unknown top-level gate field is invalid in version 1", () => { + const validate = compile(); + const extra = JSON.parse( + readFileSync( + new URL("../fixtures/release-acceptance/verified.json", import.meta.url), + "utf8" + ) + ); + extra.gates[0].unexpected = true; + assert.equal(validate(extra), false); +}); + +test("evidence member rejects parent traversal", () => { + const validate = compile(); + const report = JSON.parse( + readFileSync(new URL("../fixtures/release-acceptance/verified.json", import.meta.url), "utf8") + ); + report.gates[0].evidence[0].member = "foo/../../etc/passwd"; + assert.equal(validate(report), false); + report.gates[0].evidence[0].member = ".."; + assert.equal(validate(report), false); + report.gates[0].evidence[0].member = "foo/.."; + assert.equal(validate(report), false); + report.gates[0].evidence[0].member = String.raw`foo\..\x`; + assert.equal(validate(report), false); +}); + +test("empty required_gates cannot be VERIFIED", () => { + const validate = compile(); + const report = JSON.parse( + readFileSync(new URL("../fixtures/release-acceptance/verified.json", import.meta.url), "utf8") + ); + report.required_gates = []; + report.gates = []; + report.evidence_errors = []; + report.verdict = "VERIFIED"; + assert.equal(validate(report), false); +}); + +test("empty required_gates is valid when UNVERIFIED", () => { + const validate = compile(); + const report = JSON.parse( + readFileSync(new URL("../fixtures/release-acceptance/verified.json", import.meta.url), "utf8") + ); + report.required_gates = []; + report.gates = []; + report.evidence_errors = [ + { + code: "empty_required_set", + gate: { gate_id: "schema", suite_id: null, shard_index: null, shard_total: null }, + detail: "required_gates is empty", + }, + ]; + report.verdict = "UNVERIFIED"; + assert.equal(validate(report), true, JSON.stringify(validate.errors)); +}); + +test("unknown extensions field is invalid in version 1", () => { + const validate = compile(); + const extra = JSON.parse( + readFileSync( + new URL("../fixtures/release-acceptance/verified.json", import.meta.url), + "utf8" + ) + ); + extra.gates[0].extensions = { unexpected: true }; + assert.equal(validate(extra), false); +}); + +test("known-answer fixtures validate", () => { + const validate = compile(); + for (const name of [ + "verified.json", + "failed-pack-boot.json", + "unverified-required-skipped.json", + "infra-pack-boot.json", + ]) { + const report = JSON.parse( + readFileSync(new URL(`../fixtures/release-acceptance/${name}`, import.meta.url), "utf8") + ); + assert.equal(validate(report), true, `${name}: ${JSON.stringify(validate.errors)}`); + } +}); diff --git a/tests/unit/release-acceptance-static-adapter.test.ts b/tests/unit/release-acceptance-static-adapter.test.ts new file mode 100644 index 0000000000..64d70292a6 --- /dev/null +++ b/tests/unit/release-acceptance-static-adapter.test.ts @@ -0,0 +1,33 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { adaptCompiler } from "../../scripts/quality/release-acceptance/staticAdapter.mjs"; + +test("empty diagnostics with nonempty digest and exit 0 is PASS", () => { + const out = adaptCompiler({ + commandId: "tsc", + inputDigest: "a".repeat(64), + exitCode: 0, + diagnostics: [], + }); + assert.equal(out.status, "PASS"); +}); + +test("empty digest plus empty diagnostics is INFRA_ERROR", () => { + const out = adaptCompiler({ + commandId: "tsc", + inputDigest: "", + exitCode: 0, + diagnostics: [], + }); + assert.equal(out.status, "INFRA_ERROR"); +}); + +test("exit 1 with diagnostics is FAIL", () => { + const out = adaptCompiler({ + commandId: "tsc", + inputDigest: "a".repeat(64), + exitCode: 1, + diagnostics: ["error TS2304"], + }); + assert.equal(out.status, "FAIL"); +}); diff --git a/tests/unit/sensenova-reasoning-effort.test.ts b/tests/unit/sensenova-reasoning-effort.test.ts index d2d6f9142d..7c7e412c19 100644 --- a/tests/unit/sensenova-reasoning-effort.test.ts +++ b/tests/unit/sensenova-reasoning-effort.test.ts @@ -3,17 +3,47 @@ import test from "node:test"; import { sanitizeReasoningEffortForProvider } from "../../open-sse/executors/base/reasoningEffort.ts"; -test("sensenova/deepseek-v4-flash maps max to its explicit xhigh ceiling", () => { - const result = sanitizeReasoningEffortForProvider( +test("sensenova/deepseek-v4-flash clamps xhigh and max to high", () => { + const max = sanitizeReasoningEffortForProvider( { reasoning_effort: "max", messages: [] }, "sensenova", "deepseek-v4-flash" ); + const xhigh = sanitizeReasoningEffortForProvider( + { reasoning_effort: "xhigh", messages: [] }, + "sensenova", + "deepseek-v4-flash" + ); - assert.equal((result as Record).reasoning_effort, "xhigh"); + assert.equal((max as Record).reasoning_effort, "high"); + assert.equal((xhigh as Record).reasoning_effort, "high"); }); -test("sensenova models without an explicit effort list keep max unchanged", () => { +test("snova-prefixed openai-compatible deepseek-v4-flash clamps xhigh and max to high", () => { + const provider = "openai-compatible-chat-95565442-1b4b-4082-b428-9503ac8ca716"; + const model = "snova/deepseek-v4-flash"; + const max = sanitizeReasoningEffortForProvider( + { reasoning_effort: "max", messages: [] }, + provider, + model + ); + const xhigh = sanitizeReasoningEffortForProvider( + { reasoning_effort: "xhigh", messages: [] }, + provider, + model + ); + const high = sanitizeReasoningEffortForProvider( + { reasoning_effort: "high", messages: [] }, + provider, + model + ); + + assert.equal((max as Record).reasoning_effort, "high"); + assert.equal((xhigh as Record).reasoning_effort, "high"); + assert.equal((high as Record).reasoning_effort, "high"); +}); + +test("sensenova models without explicit effort list keep max unchanged", () => { const result = sanitizeReasoningEffortForProvider( { reasoning_effort: "max", messages: [] }, "sensenova", diff --git a/tests/unit/tls-proxy-context.test.ts b/tests/unit/tls-proxy-context.test.ts index 8312e8b584..2fee904bee 100644 --- a/tests/unit/tls-proxy-context.test.ts +++ b/tests/unit/tls-proxy-context.test.ts @@ -6,6 +6,7 @@ import { resolveProxyForRequest, runWithProxyContext, runWithTlsTracking, + isTlsFingerprintActive, setTlsClientForTest, } from "../../open-sse/utils/proxyFetch.ts"; import tlsClient, { @@ -321,6 +322,109 @@ test("new proxied TLS transport requires an explicit provider allowlist", async ); }); +test("direct TLS fingerprint skips Groq even when the provider allowlist is unset", async () => { + await withEnv( + { + ENABLE_TLS_FINGERPRINT: "true", + TLS_FINGERPRINT_PROVIDERS: undefined, + }, + async () => { + let tlsCalls = 0; + let dispatcherCalls = 0; + setTlsClientForTest( + fakeTlsClient(async () => { + tlsCalls++; + return new Response("tls"); + }), + ); + + const tracked = await runWithTlsTracking("groq", () => + proxyFetch("https://api.groq.com/openai/v1/models", {}, { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher"); + }, + }), + ); + + assert.equal(tlsCalls, 0); + assert.equal(dispatcherCalls, 1); + assert.equal(await tracked.result.text(), "dispatcher"); + assert.equal(tracked.tlsFingerprintUsed, false); + assert.equal(isTlsFingerprintActive("groq"), false); + }, + ); +}); + +test("direct TLS fingerprint skips api.groq.com when the tracking store has no provider", async () => { + await withEnv( + { + ENABLE_TLS_FINGERPRINT: "true", + TLS_FINGERPRINT_PROVIDERS: undefined, + }, + async () => { + let tlsCalls = 0; + let dispatcherCalls = 0; + setTlsClientForTest( + fakeTlsClient(async () => { + tlsCalls++; + return new Response("tls"); + }), + ); + + const tracked = await runWithTlsTracking(async () => + proxyFetch("https://api.groq.com/openai/v1/chat/completions", { + method: "POST", + body: "{}", + }, { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher"); + }, + }), + ); + + assert.equal(tlsCalls, 0); + assert.equal(dispatcherCalls, 1); + assert.equal(await tracked.result.text(), "dispatcher"); + assert.equal(tracked.tlsFingerprintUsed, false); + }, + ); +}); + +test("direct TLS fingerprint still spoofs non-Groq hosts when the allowlist is unset", async () => { + await withEnv( + { + ENABLE_TLS_FINGERPRINT: "true", + TLS_FINGERPRINT_PROVIDERS: undefined, + }, + async () => { + let tlsCalls = 0; + let dispatcherCalls = 0; + setTlsClientForTest( + fakeTlsClient(async () => { + tlsCalls++; + return new Response("tls"); + }), + ); + + const tracked = await runWithTlsTracking("openai", () => + proxyFetch("https://api.openai.com/v1/models", {}, { + undiciFetch: async () => { + dispatcherCalls++; + return new Response("dispatcher"); + }, + }), + ); + + assert.equal(tlsCalls, 1); + assert.equal(dispatcherCalls, 0); + assert.equal(await tracked.result.text(), "tls"); + assert.equal(tracked.tlsFingerprintUsed, true); + }, + ); +}); + test("caller abort propagates unchanged and never falls back", async () => { await withEnv( {