mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-28 01:52:09 +03:00
Compare commits
8 Commits
fix/v3850-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ce21232db | ||
|
|
b65ef333da | ||
|
|
925feb27b8 | ||
|
|
aa52351113 | ||
|
|
8778ea7d18 | ||
|
|
c44c0a29e8 | ||
|
|
5458026c21 | ||
|
|
b4ec7807ab |
48
.env.example
48
.env.example
@@ -1469,25 +1469,28 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
# FIRECRAWL_BASE_URL=https://api.firecrawl.dev
|
||||
# FIRECRAWL_TIMEOUT_MS=30000 # Per-request timeout (default: 30000 = 30s)
|
||||
|
||||
# ── ChatGPT browser transport (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/chatgptTlsClient.ts — native wreq-js request
|
||||
# timeout and the JS-side hard-deadline grace layered on top of it.
|
||||
# ── ChatGPT TLS sidecar (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/chatgptTlsClient.ts — wire-level timeout for
|
||||
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
|
||||
# layered on top of it when the native library is wedged.
|
||||
# OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_CHATGPT_TLS_GRACE_MS=10000
|
||||
# Max wait for the FIRST streamed byte before switching from direct streaming
|
||||
# to a buffered response, in milliseconds. Default 30000 (30s). The request's
|
||||
# hard deadline continues to apply while the buffered body is read.
|
||||
# Max wait for the FIRST streamed byte from the ChatGPT TLS sidecar before the
|
||||
# request is aborted as a dead stream, in milliseconds. Default 30000 (30s).
|
||||
# Raise it if upstream cold-starts routinely exceed the window.
|
||||
# OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS=30000
|
||||
|
||||
# ── Claude browser transport (Chromium-fingerprinted client) ──
|
||||
# Used by: open-sse/services/claudeTlsClient.ts — native wreq-js request
|
||||
# timeout and the JS-side hard-deadline grace layered on top of it.
|
||||
# ── Claude TLS sidecar (Chromium-fingerprinted client) ──
|
||||
# Used by: open-sse/services/claudeTlsClient.ts — wire-level timeout for
|
||||
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
|
||||
# layered on top of it when the native library is wedged.
|
||||
# OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_CLAUDE_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Perplexity browser transport (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/perplexityTlsClient.ts — native wreq-js request
|
||||
# timeout and the JS-side hard-deadline grace layered on top of it.
|
||||
# ── Perplexity TLS sidecar (Firefox-fingerprinted client) ──
|
||||
# Used by: open-sse/services/perplexityTlsClient.ts — wire-level timeout for
|
||||
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
|
||||
# layered on top of it when the native library is wedged.
|
||||
# OMNIROUTE_PPLX_TLS_TIMEOUT_MS=30000
|
||||
# OMNIROUTE_PPLX_TLS_GRACE_MS=10000
|
||||
|
||||
@@ -1499,16 +1502,18 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
# meta-commentary. Set to 1/true/yes/on to restore the old behavior.
|
||||
# OMNIROUTE_PPLX_SEARCH_HINT=0
|
||||
|
||||
# ── Grok web browser transport (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/grokTlsClient.ts — native wreq-js request timeout
|
||||
# and the JS-side hard-deadline grace layered on top of it.
|
||||
# ── Grok web TLS sidecar (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/grokTlsClient.ts — wire-level timeout for the
|
||||
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
|
||||
# top of it when the native library is wedged.
|
||||
# OMNIROUTE_GROK_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_GROK_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Notion web browser transport (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/notionTlsClient.ts — native wreq-js request timeout
|
||||
# and the JS-side hard-deadline grace layered on top of it. The notion-web
|
||||
# executor raises the native timeout per-request to 180000 for long generations.
|
||||
# ── Notion web TLS sidecar (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/notionTlsClient.ts — wire-level timeout for the
|
||||
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
|
||||
# top of it when the native library is wedged. The notion-web executor raises
|
||||
# the wire timeout per-request to 180000 for long generations.
|
||||
# OMNIROUTE_NOTION_TLS_TIMEOUT_MS=30000
|
||||
# OMNIROUTE_NOTION_TLS_GRACE_MS=10000
|
||||
|
||||
@@ -2520,6 +2525,11 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: src/lib/jobs/backupScheduleJob.ts
|
||||
# OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS=30000
|
||||
|
||||
# ── TLS sidecar override ──
|
||||
# Used by: open-sse/services/chatgptTlsClient.ts tests. Production deployments
|
||||
# should leave this unset; the sidecar is auto-managed.
|
||||
# OMNIROUTE_TLS_PROXY_URL=
|
||||
|
||||
# ── Skills sandbox (experimental) ──
|
||||
# Used by: src/lib/skills/builtins.ts. All values support comma lists where
|
||||
# noted in the source.
|
||||
|
||||
63
.github/workflows/docker-publish.yml
vendored
63
.github/workflows/docker-publish.yml
vendored
@@ -185,6 +185,13 @@ jobs:
|
||||
|
||||
- name: Build and push BUN base platform image by digest
|
||||
id: build-bun-base
|
||||
# Bun is a best-effort compatibility target, not a supported runtime
|
||||
# (AGENTS.md -> Environment). Its `bun run build` has been OOM-killing on
|
||||
# both arches; letting that sink the whole publish means the SUPPORTED
|
||||
# runner-base / runner-web images never reach the registry either. The
|
||||
# image is still built and pushed whenever it succeeds — only its power to
|
||||
# block the release is removed.
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
with:
|
||||
context: .
|
||||
@@ -203,6 +210,13 @@ jobs:
|
||||
|
||||
- name: Build and push BUN web platform image by digest
|
||||
id: build-bun-web
|
||||
# Bun is a best-effort compatibility target, not a supported runtime
|
||||
# (AGENTS.md -> Environment). Its `bun run build` has been OOM-killing on
|
||||
# both arches; letting that sink the whole publish means the SUPPORTED
|
||||
# runner-base / runner-web images never reach the registry either. The
|
||||
# image is still built and pushed whenever it succeeds — only its power to
|
||||
# block the release is removed.
|
||||
continue-on-error: true
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||
with:
|
||||
context: .
|
||||
@@ -230,8 +244,15 @@ jobs:
|
||||
mkdir -p /tmp/digests/base /tmp/digests/web /tmp/digests/bun-base /tmp/digests/bun-web
|
||||
touch "/tmp/digests/base/${DIGEST_BASE#sha256:}"
|
||||
touch "/tmp/digests/web/${DIGEST_WEB#sha256:}"
|
||||
touch "/tmp/digests/bun-base/${DIGEST_BUN_BASE#sha256:}"
|
||||
touch "/tmp/digests/bun-web/${DIGEST_BUN_WEB#sha256:}"
|
||||
# Empty when the (non-blocking) bun build produced no image. `if` blocks,
|
||||
# not `[ -n ] && touch`: under `set -e` a failing AND-list aborts the step,
|
||||
# which is precisely the case being handled here.
|
||||
if [ -n "$DIGEST_BUN_BASE" ]; then
|
||||
touch "/tmp/digests/bun-base/${DIGEST_BUN_BASE#sha256:}"
|
||||
fi
|
||||
if [ -n "$DIGEST_BUN_WEB" ]; then
|
||||
touch "/tmp/digests/bun-web/${DIGEST_BUN_WEB#sha256:}"
|
||||
fi
|
||||
|
||||
- name: Upload base digests
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -254,7 +275,11 @@ jobs:
|
||||
with:
|
||||
name: digests-bun-base-${{ matrix.arch }}
|
||||
path: /tmp/digests/bun-base/*
|
||||
if-no-files-found: error
|
||||
# `ignore`, not `error`: the bun build is non-blocking, so an absent
|
||||
# digest is the expected outcome of a failed/skipped bun image — the
|
||||
# manifest step already treats these tags as optional. Leaving `error`
|
||||
# here just relocates the blocker from the manifest to the upload.
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload bun-web digests
|
||||
@@ -262,7 +287,11 @@ jobs:
|
||||
with:
|
||||
name: digests-bun-web-${{ matrix.arch }}
|
||||
path: /tmp/digests/bun-web/*
|
||||
if-no-files-found: error
|
||||
# `ignore`, not `error`: the bun build is non-blocking, so an absent
|
||||
# digest is the expected outcome of a failed/skipped bun image — the
|
||||
# manifest step already treats these tags as optional. Leaving `error`
|
||||
# here just relocates the blocker from the manifest to the upload.
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
@@ -320,6 +349,9 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download bun-base digests
|
||||
# Non-blocking: the bun image is best-effort, so its artifact may not
|
||||
# exist at all. The manifest step treats these tags as optional.
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: digests-bun-base-*
|
||||
@@ -327,6 +359,9 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download bun-web digests
|
||||
# Non-blocking: the bun image is best-effort, so its artifact may not
|
||||
# exist at all. The manifest step treats these tags as optional.
|
||||
continue-on-error: true
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: digests-bun-web-*
|
||||
@@ -338,7 +373,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
|
||||
create_manifest() {
|
||||
local image="$1" suffix="$2" dir="$3"
|
||||
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
|
||||
local tags=(-t "${image}:${VERSION}${suffix}")
|
||||
if [ "$PROMOTE_LATEST" = "true" ]; then
|
||||
tags+=(-t "${image}:latest${suffix}")
|
||||
@@ -348,6 +383,10 @@ jobs:
|
||||
refs+=("${image}@sha256:$(basename "$digest_file")")
|
||||
done < <(find "$dir" -type f | sort)
|
||||
if [ "${#refs[@]}" -eq 0 ]; then
|
||||
if [ -n "$optional" ]; then
|
||||
echo "::warning::No image digests in $dir — skipping optional tag ${image}:${VERSION}${suffix}" >&2
|
||||
return 0
|
||||
fi
|
||||
echo "No image digests in $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -356,15 +395,15 @@ jobs:
|
||||
|
||||
create_manifest "${IMAGE_NAME}" "" /tmp/digests/base
|
||||
create_manifest "${IMAGE_NAME}" "-web" /tmp/digests/web
|
||||
create_manifest "${IMAGE_NAME}" "-bun" /tmp/digests/bun-base
|
||||
create_manifest "${IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web
|
||||
create_manifest "${IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
|
||||
create_manifest "${IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
|
||||
|
||||
- name: Create GHCR manifest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
create_manifest() {
|
||||
local image="$1" suffix="$2" dir="$3"
|
||||
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
|
||||
local tags=(-t "${image}:${VERSION}${suffix}")
|
||||
if [ "$PROMOTE_LATEST" = "true" ]; then
|
||||
tags+=(-t "${image}:latest${suffix}")
|
||||
@@ -374,6 +413,10 @@ jobs:
|
||||
refs+=("${image}@sha256:$(basename "$digest_file")")
|
||||
done < <(find "$dir" -type f | sort)
|
||||
if [ "${#refs[@]}" -eq 0 ]; then
|
||||
if [ -n "$optional" ]; then
|
||||
echo "::warning::No image digests in $dir — skipping optional tag ${image}:${VERSION}${suffix}" >&2
|
||||
return 0
|
||||
fi
|
||||
echo "No image digests in $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -382,8 +425,8 @@ jobs:
|
||||
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "" /tmp/digests/base
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "-web" /tmp/digests/web
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "-bun" /tmp/digests/bun-base
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
|
||||
|
||||
- name: Inspect image
|
||||
if: needs.prepare.outputs.version != 'main'
|
||||
|
||||
2
.github/workflows/electron-release.yml
vendored
2
.github/workflows/electron-release.yml
vendored
@@ -222,7 +222,7 @@ jobs:
|
||||
# optionals (@img/sharp-*, @img/sharp-libvips-*, @ngrok/ngrok-*,
|
||||
# fsevents) carry linux forks. Replace them with the forks this
|
||||
# leg's own `npm ci` resolved, then assert every bundled native
|
||||
# (better-sqlite3 prebuilds, wreq-js, onnxruntime)
|
||||
# (koffi triplets, better-sqlite3 prebuilds, wreq-js, onnxruntime)
|
||||
# can service this leg's platform/arch before packaging starts.
|
||||
run: |
|
||||
node scripts/build/standaloneBundle.mjs restore --archive web-bundle.tar.gz
|
||||
|
||||
29
.github/workflows/npm-publish.yml
vendored
29
.github/workflows/npm-publish.yml
vendored
@@ -226,6 +226,28 @@ jobs:
|
||||
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
||||
run: npm run build:cli
|
||||
|
||||
# `build:cli` assembles dist/ but does NOT write dist/BUILD_SHA — only
|
||||
# `build:release` does, by calling write-build-sha.mjs. The #10427 provenance
|
||||
# guard inside check:pack-artifact rejects an artifact with no SHA (and rejects
|
||||
# it even under OMNIROUTE_ALLOW_CANARY_BUILD=1: what cannot be identified cannot
|
||||
# be vouched for). Without this step the build+validate pair in this job is
|
||||
# structurally incompatible and fails 100% of the time — the same gap that was
|
||||
# fixed in ci.yml's Package Artifact job.
|
||||
- name: Stamp dist/BUILD_SHA for the provenance guard (#10427)
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
env:
|
||||
OMNIROUTE_BUILD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
export OMNIROUTE_BUILD_SHA="${OMNIROUTE_BUILD_SHA:0:7}"
|
||||
node scripts/build/write-build-sha.mjs
|
||||
|
||||
# The guard checks ancestry against origin/main by default, which is correct
|
||||
# here (a release tag is cut from main), but the ref has to exist locally for
|
||||
# `git merge-base` to resolve it.
|
||||
- name: Fetch main for the provenance probe
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
run: git fetch --no-tags --depth=50 origin +refs/heads/main:refs/remotes/origin/main
|
||||
|
||||
- name: Validate npm package artifact
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
run: npm run check:pack-artifact
|
||||
@@ -265,7 +287,12 @@ jobs:
|
||||
# a staged package that is never approved simply expires, with no `npm deprecate` needed.
|
||||
- name: Prove clean-install AND upgrade-over-previous both boot
|
||||
if: steps.resolve.outputs.skip != 'true'
|
||||
timeout-minutes: 30
|
||||
# 60, not 30. This gate was added in #8953 and the 2026-08-27 v3.8.50 publish
|
||||
# was the FIRST run to ever reach it — every earlier attempt died upstream, so
|
||||
# its budget had never been measured against a real run. It then blew the limit
|
||||
# on its debut: `npm pack` alone took 24m37s, leaving 5 minutes for two installs
|
||||
# and two boots. 30 was a guess; 60 is sized to the one measurement we have.
|
||||
timeout-minutes: 60
|
||||
run: npm run check:install-upgrade
|
||||
|
||||
# WS1.3 (D2, v3.8.49 plan): STAGED publishing by default — `npm stage publish`
|
||||
|
||||
40
.mailmap
Normal file
40
.mailmap
Normal file
@@ -0,0 +1,40 @@
|
||||
# .mailmap — canonical author identities for git log/shortlog/blame.
|
||||
#
|
||||
# Why this file exists: between 2026-08-13 and 2026-08-26 this checkout carried a
|
||||
# `git config --local` whose user.name was one contributor's ("Xiangzhe" / @xz-dev)
|
||||
# and whose user.email was ANOTHER contributor's (@backryun). Every commit produced
|
||||
# on this machine in that window was therefore signed with @backryun's address —
|
||||
# 237 commits, all in the -0300 timezone, while @backryun's own work commits from
|
||||
# +0900 and continued normally throughout. The local override was removed on
|
||||
# 2026-08-26; this file repairs the RECORD without rewriting published history
|
||||
# (those commits live on release/v3.8.50 and release/v3.8.51, which other sessions
|
||||
# and open PRs build on — a rewrite would force-push both and orphan the v3.8.50 tag).
|
||||
#
|
||||
# Format: Canonical Name <canonical@email> Commit Name <commit@email>
|
||||
|
||||
# --- Maintainer: several addresses used over the project's life ---
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouza.pw@gmail.com>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouza.pw@outlook.com>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouzapw@users.noreply.github.com>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Diego Souza <8016841+diegosouzapw@users.noreply.github.com>
|
||||
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diego.souza.pw@gmail.com>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <souzamiriamrodrigues790@gmail.com>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diego.souza@cdwasolutions.com.br>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> <diegosouzapw@devbox.local>
|
||||
|
||||
# --- The misattribution window: name Xiangzhe + @backryun's email, from -0300.
|
||||
# These are maintainer/session commits, NOT @backryun's contributions.
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Xiangzhe <bakryun0718@proton.me>
|
||||
diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Xiangzhe <diegosouza.pw@gmail.com>
|
||||
|
||||
# --- Xiangzhe (@xz-dev) — a distinct contributor; keep their own work intact ---
|
||||
Xiangzhe <32761048+xz-dev@users.noreply.github.com> <xz-dev@users.noreply.github.com>
|
||||
Xiangzhe <32761048+xz-dev@users.noreply.github.com> <xiangzhedev@gmail.com>
|
||||
|
||||
# --- @backryun's own alternate addresses (their real work, kept intact) ---
|
||||
backryun <24198422+backryun@users.noreply.github.com> <bakryun0718@proton.me>
|
||||
backryun <24198422+backryun@users.noreply.github.com> <backryun@daonlab.local>
|
||||
backryun <24198422+backryun@users.noreply.github.com> <busan011@ormbiz.co.kr>
|
||||
backryun <24198422+backryun@users.noreply.github.com> <backryun@users.noreply.github.com>
|
||||
@@ -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 (159 migrations) |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (160 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 110 tools (44 canonical + memory/skill/GitHub/pool/gamification/plugin/Notion/Obsidian/local-corpus/RTK modules), 3 transports (stdio / SSE / Streamable HTTP), 33 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
|
||||
71
CHANGELOG.md
71
CHANGELOG.md
@@ -93,6 +93,67 @@
|
||||
|
||||
_Living section — regenerated 2026-08-12 from all cycle commits (cycle open `ed2db6cb19` → tip). Bullets carry the merged PR and its author; direct pushes listed separately._
|
||||
|
||||
### 📊 Release by the numbers
|
||||
|
||||
| | |
|
||||
| --- | ---: |
|
||||
| 👥 People who contributed | **248** |
|
||||
| 📝 Commits in the cycle | **1,714** |
|
||||
| 🔀 Pull requests referenced | **1,666** |
|
||||
| 📋 Changelog entries | **1,182** |
|
||||
| 🙌 Contributors credited in entries | **256** |
|
||||
| 🤖 Automated dependency commits | 22 |
|
||||
|
||||
**Entries by type**
|
||||
|
||||
| Type | Count |
|
||||
| --- | ---: |
|
||||
| 🐛 Fixes | 779 |
|
||||
| ✨ Features | 169 |
|
||||
| 📚 Docs | 29 |
|
||||
| 🧹 Chore | 27 |
|
||||
| 🧪 Tests | 15 |
|
||||
| ♻️ Refactor | 5 |
|
||||
| ⚡ Performance | 3 |
|
||||
| providers | 2 |
|
||||
| 🔒 Security | 2 |
|
||||
| ⚙️ CI | 2 |
|
||||
| deps | 2 |
|
||||
| maint | 2 |
|
||||
|
||||
### 🏆 Top 25 contributors this cycle
|
||||
|
||||
_By commits in `ed2db6cb19..v3.8.50`, author identities consolidated via `.mailmap`. Bots excluded._
|
||||
|
||||
| # | Contributor | Commits |
|
||||
| ---: | --- | ---: |
|
||||
| 🥇 | diegosouzapw | 738 |
|
||||
| 🥈 | backryun | 88 |
|
||||
| 🥉 | Dizzle | 66 |
|
||||
| 4 | Ravi Tharuma | 52 |
|
||||
| 5 | Markus Hartung | 48 |
|
||||
| 6 | Bob.Hou | 42 |
|
||||
| 7 | Rouzbeh† | 38 |
|
||||
| 8 | Xiangzhe | 31 |
|
||||
| 9 | Paco Cartones | 28 |
|
||||
| 10 | Nguyen Thanh Dat | 23 |
|
||||
| 11 | Aman | 22 |
|
||||
| 12 | Will Gordon | 19 |
|
||||
| 13 | 小妍儿 ✨ | 17 |
|
||||
| 14 | adevwithpurpose | 16 |
|
||||
| 15 | Andrew B. | 10 |
|
||||
| 16 | NOXX - Commiter | 10 |
|
||||
| 17 | ignamiranda | 10 |
|
||||
| 18 | Jonathan Bailey | 9 |
|
||||
| 19 | Ke Jin | 9 |
|
||||
| 20 | Austin Liu | 8 |
|
||||
| 21 | Chewji | 8 |
|
||||
| 22 | Prudhvi Vuda | 7 |
|
||||
| 23 | benzntech | 7 |
|
||||
| 24 | rinseaid | 7 |
|
||||
| 25 | stanley | 7 |
|
||||
|
||||
|
||||
### ✨ New Features
|
||||
- **feat(search):** first-class X Search provider (`x-search`) on `POST /v1/search` and MCP `omniroute_x_search` using SuperGrok / xAI server-side `x_search`. Explicit provider or `search_type: "x"` only — never auto-selected for web. Reuses `xai-oauth` / `xao` / `xai` credentials. Not the X Developer Platform MCP. ([#10985](https://github.com/diegosouzapw/OmniRoute/issues/10985))
|
||||
- **feat(core):** add Layer A capability filter at router (#5696)
|
||||
@@ -1111,6 +1172,12 @@ _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `e
|
||||
- **fix(models):** health-check-excluded models are hidden from the `/v1/models` catalog ([#10026](https://github.com/diegosouzapw/OmniRoute/issues/10026) — thanks @ritheshcn25)
|
||||
- **fix(skills):** the CLI skills left stale by the quota subcommands are regenerated ([#10698](https://github.com/diegosouzapw/OmniRoute/issues/10698))
|
||||
- **fix(mcp):** CLI MCP call protocol issues were resolved ([#10960](https://github.com/diegosouzapw/OmniRoute/issues/10960) — thanks @YunyunZhai)
|
||||
- **fix(dashboard):** expert mode in the Combo Builder can type a provider/model pair by hand again ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — [#8285](https://github.com/diegosouzapw/OmniRoute/pull/8285) (global model search) replaced the "Manual model" block positionally with the new search panel, removing the only way to enter a model that is not in the catalog. The state and handlers behind it survived as dead code, so neither typecheck nor lint noticed the loss. The block is restored above the search panel, unchanged from its pre-#8285 form, and guarded by `tests/e2e/combos-flow.spec.ts` ("expert mode shows a single-page combo form with manual model entry").
|
||||
- **fix(sse):** a combo step pinned to an explicit connection (or a request pinned with `x-omniroute-connection`) is honored on fallback instead of silently rotating to a sibling account ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the generic account-fallback branch excluded the pinned connection after an upstream failure and re-selected another account of the same provider, so a priority combo repeating one provider/model with two different fixed accounts ran **both** attempts under the first step; the second step and its own pin never executed, and per-step attribution (`comboStepId` / `comboExecutionKey`) was wrong. Rotation is now gated on there being no forced connection, matching the stream-readiness, pre-response-timeout and account-semaphore branches. Cooldown recording is unchanged, and unpinned selection still skips burned connections.
|
||||
- **fix(cli):** the local CLI sees the full `/api/monitoring/health` payload again — `version` included — restoring the `check:pack-boot` release gate. [#11040](https://github.com/diegosouzapw/OmniRoute/pull/11040) reduced that route to a liveness-only view for non-management callers (GHSA-mvf8-qc78-5mxm), but the route is classified PUBLIC and `runAuthzPipeline` strips the machine-token header for every route class — so the PUBLIC policy stamped `anonymous` and the loopback CLI could never be recognized as a management principal. The PUBLIC policy now stamps the same loopback-gated `local-cli-token` subject the MANAGEMENT policy already did; anonymous callers still get liveness only.
|
||||
- **fix(search):** a configured search connection (Serper, Brave, Tavily…) is now used instead of being silently passed over for the free `duckduckgo-free` fallback ([#11524](https://github.com/diegosouzapw/OmniRoute/issues/11524)) — when no provider was named explicitly and the cheapest auto-selected one had no credentials, the last-resort loop ran first and `duckduckgo-free` (cost 0, no auth) always won it with empty credentials. On `/v1/responses` the call then returned `success: true` with **zero results**, so web search looked healthy while the paid connection the operator had set up was never called. Credentialed regular providers are now swept before the last-resort loop, and fallback-only providers can no longer outrank a configured one on price.
|
||||
- **fix(api):** `/v1/models` no longer blocks the stale response while it rebuilds the catalog ([#11551](https://github.com/diegosouzapw/OmniRoute/issues/11551)) — the route has been passing a `scheduleBackgroundRefresh` option since #10198, but the parameter had already been removed from `getUnifiedModelsResponse()`, so the object was silently dropped and the stale-while-revalidate rebuild still ran on a `setTimeout(..., 0)`. The builder is essentially synchronous under the App Router, so it pinned the event loop **before** the cached response was flushed — the [#8728](https://github.com/diegosouzapw/OmniRoute/pull/8728) guarantee did not actually exist for operators with large catalogs. `catalogCache` now schedules through `after()` (with a macrotask fallback outside a request scope) and the option is threaded end to end. The extra argument was invisible to CI because `typecheck:core` is a curated allowlist and `next.config.mjs` sets `ignoreBuildErrors: true`.
|
||||
- **fix(sse):** a universal handoff whose summary comes back unusable is no longer regenerated on every single model switch, which was burning paid quota on upstream calls whose answers were thrown away ([#11552](https://github.com/diegosouzapw/OmniRoute/issues/11552)) — nothing is persisted when the summary does not parse, so the next switch in the same session re-issued the same full-history summarization request and discarded it again, forever. With a switch-heavy combo strategy (weighted, random, round-robin, p2c) that landed on a large share of requests: measured at n=200, roughly **one request in four carried an extra discarded upstream call**, and that traffic skewed a weighted 70/30 combo to an observed 0.895 share for one provider even though the share actually delivered to the client was a correct 0.70. There is now an exponential back-off per (session, combo) — 5 min up to 1 h, cleared on the first successful handoff, capped at 500 tracked keys. A transient upstream failure is deliberately **not** tracked, so it still retries immediately. After the fix: 201 upstream calls for 200 requests, and the measured share matches the delivered one.
|
||||
|
||||
### 📝 Maintenance
|
||||
|
||||
@@ -1355,6 +1422,10 @@ _Living section — regenerated 2026-08-12 from all cycle commits (cycle open `e
|
||||
- **docs(i18n):** localization contributions — a complete Persian user guide ([#11254](https://github.com/diegosouzapw/OmniRoute/issues/11254)), improved and completed Turkish documentation ([#11237](https://github.com/diegosouzapw/OmniRoute/issues/11237)), the Italian README restored ([#11246](https://github.com/diegosouzapw/OmniRoute/issues/11246)), a Farsi README ([#10777](https://github.com/diegosouzapw/OmniRoute/issues/10777) — thanks @farshidrezaei), a `SETUP_GUIDE.md` correction ([#10490](https://github.com/diegosouzapw/OmniRoute/issues/10490) — thanks @realize000), a `python_requests.py` example fix ([#10731](https://github.com/diegosouzapw/OmniRoute/issues/10731) — thanks @pandaaaa1990), and a retranslation of the CLI reference and integrations guide across all 42 locales
|
||||
- **chore(repo):** repository hygiene — the self-referential `_tasks` symlink was untracked and `.gitignore` anchored so a `_tasks` symlink can never be tracked again, `.source/dynamic.ts`, `.source`, `/output/` and the Playwright CLI artifact directory were ignored, an initial `.cbmignore` was added for codebase-memory indexing, unused `.source/dynamic.ts` and `source.config.mjs` files were removed, the stray unresolved conflict marker in `ENVIRONMENT.md` was cleaned up, and the Open Collective sponsorship link was removed from the README
|
||||
- **chore(release):** localized `llm.txt` mirrors and the v3.8.50 base quality docs were synchronized, and the 363 `changelog.d` fragments were aggregated into this section
|
||||
- **fix(ci):** the pack-artifact provenance gate now checks the branch under test instead of `origin/main` ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the guard added for [#10427](https://github.com/diegosouzapw/OmniRoute/issues/10427) is correct at publish time (that workflow runs on `main`), but in a `pull_request` context the head is by construction not an ancestor of `main` and the shallow checkout never fetches it, so the probe always answered false and the job failed 100% of the time. It became visible only once it stopped being cancelled behind Build. Pre-merge it resolves the ref from `refs/pull/<N>/head`, which exists on origin even for fork PRs.
|
||||
- **test(dashboard):** the proxy-registry e2e smoke flow opens the toolbar's "More actions" overflow menu before clicking bulk assign ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — [#9870](https://github.com/diegosouzapw/OmniRoute/pull/9870) moved the action into that menu, which only renders its items while open, so the locator never resolved and the test burned its full 180 s budget. Every existing assertion is unchanged.
|
||||
- **test(api):** the `/v1/models` e2e check accepts the auth gate introduced by [#9320](https://github.com/diegosouzapw/OmniRoute/pull/9320) ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the catalog now requires auth whenever management auth is configured, and the harness boots with `INITIAL_PASSWORD` set, so the endpoint had been answering 401 since 2026-08-04 and the check was red the whole time, hidden behind a cancelled job. It now mirrors the sibling `/api/providers` check: assert the catalog shape when the catalog is served, otherwise pin the gate by status **and** error type so an unrelated 401 cannot pass for the deliberate one.
|
||||
- **chore(quality):** refreshed the combos-page ESLint suppressions after the manual-model restore ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — three `no-unused-vars` suppressions existed only because #8285 had deleted the JSX consuming that state; with the block back they are live again, and a stale suppression makes ESLint exit 2, which is what actually turned the Lint job red. The 7 `react-hooks/set-state-in-effect` plus 1 `react-hooks/immutability` errors in the same file are pre-existing (reproducible on the file's `091e2ba4da` content) and surfaced only because touching the file evicted it from the restored `.eslintcache`; they are frozen here and tracked separately rather than refactored mid-release.
|
||||
|
||||
|
||||
### 🙌 Contributors
|
||||
|
||||
16
Dockerfile
16
Dockerfile
@@ -103,11 +103,25 @@ RUN test -f package-lock.json \
|
||||
# node-gyp comes from npm's own bundled copy (deterministic, already in the image)
|
||||
# instead of `npx --yes`, which would install an arbitrary registry version
|
||||
# on-demand and run its lifecycle scripts (Sonar docker:S6505).
|
||||
#
|
||||
# tls-client-node (chatgpt-web/claude-web/grok-web/lmarena/perplexity-web TLS
|
||||
# impersonation) hits the same --ignore-scripts wall: its own postinstall.js
|
||||
# fetches a platform .so/.dylib/.dll from the bogdanfinn/tls-client GitHub
|
||||
# Releases API and is never invoked when npm ci skips lifecycle scripts. Unlike
|
||||
# better-sqlite3 above, that script never throws on failure — it only
|
||||
# `console.warn`s and exits 0 — so a rate-limited or offline build would
|
||||
# otherwise succeed silently with an empty bin/ and only fail at first request
|
||||
# in production (TlsClientUnavailableError, #7802). Run it explicitly here so
|
||||
# a broken/rate-limited fetch fails the BUILD loudly instead of shipping a
|
||||
# broken image.
|
||||
RUN --mount=type=cache,id=s/92ca8a61-c1ba-421f-a389-d48ac7258c2d-npm-cache,target=/root/.npm \
|
||||
npm ci --include=optional --no-audit --no-fund --legacy-peer-deps --ignore-scripts \
|
||||
&& (cd node_modules/better-sqlite3 \
|
||||
&& node /usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js rebuild) \
|
||||
&& node -e "require('better-sqlite3')(':memory:').close()"
|
||||
&& node -e "require('better-sqlite3')(':memory:').close()" \
|
||||
&& node node_modules/tls-client-node/scripts/postinstall.js \
|
||||
&& (test -n "$(find node_modules/tls-client-node/bin -mindepth 1 -print -quit 2>/dev/null)" \
|
||||
|| (echo "tls-client-node native binary missing after postinstall — GitHub API fetch likely rate-limited or failed (#7802)" >&2 && exit 1))
|
||||
|
||||
# Build with Turbopack (stable in Next 16, the repo default). The v3.8.27-era
|
||||
# TurbopackInternalError panic ("entered unreachable code: there must be a path to a
|
||||
|
||||
@@ -29,6 +29,11 @@ RUN if [ -d "node_modules/better-sqlite3" ]; then \
|
||||
(cd node_modules/better-sqlite3 && bunx node-gyp rebuild); \
|
||||
fi
|
||||
|
||||
# Fetch tls-client-node native binary if script exists
|
||||
RUN if [ -f "node_modules/tls-client-node/scripts/postinstall.js" ]; then \
|
||||
bun node_modules/tls-client-node/scripts/postinstall.js || true; \
|
||||
fi
|
||||
|
||||
# Disable Turbopack for Bun builder stage (Turbopack V8 internal worker bindings require Node)
|
||||
ENV OMNIROUTE_USE_TURBOPACK=0
|
||||
|
||||
|
||||
@@ -1202,7 +1202,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
|
||||
<tr><td nowrap><b>Runtime</b></td><td>Node.js 22.x / 24.x LTS — <code>>=22.22.2 <23 || >=24.0.0 <27</code></td></tr>
|
||||
<tr><td nowrap><b>Language</b></td><td>TypeScript 6.0 — <b>100% TypeScript</b> across <code>src/</code> and <code>open-sse/</code> (zero <code>any</code> in core since v2.0)</td></tr>
|
||||
<tr><td nowrap><b>Framework</b></td><td>Next.js 16 + React 19 + Tailwind CSS 4</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 120 domain modules, 159 migrations</td></tr>
|
||||
<tr><td nowrap><b>Database</b></td><td>better-sqlite3 (SQLite, WAL journaling) + LowDB (JSON legacy) — 120 domain modules, 160 migrations</td></tr>
|
||||
<tr><td nowrap><b>Memory</b></td><td>SQLite FTS5 full-text + int8-quantized vector embeddings, typed decay</td></tr>
|
||||
<tr><td nowrap><b>Schemas</b></td><td>Zod 4 — MCP tool I/O validation + API contracts</td></tr>
|
||||
<tr><td nowrap><b>Protocols</b></td><td>MCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)</td></tr>
|
||||
|
||||
@@ -24,28 +24,3 @@ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPO
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
||||
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
## wreq-js 3.0.0
|
||||
|
||||
OmniRoute distributes `wreq-js` and its seven platform-specific native addons from
|
||||
[`wreq-js@3.0.0`](https://www.npmjs.com/package/wreq-js/v/3.0.0).
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 will-work-for-meal
|
||||
Copyright (c) 2025 Oleksandr Herasymov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
||||
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(cli):** the local CLI sees the full `/api/monitoring/health` payload again — `version` included — restoring the `check:pack-boot` release gate. [#11040](https://github.com/diegosouzapw/OmniRoute/pull/11040) reduced that route to a liveness-only view for non-management callers (GHSA-mvf8-qc78-5mxm), but the route is classified PUBLIC and `runAuthzPipeline` strips the machine-token header for every route class — so the PUBLIC policy stamped `anonymous` and the loopback CLI could never be recognized as a management principal. The PUBLIC policy now stamps the same loopback-gated `local-cli-token` subject the MANAGEMENT policy already did; anonymous callers still get liveness only.
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(search):** a configured search connection (Serper, Brave, Tavily…) is now used instead of being silently passed over for the free `duckduckgo-free` fallback ([#11524](https://github.com/diegosouzapw/OmniRoute/issues/11524)) — when no provider was named explicitly and the cheapest auto-selected one had no credentials, the last-resort loop ran first and `duckduckgo-free` (cost 0, no auth) always won it with empty credentials. On `/v1/responses` the call then returned `success: true` with **zero results**, so web search looked healthy while the paid connection the operator had set up was never called. Credentialed regular providers are now swept before the last-resort loop, and fallback-only providers can no longer outrank a configured one on price.
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(api):** `/v1/models` no longer blocks the stale response while it rebuilds the catalog ([#11551](https://github.com/diegosouzapw/OmniRoute/issues/11551)) — the route has been passing a `scheduleBackgroundRefresh` option since #10198, but the parameter had already been removed from `getUnifiedModelsResponse()`, so the object was silently dropped and the stale-while-revalidate rebuild still ran on a `setTimeout(..., 0)`. The builder is essentially synchronous under the App Router, so it pinned the event loop **before** the cached response was flushed — the [#8728](https://github.com/diegosouzapw/OmniRoute/pull/8728) guarantee did not actually exist for operators with large catalogs. `catalogCache` now schedules through `after()` (with a macrotask fallback outside a request scope) and the option is threaded end to end. The extra argument was invisible to CI because `typecheck:core` is a curated allowlist and `next.config.mjs` sets `ignoreBuildErrors: true`.
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** a universal handoff whose summary comes back unusable is no longer regenerated on every single model switch, which was burning paid quota on upstream calls whose answers were thrown away ([#11552](https://github.com/diegosouzapw/OmniRoute/issues/11552)) — nothing is persisted when the summary does not parse, so the next switch in the same session re-issued the same full-history summarization request and discarded it again, forever. With a switch-heavy combo strategy (weighted, random, round-robin, p2c) that landed on a large share of requests: measured at n=200, roughly **one request in four carried an extra discarded upstream call**, and that traffic skewed a weighted 70/30 combo to an observed 0.895 share for one provider even though the share actually delivered to the client was a correct 0.70. There is now an exponential back-off per (session, combo) — 5 min up to 1 h, cleared on the first successful handoff, capped at 500 tracked keys. A transient upstream failure is deliberately **not** tracked, so it still retries immediately. After the fix: 201 upstream calls for 200 requests, and the measured share matches the delivered one.
|
||||
@@ -0,0 +1,8 @@
|
||||
- **fix(db):** `model_capabilities` is created by a migration instead of lazily on the first
|
||||
models.dev sync, so a clean install and an upgraded install converge on the same schema
|
||||
regardless of which features have run
|
||||
- **fix(ci):** `check:install-upgrade` now fails on an `npm` install truncated by ENOSPC
|
||||
(npm reports it as a warning and still exits 0), authenticates its health probe so the
|
||||
version assertion works against the hardened health payload, frees the clean-install tree
|
||||
before the upgrade phase, and no longer reports a schema divergence computed from a boot
|
||||
that never served
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(dashboard):** expert mode in the Combo Builder can type a provider/model pair by hand again ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — [#8285](https://github.com/diegosouzapw/OmniRoute/pull/8285) (global model search) replaced the "Manual model" block positionally with the new search panel, removing the only way to enter a model that is not in the catalog. The state and handlers behind it survived as dead code, so neither typecheck nor lint noticed the loss. The block is restored above the search panel, unchanged from its pre-#8285 form, and guarded by `tests/e2e/combos-flow.spec.ts` ("expert mode shows a single-page combo form with manual model entry").
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** a combo step pinned to an explicit connection (or a request pinned with `x-omniroute-connection`) is honored on fallback instead of silently rotating to a sibling account ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the generic account-fallback branch excluded the pinned connection after an upstream failure and re-selected another account of the same provider, so a priority combo repeating one provider/model with two different fixed accounts ran **both** attempts under the first step; the second step and its own pin never executed, and per-step attribution (`comboStepId` / `comboExecutionKey`) was wrong. Rotation is now gated on there being no forced connection, matching the stream-readiness, pre-response-timeout and account-semaphore branches. Cooldown recording is unchanged, and unpinned selection still skips burned connections.
|
||||
@@ -1 +0,0 @@
|
||||
- **chore(stealth):** replace the `tls-client-node` sidecar/temp-file transport used by the six web-cookie providers with the exactly pinned `wreq-js` 3.0.0 native transport, preserving streaming, proxy isolation, deadlines, EOF policies, binary responses, and cancellation while removing the obsolete downloader and native repair path ([#11753](https://github.com/diegosouzapw/OmniRoute/pull/11753)).
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(ci):** the pack-artifact provenance gate now checks the branch under test instead of `origin/main` ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the guard added for [#10427](https://github.com/diegosouzapw/OmniRoute/issues/10427) is correct at publish time (that workflow runs on `main`), but in a `pull_request` context the head is by construction not an ancestor of `main` and the shallow checkout never fetches it, so the probe always answered false and the job failed 100% of the time. It became visible only once it stopped being cancelled behind Build. Pre-merge it resolves the ref from `refs/pull/<N>/head`, which exists on origin even for fork PRs.
|
||||
@@ -1 +0,0 @@
|
||||
- **test(dashboard):** the proxy-registry e2e smoke flow opens the toolbar's "More actions" overflow menu before clicking bulk assign ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — [#9870](https://github.com/diegosouzapw/OmniRoute/pull/9870) moved the action into that menu, which only renders its items while open, so the locator never resolved and the test burned its full 180 s budget. Every existing assertion is unchanged.
|
||||
@@ -1 +0,0 @@
|
||||
- **test(api):** the `/v1/models` e2e check accepts the auth gate introduced by [#9320](https://github.com/diegosouzapw/OmniRoute/pull/9320) ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — the catalog now requires auth whenever management auth is configured, and the harness boots with `INITIAL_PASSWORD` set, so the endpoint had been answering 401 since 2026-08-04 and the check was red the whole time, hidden behind a cancelled job. It now mirrors the sibling `/api/providers` check: assert the catalog shape when the catalog is served, otherwise pin the gate by status **and** error type so an unrelated 401 cannot pass for the deliberate one.
|
||||
@@ -1 +0,0 @@
|
||||
- **chore(quality):** refreshed the combos-page ESLint suppressions after the manual-model restore ([#8875](https://github.com/diegosouzapw/OmniRoute/pull/8875)) — three `no-unused-vars` suppressions existed only because #8285 had deleted the JSX consuming that state; with the block back they are live again, and a stale suppression makes ESLint exit 2, which is what actually turned the Lint job red. The 7 `react-hooks/set-state-in-effect` plus 1 `react-hooks/immutability` errors in the same file are pre-existing (reproducible on the file's `091e2ba4da` content) and surfaced only because touching the file evicted it from the restored `.eslintcache`; they are frozen here and tracked separately rather than refactored mid-release.
|
||||
@@ -74,6 +74,12 @@
|
||||
"justification": "CC-BY-4.0 applies to the caniuse browser-support data (a dataset, not code). The Creative Commons Attribution license requires attribution when distributing — OmniRoute does not distribute caniuse-lite data directly to end users; it is consumed by browserslist/PostCSS at build time to generate CSS compatibility info. This is a widely accepted pattern in the Node.js ecosystem (caniuse-lite is in millions of projects). Attribution is satisfied by keeping the package in node_modules with its original license file.",
|
||||
"risk": "low",
|
||||
"reviewAt": "v4.0.0"
|
||||
},
|
||||
"tls-client-node": {
|
||||
"license": "Custom: LICENSE (Apache-2.0 + Commons Clause)",
|
||||
"justification": "TODO: revisar — tls-client-node uses Apache-2.0 with a 'Commons Clause' addendum that restricts 'Selling' the software (i.e., offering it as a hosted/commercial service whose value derives substantially from tls-client-node). OmniRoute is an open-source proxy; however if deployed as a paid SaaS/hosting service, this restriction could apply. The package is used by grokTlsClient.ts for Grok TLS fingerprinting. RISK: medium — legal review recommended before commercial deployment. Alternatives: consider replacing with a native TLS fingerprinting approach or a truly permissive library.",
|
||||
"risk": "medium",
|
||||
"reviewAt": "v3.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
"sqlite-vec",
|
||||
"tailwind-merge",
|
||||
"tailwindcss",
|
||||
"tls-client-node",
|
||||
"turndown",
|
||||
"turndown-plugin-gfm",
|
||||
"tsup",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_doc": "Tables that exist ONLY in databases upgraded from an older version — residue whose CREATE left the migration set in some past cycle but survives where it already existed. Harmless (nothing references them), but recorded here so check-install-upgrade.mjs can still fail on a NEW divergence. The opposite direction (a table a clean install creates but an upgrade does not) is NEVER allowlisted: it means every existing user is missing structure the code expects.",
|
||||
"_doc": "Tables that exist ONLY in databases upgraded from an older version. Two causes, and they call for different fixes: (a) residue whose CREATE left the migration set in some past cycle but survives where it already existed — allowlist it here; (b) a table created LAZILY at runtime with `CREATE TABLE IF NOT EXISTS` inside a feature code path — whether a database has it depends on whether that feature ran, so it diverges by TIMING and can show up on EITHER side. Fix (b) with a migration instead of an entry here (see src/lib/db/migrations/163_model_capabilities.sql); an allowlist entry only hides it in one direction. Either way, recorded so check-install-upgrade.mjs can still fail on a NEW divergence. The opposite direction (a table a clean install creates but an upgrade does not) is NEVER allowlisted: it means every existing user is missing structure the code expects.",
|
||||
"residualTables": {
|
||||
"cache_metrics": "Measured 2026-07-30 on a real 3.8.48 install upgraded to 3.8.49 (VPS .16, 165 MB database, 114 → 117 tables). Present in upgraded databases, absent from clean installs. No code path referenced it during the upgrade (zero `no such table` in 150 log lines, both installs healthy). Left in place rather than dropped: a DROP migration on a table we cannot prove is unused everywhere is the riskier change. Revisit when the cache subsystem is next touched."
|
||||
"cache_metrics": "Measured 2026-07-30 on a real 3.8.48 install upgraded to 3.8.49 (VPS .16, 165 MB database, 114 → 117 tables). Present in upgraded databases, absent from clean installs. Cause identified 2026-08-27: it is case (b) above — created lazily by `ensureCacheMetricsTable()` at src/lib/semanticCache.ts:34, never by a migration, so it appears only where the semantic cache has run. No code path referenced it during the upgrade (zero `no such table` in 150 log lines, both installs healthy). Left as an allowlist entry rather than promoted to a migration or dropped: unlike model_capabilities it did not block a release, and creating a table for a subsystem we cannot prove is live is not a change to make blind. Revisit when the cache subsystem is next touched."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"package": "wreq-js",
|
||||
"version": "3.0.0",
|
||||
"source": "https://registry.npmjs.org/wreq-js/-/wreq-js-3.0.0.tgz",
|
||||
"npmIntegrity": "sha512-RZCoRSevVPpH4A4B4MxbFGo/pVPFveWd2gbe4ENKpPWlKXEYklZSDESOjBMmrIsmnkHh+nhM4PNJvG+NL7wBPA==",
|
||||
"license": "MIT",
|
||||
"nativeAddons": [
|
||||
{
|
||||
"path": "rust/wreq-js.darwin-arm64.node",
|
||||
"size": 7690880,
|
||||
"sha256": "c82eec39df691adb94f2cd09a8ff51335de8587cf132cd8b3ec797469a4b5002"
|
||||
},
|
||||
{
|
||||
"path": "rust/wreq-js.darwin-x64.node",
|
||||
"size": 8192028,
|
||||
"sha256": "073b8a8a4c26aedbce7c14eef3e5567918e62e8dbf4d28296b23f9d2beec2981"
|
||||
},
|
||||
{
|
||||
"path": "rust/wreq-js.linux-arm64-gnu.node",
|
||||
"size": 8520824,
|
||||
"sha256": "861d96a78caf7ce02c9ae8d37f1c59f5b0480e3142775c32917fcfe9b88524b0"
|
||||
},
|
||||
{
|
||||
"path": "rust/wreq-js.linux-arm64-musl.node",
|
||||
"size": 8735472,
|
||||
"sha256": "2409a3578c8c440df419b4d5abe3ac149bec48881611a6dc1571b95e6246552d"
|
||||
},
|
||||
{
|
||||
"path": "rust/wreq-js.linux-x64-gnu.node",
|
||||
"size": 9048992,
|
||||
"sha256": "55b40f4602c52111dfcdcc93db83f9d0de55d0ef7540348757709d58d05a9b64"
|
||||
},
|
||||
{
|
||||
"path": "rust/wreq-js.linux-x64-musl.node",
|
||||
"size": 8974880,
|
||||
"sha256": "bd52d15b1bb4704b11561a8aa95648a6c91150082b5af0e39dd1608b7db2d317"
|
||||
},
|
||||
{
|
||||
"path": "rust/wreq-js.win32-x64-msvc.node",
|
||||
"size": 7967232,
|
||||
"sha256": "7451a8701b82c946b03ba2be2f15257260a250b9e0ed9910611b22564fbec7a9"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -62,7 +62,7 @@ Set these in the OmniRoute process environment (the daemon, e.g. via the LaunchA
|
||||
|
||||
**How to verify it worked**: run your agent/cron twice in quick succession and confirm both succeed. Before the fix, the second run typically throws `429`/`401`. After the fix, failures (if any) are retried transparently and the call completes. You can also `curl /monitoring/health` and watch the `rateLimitedUntil` field on the provider connections and the `circuitBreakers.providerBreakers[].state` for the affected providers — the state is one of `CLOSED`, `DEGRADED`, `OPEN`, or `HALF_OPEN` (see `src/shared/utils/circuitBreaker.ts`), and a provider that keeps failing will flip `CLOSED → DEGRADED → OPEN` before the reset window lets a probe through (`HALF_OPEN`).
|
||||
|
||||
**If you still see 429**: the active account for that provider has genuinely exhausted its _quota_ (not just rate). Add a second account for the same provider in the OmniRoute dashboard → Providers → Accounts, or mix in another free provider (e.g. `routeway`, `auggie`). Rotation only helps with transient rate/400/401; a hard quota exhaustion requires a second credential or a different provider.
|
||||
**If you still see 429**: the active account for that provider has genuinely exhausted its *quota* (not just rate). Add a second account for the same provider in the OmniRoute dashboard → Providers → Accounts, or mix in another free provider (e.g. `routeway`, `auggie`). Rotation only helps with transient rate/400/401; a hard quota exhaustion requires a second credential or a different provider.
|
||||
|
||||
**If you see 403 on vision models (`auto/vision`, `bazaarlink/*`)**: the connected account lacks a paid plan that includes vision, or the API key has insufficient permissions. Verify in the provider dashboard that the key scope includes vision/multimodal, or connect a paid tier account and keep it as the vision target.
|
||||
|
||||
@@ -75,7 +75,7 @@ When you run `npm install -g omniroute`, you may see a wall of warnings like `np
|
||||
The warnings come from stale peer-dependency ranges in third-party packages OmniRoute doesn't control:
|
||||
|
||||
1. **`marked-terminal` wants `marked >=1 <16`, found `marked@18`** — works fine in practice; the upstream peer range is just stale.
|
||||
2. **`deprecated prebuild-install@7.1.3`** — a transitive native-binary helper used by another dependency. The pinned `wreq-js@3.0.0` package bundles its seven supported platform addons directly; this warning does not diagnose the web-cookie transport.
|
||||
2. **`deprecated prebuild-install@7.1.3`** — the native-binary fetch helper. Only relevant later if a web-cookie provider reports a missing `tls-client-node` native binary (a separate issue, not caused by this warning).
|
||||
|
||||
**No action needed** — the warnings cannot be fully silenced without forking upstream packages.
|
||||
|
||||
@@ -148,10 +148,9 @@ desktop app, for example:
|
||||
- `resources/app/.build/next/node_modules/playwright-<hash>/lib/…/agentParser.js` and
|
||||
`workerProcessEntry.js` — [Playwright](https://playwright.dev), the browser-automation
|
||||
library used for in-app provider login and browser-backed chat.
|
||||
- `resources/app/.build/next/node_modules/wreq-js-<hash>/rust/wreq-js.win32-x64-msvc.node`
|
||||
— the declared MIT-licensed native addon from pinned `wreq-js@3.0.0`, used for
|
||||
browser-fingerprinted HTTP on some web providers. Its expected SHA-256 is recorded in
|
||||
`config/release/wreq-js-native-manifest.json`.
|
||||
- `resources/app/.build/next/node_modules/tls-client-node-<hash>/bin/tls-client-windows-64-<ver>.dll`
|
||||
— the native binary from `tls-client-node`, used for Cloudflare-tolerant HTTP on some web
|
||||
providers.
|
||||
|
||||
**Why it fires:** the Windows installer is **not yet code-signed**, so an unsigned NSIS
|
||||
installer has zero reputation and behavioral heuristics run at maximum aggression. Combined
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -438,7 +438,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -766,18 +766,18 @@ REQUEST_TIMEOUT_MS (global override)
|
||||
| `OMNIROUTE_PROVIDER_PROBE_TIMEOUT_MS` | `8000` | Timeout (ms) for the `validationRead` and `modelsProbe` presets in `src/shared/network/safeOutboundFetch.ts`. Raise for slow endpoints (Cerebras, Cloudflare AI, Groq) to prevent flapping between active/error in the dashboard. Falls back to 8000ms for invalid (<1000) or non-numeric values. |
|
||||
| `OMNIROUTE_RELAY_FETCH_TIMEOUT_MS` | `25000` | Relay-specific fetch timeout in `open-sse/utils/proxyFetch.ts` (#9158). A hung relay must fail before the client/agent timeout (~30s) so callers see a relay-specific failure instead of a generic upstream timeout. Capped at `29000` so it always fires first. |
|
||||
| `OMNIROUTE_RETRY_BACKOFF_MS` | `10` | Shared retry backoff for the direct/relay/proxy retry-once paths in `open-sse/utils/proxyFetch.ts` (#9158). `0` = retry immediately. |
|
||||
| `OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS` | `60000` | Native wreq-js request timeout (`chatgptTlsClient.ts`). |
|
||||
| `OMNIROUTE_CHATGPT_TLS_GRACE_MS` | `10000` | JS-side hard-deadline grace added on top of the native timeout. |
|
||||
| `OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS` | `30000` (30s) | Max wait for the first streamed byte before ChatGPT switches to a buffered response; the hard request deadline remains active. |
|
||||
| `OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS` | `60000` | Native wreq-js request timeout (`claudeTlsClient.ts`). |
|
||||
| `OMNIROUTE_CLAUDE_TLS_GRACE_MS` | `10000` | JS-side hard-deadline grace added on top of the native timeout. |
|
||||
| `OMNIROUTE_PPLX_TLS_TIMEOUT_MS` | `30000` | Native wreq-js request timeout (`perplexityTlsClient.ts`). |
|
||||
| `OMNIROUTE_PPLX_TLS_GRACE_MS` | `10000` | JS-side hard-deadline grace added on top of the native timeout. |
|
||||
| `OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS` | `60000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`chatgptTlsClient.ts`). |
|
||||
| `OMNIROUTE_CHATGPT_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
|
||||
| `OMNIROUTE_CHATGPT_STREAM_FIRST_BYTE_TIMEOUT_MS` | `30000` (30s) | Max wait for the first streamed byte from the ChatGPT TLS sidecar (`chatgptTlsClient.ts`) before aborting a dead stream. Raise if upstream cold-starts exceed the window. |
|
||||
| `OMNIROUTE_CLAUDE_TLS_TIMEOUT_MS` | `60000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`claudeTlsClient.ts`). |
|
||||
| `OMNIROUTE_CLAUDE_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
|
||||
| `OMNIROUTE_PPLX_TLS_TIMEOUT_MS` | `30000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`perplexityTlsClient.ts`). |
|
||||
| `OMNIROUTE_PPLX_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
|
||||
| `OMNIROUTE_PPLX_SEARCH_HINT` | `0` (off) | Appends "You have built-in web search. Answer questions directly using search results." to the caller's system message (`perplexity-web/protocol.ts`). Off by default — Perplexity searches anyway, and the sentence leaks into replies as meta-commentary for coding clients. Set `1`/`true`/`yes`/`on` to restore. |
|
||||
| `OMNIROUTE_GROK_TLS_TIMEOUT_MS` | `60000` | Native wreq-js request timeout (`grokTlsClient.ts`). |
|
||||
| `OMNIROUTE_GROK_TLS_GRACE_MS` | `10000` | JS-side hard-deadline grace added on top of the native timeout. |
|
||||
| `OMNIROUTE_NOTION_TLS_TIMEOUT_MS` | `30000` | Native wreq-js request timeout (`notionTlsClient.ts`); `notion-web` raises it per request to `180000` for long generations. |
|
||||
| `OMNIROUTE_NOTION_TLS_GRACE_MS` | `10000` | JS-side hard-deadline grace added on top of the native timeout. |
|
||||
| `OMNIROUTE_GROK_TLS_TIMEOUT_MS` | `60000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`grokTlsClient.ts`). |
|
||||
| `OMNIROUTE_GROK_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
|
||||
| `OMNIROUTE_NOTION_TLS_TIMEOUT_MS` | `30000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`notionTlsClient.ts`); the `notion-web` executor raises it per-request to `180000` for long generations. |
|
||||
| `OMNIROUTE_NOTION_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
|
||||
| `OMNIROUTE_BROWSER_POOL` | `on` | Shared Playwright browser pool for browser-backed web-cookie chat (`browserPool.ts`); set `off` to disable. |
|
||||
| `WEB_COOKIE_USE_BROWSER` | `0` | Opt a web-cookie chat request into the browser-backed path (`browserBackedChat.ts`); `1` to enable. |
|
||||
| `KIMI_WEB_BASE_URL` | `https://www.kimi.ai` | Base URL for the Kimi Web (international kimi.ai Connect-RPC) executor (`kimi-web.ts`); override only for mirror/proxy endpoints. |
|
||||
@@ -1310,6 +1310,7 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy),
|
||||
| `DB_BACKUP_MAX_FILES` | `20` | `src/lib/db/backup.ts`, `src/lib/db/migrationRunner.ts` | Maximum SQLite backup files retained on disk. Applies to manual/scheduled backups and to pre-migration snapshots. Overrides the value saved from Settings → Database backup retention. |
|
||||
| `DB_BACKUP_RETENTION_DAYS` | `0` | `src/lib/db/backup.ts`, `src/lib/db/migrationRunner.ts` | Maximum age (days) of retained backups. `0` disables age-based pruning. Applies to manual/scheduled backups and to pre-migration snapshots. Overrides the value saved from Settings → Database backup retention. |
|
||||
| `OMNIROUTE_BACKUP_SCHEDULE_JOB_INTERVAL_MS` | `30000` | `src/lib/jobs/backupScheduleJob.ts` | Tick interval (ms) of the server-side job that executes `backup-schedule.json`. Must stay well under the 1-minute cron granularity; values below `5000` or unparseable fall back to `30000`. |
|
||||
| `OMNIROUTE_TLS_PROXY_URL` | _(unset)_ | `open-sse/services/chatgptTlsClient.ts` | Override the TLS sidecar URL for tests. Production should leave unset. |
|
||||
| `CONTAINER_HOST` | `docker` | `scripts/check-permissions.sh` | Container runtime hint for the entrypoint permission check. Set to `podman` for any Podman topology. Because the container cannot determine whether the engine is local or reached through Podman Machine, the warning stays topology-neutral and points to `contrib/podman/README.md`. |
|
||||
| `QUOTA_STORE_DRIVER` | `sqlite` | `src/lib/quota/storeFactory.ts` | Quota-share consumption store backend: `sqlite` (default) or `redis`. |
|
||||
| `QUOTA_STORE_REDIS_URL` | _(unset)_ | `src/lib/quota/storeFactory.ts` | Redis connection string used when `QUOTA_STORE_DRIVER=redis` (e.g. `redis://localhost:6379`). |
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: "Stealth Guide"
|
||||
version: 3.8.50
|
||||
lastUpdated: 2026-08-26
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Stealth Guide
|
||||
|
||||
> **Source of truth:** `open-sse/utils/tlsClient.ts`, `open-sse/services/{tlsClientBase,chatgptTlsClient,claudeTlsClient,perplexityTlsClient,grokTlsClient,notionTlsClient,lmarenaTlsClient,claudeCodeCCH,claudeCodeFingerprint,claudeCodeObfuscation,claudeCodeCompatible}.ts`, `open-sse/config/cliFingerprints.ts`, `src/mitm/`
|
||||
> **Last updated:** 2026-08-26 — v3.8.50
|
||||
> **Source of truth:** `open-sse/utils/tlsClient.ts`, `open-sse/services/{chatgptTlsClient,claudeCodeCCH,claudeCodeFingerprint,claudeCodeObfuscation,claudeCodeCompatible}.ts`, `open-sse/config/cliFingerprints.ts`, `src/mitm/`
|
||||
> **Last updated:** 2026-06-28 — v3.8.40
|
||||
> **Audience:** Engineers maintaining provider-specific stealth integrations.
|
||||
|
||||
OmniRoute integrates with providers whose edges actively fingerprint non-official clients (TLS JA3/JA4, header ordering, JSON body shape, integrity tokens). This page documents the stealth surfaces OmniRoute exposes and where they are implemented.
|
||||
@@ -29,38 +29,17 @@ Lazy-loaded `wreq-js` session that impersonates **Chrome 124 on macOS**. Used as
|
||||
- Timeout: `TLS_CLIENT_TIMEOUT_MS` (inherits from `FETCH_TIMEOUT_MS`, default 600000)
|
||||
- `wreq-js` Response is fetch-compatible (`headers`, `text()`, `json()`, `clone()`, `body`).
|
||||
|
||||
### Web-cookie provider transport — wreq-js 3.0.0
|
||||
### `open-sse/services/chatgptTlsClient.ts` — tls-client-node (Firefox 148)
|
||||
|
||||
`open-sse/services/tlsClientBase.ts` is the shared transport for ChatGPT, Claude, Perplexity,
|
||||
Grok, Notion, and LMArena web sessions. Each thin provider wrapper selects a browser/OS profile;
|
||||
the base loads `wreq-js` lazily, reuses only transport-level connections keyed by
|
||||
profile + OS + resolved proxy, and gives every request an ephemeral cookie scope. It never shares a
|
||||
wreq session or cookie jar between accounts or requests.
|
||||
Dedicated TLS impersonator for `chatgpt.com`. ChatGPT's Cloudflare config pins `cf_clearance` to JA3/JA4 + HTTP/2 SETTINGS frame ordering — undici's handshake gets `cf-mitigated: challenge` even with valid cookies.
|
||||
|
||||
| Provider | Profile | Emulated OS | Stream EOF policy |
|
||||
| ---------- | ------------- | ----------- | -------------------------------- |
|
||||
| ChatGPT | `firefox_148` | macOS | include `[DONE]` |
|
||||
| Claude | `chrome_146` | Linux | include `[DONE]` |
|
||||
| Perplexity | `firefox_148` | macOS | include `event: end_of_stream` |
|
||||
| Grok | `chrome_146` | Linux | exclude `[DONE]` |
|
||||
| Notion | `chrome_146` | Windows | include `[DONE]` |
|
||||
| LMArena | `chrome_146` | Windows | no sentinel; close on native EOF |
|
||||
|
||||
- Streaming uses the native response `ReadableStream` directly; no temp file or sidecar is created.
|
||||
- Up to 256 initial bytes are inspected before exposing a stream. SSE providers buffer non-SSE
|
||||
errors; Grok/LMArena map Cloudflare challenges to `403` and HTML interstitials to `502`.
|
||||
- The native request timeout remains wrapped by an absolute JS hard deadline. A hang invalidates
|
||||
and closes only the affected profile/OS/proxy transport before the next request recreates it.
|
||||
- Proxy resolution priority is per-call `proxyUrl` → request-scoped account/dashboard context →
|
||||
`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY` (including lowercase variants). Resolution errors fail
|
||||
closed instead of leaking a direct connection. LMArena deliberately resolves against `arena.ai`.
|
||||
- `byteResponse` returns a content-typed `data:` URL without UTF-8 corruption.
|
||||
- Errors are `TlsClientUnavailableError` (package/addon unavailable) and `TlsClientHangError`
|
||||
(deadline exceeded).
|
||||
|
||||
The profiles are supported by the pinned package, but real WAF acceptance can change independently
|
||||
of local contract tests. Validate fingerprint changes against an explicitly authorized live account
|
||||
before claiming parity with an upstream browser.
|
||||
- Profile: `firefox_148` (must match the Firefox 148 `User-Agent` sent)
|
||||
- Mode: `runtimeMode: "native"` (koffi-loaded shared library; avoids managed sidecar HTTP)
|
||||
- `withRandomTLSExtensionOrder: true`
|
||||
- `tlsFetchChatGpt(url, options)` supports streaming (writes body to temp file, tailed as `ReadableStream`)
|
||||
- Hang detection: `raceWithTimeout` + `TlsClientHangError` triggers `resetClientCache()` so the next call respawns the binding
|
||||
- Proxy resolution (priority): per-call `proxyUrl` → `OMNIROUTE_TLS_PROXY_URL` → `HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY` (the native binding does **not** read these envs itself; it must be threaded through)
|
||||
- Errors: `TlsClientUnavailableError` (binary missing), `TlsClientHangError` (binding deadlocked)
|
||||
|
||||
---
|
||||
|
||||
|
||||
4
llm.txt
4
llm.txt
@@ -14,7 +14,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
|
||||
- **Runtime:** Node.js `>=22.0.0 <23 || >=24.0.0 <27`, ES Modules (`"type": "module"`)
|
||||
- **Framework:** Next.js 16 (App Router) with TypeScript 6
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 159 migrations)
|
||||
- **Database:** SQLite via better-sqlite3 (local, zero-config, 160 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
|
||||
@@ -434,7 +434,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
4. **Environment variables:** All configuration is in `.env` (from `.env.example`). Key vars: `PORT`, `NEXT_PUBLIC_BASE_URL`, `API_KEY`, `ADMIN_PASSWORD`.
|
||||
|
||||
5. **Database layer:** Operations go through `src/lib/db/` modules (120 domain-specific files, 159 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 (120 domain-specific files, 160 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.
|
||||
|
||||
|
||||
@@ -288,6 +288,9 @@ const nextConfig = {
|
||||
"keytar",
|
||||
"wreq-js",
|
||||
"zod",
|
||||
"tls-client-node",
|
||||
"koffi",
|
||||
"tough-cookie",
|
||||
"@ngrok/ngrok",
|
||||
"@huggingface/transformers",
|
||||
// copilot-m365-web.ts imports 'ws' as a client-side WebSocket. When bundled,
|
||||
|
||||
@@ -2280,8 +2280,10 @@ async function imageUrlToCachedImageUrl(
|
||||
|
||||
if (response.text == null || response.text.length === 0) return null;
|
||||
|
||||
// The shared browser transport returns binary bodies as a
|
||||
// "data:<mime>;base64,..." string. Decode it back into bytes for the cache.
|
||||
// tls-client-node already returns binary bodies as a "data:<mime>;base64,..."
|
||||
// string (see node_modules/tls-client-node/dist/response.js — its bytes()
|
||||
// method splits on the comma to extract base64). Decode back into bytes
|
||||
// so we can hand them to the cache.
|
||||
let bytes: Buffer;
|
||||
let mime: string;
|
||||
if (/^data:[^;]{1,256};base64,/.test(response.text)) {
|
||||
|
||||
@@ -939,8 +939,8 @@ export class GrokWebExecutor extends BaseExecutor {
|
||||
|
||||
// Fetch from Grok via TLS-impersonating client (#3180).
|
||||
// Grok sits behind Cloudflare Enterprise which rejects Node's native TLS
|
||||
// fingerprint even with valid sso+sso-rw cookies. The pinned wreq-js
|
||||
// transport sends a Chrome-like handshake instead.
|
||||
// fingerprint even with valid sso+sso-rw cookies. We use tls-client-node
|
||||
// to send a Chrome-like handshake instead.
|
||||
let tlsResult: TlsFetchResult;
|
||||
try {
|
||||
tlsResult = await tlsFetchGrok(GROK_CHAT_API, {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* LMArenaExecutor — Arena (formerly LMArena) web-session provider.
|
||||
*
|
||||
* Routes requests through arena.ai create-evaluation with session cookies.
|
||||
* Upstream sits behind Cloudflare; traffic goes through wreq-js Chrome
|
||||
* impersonation with isolated ephemeral cookies (see services/lmarenaTlsClient.ts).
|
||||
* Upstream sits behind Cloudflare; traffic goes through tls-client-node Chrome
|
||||
* impersonation (see services/lmarenaTlsClient.ts).
|
||||
*
|
||||
* Helpers: open-sse/executors/lmarena/{cookie,models,stream,response}.ts
|
||||
*/
|
||||
@@ -174,6 +174,7 @@ export class LMArenaExecutor extends BaseExecutor {
|
||||
body: JSON.stringify(transformedBody),
|
||||
signal: ctx.signal,
|
||||
stream: ctx.stream,
|
||||
streamEofSymbol: "__OMNIROUTE_LMARENA_EOF_NEVER__",
|
||||
});
|
||||
|
||||
const failed = mapFailedTlsResult({
|
||||
|
||||
@@ -6,9 +6,9 @@ export const LMARENA_API_BASE = "https://arena.ai";
|
||||
export const LMARENA_STREAM_URL = `${LMARENA_API_BASE}/nextjs-api/stream/create-evaluation`;
|
||||
/**
|
||||
* Current Chrome stable UA (header surface).
|
||||
* TLS JA3/JA4 profile is separate: the provider-tested wreq-js profile is pinned
|
||||
* to chrome_146 in lmarenaTlsClient.ts while headers track the live browser string.
|
||||
* Treat that deliberate version skew as a WAF-sensitive compatibility surface.
|
||||
* TLS JA3 profile is separate: tls-client-node tops out at chrome_146 — see
|
||||
* LMARENA_PROFILE in lmarenaTlsClient.ts. Headers track the live browser string;
|
||||
* fingerprint stays at the newest native profile we can actually impersonate.
|
||||
*/
|
||||
export const LMARENA_USER_AGENT =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36";
|
||||
|
||||
@@ -114,7 +114,7 @@ export function mapTlsUnavailable(
|
||||
return {
|
||||
response: errorResponse(
|
||||
502,
|
||||
`Arena TLS impersonation unavailable: ${error.message}. Verify the wreq-js 3.0.0 native addon.`,
|
||||
`Arena TLS impersonation unavailable: ${error.message}. Install/repair tls-client-node native binary.`,
|
||||
"upstream_error",
|
||||
"TLS_CLIENT_UNAVAILABLE"
|
||||
),
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* chunk — safer than assuming unverified incremental-delta semantics.
|
||||
*
|
||||
* Auth: Cookie-based (token_v2 [+ optional space_id, notion_browser_id, user_id])
|
||||
* Method: Browser-TLS impersonation via pinned wreq-js (Chrome JA3/JA4). Plain
|
||||
* Method: Browser-TLS impersonation via tls-client-node (Chrome JA3). Plain
|
||||
* Node/undici fetch is rejected by Notion's edge with in-band
|
||||
* `temporarily-unavailable` (HTTP 200, empty assistant text) — curl/Schannel
|
||||
* and Chrome work with the same cookie + body. See services/notionTlsClient.ts.
|
||||
@@ -60,7 +60,10 @@ import {
|
||||
messagesForNotionTranscript,
|
||||
type NotionAgentOptions,
|
||||
} from "../services/notionTranscriptBuilder.ts";
|
||||
import { tlsFetchNotion, TlsClientUnavailableError } from "../services/notionTlsClient.ts";
|
||||
import {
|
||||
tlsFetchNotion,
|
||||
TlsClientUnavailableError,
|
||||
} from "../services/notionTlsClient.ts";
|
||||
|
||||
// Re-exported for unit tests that destructure `mod.<name>` on this module.
|
||||
export {
|
||||
@@ -222,6 +225,7 @@ function extractUserIdFromCookie(cookie: string): string {
|
||||
return extractNotionUserIdFromCookie(cookie);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notion's undocumented inference API does not return token usage.
|
||||
* Emit a cheap char-based estimate so clients don't see a constant
|
||||
@@ -232,7 +236,9 @@ export function estimateNotionUsage(
|
||||
messages: NotionMessage[] | undefined,
|
||||
content: string
|
||||
): { prompt_tokens: number; completion_tokens: number; total_tokens: number; estimated: true } {
|
||||
const promptText = (messages || []).map((m) => extractNotionMessageText(m?.content)).join("\n");
|
||||
const promptText = (messages || [])
|
||||
.map((m) => extractNotionMessageText(m?.content))
|
||||
.join("\n");
|
||||
// ~4 chars/token (English-ish); at least 1 when there is any text.
|
||||
const prompt_tokens = promptText ? Math.max(1, Math.ceil(promptText.length / 4)) : 0;
|
||||
const completion_tokens = content ? Math.max(1, Math.ceil(content.length / 4)) : 0;
|
||||
@@ -387,8 +393,9 @@ function buildNotionExecuteHeaders(opts: {
|
||||
const isCustom = Boolean(opts.agent?.workflowId);
|
||||
// Browser uses /agent/<workflowId without dashes>?wfv=chat for custom agents.
|
||||
const agentPathId = (opts.agent?.workflowId || "").replace(/-/g, "");
|
||||
const referer =
|
||||
isCustom && agentPathId ? `${BASE_URL}/agent/${agentPathId}?wfv=chat` : `${BASE_URL}/ai`;
|
||||
const referer = isCustom && agentPathId
|
||||
? `${BASE_URL}/agent/${agentPathId}?wfv=chat`
|
||||
: `${BASE_URL}/ai`;
|
||||
const reqHeaders: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": USER_AGENT,
|
||||
@@ -446,8 +453,11 @@ export function resolveNotionAgentOptions(
|
||||
"agent_id",
|
||||
]) || "";
|
||||
const pageFromPs =
|
||||
readProviderSpecificString(ps, ["contextPageId", "context_page_id", "notionContextPageId"]) ||
|
||||
"";
|
||||
readProviderSpecificString(ps, [
|
||||
"contextPageId",
|
||||
"context_page_id",
|
||||
"notionContextPageId",
|
||||
]) || "";
|
||||
|
||||
const readCookie = (name: string): string => {
|
||||
const m = cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`, "i"));
|
||||
@@ -467,7 +477,10 @@ export function resolveNotionAgentOptions(
|
||||
readCookie("agent_id")
|
||||
);
|
||||
const contextPageId =
|
||||
pageFromPs || readCookie("context_page_id") || readCookie("notion_context_page_id") || "";
|
||||
pageFromPs ||
|
||||
readCookie("context_page_id") ||
|
||||
readCookie("notion_context_page_id") ||
|
||||
"";
|
||||
|
||||
return {
|
||||
workflowId: workflowId || undefined,
|
||||
@@ -497,7 +510,8 @@ async function sendNotionInferenceRequest(opts: {
|
||||
body: JSON.stringify(reqBody),
|
||||
signal: signal ?? undefined,
|
||||
// Inference can take a while (tool-autoload + LLM first token).
|
||||
timeoutMs: Number.parseInt(process.env.OMNIROUTE_NOTION_TLS_TIMEOUT_MS || "", 10) || 180_000,
|
||||
timeoutMs:
|
||||
Number.parseInt(process.env.OMNIROUTE_NOTION_TLS_TIMEOUT_MS || "", 10) || 180_000,
|
||||
});
|
||||
status = tlsRes.status;
|
||||
rawText = tlsRes.text ?? "";
|
||||
@@ -620,7 +634,8 @@ export class NotionWebExecutor extends BaseExecutor {
|
||||
const inboundHeaders =
|
||||
(input.clientHeaders as Record<string, string> | null | undefined) ??
|
||||
((input as { headers?: Record<string, string> }).headers as
|
||||
Record<string, string> | undefined);
|
||||
| Record<string, string>
|
||||
| undefined);
|
||||
const clientThreadId = readClientThreadId(requestBody, inboundHeaders ?? undefined);
|
||||
// Namespace the thread cache PER CALLER (hash of the caller's cookie) AND by custom
|
||||
// agent, so (a) two users of the same Notion space never share a cached thread
|
||||
@@ -723,10 +738,7 @@ export class NotionWebExecutor extends BaseExecutor {
|
||||
|
||||
// One automatic retry for transient Notion faults — same threadId, never create again
|
||||
if (isFailedAttempt(attempt) && attempt.retryable) {
|
||||
const delayMs =
|
||||
process.env.NODE_ENV === "test" || process.env.VITEST
|
||||
? 20
|
||||
: 700 + Math.floor(Math.random() * 400);
|
||||
const delayMs = process.env.NODE_ENV === "test" || process.env.VITEST ? 20 : 700 + Math.floor(Math.random() * 400);
|
||||
await new Promise((r) => setTimeout(r, delayMs));
|
||||
attempt = await runOnce({ createThread: false, threadId });
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ import {
|
||||
import { prepareToolMessages } from "../translator/webTools.ts";
|
||||
import { buildToolModeResponse } from "./chatgptWebTools.ts";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
import { buildSessionCookieHeader, mergeRefreshedCookie } from "../utils/nextAuthCookie.ts";
|
||||
import {
|
||||
buildSessionCookieHeader,
|
||||
mergeRefreshedCookie,
|
||||
} from "../utils/nextAuthCookie.ts";
|
||||
import {
|
||||
PPLX_SSE_ENDPOINT,
|
||||
PPLX_USER_AGENT,
|
||||
@@ -359,15 +362,7 @@ export class PerplexityWebExecutor extends BaseExecutor {
|
||||
super("perplexity-web", { id: "perplexity-web", baseUrl: PPLX_SSE_ENDPOINT });
|
||||
}
|
||||
|
||||
async execute({
|
||||
model,
|
||||
body,
|
||||
stream,
|
||||
credentials,
|
||||
signal,
|
||||
log,
|
||||
onCredentialsRefreshed,
|
||||
}: ExecuteInput) {
|
||||
async execute({ model, body, stream, credentials, signal, log, onCredentialsRefreshed }: ExecuteInput) {
|
||||
const bodyObj = (body || {}) as Record<string, unknown>;
|
||||
const rawMessages = bodyObj.messages as Array<Record<string, unknown>> | undefined;
|
||||
if (!rawMessages || !Array.isArray(rawMessages) || rawMessages.length === 0) {
|
||||
@@ -501,7 +496,7 @@ export class PerplexityWebExecutor extends BaseExecutor {
|
||||
if (isCloudflareChallenge(response.text)) {
|
||||
errMsg =
|
||||
"Cloudflare blocked the request — Perplexity's edge rejected this server's TLS fingerprint " +
|
||||
"(common on VPS/datacenter IPs). Verify the wreq-js 3.0.0 native addon, " +
|
||||
"(common on VPS/datacenter IPs). Ensure tls-client-node is installed with its native binary, " +
|
||||
"or route perplexity-web through a residential proxy.";
|
||||
log?.error?.("PPLX-WEB", "Cloudflare challenge detected — TLS bypass failed");
|
||||
} else {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* Regression tests for the proxy-leak fix in chatgptTlsClient.
|
||||
*
|
||||
* Bug context (#2022): tlsFetchChatGpt() built its native transport options
|
||||
* without a `proxyUrl` field, so every chatgpt-web call
|
||||
* Bug context (#2022): tlsFetchChatGpt() built its native tls-client-node
|
||||
* requestOptions without a `proxyUrl` field, so every chatgpt-web call
|
||||
* egressed with the bare host IP regardless of the dashboard proxy config
|
||||
* or HTTP_PROXY / HTTPS_PROXY env vars. Native browser transports require the
|
||||
* resolved proxy to be passed explicitly.
|
||||
* or HTTP_PROXY / HTTPS_PROXY env vars (the koffi-loaded Go binary does not
|
||||
* consult Go's `http.ProxyFromEnvironment`).
|
||||
*
|
||||
* These tests pin the resolution-order contract:
|
||||
* 1. Per-call `options.proxyUrl` wins.
|
||||
* 2. Request-scoped dashboard/account proxy context.
|
||||
* 2. OMNIROUTE_TLS_PROXY_URL env var (single-flag opt-in).
|
||||
* 3. POSIX-standard HTTPS_PROXY / HTTP_PROXY / ALL_PROXY (and lowercase variants).
|
||||
* 4. Otherwise undefined (no proxy).
|
||||
*
|
||||
@@ -24,6 +24,7 @@ import { describe, it, beforeEach, afterEach, expect } from "vitest";
|
||||
import { tlsFetchChatGpt, __setTlsFetchOverrideForTesting } from "../chatgptTlsClient.ts";
|
||||
|
||||
const PROXY_ENV_KEYS = [
|
||||
"OMNIROUTE_TLS_PROXY_URL",
|
||||
"HTTPS_PROXY",
|
||||
"https_proxy",
|
||||
"HTTP_PROXY",
|
||||
@@ -61,6 +62,7 @@ describe("chatgptTlsClient — proxy plumbing (#2022)", async () => {
|
||||
});
|
||||
|
||||
it("per-call proxyUrl overrides everything", async () => {
|
||||
process.env.OMNIROUTE_TLS_PROXY_URL = "http://env-omni:0/";
|
||||
process.env.HTTPS_PROXY = "http://env-https:0/";
|
||||
|
||||
let observedUrl: string | undefined;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* Regression tests for the proxy-leak fix in grokTlsClient.
|
||||
*
|
||||
* Bug context (#3180): tlsFetchGrok() built its native transport options
|
||||
* without a `proxyUrl` field, so every grok-web call
|
||||
* Bug context (#3180): tlsFetchGrok() built its native tls-client-node
|
||||
* requestOptions without a `proxyUrl` field, so every grok-web call
|
||||
* egressed with the bare host IP regardless of the dashboard proxy config
|
||||
* or HTTP_PROXY / HTTPS_PROXY env vars. Native browser transports require the
|
||||
* resolved proxy to be passed explicitly.
|
||||
* or HTTP_PROXY / HTTPS_PROXY env vars (the koffi-loaded Go binary does not
|
||||
* consult Go's `http.ProxyFromEnvironment`).
|
||||
*
|
||||
* These tests pin the resolution-order contract:
|
||||
* 1. Per-call `options.proxyUrl` wins.
|
||||
* 2. Request-scoped dashboard/account proxy context.
|
||||
* 2. OMNIROUTE_TLS_PROXY_URL env var (single-flag opt-in).
|
||||
* 3. POSIX-standard HTTPS_PROXY / HTTP_PROXY / ALL_PROXY (and lowercase variants).
|
||||
* 4. Otherwise undefined (no proxy).
|
||||
*
|
||||
@@ -24,6 +24,7 @@ import { describe, it, beforeEach, afterEach, expect } from "vitest";
|
||||
import { tlsFetchGrok, __setTlsFetchOverrideForTesting } from "../grokTlsClient.ts";
|
||||
|
||||
const PROXY_ENV_KEYS = [
|
||||
"OMNIROUTE_TLS_PROXY_URL",
|
||||
"HTTPS_PROXY",
|
||||
"https_proxy",
|
||||
"HTTP_PROXY",
|
||||
@@ -61,6 +62,7 @@ describe("grokTlsClient — proxy plumbing (#3180)", async () => {
|
||||
});
|
||||
|
||||
it("per-call proxyUrl overrides everything", async () => {
|
||||
process.env.OMNIROUTE_TLS_PROXY_URL = "http://env-omni:0/";
|
||||
process.env.HTTPS_PROXY = "http://env-https:0/";
|
||||
|
||||
let observedUrl: string | undefined;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Browser-TLS-impersonating HTTP client for chatgpt.com.
|
||||
*
|
||||
* Thin re-export over the shared `tlsClientBase.ts` factory
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (wreq-js transport
|
||||
* pooling, direct streaming, proxy resolution, deadlines, SSE detection) lives
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (sidecar lifecycle,
|
||||
* streaming tail-file, proxy resolution, error classes, SSE detection) lives
|
||||
* in the base module; this file supplies only ChatGPT-specific config and
|
||||
* preserves the original public export surface.
|
||||
*/
|
||||
@@ -24,9 +24,9 @@ const STREAM_FIRST_BYTE_TIMEOUT_MS =
|
||||
export const tlsClientModule = createTlsClientModule({
|
||||
providerName: "ChatGPT",
|
||||
tlsProfile: "firefox_148",
|
||||
emulationOs: "macos",
|
||||
domain: "https://chatgpt.com",
|
||||
streamEofPolicy: "include",
|
||||
tempDirPrefix: "cgpt-stream-",
|
||||
tailFileVariant: "A",
|
||||
responseValidation: "sse",
|
||||
exportCloudflareCheck: false,
|
||||
exposeStreamingForTesting: true,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Browser-TLS-impersonating HTTP client for claude.ai.
|
||||
*
|
||||
* Thin re-export over the shared `tlsClientBase.ts` factory
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (wreq-js transport
|
||||
* pooling, direct streaming, proxy resolution, deadlines, SSE detection) lives
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (sidecar lifecycle,
|
||||
* streaming tail-file, proxy resolution, error classes, SSE detection) lives
|
||||
* in the base module; this file supplies only Claude-specific config and
|
||||
* preserves the original public export surface.
|
||||
*/
|
||||
@@ -24,13 +24,13 @@ const HARD_TIMEOUT_GRACE_MS =
|
||||
export const tlsClientModule = createTlsClientModule({
|
||||
providerName: "Claude",
|
||||
tlsProfile: `chrome_${CLAUDE_TLS_BROWSER_MAJOR_VERSION}`,
|
||||
emulationOs: "linux",
|
||||
domain: "https://claude.ai",
|
||||
streamEofPolicy: "include",
|
||||
tempDirPrefix: "cgpt-stream-",
|
||||
tailFileVariant: "A",
|
||||
responseValidation: "sse",
|
||||
exportCloudflareCheck: false,
|
||||
exposeStreamingForTesting: true,
|
||||
// Claude allows the native/hard request deadline to bound a slow first SSE byte.
|
||||
// Claude waits indefinitely for the first SSE byte (original 2-arg waitForContent).
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
hardTimeoutGraceMs: HARD_TIMEOUT_GRACE_MS,
|
||||
firstByteTimeoutMs: Number.POSITIVE_INFINITY,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* 3. Waits for Turnstile challenge to appear
|
||||
* 4. Waits for challenge to be solved (with retry)
|
||||
* 5. Extracts cf_clearance cookie
|
||||
* 6. Returns a fresh cookie for the isolated wreq-js request
|
||||
* 6. Returns fresh cookie for tls-client-node
|
||||
*/
|
||||
|
||||
import type { Browser, Page } from "playwright";
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Browser-TLS-impersonating HTTP client for grok.com.
|
||||
*
|
||||
* Thin re-export over the shared `tlsClientBase.ts` factory
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (wreq-js transport
|
||||
* pooling, direct streaming, proxy resolution, deadlines, Cloudflare challenge
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (sidecar lifecycle,
|
||||
* streaming tail-file, proxy resolution, error classes, Cloudflare challenge
|
||||
* detection) lives in the base module; this file supplies only Grok-specific
|
||||
* config and preserves the original public export surface.
|
||||
*/
|
||||
@@ -22,9 +22,9 @@ const HARD_TIMEOUT_GRACE_MS =
|
||||
export const tlsClientModule = createTlsClientModule({
|
||||
providerName: "Grok",
|
||||
tlsProfile: "chrome_146",
|
||||
emulationOs: "linux",
|
||||
domain: "https://grok.com",
|
||||
streamEofPolicy: "exclude",
|
||||
tempDirPrefix: "grok-stream-",
|
||||
tailFileVariant: "B1",
|
||||
responseValidation: "cf",
|
||||
exportCloudflareCheck: true,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Browser-TLS-impersonating HTTP client for arena.ai.
|
||||
*
|
||||
* Thin re-export over the shared `tlsClientBase.ts` factory
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (wreq-js transport
|
||||
* pooling, direct streaming, proxy resolution, deadlines, Cloudflare challenge
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (sidecar lifecycle,
|
||||
* streaming tail-file, proxy resolution, error classes, Cloudflare challenge
|
||||
* detection) lives in the base module; this file supplies only LMArena-specific
|
||||
* config and preserves the original public export surface.
|
||||
*/
|
||||
@@ -20,12 +20,11 @@ const HARD_TIMEOUT_GRACE_MS = 10_000;
|
||||
export const tlsClientModule = createTlsClientModule({
|
||||
providerName: "LMArena",
|
||||
tlsProfile: "chrome_146",
|
||||
emulationOs: "windows",
|
||||
domain: "https://lmarena.ai",
|
||||
// LMArena's proxy resolution domain is hardcoded to arena.ai, not the config domain.
|
||||
proxyDomainOverride: "https://arena.ai",
|
||||
streamEofPolicy: "none",
|
||||
streamEofSymbol: "",
|
||||
tempDirPrefix: "LMArena-stream-",
|
||||
tailFileVariant: "B2",
|
||||
responseValidation: "cf",
|
||||
exportCloudflareCheck: true,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Browser-TLS-impersonating HTTP client for app.notion.com.
|
||||
*
|
||||
* Thin re-export over the shared `tlsClientBase.ts` factory
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (wreq-js transport
|
||||
* pooling, direct streaming, proxy resolution, deadlines, SSE detection,
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (sidecar lifecycle,
|
||||
* streaming tail-file, proxy resolution, error classes, SSE detection,
|
||||
* Cloudflare challenge detection) lives in the base module; this file supplies
|
||||
* only Notion-specific config and preserves the original public export surface.
|
||||
*/
|
||||
@@ -22,9 +22,9 @@ const HARD_TIMEOUT_GRACE_MS =
|
||||
export const tlsClientModule = createTlsClientModule({
|
||||
providerName: "Notion",
|
||||
tlsProfile: "chrome_146",
|
||||
emulationOs: "windows",
|
||||
domain: "https://app.notion.com",
|
||||
streamEofPolicy: "include",
|
||||
tempDirPrefix: "pplx-stream-",
|
||||
tailFileVariant: "A",
|
||||
responseValidation: "sse",
|
||||
exportCloudflareCheck: true,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Browser-TLS-impersonating HTTP client for www.perplexity.ai.
|
||||
*
|
||||
* Thin re-export over the shared `tlsClientBase.ts` factory
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (wreq-js transport
|
||||
* pooling, direct streaming, proxy resolution, deadlines, SSE detection,
|
||||
* (`createTlsClientModule`). All provider-agnostic logic (sidecar lifecycle,
|
||||
* streaming tail-file, proxy resolution, error classes, SSE detection,
|
||||
* Cloudflare challenge detection) lives in the base module; this file supplies
|
||||
* only Perplexity-specific config and preserves the original public export
|
||||
* surface.
|
||||
@@ -23,9 +23,9 @@ const HARD_TIMEOUT_GRACE_MS =
|
||||
export const tlsClientModule = createTlsClientModule({
|
||||
providerName: "Perplexity",
|
||||
tlsProfile: "firefox_148",
|
||||
emulationOs: "macos",
|
||||
domain: "https://www.perplexity.ai",
|
||||
streamEofPolicy: "include",
|
||||
tempDirPrefix: "pplx-stream-",
|
||||
tailFileVariant: "A",
|
||||
responseValidation: "sse",
|
||||
exportCloudflareCheck: true,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
23
open-sse/services/tlsClientDownloadDir.ts
Normal file
23
open-sse/services/tlsClientDownloadDir.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { join } from "node:path";
|
||||
import { resolveDataDir } from "@/lib/dataPaths";
|
||||
|
||||
/**
|
||||
* Writable cache directory for tls-client-node's native binary.
|
||||
*
|
||||
* Without an explicit `downloadDir`, the library defaults to its own package
|
||||
* `node_modules/tls-client-node/bin`, which is root-owned on global installs
|
||||
* and fails with EACCES for normal users (#8579).
|
||||
*/
|
||||
export function resolveTlsClientDownloadDir(): string {
|
||||
return join(resolveDataDir(), "tls-client", "bin");
|
||||
}
|
||||
|
||||
export function buildNativeTlsClientOptions(): {
|
||||
runtimeMode: "native";
|
||||
downloadDir: string;
|
||||
} {
|
||||
return {
|
||||
runtimeMode: "native",
|
||||
downloadDir: resolveTlsClientDownloadDir(),
|
||||
};
|
||||
}
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -162,7 +162,8 @@
|
||||
"keytar": "^7.9.0",
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"sqlite-vec": "^0.1.9",
|
||||
"wreq-js": "3.0.0"
|
||||
"tls-client-node": "^0.2.0",
|
||||
"wreq-js": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
@@ -25387,6 +25388,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/koffi": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz",
|
||||
"integrity": "sha512-0Ie6CfD026dNfWSosDw9dPxPzO9Rlyo0N8m5r05S8YjytIpuilzMFDMY4IDy/8xQsTwpuVinhncD+S8n3bcYZQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"url": "https://liberapay.com/Koromix"
|
||||
}
|
||||
},
|
||||
"node_modules/kuler": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
|
||||
@@ -25576,6 +25588,17 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/libxmljs2/node_modules/brace-expansion": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/libxmljs2/node_modules/cacache": {
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
|
||||
@@ -35618,7 +35641,7 @@
|
||||
"version": "7.0.27",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz",
|
||||
"integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.0.27"
|
||||
@@ -35631,9 +35654,28 @@
|
||||
"version": "7.0.27",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz",
|
||||
"integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tls-client-node": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tls-client-node/-/tls-client-node-0.2.0.tgz",
|
||||
"integrity": "sha512-0PHJgaGPvMK9ly7xohviOoe8Oxos43IOIdsEhibgku4ce/3/YLhxJTPPKNQZII0PdcOjlfPweB9eRs13mWaWIg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"koffi": "^2.8.9",
|
||||
"tough-cookie": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fatihkabakk"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -35686,7 +35728,7 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
|
||||
"integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^7.0.5"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"src/types/",
|
||||
".env.example",
|
||||
"scripts/build/postinstall.mjs",
|
||||
"scripts/build/fixTlsClientNodeBinary.mjs",
|
||||
"scripts/build/fixPlaywrightAndroid.mjs",
|
||||
"bin/cli/runtime/",
|
||||
"scripts/postinstall.mjs",
|
||||
@@ -37,8 +38,6 @@
|
||||
"scripts/build/backendOnlyPages.mjs",
|
||||
"scripts/build/build-tproxy-native.mjs",
|
||||
"scripts/build/native-binary-compat.mjs",
|
||||
"scripts/build/wreqJsNative.mjs",
|
||||
"config/release/wreq-js-native-manifest.json",
|
||||
"scripts/build/build-next-isolated.mjs",
|
||||
"scripts/build/runtime-env.mjs",
|
||||
"scripts/packs/optionalPackManifest.mjs",
|
||||
@@ -348,7 +347,8 @@
|
||||
"keytar": "^7.9.0",
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"sqlite-vec": "^0.1.9",
|
||||
"wreq-js": "3.0.0"
|
||||
"tls-client-node": "^0.2.0",
|
||||
"wreq-js": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.13.0",
|
||||
|
||||
@@ -12,10 +12,12 @@ allowBuilds:
|
||||
core-js: true
|
||||
esbuild: true
|
||||
keytar: true
|
||||
koffi: true
|
||||
libxmljs2: true
|
||||
onnxruntime-node: true
|
||||
protobufjs: true
|
||||
sharp: true
|
||||
tls-client-node: true
|
||||
unrs-resolver: true
|
||||
onlyBuiltDependencies:
|
||||
- "@parcel/watcher"
|
||||
@@ -24,9 +26,11 @@ onlyBuiltDependencies:
|
||||
- "core-js"
|
||||
- "esbuild"
|
||||
- "keytar"
|
||||
- "koffi"
|
||||
- "libxmljs2"
|
||||
- "onnxruntime-node"
|
||||
- "omniroute"
|
||||
- "protobufjs"
|
||||
- "sharp"
|
||||
- "tls-client-node"
|
||||
- "unrs-resolver"
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
"core-js",
|
||||
"esbuild",
|
||||
"keytar",
|
||||
"koffi",
|
||||
"libxmljs2",
|
||||
"omniroute",
|
||||
"onnxruntime-node",
|
||||
"protobufjs",
|
||||
"sharp",
|
||||
"tls-client-node",
|
||||
"unrs-resolver"
|
||||
]
|
||||
}
|
||||
|
||||
148
scripts/build/fixTlsClientNodeBinary.mjs
Normal file
148
scripts/build/fixTlsClientNodeBinary.mjs
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* tls-client-node postinstall repair (#7802).
|
||||
*
|
||||
* tls-client-node's own postinstall.js fetches a platform-specific native
|
||||
* binary (.so/.dylib/.dll) from the bogdanfinn/tls-client GitHub Releases
|
||||
* API. That script is blocked by `npm ci --ignore-scripts` (the Dockerfile
|
||||
* builder stage runs with scripts disabled for supply-chain hygiene) and,
|
||||
* even when it does run, silently no-ops on a rate-limited/failed GitHub API
|
||||
* call instead of raising — so `node_modules/tls-client-node/bin/` can end
|
||||
* up empty with no visible signal until the first live request throws
|
||||
* TlsClientUnavailableError (chatgpt-web/claude-web/grok-web/lmarena/
|
||||
* perplexity-web all share this transport).
|
||||
*
|
||||
* This module:
|
||||
* 1. Copies an already-fetched root `bin/` into the standalone
|
||||
* `dist/node_modules/tls-client-node/bin/` bundle (same pattern as
|
||||
* fixWreqJsBinary), so the published npm package works even though its
|
||||
* own `files` allowlist never ships the binary.
|
||||
* 2. When the root `bin/` is empty (--ignore-scripts blocked it, or a
|
||||
* transient GitHub rate-limit ate the first attempt), retries the
|
||||
* module's own postinstall.js with exponential backoff instead of
|
||||
* giving up on the first failure.
|
||||
*
|
||||
* Best-effort throughout: a failure here never throws out of postinstall.mjs
|
||||
* — it only warns, matching the other fix*Binary() steps. The runtime layer
|
||||
* (perplexityTlsClient.ts and its 4 siblings) already surfaces a clear
|
||||
* TlsClientUnavailableError pointing at the missing binary, so an operator
|
||||
* who hits a still-empty bin/ after this repair gets an actionable message
|
||||
* rather than an opaque crash.
|
||||
*/
|
||||
|
||||
import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const DEFAULT_RETRY_DELAYS_MS = [1_000, 3_000, 8_000];
|
||||
|
||||
function hasAnyFile(dir) {
|
||||
if (!existsSync(dir)) return false;
|
||||
try {
|
||||
return readdirSync(dir).length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function copyBinDir(sourceDir, destDir) {
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
for (const file of readdirSync(sourceDir)) {
|
||||
copyFileSync(join(sourceDir, file), join(destDir, file));
|
||||
}
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-run tls-client-node's own postinstall.js in-process, retrying with
|
||||
* backoff when the attempt leaves `bin/` empty (covers transient GitHub API
|
||||
* rate-limiting — the upstream script itself never throws on failure, it
|
||||
* only warns, so "still empty after running it" is the only failure signal
|
||||
* available).
|
||||
*/
|
||||
async function downloadWithRetry(rootTlsClientDir, retryDelaysMs, log) {
|
||||
const postinstallScript = join(rootTlsClientDir, "scripts", "postinstall.js");
|
||||
const binDir = join(rootTlsClientDir, "bin");
|
||||
if (!existsSync(postinstallScript)) return false;
|
||||
|
||||
for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
|
||||
if (attempt > 0) {
|
||||
log(
|
||||
` ⏳ tls-client-node native binary still missing — retrying download ` +
|
||||
`(attempt ${attempt + 1}/${retryDelaysMs.length + 1}) after rate-limit/backoff...`
|
||||
);
|
||||
await sleep(retryDelaysMs[attempt - 1]);
|
||||
}
|
||||
|
||||
try {
|
||||
const { execFileSync } = await import("node:child_process");
|
||||
execFileSync(process.execPath, [postinstallScript], {
|
||||
cwd: rootTlsClientDir,
|
||||
stdio: "pipe",
|
||||
timeout: 30_000,
|
||||
});
|
||||
} catch (err) {
|
||||
log(` ⚠️ tls-client-node postinstall attempt failed: ${err.message.split("\n")[0]}`);
|
||||
}
|
||||
|
||||
if (hasAnyFile(binDir)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} opts
|
||||
* @param {string} opts.rootDir - repo root
|
||||
* @param {(msg: string) => void} [opts.log]
|
||||
* @param {number[]} [opts.retryDelaysMs] - override for tests (avoid real sleeps)
|
||||
*/
|
||||
export async function fixTlsClientNodeBinary({
|
||||
rootDir,
|
||||
log = (m) => console.log(m),
|
||||
retryDelaysMs = DEFAULT_RETRY_DELAYS_MS,
|
||||
} = {}) {
|
||||
const rootTlsClientDir = join(rootDir, "node_modules", "tls-client-node");
|
||||
const rootBinDir = join(rootTlsClientDir, "bin");
|
||||
const distTlsClientDir = join(rootDir, "dist", "node_modules", "tls-client-node");
|
||||
|
||||
if (!existsSync(rootTlsClientDir)) return;
|
||||
|
||||
if (!hasAnyFile(rootBinDir)) {
|
||||
log(
|
||||
"\n 🔧 tls-client-node native binary missing (blocked by --ignore-scripts or a " +
|
||||
"failed fetch) — attempting repair...\n"
|
||||
);
|
||||
const recovered = await downloadWithRetry(rootTlsClientDir, retryDelaysMs, log);
|
||||
if (!recovered) {
|
||||
console.warn(
|
||||
"\n ⚠️ Could not fetch tls-client-node's native binary " +
|
||||
"(GitHub API rate-limited or unreachable after retries)."
|
||||
);
|
||||
console.warn(
|
||||
" chatgpt-web/claude-web/grok-web/lmarena/perplexity-web will raise a clear " +
|
||||
"TlsClientUnavailableError on first use until this is resolved."
|
||||
);
|
||||
console.warn(
|
||||
` Manual fix: node ${join(rootTlsClientDir, "scripts", "postinstall.js")}\n`
|
||||
);
|
||||
return;
|
||||
}
|
||||
log(" ✅ tls-client-node native binary fetched successfully!\n");
|
||||
}
|
||||
|
||||
if (!existsSync(distTlsClientDir) || !hasAnyFile(rootBinDir)) return;
|
||||
|
||||
const distBinDir = join(distTlsClientDir, "bin");
|
||||
if (hasAnyFile(distBinDir)) return;
|
||||
|
||||
try {
|
||||
copyBinDir(rootBinDir, distBinDir);
|
||||
log(" ✅ tls-client-node native binary copied to standalone dist/node_modules.\n");
|
||||
} catch (err) {
|
||||
console.warn(` ⚠️ Could not copy tls-client-node binary into dist/: ${err.message}`);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@
|
||||
* matrix leg. Everything except install-machine-forked optional packages is
|
||||
* platform-independent:
|
||||
*
|
||||
* - Bundled-for-all (verify only): better-sqlite3 v13 ships Node-API prebuilds
|
||||
* for 8 platforms, wreq-js ships
|
||||
* `rust/wreq-js.<plat>-<arch>[-libc].node`, and
|
||||
* - Bundled-for-all (verify only): koffi ships every triplet under
|
||||
* `build/koffi/<os>_<arch>`, better-sqlite3 v13 ships Node-API prebuilds for
|
||||
* 8 platforms, wreq-js ships `rust/wreq-js.<plat>-<arch>[-libc].node`, and
|
||||
* onnxruntime-node ships `bin/napi-v6/<os>/<arch>`.
|
||||
* - Install-machine-forked (hydrate): `@img/sharp-*`, `@img/sharp-libvips-*`,
|
||||
* `@ngrok/ngrok-*` and macOS-only `fsevents` resolve to whichever platform
|
||||
@@ -33,7 +33,8 @@ export const HYDRATED_ROOT_PACKAGES = ["fsevents"];
|
||||
export const BUNDLED_EXEMPTIONS = new Set(["onnxruntime-node:darwin-x64"]);
|
||||
|
||||
function platformTriple(platform, arch) {
|
||||
return { dash: `${platform}-${arch}` };
|
||||
// koffi uses underscore triplets; better-sqlite3/wreq-js/onnx use dashes.
|
||||
return { koffi: `${platform}_${arch}`, dash: `${platform}-${arch}` };
|
||||
}
|
||||
|
||||
function rmrf(target) {
|
||||
@@ -105,6 +106,9 @@ export function verifyBundledNatives({ nodeModulesDir, platform, arch }) {
|
||||
const errors = [];
|
||||
const triple = platformTriple(platform, arch);
|
||||
|
||||
const koffiDir = path.join(nodeModulesDir, "koffi", "build", "koffi", triple.koffi);
|
||||
if (!fs.existsSync(koffiDir)) errors.push(`koffi: missing bundled triplet ${triple.koffi}`);
|
||||
|
||||
const sqlitePrebuild = path.join(
|
||||
nodeModulesDir,
|
||||
"better-sqlite3",
|
||||
|
||||
@@ -94,7 +94,6 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"THIRD_PARTY_NOTICES.md",
|
||||
"config/release/wreq-js-native-manifest.json",
|
||||
"bin/aliasResolver.mjs",
|
||||
"bin/chatgpt-web-codex-mcp.mjs",
|
||||
// #7808: ESM loader hook split out of bin/aliasResolver.mjs to silence CodeQL
|
||||
@@ -137,10 +136,12 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
|
||||
"scripts/build/build-next-isolated.mjs",
|
||||
"scripts/check/check-supported-node-runtime.ts",
|
||||
"scripts/build/native-binary-compat.mjs",
|
||||
"scripts/build/wreqJsNative.mjs",
|
||||
"scripts/build/postinstall.mjs",
|
||||
"scripts/build/postinstallSupport.mjs",
|
||||
"scripts/build/colocateOptionals.mjs",
|
||||
// #7802: imported by scripts/build/postinstall.mjs to repair tls-client-node's
|
||||
// native binary (chatgpt-web/claude-web/grok-web/lmarena/perplexity-web transport).
|
||||
"scripts/build/fixTlsClientNodeBinary.mjs",
|
||||
// #8859: imported by scripts/build/postinstall.mjs to repair playwright-core's
|
||||
// browser resolution on Termux/Android (no glibc, no bundled browsers).
|
||||
"scripts/build/fixPlaywrightAndroid.mjs",
|
||||
@@ -219,14 +220,13 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [
|
||||
// or the CLI fails to boot — list them REQUIRED so a regression is loud.
|
||||
"bin/aliasResolver.mjs",
|
||||
"bin/aliasResolverHook.mjs",
|
||||
"config/release/wreq-js-native-manifest.json",
|
||||
"package.json",
|
||||
"scripts/build/native-binary-compat.mjs",
|
||||
"scripts/build/postinstall.mjs",
|
||||
"scripts/build/postinstallSupport.mjs",
|
||||
"scripts/build/colocateOptionals.mjs",
|
||||
"scripts/build/fixTlsClientNodeBinary.mjs",
|
||||
"scripts/build/runtime-env.mjs",
|
||||
"scripts/build/wreqJsNative.mjs",
|
||||
// #10382: runtime imports of bin/cli/commands/packs.mjs (optional packs CLI) —
|
||||
// listed REQUIRED so their absence from the tarball fails loudly.
|
||||
"scripts/packs/optionalPackInstaller.mjs",
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
*
|
||||
* Modules repaired:
|
||||
* - better-sqlite3 (SQLite bindings)
|
||||
* - wreq-js (TLS client for OAuth and web-cookie providers)
|
||||
* - wreq-js (TLS client for OAuth providers)
|
||||
* - tls-client-node (TLS client for chatgpt-web/claude-web/grok-web/lmarena/perplexity-web)
|
||||
* - sql.js (WASM SQLite fallback runtime)
|
||||
* - node-machine-id (local CLI machine-token server runtime)
|
||||
*
|
||||
@@ -25,7 +26,15 @@
|
||||
* Fixes: https://github.com/diegosouzapw/OmniRoute/issues/7802
|
||||
*/
|
||||
|
||||
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
copyFileSync,
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -33,8 +42,8 @@ import { fileURLToPath } from "node:url";
|
||||
import { PUBLISHED_BUILD_ARCH, PUBLISHED_BUILD_PLATFORM } from "./native-binary-compat.mjs";
|
||||
import { hasStandaloneAppBundle, isTermux } from "./postinstallSupport.mjs";
|
||||
import { colocateLlmlinguaOptionals } from "./colocateOptionals.mjs";
|
||||
import { fixTlsClientNodeBinary } from "./fixTlsClientNodeBinary.mjs";
|
||||
import { fixPlaywrightAndroid } from "./fixPlaywrightAndroid.mjs";
|
||||
import { resolveWreqJsNativeBinaryName } from "./wreqJsNative.mjs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -262,7 +271,7 @@ async function fixWreqJsBinary() {
|
||||
if (process.platform === "android" || isTermux()) {
|
||||
console.log(
|
||||
" [postinstall] wreq-js: skipped on Termux/Android " +
|
||||
"(wreq-js 3.0.0 does not publish an Android native addon)"
|
||||
"(libgcc not available — OAuth TLS fingerprinting will use the fallback path)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -274,16 +283,7 @@ async function fixWreqJsBinary() {
|
||||
return;
|
||||
}
|
||||
|
||||
const binaryName = resolveWreqJsNativeBinaryName({
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
});
|
||||
if (!binaryName) {
|
||||
console.warn(
|
||||
` ⚠️ wreq-js 3.0.0 has no native addon for ${process.platform}-${process.arch}.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const binaryName = `wreq-js.${process.platform}-${process.arch}.node`;
|
||||
const appBinaryPath = join(appWreqDir, binaryName);
|
||||
const rootBinaryPath = join(rootWreqDir, binaryName);
|
||||
|
||||
@@ -312,7 +312,27 @@ async function fixWreqJsBinary() {
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Rebuild wreq-js inside dist/
|
||||
// Strategy 2: Copy entire rust/ directory from root (gets all platform binaries)
|
||||
if (existsSync(rootWreqDir)) {
|
||||
try {
|
||||
mkdirSync(appWreqDir, { recursive: true });
|
||||
const files = readdirSync(rootWreqDir);
|
||||
for (const file of files) {
|
||||
if (file.endsWith(".node")) {
|
||||
copyFileSync(join(rootWreqDir, file), join(appWreqDir, file));
|
||||
}
|
||||
}
|
||||
if (existsSync(appBinaryPath)) {
|
||||
process.dlopen({ exports: {} }, appBinaryPath);
|
||||
console.log(" ✅ wreq-js native module fixed (full copy) successfully!\n");
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(` ⚠️ wreq-js full copy failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Rebuild wreq-js inside dist/
|
||||
console.log(" 📥 Attempting npm rebuild wreq-js...");
|
||||
try {
|
||||
const { execSync } = await import("node:child_process");
|
||||
@@ -333,10 +353,8 @@ async function fixWreqJsBinary() {
|
||||
console.warn(
|
||||
`\n ⚠️ Could not fix wreq-js native module for ${process.platform}-${process.arch}.`
|
||||
);
|
||||
console.warn(" Browser-TLS OAuth and web-cookie providers may not work.");
|
||||
console.warn(
|
||||
` Manual fix: cd ${join(ROOT, "dist")} && npm install wreq-js@3.0.0 --save-exact\n`
|
||||
);
|
||||
console.warn(" OAuth-based providers (Codex, Cursor, etc.) may not work.");
|
||||
console.warn(` Manual fix: cd ${join(ROOT, "dist")} && npm install wreq-js --no-save\n`);
|
||||
}
|
||||
|
||||
async function ensureSwcHelpers() {
|
||||
@@ -446,6 +464,7 @@ async function ensureStandaloneRuntimePackages() {
|
||||
await verifyDevNativeModules();
|
||||
await fixBetterSqliteBinary();
|
||||
await fixWreqJsBinary();
|
||||
await fixTlsClientNodeBinary({ rootDir: ROOT });
|
||||
await fixPlaywrightAndroid({ rootDir: ROOT });
|
||||
await ensureSwcHelpers();
|
||||
await ensureStandaloneRuntimePackages();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user