Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
537f2b3a6b build(deps): bump github/codeql-action/init from 4.37.8 to 4.37.9
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.8 to 4.37.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](db488ddef3...cdf488f595)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 18:56:35 +00:00
329 changed files with 1422 additions and 15642 deletions

View File

@@ -2398,9 +2398,6 @@ APP_LOG_TO_FILE=true
# Bundled Codeium/language-server extension_version, distinct from Desktop.
# Must use x.y.z format; invalid/unset values use the bundled default 1.48.2.
# DEVIN_DESKTOP_EXTENSION_VERSION=1.48.2
# Optional override for the Codeium seat-management API used by Devin CLI quota.
# Used by: open-sse/services/usage/devinCli.ts. Default: https://server.codeium.com
# DEVIN_SEAT_API_URL=https://server.codeium.com
# ── Command Code (custom CLI) callback ──
# Local port used for OAuth-style callbacks from the Command Code CLI helper.

View File

@@ -1,45 +1,9 @@
name: npm ci with retry
description: >-
Install dependencies. Restores node_modules from the Actions cache when the exact
lockfile / runner / Node version / postinstall inputs match; otherwise runs npm ci
with retries for transient registry/network failures and saves the tree for the
next run.
inputs:
cache:
description: Set to "false" to skip the node_modules cache and always run npm ci.
required: false
default: "true"
description: Run npm ci with retries for transient registry/network failures.
runs:
using: composite
steps:
- name: Resolve Node version for the cache key
id: node
shell: bash
run: echo "version=$(node --version)" >> "$GITHUB_OUTPUT"
# #8084 D3 (plan 3.8.51 task 5): every job used to pay ~80-90 s of `npm ci` even
# with setup-node's npm tarball cache warm — 36 jobs per ci.yml run, ~55 min of
# runner time per run just installing. A node_modules cache keyed on EVERYTHING
# that shapes the tree lets a hit skip the install entirely.
#
# No restore-keys on purpose (same rule as the ESLint cache, #11600): a partial
# tree from another lockfile / Node / postinstall script is exactly the kind of
# silent drift a lockfile-pinned CI must never inherit. Exact key or a full npm ci.
#
# postinstall (scripts/build/postinstall.mjs + helpers) only mutates node_modules
# on a plain install — its dist/ branch is gated on dist/ existing, which never
# holds at install time in CI — so the cached tree already carries its effects.
- name: Restore node_modules
id: node-modules
if: inputs.cache == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-${{ steps.node.outputs.version }}-${{ hashFiles('package-lock.json', '.npmrc', 'scripts/build/postinstall.mjs', 'scripts/build/postinstallSupport.mjs', 'scripts/build/colocateOptionals.mjs', 'scripts/build/fixTlsClientNodeBinary.mjs', 'scripts/build/fixPlaywrightAndroid.mjs', 'scripts/build/native-binary-compat.mjs') }}
- name: npm ci (with retry)
if: steps.node-modules.outputs.cache-hit != 'true'
shell: bash
- shell: bash
run: |
set -euo pipefail
@@ -51,8 +15,7 @@ runs:
echo "npm ci attempt $attempt/$max_attempts after transient failure"
fi
# --no-audit: `audit:deps` is its own gate; the inline audit only adds latency.
if npm ci --no-audit --no-fund; then
if npm ci; then
exit 0
fi
@@ -64,8 +27,3 @@ runs:
sleep "$delay_seconds"
delay_seconds=$((delay_seconds * 2))
done
- name: node_modules restored from cache
if: steps.node-modules.outputs.cache-hit == 'true'
shell: bash
run: echo "node_modules restored from cache (key hit) — npm ci skipped"

View File

@@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
- uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
languages: javascript-typescript
queries: security-extended

View File

@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
## Project at a Glance
**OmniRoute** — unified AI proxy/router. One endpoint, 353 LLM providers, auto-fallback.
**OmniRoute** — unified AI proxy/router. One endpoint, 352 LLM providers, auto-fallback.
| Layer | Location | Purpose |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

View File

@@ -177,13 +177,6 @@ npm run test:all
# Single test file (Node.js native test runner — most tests use this)
node --import tsx/esm --test tests/unit/your-file.test.ts
# Only the unit tests impacted by your change (same TIA selector as the CI gate, #8084)
npm run test:scoped # changes in the last commit (or the working tree)
npm run test:scoped:staged # staged changes only — pairs well with a pre-commit run
npm run test:scoped:full # rebuild the import-graph map first (after adding/moving files)
# Exit 1 + "run the full suite" means a hub file (tsconfig, package.json, …) or an
# unmapped source changed — the selector fails safe, it never silently skips.
# Vitest (MCP server, autoCombo, cache)
npm run test:vitest

View File

@@ -7,7 +7,7 @@
# 🚀 OmniRoute — The Free AI Gateway
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 353 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 353 AI providers · 150+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
<img src="./docs/diagrams/readme-hero.svg" width="100%" alt="OmniRoute — Never stop coding. Every AI tool → 352 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 1595% tokens (~89% avg) — never hit limits. 352 AI providers · 150+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
</div>
@@ -210,7 +210,7 @@ curl http://localhost:20128/v1/chat/completions \
</div>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 353 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 353 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 53 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<img src="./docs/diagrams/promise-pillars.svg" width="100%" alt="The Promise — One endpoint and 352 providers. Automatic fallback keeps routing while another healthy target is available. Six pillars: resilient fallback across 352 providers · up to 95% token savings on eligible workloads · $0 to start with 150+ free tiers and 53 recurring/keyless free-forever providers · 36 CLI/agent integrations through one config · OpenAI, Claude, Gemini and Responses API compatibility at /v1 · production controls including circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals and 39,000+ static test declarations across 5,100+ tracked test files."/>
<br/>
<br/>
@@ -463,7 +463,7 @@ All **19** strategies — mix & match per combo step:
</div>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 353 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="What sets OmniRoute apart — a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 352 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>

View File

@@ -1 +0,0 @@
- **feat(ui):** enable React Compiler (`reactCompiler: true` + `babel-plugin-react-compiler`) for automatic memoization at build time ([#11783](https://github.com/diegosouzapw/OmniRoute/pull/11783)) — thanks @jonlwheat2-gif

View File

@@ -1 +0,0 @@
- **feat(sse):** treat `max` as a first-class reasoning-effort tier and clamp per model family (GLM 5.1+/DeepSeek V4+/Kimi K3+ keep native `max`; o1/MiniMax/Grok/Muse Spark clamp to their upstream ceiling) ([#11875](https://github.com/diegosouzapw/OmniRoute/pull/11875)) — thanks @Chewji9875

View File

@@ -1 +0,0 @@
- Add a runtime feature flag to disable universal context handoffs globally without changing the default behavior.

View File

@@ -1 +0,0 @@
- **feat(providers):** the provider plugin manifest now also advertises a `usage-supported` capability for the 46 providers whose usage API is accepted by the server and Dashboard routes, so integrators can distinguish "the server will serve quota for this provider" from "a fetcher is wired" without reading TypeScript. Discovery only — no fetcher or quota change. `usage-fetch` resolves on id or alias (the usage dispatcher accepts both); `usage-supported` resolves on id alone, matching the runtime guard `USAGE_SUPPORTED_PROVIDERS.includes(providerId)`. `USAGE_SUPPORTED_PROVIDERS` moved to a zero-dependency leaf (`open-sse/services/usage/supportedProviders.ts`) and is re-exported from `providers.ts`, mirroring the `fetcherProviders` leaf from #11903 and keeping the manifest a light module. ([#12214](https://github.com/diegosouzapw/OmniRoute/pull/12214)) — thanks @maxmad64bis

View File

@@ -1 +0,0 @@
- **feat(usage):** Devin CLI agentic quota (Codeium seat-management GetUserStatus) and OpenRouter key limits plus account credits now surface in Provider Limits ([#12256](https://github.com/diegosouzapw/OmniRoute/pull/12256) — thanks @Neuron-Mr-White)

View File

@@ -1 +0,0 @@
- **feat(radar):** explain Community, single-use, contributor, supporter, recovery, abuse, offers, and privacy rules before either Radar activation action, and remove the superseded fixed-PR grant promise from every UI locale ([#12342](https://github.com/diegosouzapw/OmniRoute/pull/12342))

View File

@@ -1,6 +0,0 @@
- **feat(providers):** add MaxAI as a signed, OpenAI-compatible provider serving its 13 paid chat models (GPT-5.6 / Luna / Thinking, Claude 5 Sonnet, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite, Grok 4.1-fast / 4.5, DeepSeek V3.2 / R1, Llama 3.3 70B) through OmniRoute's `/v1` endpoint, with per-request HMAC-SHA1→SM3→AES request signing, live model + context-window discovery from `/models/get_config`, and prompted tool-calling translated to OpenAI `tool_calls`
- **feat(providers):** MaxAI vision input — image_url content parts are forwarded inline in `message_content` to the 6 vision-capable models (GPT-5.6 / Luna / Thinking, Claude Haiku 4.5, Gemini 3.1 Pro / Flash-Lite)
- **feat(providers):** MaxAI image generation — 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro, sd3-medium) exposed through `POST /v1/images/generations`
- **feat(providers):** MaxAI document RAG — inline base64 file/document attachments are uploaded to MaxAI (content-addressed `doc_id`) and attached to the chat via `doc_list`
- **feat(providers):** browserless MaxAI onboarding — email device-pair login (`/api/providers/[id]/login`) and signed access-token refresh, so a connection can be created and kept fresh without a real browser or Google OAuth
- **feat(providers):** per-provider TLS impersonation profile (MaxAI presents a Windows Firefox-150 client fingerprint) so its bot-sensitive endpoints accept OmniRoute traffic

View File

@@ -1,5 +0,0 @@
- **feat(dashboard):** the `/dashboard/orchestration` snapshot hook now subscribes to the
`agents` WebSocket channel (`agent.task.updated`) instead of `requests` as its refetch
trigger, and relaxes its background poll from 5s to 30s while that WS connection is up —
falling back to the tighter 5s cadence, reprogrammed live on any connect/disconnect
transition, whenever the socket is down.

View File

@@ -1,11 +0,0 @@
- **feat(dashboard):** Orchestration canvas quick wins — search box plus state/source/provider
filter chips with a one-click clear, and per-source collapse/expand, all reflected in the URL
so a filtered/collapsed view is shareable and survives a refresh; the detail drawer gained a
"copy trace JSON" action and hardened error/empty-state and accessibility handling; the
Agents-tab edges now animate traveling particles along active (running) connections; and the
canvas node/edge status colors moved off fixed hex values onto theme-aware `--orch-status-*`
CSS custom properties, so they adapt correctly to light/dark mode.
- **chore(dashboard):** Orchestration UI hardening pass and the missing component/model test
coverage it called for — `OrchestratorNode`/`ActivityNode`/`OverflowNode` rendering, the
`?node=`/overflow-click page routing, the Agents-tab orchestrator-click no-op and
`showCompleted` toggle, and the overview kanban's done-column sort order (#12270, #12271).

View File

@@ -1,3 +0,0 @@
- Fixed the v3.8.50 Costs and Analytics dashboards so flat-rate Claude Code usage can be shown as an explicitly requested token-price estimate without changing default billed-cost semantics.
- Fixed archived usage retention so each request is priced individually instead of pricing a day's summed tokens once, which understated archived cost whenever a day mixed cache-heavy and ordinary requests.
- Fixed the Costs dashboard so it discloses when displayed figures include flat-rate token-price estimates instead of labelling them as billed spend, using the flag the analytics API already returns; the month-end projection and the CSV/JSON exports carry the same marker, and billed-cost mode is unchanged.

View File

@@ -1 +0,0 @@
- **perf(compression):** OOM mitigations for large payload hashing, memoization, and token estimation ([#11844](https://github.com/diegosouzapw/OmniRoute/pull/11844) — thanks @AndrianBalanescu)

View File

@@ -1,5 +0,0 @@
- **fix(memory):** self-hosted embedding endpoints now vectorize — the vector width is
measured from the first embedding that comes back instead of being read from a registry
that cannot describe them, so `vec_memories` is created and memories stop piling up
unvectorized behind a green health check
([#12180](https://github.com/diegosouzapw/OmniRoute/pull/12180)) — thanks @kanade-hoshino

View File

@@ -1 +0,0 @@
- **fix(memory):** Embedding Model Quick select, Embedding Source remote dropdown, and Rerank selector now list every configured provider with embedding/rerank support instead of only chat-catalog text matches plus OpenRouter live discovery; a generic OpenAI-compatible `/embeddings` + Cohere-compatible `/rerank` runtime fallback resolves any configured chat provider's embedding/rerank endpoint, so unlisted providers no longer fail with "Unknown embedding provider"; both memory selectors gained a free-text model override

View File

@@ -3,7 +3,6 @@
"_justifications": {
"@testing-library/dom": "Peer dep obrigatoria de @testing-library/react v16 (adicionada no PR #11224); Refs #9985.",
"@testing-library/user-event": "Utilitario oficial do ecossistema testing-library para testes de UI (adicionada no PR #11224); Refs #9985.",
"babel-plugin-react-compiler": "Official React Compiler Babel plugin (facebook/react, MIT). Required peer of Next.js 16 `reactCompiler: true`; Next declares it optional (`*`) and does not auto-install. Added by PR #11783 / issue #67.",
"eslint-plugin-react-hooks": "React Hooks lint rules (set-state-in-effect, immutability, refs, purity) pinned at 7.0.1 by the release/v3.8.51 cycle; the 224 findings it raised are tracked in #11924. Refs #11924."
},
"allowed": [
@@ -45,7 +44,6 @@
"ajv",
"ajv-formats",
"axios",
"babel-plugin-react-compiler",
"bcryptjs",
"better-sqlite3",
"bottleneck",

View File

@@ -3373,7 +3373,7 @@
},
"tests/unit/combo-routing-engine.test.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 268
"count": 267
}
},
"tests/unit/combo-same-provider-cascade.test.ts": {

View File

@@ -1,6 +1,4 @@
{
"_rebaseline_2026_09_02_11461_maxai_tls_profile": "PR #11461 (arminanton, feat/maxai-provider) own growth, three files at existing per-provider chokepoints: open-sse/utils/proxyFetch.ts 1241->1261 (+20, the TLS_PROVIDER_PROFILE map giving MaxAI a Windows/firefox_150 impersonation profile instead of the tlsClient chrome_124/macos default); open-sse/handlers/imageGeneration.ts 3231->3243 (+12, the maxai-image format branch); src/app/api/providers/[id]/models/route.ts 2381->2429 (+48, live model listing via maxaiModels). Additive data, same no-split rationale as _rebaseline_2026_08_20_10531_freebuff_provider.",
"_rebaseline_2026_09_02_11460_flat_rate_estimates": "PR #11460 (xiaoyaner0201, fix/11459-cc-cost-estimates) own growth: src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx 1283->1319 (+36) — the flat-rate estimate labelling and the includeFlatRateEstimates opt-in on the Costs dashboard. #11460 merged first so this ratchet re-tightening measures the real post-merge LOC; the cap still drops 2002->1319 (-683) versus the 2026-08-10 +30% loosening this PR reverses. Same own-growth rationale as _rebaseline_2026_08_20_10531_freebuff_provider.",
"_rebaseline_2026_08_31_chatgpt_web_v4_vendor": "Pinned MIT vendor refresh from codex-chatgpt-web 0.1.16 to v4.0.6 (commit 09877fa21ffdbf20979623ef501046fc02a750d7). browser-worker.ts is preserved as the reviewed upstream browser protocol implementation; splitting the vendored file would destroy source parity and make future security/liveness updates unauditable. OmniRoute-specific DATA_DIR, Docker CDP, credential-marker, and XML decoding adaptations are covered by the ChatGPT Web Codex focused suite.",
"_rebaseline_2026_08_20_10531_freebuff_provider": "PR #10531 (adrianaryaputra, feat/freebuff-provider-support, closes #6793) own growth: src/shared/constants/providers/apikey/gateways.ts 1283->1298 (+15, the freebuff APIKEY_PROVIDERS_GATEWAYS catalog entry, additive data at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines) and src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx 1062->1067 (+5, freebuff credential placeholder/hint at the existing per-provider switch chokepoint). Covered by tests/unit/freebuff-provider.test.ts (9/9 passing).",
"_rebaseline_2026_08_31_12212_openapi_generated": "PR #12212 (docs audit follow-up nº 3): src/app/docs/lib/openapi.generated.ts 171->1347 — the module is emitted by scripts/docs/gen-openapi-module.mjs from docs/openapi.yaml, and the spec now documents all 692 implemented routes (was 276), so the generated output grew with the spec. Frozen at the generator output size; shrink by slimming the spec, never by hand-editing the generated module. Covered by tests/unit/openapi-security-tiers.test.ts (6/6) and the check:api-docs-refs gate (692/692 paths with a real route).",
@@ -198,33 +196,43 @@
"_rebaseline_2026_08_24_video_bridge_fu01_fu03_fu04_result_cache_tests": "PRs #11362 (FU-01 cache hardening) + #11382 (FU-03 visual dedup policy identity) + #11383 (FU-04 focused analysis mode) own test growth: videoBridgeResultCache.test.ts <1000->1040, +40 (sum of three stacked PRs boarded together in the same merge-batch, each adding its own cache-identity assertions on the shared result-cache seam). Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
"_rebaseline_basered_codebuddy_cn": "Base-red fix (#4664 CodeBuddy CN): oauth-providers-config.test.ts 867->870 (+3) to align the EXPECTED provider list/config with the codebuddy-cn provider that #4664 added to the registry without updating this test (it asserts 'exactly once').",
"_rebaseline_pr4613_compatible_provider_groups": "Reconcile #4613 already-merged growth: providers-page-utils.test.ts 1004->1052 (+48, buildCompatibleProviderGroups partition unit test). Fast-gate PR->release does not run check:file-size, so this surfaced post-merge.",
"tests/integration/chat-pipeline.test.ts": 1644,
"tests/unit/account-fallback-service.test.ts": 2008,
"tests/unit/batch_api.test.ts": 1345,
"tests/unit/cc-compatible-provider.test.ts": 1225,
"tests/unit/chatcore-translation-paths.test.ts": 3447,
"tests/unit/chatgpt-web.test.ts": 4911,
"tests/unit/combo-routing-engine.test.ts": 3625,
"tests/unit/db-migration-runner.test.ts": 1509,
"tests/unit/executor-codex.test.ts": 1465,
"tests/unit/executor-default-base.test.ts": 1632,
"tests/unit/grok-web.test.ts": 2437,
"tests/unit/image-generation-handler.test.ts": 2110,
"tests/unit/models-catalog-route.test.ts": 1652,
"tests/unit/perplexity-web.test.ts": 1384,
"tests/unit/provider-models-route.test.ts": 1783,
"tests/unit/provider-validation-specialty.test.ts": 2912,
"tests/unit/reasoning-cache.test.ts": 1291,
"tests/unit/route-edge-coverage.test.ts": 1244,
"tests/unit/sse-auth.test.ts": 1697,
"tests/unit/stream-utils.test.ts": 2517,
"tests/unit/token-refresh-service.test.ts": 1407,
"tests/unit/translator-openai-responses-req.test.ts": 1470,
"tests/unit/translator-openai-to-gemini.test.ts": 1625,
"tests/unit/translator-openai-to-kiro.test.ts": 1275,
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1234,
"tests/unit/usage-service-hardening.test.ts": 1487,
"tests/unit/vscode-token-routes.test.ts": 1267
"tests/integration/chat-pipeline.test.ts": 2493,
"tests/integration/chatcore-compression-integration.test.ts": 1738,
"tests/integration/skills-pipeline.test.ts": 1211,
"tests/unit/account-fallback-service.test.ts": 2439,
"tests/unit/adobe-firefly.test.ts": 1773,
"tests/unit/batch_api.test.ts": 2066,
"tests/unit/cc-compatible-provider.test.ts": 1899,
"tests/unit/chatcore-translation-paths.test.ts": 4487,
"tests/unit/combo-routing-engine.test.ts": 5393,
"tests/unit/db-migration-runner.test.ts": 2339,
"tests/unit/deepseek-web.test.ts": 1704,
"tests/unit/executor-antigravity.test.ts": 1713,
"tests/unit/executor-codex.test.ts": 2090,
"tests/unit/executor-default-base.test.ts": 2370,
"tests/unit/grok-web.test.ts": 3802,
"tests/unit/image-generation-handler.test.ts": 3166,
"tests/unit/model-sync-route.test.ts": 1586,
"tests/unit/models-catalog-route.test.ts": 2553,
"tests/unit/perplexity-web.test.ts": 2115,
"tests/unit/provider-models-route.test.ts": 2788,
"tests/unit/provider-validation-specialty.test.ts": 4656,
"tests/unit/providers-page-utils.test.ts": 1726,
"tests/unit/response-sanitizer.test.ts": 1659,
"tests/unit/route-edge-coverage.test.ts": 1936,
"tests/unit/search-handler-extended.test.ts": 1671,
"tests/unit/sse-auth.test.ts": 2512,
"tests/unit/stream-utils.test.ts": 3814,
"tests/unit/token-refresh-service.test.ts": 2150,
"tests/unit/translator-openai-responses-req.test.ts": 1863,
"tests/unit/translator-openai-to-gemini.test.ts": 2531,
"tests/unit/translator-openai-to-kiro.test.ts": 1990,
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1925,
"tests/unit/usage-service-hardening.test.ts": 2314,
"tests/unit/vscode-token-routes.test.ts": 1960,
"tests/unit/guardrails/videoBridgeResultCache.test.ts": 1248,
"tests/unit/reasoning-cache.test.ts": 1616,
"tests/unit/chatgpt-web.test.ts": 4911
},
"_rebaseline_2026_06_09": "Re-baseline consciente pre-release v3.8.19: 9 arquivos cresceram durante o ciclo (features mergeadas: RequestLoggerV2 +281 request-logger rework, stream +101, combo +73, chatCore +45, catalog +32 fable-5/catalog-flag, callLogs +4, accountFallback +2, usageHistory novo 840) + core.ts +7 (fix resetAllDbModuleState, PR 3536). A catraca segue valendo destes valores — proximo crescimento falha. Decisao: encolher (esp. RequestLoggerV2/chatCore) e a issue #3501 ficam para o ciclo seguinte.",
"_rebaseline_2026_06_11_phase1f": "Phase 1f (#3501): ProviderDetailPageClient.tsx 4948→4062 (-886 LOC); 3 novos hooks extraídos. useProviderConnections.ts=954 acima do cap=800 — justificado: extração direta do god-component (zero lógica nova), própria redução do cliente supera o custo. useProviderSettings.ts=263 e useProviderModels.ts=154 já abaixo do cap.",
@@ -359,96 +367,139 @@
"_rebaseline_2026_07_24_responses_toolcalls_log_summary": "hartmark, fix/responses-tool-calls-log-summary own growth: open-sse/translator/response/openai-responses.ts 1163->1174 (+11). closeToolCall() now also writes the completed tool call into the shared state.toolCalls Map (already populated by the openai-to-claude / claude-to-openai / gemini-to-openai response translators) so stream.ts's completion-log summary builder (which reads state.toolCalls, not this translator's own funcCallIds/funcNames/funcArgsBuf bookkeeping) reports finish_reason \"tool_calls\" and message.tool_calls for openai->openai-responses translated streams instead of always logging \"stop\" with no tool_calls — the actual client-facing SSE events were already correct; only the persisted call-log summary was wrong. Irreducible call-site addition at the existing tool-call-close chokepoint. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts.",
"_rebaseline_2026_07_25_8476_combo_input_bound_homogeneous_scope": "PR #8476 (herjarsa, fix/8375-8459-combo-image-fixes, #8375) own growth: open-sse/services/combo.ts 3642->3679 (+37 net: +29 the PR's own isInputBoundFailure short-circuit for deterministic context_length_exceeded/context_window_exceeded failures, +8 a /green-prs pre-merge fix scoping that short-circuit to homogeneous remainders only — the shipped code fired unconditionally on ANY target, regressing the intentional heterogeneous-combo fallback #6637/isContextOverflow400 protects, exactly as flagged by this PR's own review evidence but never actually implemented in the branch). The fix compares orderedTargets[i+1..] modelStr against the failing target's modelStr at the existing executeTarget dispatch chokepoint (mirrors the sameProviderNext precedent a few lines below) — irreducible call-site wiring, not extractable without hiding the dispatch boundary. Covered by tests/unit/combo-input-bound-failure-8375.test.ts (homogeneous pool still short-circuits) and the new tests/unit/combo-input-bound-heterogeneous-8375.test.ts (heterogeneous combo now correctly falls through to the larger-context target).",
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 — check-file-size.mjs counts LOC via split(\"\\n\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry": "DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web (Codex) provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legitima acima do cap; gateways.ts = god-file de catalogo de providers que cresceu com PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o proprio PR #9421 quebrou o arquivo); bridge.ts (PR #8949) = ponte Chromium vendored; proxyFetch.ts 1207->1220 = drift herdado de merges. Owner autorizou rebaseline com anotacao (2026-08-11).",
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"open-sse/executors/antigravity.ts": 2384,
"open-sse/executors/base.ts": 2559,
"open-sse/executors/codex.ts": 2438,
"open-sse/executors/cursor.ts": 2439,
"open-sse/executors/deepseek-web.ts": 1791,
"open-sse/executors/grok-web.ts": 1629,
"open-sse/executors/muse-spark-web.ts": 2192,
"open-sse/handlers/chatCore.ts": 7895,
"open-sse/handlers/imageGeneration.ts": 4838,
"open-sse/handlers/responseSanitizer.ts": 1760,
"open-sse/handlers/search.ts": 2397,
"open-sse/handlers/videoGeneration.ts": 1659,
"open-sse/mcp-server/schemas/tools.ts": 2423,
"open-sse/mcp-server/server.ts": 2259,
"open-sse/mcp-server/tools/advancedTools.ts": 1748,
"open-sse/services/accountFallback.ts": 3086,
"open-sse/services/adobeFireflyBrowserLogin.ts": 2126,
"open-sse/services/adobeFireflyClient.ts": 4679,
"open-sse/services/adobeFireflySession.ts": 1565,
"open-sse/services/claudeCodeCompatible.ts": 1876,
"open-sse/services/combo.ts": 5691,
"open-sse/services/compression/strategySelector.ts": 1655,
"open-sse/services/compression/engines/ccr/index.ts": 1229,
"_rebaseline_2026_08_22_11084_ccr_caller_gate": "PR #11084 (HouMinXi) own growth: open-sse/services/compression/engines/ccr/index.ts 1000->1024 (first listing — the engine was unlisted and drifted just over the 1000 cap; +24 are the callerSupportsCcrRetrieve gate that skips replacement entirely for callers without the retrieve tool, closing the stranded-prompt incident measured in production). Covered by tests/unit/compression/ccr-non-mcp-full-prompt-loss-7746.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"open-sse/services/contextManager.ts": 1202,
"_rebaseline_2026_08_22_11113_purify_system_first": "PR #11113 (ggdayup) own growth: open-sse/services/contextManager.ts 1000->1001 (+1, purifyHistory merges the compression notice into the leading system message instead of splicing a second one mid-array — live-confirmed TokenRouter 400s; the +1 is the merge-into-leading branch, not extractable). Covered by tests/unit/context-manager-purify-system-first.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"open-sse/services/rateLimitManager.ts": 1821,
"open-sse/translator/response/openai-responses.ts": 1983,
"open-sse/utils/cursorAgentProtobuf.ts": 2348,
"open-sse/utils/stream.ts": 4508,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 2165,
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1608,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 4863,
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1665,
"src/app/(dashboard)/dashboard/combos/page.tsx": 7337,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 2002,
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1595,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 4080,
"src/app/(dashboard)/dashboard/health/page.tsx": 1817,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 2066,
"src/app/(dashboard)/dashboard/providers/page.tsx": 3033,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1874,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1590,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 2294,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1752,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 2542,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 2454,
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1604,
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 3351,
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1746,
"src/app/api/providers/[id]/models/route.ts": 3683,
"src/app/api/v1/models/catalog.ts": 2492,
"src/lib/db/apiKeys.ts": 2386,
"src/lib/db/core.ts": 2558,
"src/lib/db/migrationRunner.ts": 1718,
"src/lib/db/models.ts": 1712,
"src/lib/db/providers.ts": 1613,
"src/lib/memory/retrieval.ts": 1674,
"src/lib/tailscaleTunnel.ts": 1876,
"src/lib/usage/providerLimits.ts": 1581,
"src/shared/components/OAuthModal.tsx": 1769,
"src/shared/components/RequestLoggerV2.tsx": 2542,
"src/shared/components/analytics/charts.tsx": 1616,
"src/shared/services/cliRuntime.ts": 1751,
"src/sse/handlers/chat.ts": 2992,
"src/sse/services/auth.ts": 4132,
"_rebaseline_2026_08_28_mergebatch_v3851_provenance_sweep_batch6": "/merge-batch 2026-08-27/28 (v3.8.51) provider/asset provenance & legal compliance sweep — combining the Designer Web + Felo Web + Runtime + GPL-derived (Raycast/Hailuo Web, #11691) retirement guards at their shared chokepoints: src/sse/services/auth.ts 3432->3443 (+11, getProviderCredentials()'s two sequential retirement-check if-blocks plus getModelInfoOrRetirementResponse() catch-branch wiring), src/sse/handlers/chatHelpers.ts 1019->1037 (+18, the combined retirement-error catch branches in the executor dispatch path), src/shared/constants/providers/apikey/gateways.ts 1330->1347 (+17, catalog drift from the same PR chain since the prior 2026-08-11 rebaseline), open-sse/services/autoCombo/virtualFactory.ts 1130->1132 (+2, retirement guard import wiring at the virtual-instance factory chokepoint). Each guard call is irreducible per-mechanism wiring at pre-existing chokepoints (getExecutor, resolveExecutorWithProxy, chat.ts/chatHelpers.ts catch branches, providers.ts write paths) — combining them is additive, not a new branch. Covered by the focused test suites of each boarded PR (chatcore-executor-proxy.test.ts, provider-node-reserved-prefix.test.ts, gpl-derived-provider-removals.test.ts, migration-166-retire-gpl-derived-providers.test.ts, among others).",
"_rebaseline_2026_08_24_lasterror_provider_error_detail": "PR (ntdat812) own growth: src/sse/services/auth.ts 3344->3346 (+2). One line is the import of describeUpstreamFailure from @/shared/utils/upstreamError, which replaces the string-only collapse `typeof errorText === \"string\" ? errorText.slice(0, 100) : \"Provider error\"` at the single markAccountUnavailable chokepoint (net 0 lines there) — the logic itself lives in upstreamError.ts, next to the extractErrorMessage it reuses, so nothing else moved into this file. The second line is the repo's own lint-staged prettier pass splitting a pre-existing two-statements-on-one-line at getProviderCredentials (`invalidateManagedLease(...); log.warn(...)`); it re-applies on any commit that touches this file, so it is not separable from the change. Covered by tests/unit/provider-error-detail-lastError.test.ts.",
"_rebaseline_2026_08_23_11186_synced_inventory_routing": "PR #11186 (pacocartones) own growth: src/sse/services/auth.ts 3260->3337 (+77, loadAdvertisedModelsForSelfHostedConnections + the modelNotAdvertised candidate-filter predicate — pins chat routing to the connection whose synced inventory actually advertises the model, fixing spurious model-not-found on multi-host self-hosted setups; at the existing credential-selection chokepoint, not extractable without splitting the selection flow). Covered by tests/unit/chat-routing-synced-inventory-11089.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"tests/unit/account-fallback-service.test.ts": 2453,
"tests/unit/provider-validation-specialty.test.ts": 4656,
"open-sse/executors/hyperagent.ts": 1601,
"src/lib/tokenHealthCheck.ts": 1643,
"open-sse/executors/default.ts": 1626,
"open-sse/executors/kiro.ts": 1668,
"open-sse/translator/request/openai-to-kiro.ts": 1649,
"open-sse/utils/sseHeartbeat.ts": 233,
"open-sse/utils/proxyFetch.ts": 1493,
"_rebaseline_2026_08_23_11177_dns_retry_classification": "PR #11177 (rqzbeh) own growth: proxyFetch.ts 1239->1244 (+5, EAI_AGAIN/ENOTFOUND/ETIMEDOUT join the retryable dispatcher classification alongside ECONNREFUSED — bounded socket retries for transient DNS failures, part of the #10443 Hermes→Antigravity stream-drop fixes). Covered by tests/unit/proxy-fetch-dns-retry-10443.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry: DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web (Codex) provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legítima acima do cap; gateways.ts = god-file de catálogo de providers que cresceu com os PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o próprio PR #9421 foi o que quebrou o arquivo; sem split até o release, congelado no tamanho atual). Owner autorizou rebaseline com anotação (2026-08-11).": {
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1062,
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
"src/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1250
},
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1408,
"_rebaseline_2026_08_23_11207_aws_polly_fields": "PR #11207 (rafacpti23, draft) own growth: AddApiKeyModal.tsx 1082->1173 (+91, AWS SigV4 credential fields for aws-polly — Access Key ID / Region / optional Session Token blocks with providerText i18n labels, at the existing per-provider form-section chokepoint; the file is the known god-modal with repeated dated rebaselines). Covered by tests/unit/dashboard/aws-polly-connection-modal-fields.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_22_11156_enter_check_disabled": "PR #11156 (rqzbeh) own growth: AddApiKeyModal.tsx 1080->1082 (+2, Enter keydown handler now mirrors the isCheckDisabled condition — owner-requested post-merge polish from #11056; the rest of the diff is Prettier reflow). Covered by tests/unit/ui/add-api-key-modal-enter-key.test.tsx (jsdom render test, Enter dispatch assertions).",
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1262,
"src/shared/components/ModelSelectModal.tsx": 1366,
"src/shared/constants/providers/apikey/gateways.ts": 1618,
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1665,
"open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/browser-worker.ts": 4410,
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry": "DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web (Codex) provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legitima acima do cap; gateways.ts = god-file de catalogo de providers que cresceu com PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o proprio PR #9421 quebrou o arquivo); bridge.ts (PR #8949) = ponte Chromium vendored; proxyFetch.ts 1207->1220 = drift herdado de merges. Owner autorizou rebaseline com anotacao (2026-08-11).",
"src/lib/modelCapabilities.ts": 1287,
"_rebaseline_2026_08_21_11034_effort_variants": "DRIFT do tip (base-red #9985): modelCapabilities.ts 1016->1072 (+56) acumulado por PRs ja mergeadas no release/v3.8.50 — principalmente #11034 (resolve effort-variant capabilities a partir do modelo base), alem de #10963/#11040/#10987 growth dos catalogos. Tip puro ficou vermelho neste gate; rebaseline no tip por push direto (owner pre-autorizou crescimento legitimo). Nao tocou no arquivo da #11038.",
"src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts": 1217,
"open-sse/config/imageRegistry.ts": 1241,
"src/sse/handlers/chatHelpers.ts": 1245,
"src/shared/middleware/chatBodyAdmission.ts": 1342,
"_rebaseline_2026_08_22_11020_sigterm_drain": "PR #11020 (RaviTharuma) own growth: chatBodyAdmission.ts 1005->1009 (+4, heavyweight admission leases now increment the SIGTERM drain counter and releaseChatAdmissionWhenDone holds it for the SSE lifetime — closes #11015; +4 are the lease/drain wiring lines at the existing admission chokepoint). Covered by tests/unit/chat-body-admission.test.ts heavyweight-lease cases. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_20_10668_tabitoken_gateway": "#10668 (yawar-aquil) own catalog growth: src/shared/constants/providers/apikey/gateways.ts 1268->1283 (+15, entirely this PR diff -- one new tabitoken gateway entry, data lines only; base moved from 1255 to 1268 via other merges since the PR forked). Not combination drift: reproducible on the PR branch alone, so the WS5.5 release-captain rule does not apply. Extraction is not available -- the file is pure data (own header: \"Pure data; merged by apikey/index.ts via spread\") and already split into 6 family files under apikey/. Same precedent as _rebaseline_2026_08_14_imagetotext_servicekinds (#10275/#10291, gateways.ts 1250->1255, data lines only) and _rebaseline_2026_08_11_v3850_merge_storm_provider_registry (owner-authorized for this same file).",
"_rebaseline_2026_08_20_10878_10799_provider_health_probes": "PRs #10878 (unsupported OpenAI-like validation probes stay neutral) + #10799 (preserve credential health on inconclusive NVIDIA-timeout/Antigravity-400 probes) own growth: src/app/api/providers/[id]/test/route.ts 946->1025 (+79, sum of both boarded together). Both add narrowly-scoped classification branches at the existing test-route dispatch chokepoint (unsupported-capability skip, credential-inconclusive detection) rather than new files, mirroring the prior 2026_06_27_5193 rebaseline of the same file. Covered by tests/unit/provider-validation-unsupported-neutral.test.ts + tests/unit/provider-health-inconclusive-probes.test.ts.",
"_rebaseline_2026_08_21_10859_vision_bridge_catalog": "#10859 own growth (Vision Bridge fixes #10808/#10809): src/lib/modelCapabilities.ts 1006->1016 (+10, cmd/gpt-5.3-codex* text-only capability resolution) and open-sse/executors/commandCode.ts 988->1023 (+35, Command Code wire-model normalization for bare ids + reasoning field fallback for opencode-routed gateways). Cohesive bug fixes at the existing capability-resolution / executor chokepoints; not extractable mid-fix. Covered by tests/unit/model-capabilities-command-code-codex-textonly-10703.test.ts, tests/unit/command-code-vision.test.ts, tests/unit/opencode-mimo-reasoning-details-nonstream.test.ts. Pushed directly to release (own-session miss: the original rebaseline was made in a throwaway validation worktree and never landed on the PR branch or the release before merge).",
"_rebaseline_2026_08_21_10907_sticky_pin_clear": "#10907 own growth: open-sse/executors/commandCode.ts 1023->1038 (+15, effort-suffix sanitization threading for the sticky-pin-clear fix). Cohesive change at the existing executor chokepoint. Covered by tests/unit/command-code-executor.test.ts.",
"_rebaseline_2026_08_21_10986_reasoning_only_content": "#10986 own growth: open-sse/executors/commandCode.ts 1038->1059 (+21, reasoning-only content fallback — when upstream emits only reasoning-delta events and never a text-delta, surface the reasoning text as message.content in createJsonResponse and emit a synthetic content delta in createStreamResponse). Cohesive bug fix at the existing executor chokepoint (mirrors precedent style of #10907/#10859). Covered by tests/unit/command-code-executor.test.ts (2 new cases: non-stream + streaming).",
"_rebaseline_2026_08_21_11069_m365_har_import": "#11069 own growth: AddApiKeyModal.tsx 1073->1080 (+7 = Import .har file button for the copilot-m365-web credential modal — M365 is the only provider whose credential (access_token+chathubPath) must be extracted from a DevTools HAR WebSocket URL, added as a new modal affordance). Cohesive UI at the existing modal chokepoint; not extractable. Covered by tests/unit/m365-har-import*.test.ts.",
"_rebaseline_2026_08_23_11141_oauth_400_recovery": "PR #11141 (HouMinXi) own growth: test/route.ts 1025->1215 (+190, the reactive-400 recovery path — a fully rebuilt probe for refresh+retry on refreshable non-rotating connections, with inconclusive-status preservation and rotating-provider exclusion; all growth is the new probe builder + guards at the existing test-route dispatch, extraction would split the retry flow mid-logic). Covered by tests/unit/oauth-400-recovery.test.ts (8, bug-injection proof). Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_23_tip_drift_post_batch0823": "Tip drift after the 2026-08-23 merge wave: chatBodyAdmission.ts 1009->1118 (+109, gate count incl. +1) and auth.ts 3337->3344 (+7), both grown by merges already on origin/release/v3.8.50 (verified identical on the pristine tip) — not by the codex-appserver-hardening PR that carries this bump. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_24_11355_cooldown_recovery_guards": "PR #11355 own growth: test/route.ts 1215->1237, +22 (startup crash-recovery guard: clearStaleCrashCooldowns() now parses the persisted rate_limited_until deadline and skips clearing rows still genuinely in the future, instead of clearing every non-terminal cooldown unconditionally). Cohesive fix at the existing test-route dispatch chokepoint alongside the #11141 probe builder. Covered by tests/unit/startup-stale-cooldown-recovery.test.ts + tests/unit/repro-zai-cooldown-cleared-by-connection-test.test.ts.",
"_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
"open-sse/services/autoCombo/virtualFactory.ts": 1374,
"_rebaseline_2026_08_29_9133_candidates_inspector_skip_flag": "#9133 own growth: open-sse/services/autoCombo/virtualFactory.ts 1138->1139 (+1, net of extraction). Fix: prepareVirtualAutoComboInputs gained an opt-in `skip` parameter so the read-only #7819 candidate inspector (open-sse/handlers/autoComboCandidates.ts) can build the FULL, unfiltered pool and decorate a resilience-blocked candidate as reachable:false instead of filterResilienceBlockedCandidates silently dropping the row before the inspector ever sees it (routing is unaffected — it never passes `skip`). The connectionsById map-building loop was extracted to buildConnectionResilienceMap() in resilienceCandidateFilter.ts (net 0 there since Prettier still breaks the call over multiple lines) and the now-unused ConnectionResilienceView import was dropped; the sole remaining growth is the new `skip` default parameter itself, which Prettier always places on its own line once the preceding options object parameter already breaks across lines — not further reducible without splitting prepareVirtualAutoComboInputs's signature away from its own body. Covered by tests/unit/auto-combo-candidates-locked-model-visible.test.ts (TDD repro: red before the fix, green after) plus the existing tests/unit/noauth-autocombo-lockout-7623.test.ts and tests/unit/auto-combo-credentialed-model-pool.test.ts (unaffected routing-path behavior).",
"_rebaseline_2026_08_29_11481_model_exposure_list": "Feature #11481 (explicit model exposure allow/deny list for /v1/models, mirrored into auto/* combo pools) own growth on top of #9133's +1: open-sse/services/autoCombo/virtualFactory.ts 1139->1145 (measured real line count after both #9133 and #11481 merged together = one import line for filterModelExposureCandidates plus the filter-and-reassign block at the existing buildPreparedPool chokepoint, immediately after the filterPaidOnlyCandidates call it mirrors — the exact pattern #6512 already established for hidePaidModels). The actual predicate (isModelExposureAllowed, glob support via the shared globToRegex matcher) lives in the new src/shared/utils/modelExposureList.ts leaf, and the pool-filter wrapper lives in the new open-sse/services/autoCombo/modelExposureFilter.ts leaf (both well under cap) — this file only carries the minimal call-site wiring plus import, not extractable further without hiding the buildPreparedPool filter chain. Covered by tests/unit/autoCombo/model-exposure-filter-11481.test.ts (pure filter, all branches) and tests/unit/model-exposure-list.test.ts (predicate).",
"_rebaseline_2026_08_28_mergebatch_v3851_qwen_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11713 (Qwen Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1132->1135 (+3, combining the Designer + Runtime retirement-guard filter into the single runtimeConnections predicate at the existing candidate-pool chokepoint, now excluding Qwen Web alongside Felo Web). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.",
"_rebaseline_2026_08_28_mergebatch_v3851_chatgptweb_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11754 (common ChatGPT Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1135->1138 (+3, an early `available` connection filter for the retired chatgpt-web/cgpt-web ids applied to both the active and disabled-noauth connection lists, ahead of the existing Designer+Runtime runtimeConnections filter). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.",
"src/lib/cloudflaredTunnel.ts": 1294,
"src/shared/components/RequestLoggerDetail.tsx": 1334,
"_rebaseline_2026_08_30_11703_json_tree_viewer": "/merge-batch 2026-08-30 (v3.8.51): #11703 (hartmark) own growth: src/shared/components/RequestLoggerDetail.tsx 1018->1111 (+93). The 2026-07-22 annotation on this same file said 'no further growth without split rationale' — this PR does split: the collapsible-JSON-tree rendering logic itself lives in the sibling RequestLoggerDetail.sections.tsx (PayloadSection/StreamSection extraction, +82 lines there) plus two new leaves (JsonTreeExpandControls.tsx, useTimestampTitles.ts) and a new store (jsonTreeExpandStore.ts) — all well under cap. The +93 remaining here is the irreducible call-site wiring: import + mount JsonTreeExpandControls, wire the per-section expand-level state and timestamp-tooltip hook into the existing detail panel layout. Covered by the PR's own tests/unit/dashboard/payload-section-collapsible-json.test.tsx, timestamp-titles.test.tsx, tests/unit/shared/json-tree-expand-store.test.ts, short-call-id.test.ts (43/43 vitest + 11/11 native pass).",
"src/app/api/providers/[id]/test/route.ts": 1506,
"src/lib/guardrails/videoBridgeRuntime.ts": 1211,
"_rebaseline_2026_08_28_mergebatch_v3851_ratchet_bank_reconcile": "/merge-batch 2026-08-28 (v3.8.51): boarding #11702 (fix/verify-ratchet-bank object-note comparator) surfaced a large stale `frozen`/`testFrozen` snapshot on PR #11702's own branch (forked before the 08-11 banking outage — see the object-valued `_rebaseline_2026_08_11_v3850_merge_storm_provider_registry` note above, the exact bug #11702 fixes in the verifier) — its conflicting block duplicated ~85 already-tracked files with sizes smaller than the current release tip, and still listed open-sse/executors/chatgpt-web.ts (deleted by the #11754 retirement). Resolved by re-measuring every file in the union of both sides directly on the boarded tree (split(\"\\n\").length, matching check-file-size.mjs) rather than trusting either stale snapshot; dropped the dead chatgpt-web.ts entry; kept the two genuinely-new entries PR #11702's branch had that this tip did not yet track (src/app/api/providers/[id]/test/route.ts, src/lib/guardrails/videoBridgeRuntime.ts, both re-measured). Same reconciliation applied to the testFrozen block above.",
"open-sse/executors/chatgpt-web.ts": 5056,
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry: DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legítima acima do cap; gateways.ts = god-file de catálogo de providers que cresceu com os PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o próprio PR #9421 foi o que quebrou o arquivo; sem split até o release, congelado no tamanho atual). Owner autorizou rebaseline com anotação (2026-08-11).": {
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1062,
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
"src/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1250
},
"_rebaseline_2026_08_20_10668_tabitoken_gateway": "#10668 (yawar-aquil) own catalog growth: src/shared/constants/providers/apikey/gateways.ts 1268->1283 (+15, entirely this PR diff -- one new tabitoken gateway entry, data lines only; base moved from 1255 to 1268 via other merges since the PR forked). Not combination drift: reproducible on the PR branch alone, so the WS5.5 release-captain rule does not apply. Extraction is not available -- the file is pure data (own header: \"Pure data; merged by apikey/index.ts via spread\") and already split into 6 family files under apikey/. Same precedent as _rebaseline_2026_08_14_imagetotext_servicekinds (#10275/#10291, gateways.ts 1250->1255, data lines only) and _rebaseline_2026_08_11_v3850_merge_storm_provider_registry (owner-authorized for this same file).",
"_rebaseline_2026_08_20_10878_10799_provider_health_probes": "PRs #10878 (unsupported OpenAI-like validation probes stay neutral) + #10799 (preserve credential health on inconclusive NVIDIA-timeout/Antigravity-400 probes) own growth: src/app/api/providers/[id]/test/route.ts 946->1025 (+79, sum of both boarded together). Both add narrowly-scoped classification branches at the existing test-route dispatch chokepoint (unsupported-capability skip, credential-inconclusive detection) rather than new files, mirroring the prior 2026_06_27_5193 rebaseline of the same file. Covered by tests/unit/provider-validation-unsupported-neutral.test.ts + tests/unit/provider-health-inconclusive-probes.test.ts.",
"_rebaseline_2026_08_21_10859_vision_bridge_catalog": "#10859 own growth (Vision Bridge fixes #10808/#10809): src/lib/modelCapabilities.ts 1006->1016 (+10, cmd/gpt-5.3-codex* text-only capability resolution) and open-sse/executors/commandCode.ts 988->1023 (+35, Command Code wire-model normalization for bare ids + reasoning field fallback for opencode-routed gateways). Cohesive bug fixes at the existing capability-resolution / executor chokepoints; not extractable mid-fix. Covered by tests/unit/model-capabilities-command-code-codex-textonly-10703.test.ts, tests/unit/command-code-vision.test.ts, tests/unit/opencode-mimo-reasoning-details-nonstream.test.ts. Pushed directly to release (own-session miss: the original rebaseline was made in a throwaway validation worktree and never landed on the PR branch or the release before merge).",
"_rebaseline_2026_08_21_10907_sticky_pin_clear": "#10907 own growth: open-sse/executors/commandCode.ts 1023->1038 (+15, effort-suffix sanitization threading for the sticky-pin-clear fix). Cohesive change at the existing executor chokepoint. Covered by tests/unit/command-code-executor.test.ts.",
"_rebaseline_2026_08_21_10986_reasoning_only_content": "#10986 own growth: open-sse/executors/commandCode.ts 1038->1059 (+21, reasoning-only content fallback — when upstream emits only reasoning-delta events and never a text-delta, surface the reasoning text as message.content in createJsonResponse and emit a synthetic content delta in createStreamResponse). Cohesive bug fix at the existing executor chokepoint (mirrors precedent style of #10907/#10859). Covered by tests/unit/command-code-executor.test.ts (2 new cases: non-stream + streaming).",
"_rebaseline_2026_08_21_11034_effort_variants": "DRIFT do tip (base-red #9985): modelCapabilities.ts 1016->1072 (+56) acumulado por PRs ja mergeadas no release/v3.8.50 — principalmente #11034 (resolve effort-variant capabilities a partir do modelo base), alem de #10963/#11040/#10987 growth dos catalogos. Tip puro ficou vermelho neste gate; rebaseline no tip por push direto (owner pre-autorizou crescimento legitimo). Nao tocou no arquivo da #11038.",
"_rebaseline_2026_08_21_11069_m365_har_import": "#11069 own growth: AddApiKeyModal.tsx 1073->1080 (+7 = Import .har file button for the copilot-m365-web credential modal — M365 is the only provider whose credential (access_token+chathubPath) must be extracted from a DevTools HAR WebSocket URL, added as a new modal affordance). Cohesive UI at the existing modal chokepoint; not extractable. Covered by tests/unit/m365-har-import*.test.ts.",
"_rebaseline_2026_08_22_11020_sigterm_drain": "PR #11020 (RaviTharuma) own growth: chatBodyAdmission.ts 1005->1009 (+4, heavyweight admission leases now increment the SIGTERM drain counter and releaseChatAdmissionWhenDone holds it for the SSE lifetime — closes #11015; +4 are the lease/drain wiring lines at the existing admission chokepoint). Covered by tests/unit/chat-body-admission.test.ts heavyweight-lease cases. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_22_11084_ccr_caller_gate": "PR #11084 (HouMinXi) own growth: open-sse/services/compression/engines/ccr/index.ts 1000->1024 (first listing — the engine was unlisted and drifted just over the 1000 cap; +24 are the callerSupportsCcrRetrieve gate that skips replacement entirely for callers without the retrieve tool, closing the stranded-prompt incident measured in production). Covered by tests/unit/compression/ccr-non-mcp-full-prompt-loss-7746.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_22_11113_purify_system_first": "PR #11113 (ggdayup) own growth: open-sse/services/contextManager.ts 1000->1001 (+1, purifyHistory merges the compression notice into the leading system message instead of splicing a second one mid-array — live-confirmed TokenRouter 400s; the +1 is the merge-into-leading branch, not extractable). Covered by tests/unit/context-manager-purify-system-first.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_22_11156_enter_check_disabled": "PR #11156 (rqzbeh) own growth: AddApiKeyModal.tsx 1080->1082 (+2, Enter keydown handler now mirrors the isCheckDisabled condition — owner-requested post-merge polish from #11056; the rest of the diff is Prettier reflow). Covered by tests/unit/ui/add-api-key-modal-enter-key.test.tsx (jsdom render test, Enter dispatch assertions).",
"_rebaseline_2026_08_23_11141_oauth_400_recovery": "PR #11141 (HouMinXi) own growth: test/route.ts 1025->1215 (+190, the reactive-400 recovery path — a fully rebuilt probe for refresh+retry on refreshable non-rotating connections, with inconclusive-status preservation and rotating-provider exclusion; all growth is the new probe builder + guards at the existing test-route dispatch, extraction would split the retry flow mid-logic). Covered by tests/unit/oauth-400-recovery.test.ts (8, bug-injection proof). Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_23_11177_dns_retry_classification": "PR #11177 (rqzbeh) own growth: proxyFetch.ts 1239->1244 (+5, EAI_AGAIN/ENOTFOUND/ETIMEDOUT join the retryable dispatcher classification alongside ECONNREFUSED — bounded socket retries for transient DNS failures, part of the #10443 Hermes→Antigravity stream-drop fixes). Covered by tests/unit/proxy-fetch-dns-retry-10443.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_23_11186_synced_inventory_routing": "PR #11186 (pacocartones) own growth: src/sse/services/auth.ts 3260->3337 (+77, loadAdvertisedModelsForSelfHostedConnections + the modelNotAdvertised candidate-filter predicate — pins chat routing to the connection whose synced inventory actually advertises the model, fixing spurious model-not-found on multi-host self-hosted setups; at the existing credential-selection chokepoint, not extractable without splitting the selection flow). Covered by tests/unit/chat-routing-synced-inventory-11089.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_23_11207_aws_polly_fields": "PR #11207 (rafacpti23, draft) own growth: AddApiKeyModal.tsx 1082->1173 (+91, AWS SigV4 credential fields for aws-polly — Access Key ID / Region / optional Session Token blocks with providerText i18n labels, at the existing per-provider form-section chokepoint; the file is the known god-modal with repeated dated rebaselines). Covered by tests/unit/dashboard/aws-polly-connection-modal-fields.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_23_tip_drift_post_batch0823": "Tip drift after the 2026-08-23 merge wave: chatBodyAdmission.ts 1009->1118 (+109, gate count incl. +1) and auth.ts 3337->3344 (+7), both grown by merges already on origin/release/v3.8.50 (verified identical on the pristine tip) — not by the codex-appserver-hardening PR that carries this bump. Owner pre-authorized baseline bumps 2026-08-22.",
"_rebaseline_2026_08_24_11355_cooldown_recovery_guards": "PR #11355 own growth: test/route.ts 1215->1237, +22 (startup crash-recovery guard: clearStaleCrashCooldowns() now parses the persisted rate_limited_until deadline and skips clearing rows still genuinely in the future, instead of clearing every non-terminal cooldown unconditionally). Cohesive fix at the existing test-route dispatch chokepoint alongside the #11141 probe builder. Covered by tests/unit/startup-stale-cooldown-recovery.test.ts + tests/unit/repro-zai-cooldown-cleared-by-connection-test.test.ts.",
"_rebaseline_2026_08_24_lasterror_provider_error_detail": "PR (ntdat812) own growth: src/sse/services/auth.ts 3344->3346 (+2). One line is the import of describeUpstreamFailure from @/shared/utils/upstreamError, which replaces the string-only collapse `typeof errorText === \"string\" ? errorText.slice(0, 100) : \"Provider error\"` at the single markAccountUnavailable chokepoint (net 0 lines there) — the logic itself lives in upstreamError.ts, next to the extractErrorMessage it reuses, so nothing else moved into this file. The second line is the repo's own lint-staged prettier pass splitting a pre-existing two-statements-on-one-line at getProviderCredentials (`invalidateManagedLease(...); log.warn(...)`); it re-applies on any commit that touches this file, so it is not separable from the change. Covered by tests/unit/provider-error-detail-lastError.test.ts.",
"_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
"_rebaseline_2026_08_28_mergebatch_v3851_chatgptweb_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11754 (common ChatGPT Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1135->1138 (+3, an early `available` connection filter for the retired chatgpt-web/cgpt-web ids applied to both the active and disabled-noauth connection lists, ahead of the existing Designer+Runtime runtimeConnections filter). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.",
"_rebaseline_2026_08_28_mergebatch_v3851_provenance_sweep_batch6": "/merge-batch 2026-08-27/28 (v3.8.51) provider/asset provenance & legal compliance sweep — combining the Designer Web + Felo Web + Runtime + GPL-derived (Raycast/Hailuo Web, #11691) retirement guards at their shared chokepoints: src/sse/services/auth.ts 3432->3443 (+11, getProviderCredentials()'s two sequential retirement-check if-blocks plus getModelInfoOrRetirementResponse() catch-branch wiring), src/sse/handlers/chatHelpers.ts 1019->1037 (+18, the combined retirement-error catch branches in the executor dispatch path), src/shared/constants/providers/apikey/gateways.ts 1330->1347 (+17, catalog drift from the same PR chain since the prior 2026-08-11 rebaseline), open-sse/services/autoCombo/virtualFactory.ts 1130->1132 (+2, retirement guard import wiring at the virtual-instance factory chokepoint). Each guard call is irreducible per-mechanism wiring at pre-existing chokepoints (getExecutor, resolveExecutorWithProxy, chat.ts/chatHelpers.ts catch branches, providers.ts write paths) — combining them is additive, not a new branch. Covered by the focused test suites of each boarded PR (chatcore-executor-proxy.test.ts, provider-node-reserved-prefix.test.ts, gpl-derived-provider-removals.test.ts, migration-166-retire-gpl-derived-providers.test.ts, among others).",
"_rebaseline_2026_08_28_mergebatch_v3851_qwen_retirement": "/merge-batch 2026-08-28 (v3.8.51): #11713 (Qwen Web retirement) own growth: open-sse/services/autoCombo/virtualFactory.ts 1132->1135 (+3, combining the Designer + Runtime retirement-guard filter into the single runtimeConnections predicate at the existing candidate-pool chokepoint, now excluding Qwen Web alongside Felo Web). Irreducible per-mechanism wiring, additive not a new branch. Covered by tests/unit/virtual-auto-combo.test.ts.",
"_rebaseline_2026_08_28_mergebatch_v3851_ratchet_bank_reconcile": "/merge-batch 2026-08-28 (v3.8.51): boarding #11702 (fix/verify-ratchet-bank object-note comparator) surfaced a large stale `frozen`/`testFrozen` snapshot on PR #11702's own branch (forked before the 08-11 banking outage — see the object-valued `_rebaseline_2026_08_11_v3850_merge_storm_provider_registry` note above, the exact bug #11702 fixes in the verifier) — its conflicting block duplicated ~85 already-tracked files with sizes smaller than the current release tip, and still listed open-sse/executors/chatgpt-web.ts (deleted by the #11754 retirement). Resolved by re-measuring every file in the union of both sides directly on the boarded tree (split(\"\\n\").length, matching check-file-size.mjs) rather than trusting either stale snapshot; dropped the dead chatgpt-web.ts entry; kept the two genuinely-new entries PR #11702's branch had that this tip did not yet track (src/app/api/providers/[id]/test/route.ts, src/lib/guardrails/videoBridgeRuntime.ts, both re-measured). Same reconciliation applied to the testFrozen block above.",
"_rebaseline_2026_08_29_11481_model_exposure_list": "Feature #11481 (explicit model exposure allow/deny list for /v1/models, mirrored into auto/* combo pools) own growth on top of #9133's +1: open-sse/services/autoCombo/virtualFactory.ts 1139->1145 (measured real line count after both #9133 and #11481 merged together = one import line for filterModelExposureCandidates plus the filter-and-reassign block at the existing buildPreparedPool chokepoint, immediately after the filterPaidOnlyCandidates call it mirrors — the exact pattern #6512 already established for hidePaidModels). The actual predicate (isModelExposureAllowed, glob support via the shared globToRegex matcher) lives in the new src/shared/utils/modelExposureList.ts leaf, and the pool-filter wrapper lives in the new open-sse/services/autoCombo/modelExposureFilter.ts leaf (both well under cap) — this file only carries the minimal call-site wiring plus import, not extractable further without hiding the buildPreparedPool filter chain. Covered by tests/unit/autoCombo/model-exposure-filter-11481.test.ts (pure filter, all branches) and tests/unit/model-exposure-list.test.ts (predicate).",
"_rebaseline_2026_08_29_9133_candidates_inspector_skip_flag": "#9133 own growth: open-sse/services/autoCombo/virtualFactory.ts 1138->1139 (+1, net of extraction). Fix: prepareVirtualAutoComboInputs gained an opt-in `skip` parameter so the read-only #7819 candidate inspector (open-sse/handlers/autoComboCandidates.ts) can build the FULL, unfiltered pool and decorate a resilience-blocked candidate as reachable:false instead of filterResilienceBlockedCandidates silently dropping the row before the inspector ever sees it (routing is unaffected — it never passes `skip`). The connectionsById map-building loop was extracted to buildConnectionResilienceMap() in resilienceCandidateFilter.ts (net 0 there since Prettier still breaks the call over multiple lines) and the now-unused ConnectionResilienceView import was dropped; the sole remaining growth is the new `skip` default parameter itself, which Prettier always places on its own line once the preceding options object parameter already breaks across lines — not further reducible without splitting prepareVirtualAutoComboInputs's signature away from its own body. Covered by tests/unit/auto-combo-candidates-locked-model-visible.test.ts (TDD repro: red before the fix, green after) plus the existing tests/unit/noauth-autocombo-lockout-7623.test.ts and tests/unit/auto-combo-credentialed-model-pool.test.ts (unaffected routing-path behavior).",
"_rebaseline_2026_08_30_11703_json_tree_viewer": "/merge-batch 2026-08-30 (v3.8.51): #11703 (hartmark) own growth: src/shared/components/RequestLoggerDetail.tsx 1018->1111 (+93). The 2026-07-22 annotation on this same file said 'no further growth without split rationale' — this PR does split: the collapsible-JSON-tree rendering logic itself lives in the sibling RequestLoggerDetail.sections.tsx (PayloadSection/StreamSection extraction, +82 lines there) plus two new leaves (JsonTreeExpandControls.tsx, useTimestampTitles.ts) and a new store (jsonTreeExpandStore.ts) — all well under cap. The +93 remaining here is the irreducible call-site wiring: import + mount JsonTreeExpandControls, wire the per-section expand-level state and timestamp-tooltip hook into the existing detail panel layout. Covered by the PR's own tests/unit/dashboard/payload-section-collapsible-json.test.tsx, timestamp-titles.test.tsx, tests/unit/shared/json-tree-expand-store.test.ts, short-call-id.test.ts (43/43 vitest + 11/11 native pass).",
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"open-sse/executors/antigravity.ts": 1665,
"open-sse/executors/base.ts": 1751,
"open-sse/executors/chatgpt-web.ts": 5056,
"open-sse/executors/codex.ts": 1499,
"open-sse/executors/cursor.ts": 1759,
"open-sse/executors/muse-spark-web.ts": 1405,
"open-sse/handlers/chatCore.ts": 5946,
"open-sse/handlers/imageGeneration.ts": 3243,
"open-sse/handlers/search.ts": 1789,
"open-sse/mcp-server/schemas/tools.ts": 1621,
"open-sse/mcp-server/server.ts": 1572,
"open-sse/services/accountFallback.ts": 2422,
"open-sse/services/adobeFireflyBrowserLogin.ts": 1401,
"open-sse/services/combo.ts": 4023,
"open-sse/translator/response/openai-responses.ts": 1466,
"open-sse/utils/cursorAgentProtobuf.ts": 1547,
"open-sse/utils/proxyFetch.ts": 1261,
"open-sse/utils/stream.ts": 3072,
"open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/browser-worker.ts": 4398,
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1322,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1344,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3186,
"src/app/(dashboard)/dashboard/combos/page.tsx": 5012,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1319,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631,
"src/app/(dashboard)/dashboard/providers/page.tsx": 2007,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1606,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1597,
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2152,
"src/app/api/providers/[id]/models/route.ts": 2429,
"src/app/api/providers/[id]/test/route.ts": 1252,
"src/app/api/v1/models/catalog.ts": 2066,
"src/app/docs/lib/openapi.generated.ts": 1347,
"src/lib/db/apiKeys.ts": 1610,
"src/lib/db/core.ts": 1740,
"src/lib/db/migrationRunner.ts": 1201,
"src/lib/tailscaleTunnel.ts": 1208,
"src/lib/tokenHealthCheck.ts": 1218,
"src/shared/components/RequestLoggerV2.tsx": 1718,
"src/shared/constants/providers/apikey/gateways.ts": 1439,
"src/shared/services/cliRuntime.ts": 1296,
"src/sse/handlers/chat.ts": 2375,
"src/sse/services/auth.ts": 3420,
"tests/unit/account-fallback-service.test.ts": 2453,
"tests/unit/provider-validation-specialty.test.ts": 4656
"open-sse/executors/commandCode.ts": 1271,
"src/app/docs/lib/openapi.generated.ts": 1347
},
"_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.",
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (353 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 86 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (352 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 86 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.348;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
<rect width="1200" height="350" fill="#0d1117"/>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 353 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 352 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<desc>Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses.</desc>
<defs>
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 353 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 150+ providers with a free tier and 53 recurring or keyless free-forever providers. Every tool works: 36 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 352 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 150+ providers with a free tier and 53 recurring or keyless free-forever providers. Every tool works: 36 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
<desc>Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle.</desc>
<defs>
<pattern id="gridPaperP" width="32" height="32" patternUnits="userSpaceOnUse">
@@ -21,7 +21,7 @@
<line x1="150" y1="53" x2="1160" y2="53" stroke="#232b38" stroke-width="1.5"/>
</g>
<g>
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">353 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">352 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
</g>
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
@@ -38,7 +38,7 @@
<line x1="3.9" y1="3.9" x2="18.1" y2="18.1"/>
</g>
<text x="102" y="170" font-size="18" font-weight="800" fill="#74b9ff">Never hit limits</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 353 providers in</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 352 providers in</text>
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over while a healthy target remains.</text>
</g>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 353 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 353 AI providers, 150+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 352 providers — 150+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 352 AI providers, 150+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
<desc>Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame.</desc>
<defs>
<pattern id="gridPaperH" width="32" height="32" patternUnits="userSpaceOnUse">
@@ -28,7 +28,7 @@
<text x="48" y="138" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="60" font-weight="800" fill="#e9edf3">Never stop coding<tspan fill="#a855f7">.</tspan></text>
<!-- subheadline -->
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">353 providers</tspan><tspan fill="#7ee787" font-weight="800">150+ free</tspan> — through one endpoint.</text>
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">352 providers</tspan><tspan fill="#7ee787" font-weight="800">150+ free</tspan> — through one endpoint.</text>
<!-- plug line -->
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity&#160;&#160;&#160;&#160;<tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,17 +1,13 @@
---
title: "Radar Free-Model Catalog"
version: 3.8.51
lastUpdated: 2026-09-01
version: 3.8.50
lastUpdated: 2026-08-13
---
# Radar Free-Model Catalog
> **Source of truth:** `src/lib/radar/`, `src/lib/db/radar.ts`, `src/app/api/radar/`
> **Last updated:** 2026-09-01 — v3.8.51
> **Hosted-service evidence boundary:** server-side rules described here were verified on
> 2026-09-01 against the intentionally private Radar server at exact revision
> `main@dce70f004364912f3f144cdb69f4cbcde16093ed`. That implementation is not distributed in
> this OSS repository; hosted availability remains a separate operational state.
> **Last updated:** 2026-08-13 — v3.8.50
Radar is an **optional add-on** that overlays a signed, freshly-curated free-model
catalog on top of the release baseline (`FREE_MODEL_BUDGETS` in
@@ -28,7 +24,7 @@ is never mutated on disk — see
---
## Delivery status in v3.8.51
## Delivery status in v3.8.50
The following status distinguishes what this OSS release implements from later Radar
workstreams. It is a code-level status, not a promise that a particular hosted deployment
@@ -113,17 +109,10 @@ When both are on, the sync path is:
`Authorization: Bearer <supporter key>` header (see below). Servers default to the separately
signed v1 transition artifact when the schema header is absent, so older installed clients keep
receiving updates.
2. This is a download-only application flow, but it is still an HTTPS request. The hosted
infrastructure receives ordinary connection metadata such as the source IP. When a supporter
key is configured, sync also sends that key in the Bearer header so the service can resolve the
entitlement. At the exact private-server revision identified in the evidence boundary above,
feed-request accounting uses key hashes, aggregate usage, and a daily rotating truncated HMAC
of the IP for manual abuse review; those tables persist neither the key nor the IP in raw form.
Infrastructure access logs and the encrypted delivery outbox are separate operational
boundaries.
3. OmniRoute never sends prompts, responses, conversations, provider credentials, model traffic,
uptime, latency, or the local provider configuration to the Radar service.
4. The response is verified, validated, and cached locally (see
2. Nothing about the request, the operator, or their traffic is uploaded — it is a
plain, unauthenticated-by-default GET. OmniRoute never posts usage data, provider
configuration, or model traffic to the feed service.
3. The response is verified, validated, and cached locally (see
[Security model](#security-model)). Radar has exactly four server-side network paths:
`syncRadar()` for the catalog, `syncRadarReferrals()` for referrals, and
`syncRadarOffers()` / `syncRadarIntel()` for supporter-only offers and Intel.
@@ -144,42 +133,6 @@ that lets the feed service decide which tier to serve (see
---
## Access and safety rules shown before opt-in
The inactive dashboard renders these rules from
`src/app/(dashboard)/dashboard/radar/RadarAccessExplainer.tsx` **before** either activation action.
The canonical access scale is:
| Level | Eligibility | Access | Repeat/expiration rule |
| --------------------- | --------------------------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------- |
| Community | Anyone; no key | Complete catalog delayed by about 30 days | Always available; no issuance |
| Star + follow | GitHub OAuth verifies both a star on the repository and a follow of the owner | One live catalog read, then Community | One issuance per login; never reissued |
| Contributor Top 10 | Positions 110 in the latest complete weekly ranking | 365 live days | Claimed on demand; leaving the ranking does not shorten an awarded period |
| Contributor Top 100 | Positions 11100 in that ranking | 90 live days | Same on-demand/idempotent claim rule |
| Supporter purchase | One-time 6-month, 1-year, or lifetime purchase | Live catalog, signed live offers, and Intel | No automatic renewal |
| Donation/manual grant | Owner-reviewed donation or an owner grant for an explicit number of days/lifetime | Same live entitlement for the granted period | Audited, idempotent grant |
Merged PRs, commits, and changed lines are **ranking inputs only**. A login outside the Top 100 gets
no contributor grant regardless of PR count. Finite purchases, donations, contributor periods, and
manual grants accumulate from the current expiration; lifetime dominates. A rank change never
retroactively revokes or shortens time already awarded.
The hosted license is personal and the user-facing rule is one active installation at a time. This
release does **not** claim a hardware lock: the OSS sync does not fingerprint hardware or maintain a
cryptographic device lease. At the verified private-server revision above, implemented enforcement
is entitlement validation plus a manual-review signal when the same live key is seen from a fourth
distinct IP within 24 hours. That signal never blocks or revokes a key automatically. Recovery
revokes and replaces the lost key while preserving the existing expiration; it does not restart the
purchased or granted period.
Live offers are manually curated and can change or expire. The opt-in screen also names the exact
privacy boundary: signed catalog/referral metadata is downloaded; a valid key additionally unlocks
signed offers and Intel; the Bearer key and normal connection metadata reach the hosted service;
prompts, responses, conversations, provider credentials, model traffic, uptime, latency, and local
provider configuration do not.
---
## Getting a supporter key
The activation screen (`/dashboard/radar`) links out to two flows for **obtaining** a
@@ -189,12 +142,11 @@ destination pages, not in this repo (spec decision D14).
- **"I'm a contributor"** — opens `RADAR_CONTRIBUTOR_CLAIM_URL` (default
`https://radar.omniroute.online/auth/github`), a GitHub OAuth claim flow hosted on
the private Radar server. It checks the latest complete weekly ranking: Top 10 receives 365 days
and positions 11100 receive 90 days. Outside the Top 100, PR count never grants access; the flow
instead checks the separate star + follow single-use level.
the private radar server. It verifies the visitor's GitHub account and grants a
supporter key to anyone with 5+ merged pull requests or a top-100 contributor spot
on the repo.
- **"Support the project"** — opens `RADAR_SUPPORTER_PLANS_URL` (default
`https://radar.omniroute.online/planos`), the hosted page for the one-time 6-month, 1-year, and
lifetime options. The OSS page still displays no monetary value.
`https://radar.omniroute.online/planos`), the payment/plans page.
Both URLs are resolved server-side (`src/lib/radar/links.ts`, same env-override
pattern as `RADAR_FEED_URL`) and relayed to the dashboard through the existing

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -4,7 +4,7 @@
---
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -6199,22 +6199,6 @@ paths:
"200":
description: Health status
/api/monitoring/compression:
get:
tags: [System]
summary: Get compression result-memo statistics
description: >-
In-process compression result-memo observability snapshot — size, capacity,
lifetime hits/misses/hitRate plus 1m/5m/15m/1h windowed rates. Lightweight
(no DB, no provider reads) companion to `GET /api/monitoring/health` intended
for frequent polling. Sent with `Cache-Control: no-store, no-cache,
must-revalidate`. Counters reset on process restart.
responses:
"200":
description: Compression memo stats (`compression.memo` + `timestamp`)
"503":
description: Compression stats unavailable
/api/rate-limits:
get:
tags: [System]

View File

@@ -428,7 +428,6 @@ Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex,
| `DEVIN_BRIDGE_OPUS_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used when Claude Code requests its Opus default. |
| `DEVIN_BRIDGE_HAIKU_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used when Claude Code requests its Haiku default. |
| `DEVIN_BRIDGE_SUBAGENT_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used for Claude Code subagents. |
| `DEVIN_SEAT_API_URL` | `https://server.codeium.com` | `open-sse/services/usage/devinCli.ts` | Optional override for the Codeium seat-management API used by Devin CLI quota (`GetUserStatus`). |
| `AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Absolute-path override for the Augment (Auggie) CLI binary used by the local `auggie` provider. Falls back to `CLI_AUGGIE_BIN`, then a PATH lookup. |
| `CLI_AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Alias override for the Augment (Auggie) CLI binary path (checked after `AUGGIE_BIN`). |
| `ZCODE_BIN` | `zcode` | `open-sse/executors/zcode.ts` | Binary used for the local `zcode` provider's stdio client. Falls back to `zcode` on PATH. |

View File

@@ -48,7 +48,7 @@ The manifest contains:
- JSON-safe model metadata such as context length, vision/reasoning flags, and
unsupported params
- capability tags including `apikey`, `oauth`, `custom-executor`,
`passthrough-models`, `responses`, `sidecar-candidate`, `usage-fetch`, and `usage-supported`
`passthrough-models`, `responses`, `sidecar-candidate`, and `usage-fetch`
The manifest intentionally excludes:
@@ -74,7 +74,6 @@ re-reading the TypeScript sources.
| `custom-executor` | Runs a non-default executor, so it stays on the TypeScript path. |
| `sidecar-candidate` | Mirrors `sidecar.eligible` — safe to consider for sidecar import. |
| `usage-fetch` | Has a wired usage or quota fetcher (`getUsageForProvider`). |
| `usage-supported` | The usage API accepts this provider (`isSupportedUsageConnection`). |
`usage-fetch` is discovery only. It reports that OmniRoute knows how to read usage for the
provider; it does not activate fetching, change quota semantics, or imply that the
@@ -87,16 +86,6 @@ with aliases and is slightly longer than the number of tagged providers: entries
not chat providers in the manifest registry (for example the `firecrawl` search provider
and the `amazon-q` ACP provider) have no manifest entry to tag.
`usage-supported` answers whether the server and Dashboard usage routes accept a connection
for the provider. It mirrors `isSupportedUsageConnection()` (`src/lib/usage/providerLimits.ts`)
and `supportsProviderQuota()` (`src/shared/utils/providerQuotaVisibility.ts`), both gated by
`USAGE_SUPPORTED_PROVIDERS` (`open-sse/services/usage/supportedProviders.ts`). Unlike
`usage-fetch`, it is emitted on the provider id alone — the runtime guard does
`USAGE_SUPPORTED_PROVIDERS.includes(providerId)` with no alias resolution, so the manifest
keeps the same rule. The two tags have different perimeters: 3 providers carry only
`usage-fetch` (`opencode`, `opencode-zen`, `xai`) and 1 carries only
`usage-supported` (`xiaomi-mimo-token-plan`), so one does not imply the other.
## Sidecar Use
Sidecars should treat `sidecar.eligible` as a conservative candidate signal, not

View File

@@ -1,16 +1,16 @@
---
title: "Provider Reference"
version: 3.8.51
lastUpdated: 2026-09-02
lastUpdated: 2026-08-30
---
# Provider Reference
> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
> Regenerate with: `npm run gen:provider-reference`
> **Last generated:** 2026-09-02
> **Last generated:** 2026-08-30
Total providers: **353**. See category breakdown below.
Total providers: **352**. See category breakdown below.
## Categories
@@ -80,7 +80,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `zed` | `zd` | Zed IDE | OAuth | [link](https://zed.dev) | Zed stores LLM provider credentials (OpenAI, Anthropic, Google, Mistral, xAI) in the OS keychain. Use the Import button below to discover and import them automatically. |
| `zed-hosted` | — | Zed Hosted Models | OAuth | [link](https://zed.dev) | Sign in with your Zed account (native-app sign-in). OmniRoute generates a one-time RSA keypair and opens zed.dev to authorize it — on a remote/headless install, copy the resulting 127.0.0.1 callback URL from your browser's address bar and paste it back here. Distinct from the 'Zed IDE' credential-import entry above: this proxies chat completions through Zed's own hosted model aggregator (cloud.zed.dev), fronting Anthropic/OpenAI/Google/xAI models under your Zed plan. |
## Web Cookie Providers (32)
## Web Cookie Providers (31)
| ID | Alias | Name | Tags | Website | Notes | Tool calling |
|----|-------|------|------|---------|-------|--------------|
@@ -102,7 +102,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
| `inner-ai` | `in-ai` | Inner.ai (Subscription) | Web cookie | [link](https://app.innerai.com) | Paste your token cookie and email separated by a space: open DevTools → Application → Cookies → .innerai.com, copy the token value, then append a space and your Inner.ai login email. Example: eyJhbG... user@example.com | emulated |
| `kimi-web` | `kimi-web` | Kimi Web | Web cookie | [link](https://www.kimi.ai) | Paste access_token from www.kimi.ai DevTools → Application → Local Storage. A legacy kimi-auth cookie is also accepted. | — |
| `lmarena` | `lma` | Arena (Free) | Web cookie | [link](https://arena.ai) | Paste the full Cookie header from arena.ai (DevTools → Network → request → Cookie). Include arena-auth-prod-v1.0/.1… and cf_clearance/__cf_bm when present. OmniRoute uses Chrome TLS impersonation; if Arena still 403s, set providerSpecificData.recaptchaV3Token from a live browser session. | — |
| `maxai` | `mx` | MaxAI | Web cookie | [link](https://www.maxai.co) | Sign in once (email code or browser) to mint a MaxAI access token. OmniRoute signs each request, routes it through residential egress, and refreshes the token browserlessly, so a connection stays valid for about a year without re-login. | emulated |
| `muse-spark-web` | `ms-web` | Muse Spark Web (Meta AI) | Web cookie | [link](https://www.meta.ai) | Paste your ecto_1_sess cookie AND the ecto1:... WS auth token from meta.ai. Capture the ecto1: token in DevTools → Network → WS → the clippy request's Authorization query param. Example: ecto_1_sess=4240a308...NVDg0; ecto1:ABCD... | emulated |
| `notion-web` | `nw` | Notion AI Web (Unofficial/Experimental) | Web cookie | [link](https://www.notion.so) | Paste only the token_v2 cookie VALUE from app.notion.com (DevTools → Application → Cookies → token_v2). Do not paste token_v2= or the full Cookie header. Workspace is auto-detected; space_id / notion_user_id are optional. | — |
| `perplexity-web` | `pplx-web` | Perplexity Web (Pro/Max) | Web cookie | [link](https://www.perplexity.ai) | Paste your __Secure-next-auth.session-token cookie value from perplexity.ai | emulated |
@@ -441,7 +440,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)
- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (107 implementations)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (104 implementations)
- Translators: [`open-sse/translator/`](../../open-sse/translator/)
## See Also

View File

@@ -1,6 +1,6 @@
# OmniRoute
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 352 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
## Overview
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **353 AI providers** with automatic format translation
- **352 AI providers** with automatic format translation
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
- **19 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline
- **4-tier fallback**: Subscription → API Key → Cheap → Free

View File

@@ -223,9 +223,6 @@ const nextConfig = {
...(isContributorBuild ? {} : { output: "standalone" }),
compress: true,
productionBrowserSourceMaps: false,
// Issue #67: enable React Compiler — automates memoization, removes manual useCallback/useMemo debt.
// See: https://next.dev/blog/react-compiler
reactCompiler: true,
// OmniRoute is a proxy for AI APIs — request bodies routinely include
// multi-MB payloads (vision models, image edits, base64-encoded files,
// long chat histories with embedded images). Next.js's Server Action
@@ -333,13 +330,6 @@ const nextConfig = {
// analysis can't follow _require.resolve("sql.js/package.json") and spams
// build warnings. Externalizing silences them without changing behaviour.
"sql.js",
// tiktoken's node build reads tiktoken_bg.wasm via __dirname-relative
// fs.readFileSync at import time. When bundled, the wasm asset is not
// traced into the server chunk and page-data collection for any route
// importing the vendored ChatGPT Web tokenizer fails with
// "Missing tiktoken_bg.wasm". Externalizing keeps the require at runtime
// where node_modules/tiktoken/tiktoken_bg.wasm resolves normally.
"tiktoken",
// sqlite-vec ships a native vec0.so loaded at runtime via createRequire().
// Turbopack otherwise tries to bundle the .so and fails with "Unknown module
// type"; externalizing it keeps the require at runtime (like better-sqlite3).

View File

@@ -408,7 +408,6 @@ export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = {
},
],
},
};
const EMBEDDING_PROVIDER_ALIASES: Record<string, string> = {
@@ -471,38 +470,6 @@ export function getEmbeddingProvider(providerId: string): EmbeddingProvider | nu
return EMBEDDING_PROVIDERS[resolveEmbeddingProviderId(providerId)] || null;
}
/**
* Derive an OpenAI-compatible embeddings config for a chat provider that has NO
* curated EMBEDDING_PROVIDERS entry. Works for any registry provider whose base
* URL ends in /chat/completions by swapping that suffix for /embeddings (groq,
* mistral, together, upstage, fireworks, nvidia, vercel-ai-gateway, ...).
* Dynamic-URL providers (no usable static base) derive to
* null — they need bespoke URL handling, not a bogus endpoint.
*
* This is a FALLBACK only: callers must check getEmbeddingProvider() first so
* curated entries keep their specialized configuration.
*/
export function deriveEmbeddingProviderForChatProvider(
providerId: string,
chatEntry: { id?: string; baseUrl?: string | string[] } | null | undefined
): EmbeddingProvider | null {
if (!chatEntry) return null;
const rawBase = Array.isArray(chatEntry.baseUrl)
? chatEntry.baseUrl[0]
: chatEntry.baseUrl;
if (!rawBase || typeof rawBase !== "string") return null;
// stripTrailingSlashes-equivalent without importing open-sse utils here:
const base = rawBase.replace(/\/+$/, "");
if (!base.endsWith("/chat/completions")) return null;
return {
id: providerId,
baseUrl: `${base.slice(0, -"/chat/completions".length)}/embeddings`,
authType: "apikey",
authHeader: "bearer",
models: [],
};
}
/**
* Parse embedding model string (format: "provider/model" or just "model")
* Returns { provider, model }
@@ -518,18 +485,6 @@ export function parseEmbeddingModel(
const slashIdx = modelStr.indexOf("/");
if (slashIdx > 0) {
const rawProvider = modelStr.slice(0, slashIdx);
// A configured provider_node whose prefix exactly equals the requested
// provider segment always wins — even when that segment is also an alias
// of a curated provider (a local node must not be hijacked by a registry
// alias). Same exact-match precedence documented for
// EMBEDDING_MODEL_ALIASES above.
const dynamicExact =
dynamicProviders && dynamicProviders.find((dp) => dp.id === rawProvider);
if (dynamicExact) {
return { provider: rawProvider, model: modelStr.slice(slashIdx + 1) };
}
const resolvedProvider = resolveEmbeddingProviderId(rawProvider);
if (EMBEDDING_PROVIDERS[resolvedProvider]) {

View File

@@ -256,26 +256,6 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
supportedSizes: ["1024x1024", "1024x1792", "1792x1024", "1024x1536", "1536x1024"],
},
maxai: {
id: "maxai",
alias: "mx",
baseUrl: "https://api.maxai.me/gpt/get_image_generate_response",
authType: "apikey",
authHeader: "bearer",
format: "maxai-image",
models: [
{ id: "gpt-image-1", name: "GPT Image 1 (MaxAI)" },
{ id: "dall-e-3", name: "DALL-E 3 (MaxAI)" },
{ id: "flux-1-schnell", name: "FLUX.1 [schnell] (MaxAI)" },
{ id: "flux-1-dev", name: "FLUX.1 [dev] (MaxAI)" },
{ id: "flux-1-pro", name: "FLUX.1 [pro] (MaxAI)" },
{ id: "sd3-medium", name: "Stable Diffusion 3 Medium (MaxAI)" },
],
// gpt-image-1/dall-e-3 are size-snapped to 1024x1024 by the handler; flux
// models pass any size through.
supportedSizes: ["1024x1024", "1024x1536", "1536x1024", "1024x1792", "1792x1024"],
},
xai: {
id: "xai",
baseUrl: "https://api.x.ai/v1/images/generations",

View File

@@ -1,6 +1,5 @@
import type { RegistryEntry, RegistryModel } from "./providers/shared.ts";
import { USAGE_FETCHER_PROVIDERS } from "../services/usage/fetcherProviders.ts";
import { USAGE_SUPPORTED_PROVIDERS } from "../services/usage/supportedProviders.ts";
export type ProviderPluginCapability =
| "apikey"
@@ -9,8 +8,7 @@ export type ProviderPluginCapability =
| "passthrough-models"
| "responses"
| "sidecar-candidate"
| "usage-fetch"
| "usage-supported";
| "usage-fetch";
export interface ProviderPluginModel {
id: string;
@@ -68,15 +66,6 @@ const SIDECAR_COMPATIBLE_EXECUTORS = new Set(["default"]);
*/
const USAGE_FETCHER_PROVIDER_SET = new Set<string>(USAGE_FETCHER_PROVIDERS);
/**
* Providers whose usage API is accepted by dashboard/server routes (#10078).
* Unlike USAGE_FETCHER_PROVIDERS this gate is checked with a plain
* `USAGE_SUPPORTED_PROVIDERS.includes(providerId)` — no alias resolution —
* so the manifest must emit on the identifier alone to stay faithful to the
* runtime guard.
*/
const USAGE_SUPPORTED_PROVIDER_SET = new Set<string>(USAGE_SUPPORTED_PROVIDERS);
function compactObject<T extends Record<string, unknown>>(value: T): Partial<T> {
return Object.fromEntries(
Object.entries(value).filter(([, entryValue]) => entryValue !== undefined)
@@ -153,9 +142,6 @@ function capabilitiesFor(entry: RegistryEntry, eligible: boolean): ProviderPlugi
) {
capabilities.add("usage-fetch");
}
if (USAGE_SUPPORTED_PROVIDER_SET.has(entry.id)) {
capabilities.add("usage-supported");
}
return [...capabilities].sort();
}

View File

@@ -210,7 +210,6 @@ import { pollinationsProvider } from "./registry/pollinations/index.ts";
import { veoaifree_webProvider } from "./registry/veoaifree-web/index.ts";
import { codexProvider } from "./registry/codex/index.ts";
import { codexAppServerProvider } from "./registry/codex-app-server/index.ts";
import { maxaiProvider } from "./registry/maxai/index.ts";
import { veniceProvider } from "./registry/venice/index.ts";
import { kiroProvider } from "./registry/kiro/index.ts";
import { openadapterProvider } from "./registry/openadapter/index.ts";
@@ -478,7 +477,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
"veoaifree-web": veoaifree_webProvider,
codex: codexProvider,
"codex-app-server": codexAppServerProvider,
maxai: maxaiProvider,
venice: veniceProvider,
kiro: kiroProvider,
byteplus: byteplusProvider,

View File

@@ -1,26 +0,0 @@
import type { RegistryEntry } from "../../shared.ts";
import { MAXAI_REGISTRY_MODELS } from "../../../../executors/maxai/catalog.ts";
/**
* MaxAI — the MaxAI web app (chat.maxai.co / api.maxai.me) as an OpenAI-compatible
* provider. A signed web-app port (like zai-web): each request carries a
* per-request `X-Authorization` signature + a Bearer access token minted by the
* browser-mint flow. Runs over residential egress with a Firefox TLS fingerprint.
*
* authType `apikey`/authHeader `bearer`: the OpenAI-style access token is stored
* on the connection and replayed as `Authorization: Bearer`; the device id +
* user id ride in providerSpecificData and are folded into the signature. The
* token is refreshed out-of-band by the browser-mint (the `/oauth` refresh
* endpoint is deep-TLS-gated), so there is no central token-refresh case.
*/
export const maxaiProvider: RegistryEntry = {
id: "maxai",
alias: "mx",
format: "openai",
executor: "maxai",
baseUrl: "https://api.maxai.me",
authType: "apikey",
authHeader: "bearer",
defaultContextLength: 128000,
models: MAXAI_REGISTRY_MODELS,
};

View File

@@ -78,8 +78,8 @@ export const VOLCENGINE_AGENT_PLAN_MODELS: RegistryModel[] = [
name: "MiniMax M3 (Agent Plan)",
contextLength: 1048576,
toolCalling: true,
supportsVision: true,
supportsReasoning: true,
supportsVision: true,
},
{
id: "deepseek-v4-pro-260425",

View File

@@ -54,8 +54,8 @@ export const VOLCENGINE_CODING_PLAN_MODELS: RegistryModel[] = [
name: "MiniMax M3 (Coding Plan)",
contextLength: 1048576,
toolCalling: true,
supportsVision: true,
supportsReasoning: true,
supportsVision: true,
},
{
id: "deepseek-v4-pro",

View File

@@ -218,29 +218,3 @@ export function getAllRerankModels() {
}
return models;
}
/**
* Derive a Cohere-compatible rerank config for a chat provider that has NO
* curated RERANK_PROVIDERS entry. Works for any registry provider whose base
* URL ends in /chat/completions by swapping that suffix for /rerank (groq,
* mistral, vercel-ai-gateway, ...). Dynamic-URL providers (no usable static
* base, e.g. dynamic account-scoped hosts) derive to null — they need bespoke
* URL handling.
*
* This is a FALLBACK only: callers must check getRerankProvider() first so
* curated entries keep their specialized configuration and format adapters.
*/
export function deriveRerankProviderForChatProvider(providerId, chatEntry) {
if (!chatEntry) return null;
const rawBase = Array.isArray(chatEntry.baseUrl) ? chatEntry.baseUrl[0] : chatEntry.baseUrl;
if (!rawBase || typeof rawBase !== "string") return null;
const base = rawBase.replace(/\/+$/, "");
if (!base.endsWith("/chat/completions")) return null;
return {
id: providerId,
baseUrl: `${base.slice(0, -"/chat/completions".length)}/rerank`,
authType: "apikey",
authHeader: "bearer",
models: [],
};
}

View File

@@ -50,50 +50,6 @@ export const GITHUB_REASONING_EFFORT_OPT_IN_PATTERN = /claude[-_.]?(?:opus|sonne
export const GITHUB_NO_REASONING_EFFORT_PATTERN = /(claude|haiku|oswe)/i;
const NVIDIA_GLM_52_PATTERN = /z-ai\/glm-5\.2\b/i;
/**
* Model families whose top reasoning tier in their native API or upstream gateways
* is `max` (rather than `xhigh`):
* - GLM 5.1+ / 6.0+ (Z.AI / Zhipu GLM-5.1, GLM-5.2, GLM-5.3, GLM-5.3-flash, GLM-5.4, GLM-6...)
* - DeepSeek V4+ (Flash, Pro, Flash-Vision, ...)
* - Moonshot Kimi K3+ (Kimi K3, K4...)
*/
export const MAX_TIER_REASONING_MODEL_PATTERN =
/(?:^|\/|\b)(?:glm-(?:5\.[1-9]|5\.\d+|[6-9]|\d{2,})|deepseek-v(?:[4-9]|\d{2,})|kimi-k(?:[3-9]|\d{2,}))/i;
export const O1_O3_REASONING_MODELS_PATTERN = /(?:^|\/|\b)(?:o1-mini|o1|o3-mini|o3-pro|o3)(?:$|-)/i;
export const O1_PREVIEW_PATTERN = /(?:^|\/|\b)o1-preview(?:$|-)/i;
export const MUSE_SPARK_PATTERN = /(?:^|\/|\b)muse-spark/i;
export const MINIMAX_REASONING_PATTERN = /(?:^|\/|\b)minimax(?:-m3|-m2)/i;
export const GROK_45_PATTERN = /(?:^|\/|\b)grok-4\.5/i;
export const GROK_46_PATTERN = /(?:^|\/|\b)grok-4\.6/i;
export const GLM_53_FAMILY_PATTERN = /(?:^|\/|\b)glm-5\.3(?:$|-)/i;
export const GLM_52_FAMILY_PATTERN = /(?:^|\/|\b)glm-5\.2(?:$|-)/i;
export function isCommandCodeProvider(provider: string): boolean {
return (
provider === "command-code" ||
provider === "cmd" ||
provider === "command_code"
);
}
export function isOllamaCloudProvider(provider: string): boolean {
return (
provider === "ollama-cloud" ||
provider === "ollamacloud" ||
provider === "ollama_cloud"
);
}
export function isOpencodeGoProvider(provider: string): boolean {
return (
provider === "opencode-go" ||
provider === "opencode-zen" ||
provider === "opencode" ||
provider === "opencode_go"
);
}
type ReasoningSanitizeLog = {
info?: (tag: string, msg: string) => void;
};
@@ -198,21 +154,23 @@ export function supportsMaxEffortForProvider(provider: string, model: string): b
const isClaude =
(provider === PROVIDER_CLAUDE || isClaudeCodeCompatible(provider)) &&
supportsClaudeMaxEffort(resolvedModelId);
const isOpencodeGo = isOpencodeGoProvider(provider);
const isOllamaCloud = isOllamaCloudProvider(provider);
// opencode-go proxies DeepSeek with the native DeepSeek API contract, which
// accepts {high, max} literally. Without this opt-in, max would be
// normalized to xhigh (the OmniRoute-internal top tier) and rejected by the
// upstream. Scoped to opencode-go deliberately: OpenRouter's DeepSeek path
// (pi#4055) is the documented inverse and expects xhigh, not max.
// Ollama Cloud also accepts literal max (for example GLM 5.2 supports
// low|medium|high|max|none) and rejects xhigh; xhigh is mapped to max by the
// provider guard in sanitizeReasoningEffortForProvider.
const isOpencodeGoDeepSeek =
(provider === "opencode-go" || provider === "opencode-zen") &&
resolvedModelId.toLowerCase().includes("deepseek");
const isOllamaCloud = provider === "ollama-cloud";
const isMoonshotK3 = /^kimi-k3(?:$|-)/i.test(resolvedModelId);
const isCommandCode = isCommandCodeProvider(provider);
const isMaxTierModel =
MAX_TIER_REASONING_MODEL_PATTERN.test(resolvedModelId) ||
MAX_TIER_REASONING_MODEL_PATTERN.test(model);
return (
isClaude ||
isOpencodeGo ||
isOllamaCloud ||
isMoonshotK3 ||
isCommandCode ||
isMaxTierModel
);
// Command Code's upstream API accepts the literal DeepSeek/OpenAI effort value
// `max`; do not rewrite it to OmniRoute's internal `xhigh` spelling.
const isCommandCode = provider === "command-code";
return isClaude || isOpencodeGoDeepSeek || isOllamaCloud || isMoonshotK3 || isCommandCode;
}
// ── Effort carrier helpers (#7044) ──────────────────────────────────────────
@@ -309,15 +267,6 @@ export function sanitizeReasoningEffortForProvider(
const effortStr = typeof c.effort === "string" ? c.effort.toLowerCase() : "";
const modelStr = model || "";
// ── o1-preview: does not accept reasoning_effort parameter at all ─────────
if (O1_PREVIEW_PATTERN.test(modelStr)) {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: removed unsupported reasoning_effort for o1-preview`
);
return stripEffortValue(b, c);
}
const githubOptIn =
provider === "github" && GITHUB_REASONING_EFFORT_OPT_IN_PATTERN.test(modelStr);
const rejecting =
@@ -331,136 +280,6 @@ export function sanitizeReasoningEffortForProvider(
return stripEffortValue(b, c);
}
// ── GLM-5.3 and GLM-5.3-FLASH specific rules ──────────────────────────────
// Supported options: max (default & recommended), high, low.
// none/minimal/low → low; medium/high → high; xhigh/max → max.
// In addition, GLM-5.3+ forces thinking; thinking.type="disabled" is rejected upstream.
if (GLM_53_FAMILY_PATTERN.test(modelStr)) {
let mappedGlm53 = "max";
if (effortStr === "none" || effortStr === "minimal" || effortStr === "low") {
mappedGlm53 = "low";
} else if (effortStr === "medium" || effortStr === "high") {
mappedGlm53 = "high";
} else if (effortStr === "xhigh" || effortStr === "max" || effortStr === "ultra") {
mappedGlm53 = "max";
}
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: mapped reasoning_effort ${effortStr}${mappedGlm53} (GLM-5.3 contract)`
);
let updated = writeEffortValue(b, mappedGlm53, c);
const thinkingObj = updated.thinking;
if (
thinkingObj &&
typeof thinkingObj === "object" &&
!Array.isArray(thinkingObj) &&
(thinkingObj as Record<string, unknown>).type === "disabled"
) {
updated = {
...updated,
thinking: {
...(thinkingObj as Record<string, unknown>),
type: "enabled",
},
};
}
return updated;
}
// ── GLM-5.2 specific rules ────────────────────────────────────────────────
// none/minimal stop thinking (none); low/medium → high; xhigh/max → max; high → high.
if (GLM_52_FAMILY_PATTERN.test(modelStr)) {
let mappedGlm52 = "max";
if (effortStr === "none" || effortStr === "minimal") {
mappedGlm52 = "none";
} else if (effortStr === "low" || effortStr === "medium") {
mappedGlm52 = "high";
} else if (effortStr === "xhigh" || effortStr === "max" || effortStr === "ultra") {
mappedGlm52 = "max";
} else if (effortStr === "high") {
mappedGlm52 = "high";
}
if (mappedGlm52 !== effortStr) {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: mapped reasoning_effort ${effortStr}${mappedGlm52} (GLM-5.2 contract)`
);
return writeEffortValue(b, mappedGlm52, c);
}
return body;
}
// ── Muse Spark models (muse-spark-1.2, etc.) ─────────────────────────────
// Accepts minimal|low|medium|high|xhigh. Rejects none (400) and max.
// max/ultra → xhigh; none → minimal.
if (MUSE_SPARK_PATTERN.test(modelStr)) {
if (effortStr === "max" || effortStr === "ultra") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → xhigh (Muse Spark ceiling)`
);
return writeEffortValue(b, "xhigh", c);
}
if (effortStr === "none") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: clamped reasoning_effort none → minimal (Muse Spark floor)`
);
return writeEffortValue(b, "minimal", c);
}
return body;
}
// ── OpenAI o1 / o3-mini models ───────────────────────────────────────────
// Accepts only low|medium|high. Clamp xhigh/max/ultra → high.
if (O1_O3_REASONING_MODELS_PATTERN.test(modelStr)) {
if (effortStr === "xhigh" || effortStr === "max" || effortStr === "ultra") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → high (o1/o3-mini ceiling)`
);
return writeEffortValue(b, "high", c);
}
return body;
}
// ── MiniMax models ───────────────────────────────────────────────────────
// Accepts none|minimal|low|medium|high. Clamp xhigh/max/ultra → high.
if (MINIMAX_REASONING_PATTERN.test(modelStr)) {
if (effortStr === "xhigh" || effortStr === "max" || effortStr === "ultra") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → high (MiniMax ceiling)`
);
return writeEffortValue(b, "high", c);
}
return body;
}
// ── xAI Grok models ──────────────────────────────────────────────────────
// Grok 4.6 accepts low|medium|high|xhigh (clamp max/ultra → xhigh).
// Grok 4.5 accepts low|medium|high (clamp xhigh/max/ultra → high).
if (GROK_46_PATTERN.test(modelStr)) {
if (effortStr === "max" || effortStr === "ultra") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → xhigh (Grok 4.6 ceiling)`
);
return writeEffortValue(b, "xhigh", c);
}
return body;
}
if (GROK_45_PATTERN.test(modelStr)) {
if (effortStr === "xhigh" || effortStr === "max" || effortStr === "ultra") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → high (Grok 4.5 ceiling)`
);
return writeEffortValue(b, "high", c);
}
return body;
}
// `minimal` is a sub-`low` reasoning tier some catalogs advertise (e.g.
// Muse Spark via models.dev) and the Codex provider accepts natively — but
// Command Code rejects it outright:
@@ -468,7 +287,7 @@ export function sanitizeReasoningEffortForProvider(
// "low"|"medium"|"high"|"xhigh"|"max" at "params.reasoning_effort"
// Map it to the closest supported value (`low`) for command-code only;
// other providers (codex etc.) keep their native `minimal` handling.
if (isCommandCodeProvider(provider) && effortStr === "minimal") {
if (provider === "command-code" && effortStr === "minimal") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: mapped reasoning_effort minimal → low`
@@ -476,23 +295,10 @@ export function sanitizeReasoningEffortForProvider(
return writeEffortValue(b, "low", c);
}
// Providers and model families whose top reasoning tier is `max` natively
// (or whose gateways expect `max` rather than OmniRoute's internal `xhigh`):
// - Command Code (`command-code` / `cmd`)
// - Ollama Cloud (`ollama-cloud` / `ollamacloud`)
// - OpenCode Go (`opencode-go` / `opencode-zen` / `opencode`)
// - GLM 5.1+ / 6.0+ (Z.AI / Zhipu GLM-5.1, GLM-5.2, GLM-5.3, GLM-5.4...)
// - DeepSeek V4+ (Flash, Pro, Vision, ...)
// - Kimi K3+ (Moonshot AI K3, K4, ...)
// OpenRouter (pi#4055) is excluded because OpenRouter's normalized API expects xhigh.
const isMaxTierTarget =
provider !== "openrouter" &&
(isCommandCodeProvider(provider) ||
isOllamaCloudProvider(provider) ||
isOpencodeGoProvider(provider) ||
MAX_TIER_REASONING_MODEL_PATTERN.test(modelStr));
if (isMaxTierTarget && effortStr === "xhigh") {
// Command Code accepts the literal top-tier value `max`, while the shared
// standardization stage may have already represented the client's `max` as
// OmniRoute's internal `xhigh`. Convert it back before the upstream request.
if (provider === "command-code" && effortStr === "xhigh") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: normalized reasoning_effort xhigh → max`
@@ -500,6 +306,18 @@ export function sanitizeReasoningEffortForProvider(
return writeEffortValue(b, "max", c);
}
// Ollama Cloud accepts low|medium|high|max|none and rejects xhigh. Map
// xhigh → max (its literal top tier) before the generic xhigh handling so
// passthrough (unregistered) models are covered too — the registry opt-out
// only covers known models.
if (provider === "ollama-cloud" && effortStr === "xhigh") {
log?.info?.(
"REASONING_SANITIZE",
`${provider}/${modelStr}: mapped reasoning_effort xhigh → max`
);
return writeEffortValue(b, "max", c);
}
// Native DeepSeek (api.deepseek.com) — V4 Pro and Flash use the native
// {low, high, max} vocabulary, while other model ids retain the {high, max}
// floor. OmniRoute's internal top tier xhigh maps to DeepSeek's literal max,
@@ -545,6 +363,14 @@ export function sanitizeReasoningEffortForProvider(
// and the requested effort falls outside that vocabulary, remap to the
// nearest declared tier: the smallest ranked value ≥ the request, else the
// highest declared (a request above the ceiling lands on the ceiling).
// Live case: opencode-go/ox-alpha-free (Console Go) only accepts
// {low, high, max} — a client's reasoning_effort:"medium" reached the
// upstream verbatim and 400'd every turn ("[1210] This model always engages
// in thinking and cannot be disabled; please use low, high, or max"). The
// learned-caps path can't help here (it only clamps down from xhigh/max,
// and this error text isn't a parseable enum), so the declaration is the
// only source of truth. Models without an explicit declaration keep
// #8057's trust-the-upstream pass-through.
const providerModelIdForClamp = modelStr.startsWith(`${provider}/`)
? modelStr.slice(provider.length + 1)
: modelStr;

View File

@@ -109,7 +109,6 @@ function parseGlmEffortTier(model: string): GlmEffortTier | null {
* https://docs.z.ai/guides/overview/concept-param
*/
const GLM_THINKING_MODEL_PATTERN = /^glm-5\.(?:[2-9]|\d{2,})/i;
const GLM_53_OR_HIGHER_PATTERN = /^glm-5\.(?:[3-9]|\d{2,})/i;
function isGlmThinkingModel(model: string): boolean {
return GLM_THINKING_MODEL_PATTERN.test(model);
@@ -349,15 +348,6 @@ export class GlmExecutor extends DefaultExecutor {
}
if (transport === "openai") {
// GLM-5.3+ rejects thinking.type "disabled". Ensure thinking is enabled
// when targeting GLM-5.3 or higher.
if (record && GLM_53_OR_HIGHER_PATTERN.test(effectiveModel)) {
const existingThinking = asRecord(record.thinking);
if (existingThinking?.type === "disabled") {
record.thinking = { ...existingThinking, type: "enabled" };
}
}
// GLM-5.3 effort tiers: inject the documented `reasoning_effort` param and
// force thinking on — 5.3 rejects thinking.type "disabled", and an effort
// tier without thinking would silently drop the selector upstream.

View File

@@ -44,7 +44,6 @@ const lazyExecutors: Record<string, () => Promise<BaseExecutor>> = {
import("./codex-app-server.ts").then(
(m) => new m.CodexAppServerExecutor({}, "codex-app-server")
),
maxai: () => import("./maxai.ts").then((m) => new m.MaxAiExecutor()),
"chatgpt-web-codex": () =>
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
"cgpt-codex": () =>

View File

@@ -1,620 +0,0 @@
/**
* MaxAiExecutor — MaxAI web-app chat as an OpenAI-compatible OmniRoute provider.
*
* MaxAI (chat.maxai.co / api.maxai.me) is a consumer web app with no public API.
* This executor reproduces the web app's own signed request to `/gpt/cwc/chat`:
* • per-request `X-Authorization` signature (see ./signing.ts),
* • Firefox-150 identity headers + Bearer access token,
* • the full OpenAI transcript flattened into one `message_content` block
* (stateless-full-history; see ./protocol.ts),
* • SSE response parsed for text deltas, with inline `<think>` reasoning split
* out into `reasoning_content` (see ./stream.ts).
*
* Egress + TLS: the request MUST exit a residential IP (MaxAI bot-bans datacenter
* IPs). OmniRoute routes the executor's `fetch()` through the per-connection proxy
* (a residential HTTP proxy) transparently, and applies the wreq-js Firefox TLS
* fingerprint when enabled. This executor does not open its own socket; it uses
* the ambient patched `fetch`, so the proxy + TLS overlay apply automatically.
*
* Auth refresh: MaxAI's `/oauth/refresh_access_token` is deep-TLS-gated and cannot
* be called by any HTTP client (only a real browser passes). The access token is
* therefore minted/refreshed out-of-band by OmniRoute's own browser-mint flow
* (see maxaiBrowserLogin); this executor only consumes the stored credential.
*/
import { BaseExecutor, type ExecuteInput, type ExecutorExecuteResult } from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
import { sanitizeErrorMessage } from "../utils/error.ts";
import { resolveMaxaiCredential, type MaxaiCredential } from "./maxai/credentials.ts";
import { buildMaxaiSignedHeaders } from "./maxai/signing.ts";
import { ensureMaxaiConstants } from "./maxai/constantsStore.ts";
import { maxaiAccessTokenNeedsRefresh, maxaiRefreshAccessToken } from "./maxai/refresh.ts";
import {
assembleMaxaiContext,
buildMaxaiChatBody,
extractCurrentTurnImages,
MAXAI_BASE_URL,
MAXAI_CHAT_PATH,
maxaiStaticHeaders,
newConversationId,
} from "./maxai/protocol.ts";
import { resolveMaxaiDocList, type MaxaiDocListEntry } from "./maxai/documents.ts";
import { estimateMaxaiTokens, isMaxaiTextFrame, ThinkSplitter } from "./maxai/stream.ts";
import { prepareToolMessages, parseToolCallsFromText } from "../translator/webTools.ts";
import { buildToolModeResponse } from "./chatgptWebTools.ts";
const JSON_HEADERS = { "Content-Type": "application/json" };
const SSE_HEADERS = {
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"Content-Type": "text/event-stream; charset=utf-8",
};
interface OpenAiChatBody {
messages?: Array<{
role?: string;
content?: unknown;
tool_calls?: unknown;
tool_call_id?: string;
}>;
model?: string;
}
function errorResponse(status: number, message: string, code: string): Response {
return new Response(
JSON.stringify({
error: {
code,
message: sanitizeErrorMessage(message),
type: status >= 500 ? "provider_error" : "invalid_request_error",
},
}),
{ status, headers: JSON_HEADERS }
);
}
/**
* Wrap a Response into the executor wrapper contract shape
* `{response, url, headers, transformedBody}` that `chatCore.ts` and the
* web-cookie/noauth sweep (tests/unit/executor-web-cookie-sweep.test.ts)
* require. `headers` and `transformedBody` are the ACTUAL upstream request
* headers and body — chatCore surfaces them as the provider-request-capture
* ("what we actually sent") in the dashboard and uses the body for service-tier
* and prompt-cache metadata (chatCore.ts:3680-3688), mirroring the shape returned
* by every web-cookie sibling (venice-web.ts:92-94, poe-web.ts:121-123). Error
* paths that fail BEFORE a request is assembled pass no capture — honestly empty,
* because nothing was sent upstream.
*/
function wrap(
response: Response,
url: string,
capture?: { headers?: Record<string, string>; transformedBody?: unknown }
): { response: Response; url: string; headers: Record<string, string>; transformedBody: unknown } {
return {
response,
url,
headers: capture?.headers ?? {},
transformedBody: capture?.transformedBody ?? null,
};
}
/**
* Detect a tool "narration miss": the model produced no parseable <tool> block
* but its text shows it was ABOUT to call a tool (talks about the <tool> block
* or names a requested tool). This is the occasional reasoning-model failure
* mode (e.g. deepseek-r1) where it reasons about the call instead of emitting
* it. A true refusal or a normal answer returns false, so we never retry those.
*/
function isToolNarrationMiss(text: string, requestedTools: unknown): boolean {
if (!text) return false;
if (/<tool\b/.test(text)) return true; // mentioned the tag but it didn't parse
const names = Array.isArray(requestedTools)
? (requestedTools as Array<{ function?: { name?: unknown } }>)
.map((t) => (typeof t?.function?.name === "string" ? t.function.name : ""))
.filter(Boolean)
: [];
// Names it a tool AND signals intent to use it (not merely mentioning it).
const intent = /\b(I('| wi)ll|let me|I can|going to|need to)\b/i.test(text);
return intent && names.some((n) => text.includes(n));
}
/** A short, soft nudge appended to the transcript for the single retry turn. */
function toolNudge(originalText: string): string {
return (
originalText +
"\n\n[A quick note: if a client tool would help answer this, please go ahead " +
"and emit the <tool> block directly rather than describing it — just the block " +
"on its own line. If no tool is needed, a normal answer is perfectly fine.]"
);
}
/** Emit one OpenAI `chat.completion.chunk`. */
function chunk(
controller: ReadableStreamDefaultController,
id: string,
created: number,
model: string,
delta: Record<string, unknown>,
finish: string | null = null
): void {
const payload = {
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta, finish_reason: finish }],
};
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(payload)}\n\n`));
}
export class MaxAiExecutor extends BaseExecutor {
constructor() {
super("maxai", PROVIDERS.maxai ?? { id: "maxai", baseUrl: MAXAI_BASE_URL });
}
override async execute(input: ExecuteInput): Promise<ExecutorExecuteResult> {
// The MaxAI chat endpoint URL is the wrapper's `url` for every return path
// (error and success alike), so define it once up front.
const url = MAXAI_BASE_URL + MAXAI_CHAT_PATH;
const cred = resolveMaxaiCredential(
input.credentials?.providerSpecificData,
input.credentials?.accessToken
);
if (!cred) {
return wrap(
errorResponse(
401,
"MaxAI connection is not configured (missing access token, device id, or user id). Sign in to mint a token.",
"maxai_unconfigured"
),
url
);
}
// Proactively refresh a near-expiry access token (browserless; see ./maxai/refresh.ts).
// Failures here are non-fatal: we fall through with the existing token, and a
// genuinely-dead token surfaces as a 401/418 below (prompting a re-mint).
const accessToken = await this.ensureFreshAccess(cred, input);
const body = (input.body ?? {}) as OpenAiChatBody;
// Tool-calling (prompted protocol): when the request carries tools[], inject
// the <tool> contract into the messages so the model learns the client tools
// and how to invoke them (see translator/webTools.ts). MaxAI has no native
// function-calling; this is the same prompted-tool shim the web-cookie
// providers use. The response side parses <tool> blocks back into tool_calls.
const { hasTools, requestedTools, effectiveMessages } = prepareToolMessages(
body as Record<string, unknown>,
(body.messages ?? []) as Array<{ role: string; content: unknown }>
);
let text: string;
try {
text = assembleMaxaiContext(effectiveMessages);
} catch {
return wrap(
errorResponse(400, "No user message to send to MaxAI.", "maxai_empty_request"),
url
);
}
// Vision input: attach the CURRENT user turn's images (data: / http(s):) to
// message_content so vision-capable MaxAI models actually see them. Extract
// from the original messages (pre-tool-munging); text stays flattened.
const originalMessages = (body.messages ?? []) as Array<{ role?: string; content?: unknown }>;
const imageUrls = extractCurrentTurnImages(originalMessages);
// Doc-RAG: upload any inline documents (base64 file/input_file/document
// parts) on the current turn to /app/upload_document and attach the
// resulting doc_list to the chat body. Best-effort: upload failures are
// skipped and the chat proceeds without the doc.
let docList: MaxaiDocListEntry[] = [];
try {
docList = await resolveMaxaiDocList(
originalMessages,
{ accessToken, userId: cred.userId, deviceId: cred.deviceId },
{ signal: input.signal ?? undefined }
);
} catch {
docList = [];
}
const constants = await ensureMaxaiConstants({ signal: input.signal });
if (!constants) {
return wrap(
errorResponse(
401,
"MaxAI signing constants unavailable (extraction failed); cannot sign the request.",
"maxai_auth_error"
),
url
);
}
const conversationId = newConversationId();
const chatBody = buildMaxaiChatBody({
conversationId,
text,
modelName: input.model,
appVersion: constants.appVersion,
imageUrls,
docList: docList.length ? docList : undefined,
});
const signedHeaders = buildMaxaiSignedHeaders(
{
path: MAXAI_CHAT_PATH,
userId: cred.userId,
deviceId: cred.deviceId,
},
constants
);
const headers: Record<string, string> = {
...maxaiStaticHeaders(),
...signedHeaders,
Authorization: `Bearer ${accessToken}`,
...(input.upstreamExtraHeaders ?? {}),
};
let upstream: Response;
try {
upstream = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(chatBody),
signal: input.signal ?? undefined,
});
} catch (err) {
return wrap(
errorResponse(
502,
`MaxAI request failed: ${sanitizeErrorMessage(err instanceof Error ? err.message : err)}`,
"maxai_transport_error"
),
url
);
}
if (upstream.status !== 200 || !upstream.body) {
const detail = await upstream.text().catch(() => "");
// 401/418 = auth expired/masked-reject; surface so the caller can prompt a re-mint.
// A body-too-large rejection (MaxAI answers 422 "...message you submitted being
// too long...") is INPUT-bound: classify it as context_length_exceeded so
// OmniRoute's compression/overflow pipeline can shrink and retry instead of
// treating it as an opaque provider error.
const tooLong = /too\s+long|exceeds?\b.*\bcontext|context.*(?:exceeded|too long|limit)/i.test(
detail
);
if (tooLong) {
return wrap(
errorResponse(
400,
`MaxAI request exceeds the context limit: ${sanitizeErrorMessage(detail.slice(0, 200))}`,
"context_length_exceeded"
),
url
);
}
const status = upstream.status === 418 ? 401 : upstream.status || 502;
return wrap(
errorResponse(
status,
`MaxAI upstream ${upstream.status}: ${sanitizeErrorMessage(detail.slice(0, 300))}`,
upstream.status === 401 || upstream.status === 418
? "maxai_auth_error"
: "maxai_upstream_error"
),
url
);
}
const id = `chatcmpl-${conversationId}`;
const created = Math.floor(Date.now() / 1000);
const promptTokens = estimateMaxaiTokens(text);
// Tool mode: MaxAI streams plain text, and the <tool> protocol is only
// parseable once the full reply is in hand. So when tools are active we
// buffer the whole body, build a chat.completion, and let the shared shim
// parse <tool> blocks into tool_calls (emitting a terminal SSE replay for
// streaming callers). This mirrors every web-cookie provider's tool path.
if (hasTools) {
const raw = await upstream.text();
let { reasoning, answer } = collectNonStream(raw);
// Reliability: if the model narrated about the tool but emitted no
// parseable <tool> block (occasional reasoning-model miss), do ONE gentle
// nudged retry and keep it only if it actually produces a tool call.
const firstHasToolCall = !!parseToolCallsFromText(answer, "probe", requestedTools).toolCalls;
if (!firstHasToolCall && isToolNarrationMiss(reasoning + "\n" + answer, requestedTools)) {
const retry = await this.retryToolTurn(cred, accessToken, input, toolNudge(text));
if (retry && parseToolCallsFromText(retry.answer, "probe", requestedTools).toolCalls) {
reasoning = retry.reasoning;
answer = retry.answer;
input.log?.debug?.("maxai", "tool narration-miss recovered via one nudged retry");
}
}
const completionTokens = estimateMaxaiTokens(reasoning + answer);
const buffered = new Response(
JSON.stringify({
id,
object: "chat.completion",
created,
model: input.model,
choices: [
{
index: 0,
message: {
role: "assistant",
content: answer,
...(reasoning ? { reasoning_content: reasoning } : {}),
},
finish_reason: "stop",
},
],
usage: {
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
total_tokens: promptTokens + completionTokens,
},
}),
{ status: 200, headers: JSON_HEADERS }
);
const response = await buildToolModeResponse(buffered, requestedTools, input.stream, {
cid: id,
created,
model: input.model,
idSeed: "maxai",
});
return wrap(response, url, { headers, transformedBody: chatBody });
}
if (input.stream) {
const stream = this.buildStream(upstream.body, id, created, input.model, promptTokens);
return wrap(new Response(stream, { status: 200, headers: SSE_HEADERS }), url, {
headers,
transformedBody: chatBody,
});
}
// Non-streaming: collect the whole SSE body, split think, build a chat.completion.
const raw = await upstream.text();
const { reasoning, answer } = collectNonStream(raw);
const completionTokens = estimateMaxaiTokens(reasoning + answer);
const response = {
id,
object: "chat.completion",
created,
model: input.model,
choices: [
{
index: 0,
message: {
role: "assistant",
content: answer,
...(reasoning ? { reasoning_content: reasoning } : {}),
},
finish_reason: "stop",
},
],
usage: {
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
total_tokens: promptTokens + completionTokens,
},
};
return wrap(
new Response(JSON.stringify(response), { status: 200, headers: JSON_HEADERS }),
url,
{ headers, transformedBody: chatBody }
);
}
/**
* Return a non-expired access token, refreshing browserlessly when the stored
* one is missing or within the expiry margin and a refresh token is available.
* Persists a freshly-minted token via `onCredentialsRefreshed`. Never throws —
* on any refresh failure it returns the original token so the request still
* proceeds (a truly-dead token then surfaces as an upstream 401/418).
*/
private async ensureFreshAccess(cred: MaxaiCredential, input: ExecuteInput): Promise<string> {
if (!cred.refreshToken) return cred.accessToken;
if (!maxaiAccessTokenNeedsRefresh(cred.accessToken)) return cred.accessToken;
const result = await maxaiRefreshAccessToken({
refreshToken: cred.refreshToken,
deviceId: cred.deviceId,
userId: cred.userId,
signal: input.signal ?? undefined,
});
if (!result.ok || !result.accessToken) {
input.log?.warn?.(
"maxai",
`access-token refresh failed (${result.status}); using existing token`
);
return cred.accessToken;
}
// Persist the new access token (merged into providerSpecificData) so the next
// request starts fresh. The refresh token and device id are unchanged.
try {
await input.onCredentialsRefreshed?.({
accessToken: result.accessToken,
providerSpecificData: {
...(input.credentials?.providerSpecificData ?? {}),
maxaiAccessToken: result.accessToken,
},
});
} catch (err) {
input.log?.warn?.(
"maxai",
`refreshed token persist failed: ${sanitizeErrorMessage(err instanceof Error ? err.message : err)}`
);
}
return result.accessToken;
}
/**
* Run a single follow-up MaxAI turn with a gentle nudge appended, used to
* recover a reasoning-model "narration miss" (the model talked ABOUT the
* <tool> block instead of emitting it). Bounded to one extra call; returns the
* split { reasoning, answer } or null on any failure (caller keeps the original).
*/
private async retryToolTurn(
cred: MaxaiCredential,
accessToken: string,
input: ExecuteInput,
nudgedText: string
): Promise<{ reasoning: string; answer: string } | null> {
try {
const constants = await ensureMaxaiConstants({ signal: input.signal });
if (!constants) return null;
const retryBody = buildMaxaiChatBody({
conversationId: newConversationId(),
text: nudgedText,
modelName: input.model,
appVersion: constants.appVersion,
});
const headers: Record<string, string> = {
...maxaiStaticHeaders(),
...buildMaxaiSignedHeaders(
{
path: MAXAI_CHAT_PATH,
userId: cred.userId,
deviceId: cred.deviceId,
},
constants
),
Authorization: `Bearer ${accessToken}`,
...(input.upstreamExtraHeaders ?? {}),
};
const res = await fetch(MAXAI_BASE_URL + MAXAI_CHAT_PATH, {
method: "POST",
headers,
body: JSON.stringify(retryBody),
signal: input.signal ?? undefined,
});
if (res.status !== 200 || !res.body) return null;
return collectNonStream(await res.text());
} catch {
return null;
}
}
/** Bridge the MaxAI SSE body into an OpenAI chat.completion.chunk stream. */
private buildStream(
source: ReadableStream<Uint8Array>,
id: string,
created: number,
model: string,
promptTokens: number
): ReadableStream {
const splitter = new ThinkSplitter();
const decoder = new TextDecoder();
let sseBuf = "";
let sentRole = false;
let completionChars = 0;
const emitDelta = (controller: ReadableStreamDefaultController, r: string, a: string) => {
if (!sentRole && (r || a)) {
chunk(controller, id, created, model, { role: "assistant" });
sentRole = true;
}
if (r) {
chunk(controller, id, created, model, { reasoning_content: r });
completionChars += r.length;
}
if (a) {
chunk(controller, id, created, model, { content: a });
completionChars += a.length;
}
};
const processFrame = (controller: ReadableStreamDefaultController, jsonStr: string) => {
if (!jsonStr || jsonStr === "[DONE]") return;
let frame: unknown;
try {
frame = JSON.parse(jsonStr);
} catch {
return;
}
if (isMaxaiTextFrame(frame)) {
const { reasoning, answer } = splitter.feed(frame.text);
emitDelta(controller, reasoning, answer);
}
};
return new ReadableStream({
async start(controller) {
const reader = source.getReader();
try {
for (;;) {
const { done, value } = await reader.read();
if (done) break;
sseBuf += decoder.decode(value, { stream: true });
let nl: number;
while ((nl = sseBuf.indexOf("\n")) !== -1) {
const line = sseBuf.slice(0, nl).trim();
sseBuf = sseBuf.slice(nl + 1);
if (line.startsWith("data:")) processFrame(controller, line.slice(5).trim());
}
}
// flush held tail from the think splitter
const tail = splitter.flush();
emitDelta(controller, tail.reasoning, tail.answer);
// final chunk with usage + finish
const completionTokens = estimateMaxaiTokens("x".repeat(completionChars));
const finalChunk = {
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
usage: {
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
total_tokens: promptTokens + completionTokens,
},
};
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(finalChunk)}\n\n`));
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
controller.close();
} catch (err) {
try {
controller.error(err);
} catch {
/* already errored */
}
} finally {
reader.releaseLock();
}
},
});
}
}
/** Collect a full MaxAI SSE body into split { reasoning, answer } (non-stream). */
function collectNonStream(raw: string): { reasoning: string; answer: string } {
const splitter = new ThinkSplitter();
let reasoning = "";
let answer = "";
for (const line of raw.split("\n")) {
const s = line.trim();
if (!s.startsWith("data:")) continue;
const js = s.slice(5).trim();
if (!js || js === "[DONE]") continue;
let frame: unknown;
try {
frame = JSON.parse(js);
} catch {
continue;
}
if (isMaxaiTextFrame(frame)) {
const out = splitter.feed(frame.text);
reasoning += out.reasoning;
answer += out.answer;
}
}
const tail = splitter.flush();
return { reasoning: reasoning + tail.reasoning, answer: answer + tail.answer };
}

View File

@@ -1,76 +0,0 @@
/**
* MaxAI model catalog + provider-enum mapping. Ported from the MaxAI v3 client
* (catalog/context_windows.py, tools/provider_enum.py). All 13 chat models are
* PAID (the free `mistral-7b-instruct-free` is a window-lookup fallback only and
* is not offered). Context windows are the MaxAI-reported values.
*/
import type { RegistryModel } from "../../config/providers/shared.ts";
interface MaxaiModelSpec {
id: string;
name: string;
contextLength: number;
supportsReasoning?: boolean;
/**
* Vision-capable (accepts image_url input). Sourced from MaxAI's live
* `/models/get_config` `capabilities.vision` (verified 2026-08); the executor
* forwards image parts inline in message_content for these. Live discovery
* (services/maxaiModels.ts) overrides this from the catalog at runtime; this
* static flag keeps the offline registry in agreement.
*/
supportsVision?: boolean;
}
/** The 13 offered paid chat models (group order: FAST, SMART, REASONING). */
export const MAXAI_MODELS: MaxaiModelSpec[] = [
// FAST
{ id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextLength: 1_050_000, supportsVision: true },
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5", contextLength: 200_000, supportsVision: true },
{ id: "gemini-3-1-flash-lite", name: "Gemini 3.1 Flash Lite", contextLength: 1_000_000, supportsVision: true },
{ id: "grok-4-1-fast-non-reasoning", name: "Grok 4.1 Fast", contextLength: 2_000_000 },
{ id: "llama-3.3-70b", name: "Llama 3.3 70B", contextLength: 128_000 },
{ id: "deepseek-v3.2", name: "DeepSeek V3.2", contextLength: 128_000 },
// SMART
{ id: "gpt-5.6", name: "GPT-5.6", contextLength: 1_050_000, supportsVision: true },
{ id: "claude-5-sonnet", name: "Claude 5 Sonnet", contextLength: 1_000_000 },
{
id: "grok-4-1-fast-reasoning",
name: "Grok 4.1 Fast (Reasoning)",
contextLength: 2_000_000,
supportsReasoning: true,
},
// REASONING
{
id: "gpt-5.6-thinking",
name: "GPT-5.6 Thinking",
contextLength: 1_050_000,
supportsReasoning: true,
supportsVision: true,
},
{
id: "gemini-3.1-pro-preview",
name: "Gemini 3.1 Pro Preview",
contextLength: 1_000_000,
supportsReasoning: true,
supportsVision: true,
},
{ id: "grok-4.5", name: "Grok 4.5", contextLength: 500_000, supportsReasoning: true },
{ id: "deepseek-r1", name: "DeepSeek R1", contextLength: 128_000, supportsReasoning: true },
];
/** RegistryModel[] form for the provider registry entry. */
export const MAXAI_REGISTRY_MODELS: RegistryModel[] = MAXAI_MODELS.map((m) => ({
id: m.id,
name: m.name,
contextLength: m.contextLength,
toolCalling: true, // prompted tool-calling (no native API, but supported via the tool protocol)
...(m.supportsReasoning ? { supportsReasoning: true } : {}),
...(m.supportsVision ? { supportsVision: true } : {}),
}));
/** Default context window for an unknown model. */
export const MAXAI_DEFAULT_CONTEXT = 128_000;
export function maxaiContextWindow(modelId: string): number {
return MAXAI_MODELS.find((m) => m.id === modelId)?.contextLength ?? MAXAI_DEFAULT_CONTEXT;
}

View File

@@ -1,427 +0,0 @@
/**
* MaxAI web-app signing constants — extracted live from the public JS bundle.
*
* MaxAI's request signer needs a small set of CLIENT-SIDE constants that its own
* front-end ships VERBATIM in the public `www.maxai.co` JavaScript bundle
* (identical for every visitor, no per-user or server secret). OmniRoute EXTRACTS
* them from the live bundle and persists them, so if MaxAI ever rotates a value —
* or a Next.js rebuild renumbers its chunks — the provider self-heals on the next
* login or daily refresh instead of hard-failing every signed call.
*
* NOTHING id/key/version-shaped is hardcoded anywhere (source OR tests). Every
* such value (hmacKey, aesKey, docIdKey, ctxKey, appVersion) is discovered at
* runtime and validated; the repo carries no scannable secret and no build-
* specific chunk number.
*
* WHAT is extracted, and from WHERE (all are plain, public static assets):
* pages/_app-*.js — the Next.js app-entry chunk (framework-STABLE name, not a
* MaxAI chunk number). Webpack module 69319 inside it defines the constants as
* export getters we follow to their string literals:
* - hmacKey export `Mn` → a hex string (HMAC-SHA1 → SM3 keying)
* - aesKey export `Rl` → a hex string (CryptoJS AES passphrase)
* - docIdKey export `U0` → a UUID (doc-upload HMAC key)
* - appVersion the sole `webpage_x.y.z` literal (folded into the sign_str)
* the SIGNER chunk — a NUMBERED chunk whose id changes across builds, so it is
* located by CONTENT FINGERPRINT (never by number): the chunk that assembles
* the signed payload, recognised by the ctx-slot pattern `"<40hex>":{a:…}` next
* to the `(0,r.nj)("<hex>")` header-name decoders. From it we read:
* - ctxKey the 40-hex payload content-slot label
* - headerNames the `nj("<hex>")` calls = hex→ASCII header/slot names
*
* The extracted set is SHAPE-validated (hex/UUID/version regexes) before it is
* trusted; the ULTIMATE validation is the first live signed call (a wrong value
* is rejected by MaxAI, which triggers a re-extract). Only the plain, non-secret
* HTTP header NAMES (e.g. "X-Authorization") keep in-code defaults, so a transient
* miss on the signer chunk can't break a signer that already has valid keys;
* extraction still overrides them when present.
*/
import { createHmac, createHash } from "node:crypto";
/** The public bundle base. `/app/` is the SPA entry that references the chunks. */
export const MAXAI_WEBAPP_ORIGIN = "https://www.maxai.co";
export const MAXAI_WEBAPP_APP_PATH = "/app/";
/** Settings key under which the extracted constants bundle is persisted. */
export const MAXAI_CONSTANTS_SETTINGS_KEY = "maxaiSigningConstants";
/** Firefox-150 UA used for the (unauthenticated) static-asset fetches. */
const FETCH_UA =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0";
/**
* The header/slot NAMES the signer emits. These are standard HTTP header names
* (not secrets, not id/key/version-shaped), so in-code defaults are appropriate;
* extraction overrides any that the signer chunk exposes.
*/
export interface MaxaiHeaderNames {
authorization: string; // "X-Authorization"
clientDomain: string; // "X-Client-Domain"
clientPath: string; // "X-Client-Path"
random: string; // "X-Random"
browserName: string; // "X-Browser-Name"
browserVersion: string; // "X-Browser-Version"
browserMajor: string; // "X-Browser-Major"
appVersionHeader: string; // "X-App-Version"
appEnvHeader: string; // "X-App-Env"
appEnvValue: string; // "MaxAI-Browser-Extension"
tSlot: string; // "t"
pSlot: string; // "p"
dSlot: string; // "d"
}
/** The full set of signing constants the MaxAI signer depends on. */
export interface MaxaiSigningConstants {
/** HMAC-SHA1 → SM3 keying material (extracted; no in-code default). */
hmacKey: string;
/** CryptoJS AES passphrase (extracted; no in-code default). */
aesKey: string;
/** Version string folded into the signature `sign_str` (extracted). */
appVersion: string;
/** Payload content-slot label, 40-hex (extracted; no in-code default). */
ctxKey: string;
/** Doc-upload HMAC key, UUID (extracted; no in-code default). */
docIdKey: string;
/** Header/slot names emitted by the signer. */
headerNames: MaxaiHeaderNames;
/** Provenance for the persisted record. */
source?: "extracted";
extractedAt?: number;
}
/**
* Default HTTP header NAMES (standard, non-secret labels). Extraction overrides
* any the signer chunk exposes; these keep a signer with valid keys working even
* if the signer chunk momentarily can't be located.
*/
export const MAXAI_DEFAULT_HEADER_NAMES: MaxaiHeaderNames = {
authorization: "X-Authorization",
clientDomain: "X-Client-Domain",
clientPath: "X-Client-Path",
random: "X-Random",
browserName: "X-Browser-Name",
browserVersion: "X-Browser-Version",
browserMajor: "X-Browser-Major",
appVersionHeader: "X-App-Version",
appEnvHeader: "X-App-Env",
appEnvValue: "MaxAI-Browser-Extension",
tSlot: "t",
pSlot: "p",
dSlot: "d",
};
/** Raw pieces the parser can pull from the two chunks (any may be absent). */
export interface MaxaiParsedConstants {
hmacKey: string | null;
aesKey: string | null;
appVersion: string | null;
ctxKey: string | null;
docIdKey: string | null;
headerNames: Partial<MaxaiHeaderNames>;
}
/** Resolve a webpack export getter `Name:function(){return VAR}` → the `VAR="…"` literal. */
export function resolveWebpackGetter(src: string, exportName: string): string | null {
const getter = new RegExp(
`${exportName}\\s*:\\s*function\\s*\\(\\)\\s*\\{\\s*return\\s+([A-Za-z_$][\\w$]*)\\s*\\}`
);
let m = src.match(getter);
if (!m) {
const arrow = new RegExp(`${exportName}\\s*:\\s*\\(\\)\\s*=>\\s*([A-Za-z_$][\\w$]*)`);
m = src.match(arrow);
}
if (!m) return null;
const varName = m[1];
const assign = new RegExp(`\\b${varName}\\s*=\\s*"([^"]+)"`);
const am = src.match(assign);
return am ? am[1] : null;
}
/** Decode the `(0,r.nj)("<hex>")` header-name calls (nj = hex→ASCII). */
export function decodeNjHeaderNames(signerChunk: string): string[] {
const out = new Set<string>();
for (const m of signerChunk.matchAll(/nj\)\("([0-9a-f]+)"\)/g)) {
try {
const decoded = Buffer.from(m[1], "hex").toString("utf8");
// Keep only printable ASCII header-ish tokens (drop numeric ja3 codes etc).
if (/^[\x20-\x7e]+$/.test(decoded)) out.add(decoded);
} catch {
// skip malformed hex
}
}
return [...out];
}
/** Map the decoded header-name list onto the structured MaxaiHeaderNames slots. */
function mapHeaderNames(decoded: string[]): Partial<MaxaiHeaderNames> {
const has = (v: string) => decoded.includes(v);
const out: Partial<MaxaiHeaderNames> = {};
if (has("X-Authorization")) out.authorization = "X-Authorization";
if (has("X-Client-Domain")) out.clientDomain = "X-Client-Domain";
if (has("X-Client-Path")) out.clientPath = "X-Client-Path";
if (has("X-Random")) out.random = "X-Random";
if (has("X-Browser-Name")) out.browserName = "X-Browser-Name";
if (has("X-Browser-Version")) out.browserVersion = "X-Browser-Version";
if (has("X-Browser-Major")) out.browserMajor = "X-Browser-Major";
if (has("X-App-Version")) out.appVersionHeader = "X-App-Version";
if (has("X-App-Env")) out.appEnvHeader = "X-App-Env";
if (has("MaxAI-Browser-Extension")) out.appEnvValue = "MaxAI-Browser-Extension";
return out;
}
/** Extract the 40-hex payload content-slot label from the signer chunk. */
export function extractCtxKey(signerChunk: string): string | null {
return (signerChunk.match(/"([0-9a-f]{40})"\s*:\s*\{\s*a\s*:/) || [])[1] ?? null;
}
/**
* Content fingerprint for the SIGNER chunk (build-independent). The signer chunk
* is the one that both (a) carries the ctx payload slot `"<40hex>":{a:…}` and
* (b) decodes header names via `(0,r.nj)("<hex>")`. Matching BOTH avoids a false
* positive on any unrelated chunk that merely contains a 40-hex string.
*/
export function looksLikeSignerChunk(js: string): boolean {
return extractCtxKey(js) !== null && /nj\)\("[0-9a-f]+"\)/.test(js);
}
/**
* Parse the two bundle chunks into raw constants. Pure (no network) so it is
* unit-tested directly against synthetic fixtures.
*/
export function parseMaxaiConstants(
appChunk: string,
signerChunk: string
): MaxaiParsedConstants {
const decoded = decodeNjHeaderNames(signerChunk);
return {
hmacKey: resolveWebpackGetter(appChunk, "Mn"),
aesKey: resolveWebpackGetter(appChunk, "Rl"),
docIdKey: resolveWebpackGetter(appChunk, "U0"),
appVersion: (appChunk.match(/"(webpage_\d+\.\d+\.\d+)"/) || [])[1] ?? null,
ctxKey: extractCtxKey(signerChunk),
headerNames: mapHeaderNames(decoded),
};
}
/** A MaxAI signing key is a 40+ char lowercase hex string. */
function isHexKey(v: string | null | undefined): boolean {
return typeof v === "string" && /^[0-9a-f]{40,}$/.test(v);
}
/** A doc-id key is a UUID (v4-shaped). */
function isUuidKey(v: string | null | undefined): boolean {
return typeof v === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(v);
}
/** A MaxAI app_version tag looks like `webpage_x.y.z`. */
function isAppVersion(v: string | null | undefined): boolean {
return typeof v === "string" && /^webpage_\d+\.\d+\.\d+$/.test(v);
}
/**
* Fold parsed pieces into a full constants object. The five extracted values
* (hmacKey, aesKey, ctxKey, docIdKey, appVersion) are ALL required and must be
* well-formed — return null otherwise, so we never persist a half-configured
* signer. Only the plain HTTP header names fall back to the standard defaults.
*/
export function assembleMaxaiConstants(
parsed: MaxaiParsedConstants
): MaxaiSigningConstants | null {
if (!isHexKey(parsed.hmacKey) || !isHexKey(parsed.aesKey)) return null;
if (!isHexKey(parsed.ctxKey)) return null;
if (!isUuidKey(parsed.docIdKey)) return null;
if (!isAppVersion(parsed.appVersion)) return null;
return {
hmacKey: parsed.hmacKey as string,
aesKey: parsed.aesKey as string,
appVersion: parsed.appVersion as string,
ctxKey: parsed.ctxKey as string,
docIdKey: parsed.docIdKey as string,
headerNames: { ...MAXAI_DEFAULT_HEADER_NAMES, ...parsed.headerNames },
source: "extracted",
extractedAt: Date.now(),
};
}
/** True when a constants object is structurally well-formed (all 5 values valid). */
export function isValidConstantsShape(c: MaxaiSigningConstants | null | undefined): boolean {
if (!c) return false;
return (
isHexKey(c.hmacKey) &&
isHexKey(c.aesKey) &&
isHexKey(c.ctxKey) &&
isUuidKey(c.docIdKey) &&
isAppVersion(c.appVersion) &&
!!c.headerNames
);
}
/**
* A signature vector: a (path, reqTime, userId, appVersion) tuple and the SM3
* proof it should produce. Used to prove the signing ALGORITHM in unit tests with
* mock keys — the runtime does NOT embed any real vector (its trust anchor is the
* live signed probe). `reproduceProof` is a pure helper over the same math.
*/
export interface MaxaiSignatureVector {
path: string;
reqTime: number;
userId: string;
appVersion: string;
expectedProof: string;
}
/** Reproduce the SM3 proof `p` for a (path, reqTime, userId, appVersion) under a key. */
export function reproduceProof(
hmacKey: string,
vector: Omit<MaxaiSignatureVector, "expectedProof">
): string {
const signStr = `${vector.appVersion}:${vector.reqTime}:${vector.path}:${vector.userId}`;
const sha1 = createHmac("sha1", Buffer.from(`${vector.reqTime}:${hmacKey}`, "utf8"))
.update(Buffer.from(signStr, "utf8"))
.digest("hex");
return createHash("sm3")
.update(Buffer.from(`${vector.reqTime}:${sha1}:${hmacKey}`, "utf8"))
.digest("hex");
}
/**
* Runtime validation of an extracted/stored constants set. SHAPE-based on purpose:
* we carry no real signature vector in source, so the definitive check is the
* first live signed call (a wrong value is rejected by MaxAI → re-extract). An
* optional `vector` enables proof-based checking in tests with mock keys.
*/
export function validateMaxaiConstants(
constants: MaxaiSigningConstants,
vector?: MaxaiSignatureVector
): boolean {
if (!isValidConstantsShape(constants)) return false;
if (!vector) return true;
try {
return reproduceProof(constants.hmacKey, vector) === vector.expectedProof;
} catch {
return false;
}
}
/**
* Fetch a text asset with the Firefox UA through the ambient (residential) fetch.
* Injectable for tests. Returns "" on any failure (caller treats empty as miss).
*/
async function fetchText(
url: string,
fetchImpl: typeof fetch,
signal?: AbortSignal | null
): Promise<string> {
try {
const res = await fetchImpl(url, {
headers: { "User-Agent": FETCH_UA, Accept: "*/*" },
signal: signal ?? undefined,
});
if (!res.ok) return "";
return await res.text();
} catch {
return "";
}
}
/** All `/_next/static/chunks/...js` URLs referenced by the app HTML, in order. */
export function allChunkUrls(html: string): string[] {
const seen = new Set<string>();
const out: string[] = [];
for (const m of html.matchAll(/\/_next\/static\/chunks\/[A-Za-z0-9/_-]+\.js/g)) {
if (!seen.has(m[0])) {
seen.add(m[0]);
out.push(m[0]);
}
}
return out;
}
/**
* From the `/app/` HTML, resolve the app-entry chunk (by its stable Next.js
* `pages/_app-*.js` name) and the list of candidate numbered chunks to scan for
* the signer chunk BY CONTENT. No specific chunk number is ever assumed.
*/
export function findChunkUrls(html: string): {
appChunk: string | null;
candidateChunks: string[];
} {
const urls = allChunkUrls(html);
let appChunk: string | null = null;
const candidateChunks: string[] = [];
for (const p of urls) {
if (/\/pages\/_app-[a-z0-9]+\.js$/i.test(p)) {
appChunk = p;
} else if (/\/chunks\/[A-Za-z0-9]+-[a-z0-9]+\.js$/i.test(p)) {
// Any hashed vendor/number chunk is a signer-chunk candidate; we identify
// the real one by content, not by its (build-specific) name.
candidateChunks.push(p);
}
}
return { appChunk, candidateChunks };
}
export interface FetchConstantsOptions {
fetchImpl?: typeof fetch;
signal?: AbortSignal | null;
/** Override the origin (tests). */
origin?: string;
/** Cap on how many candidate chunks to scan for the signer chunk (default 80). */
maxScanChunks?: number;
}
/**
* Locate + fetch the signer chunk text by CONTENT (never by number): scan the
* candidate chunks referenced in the app HTML and return the first whose content
* matches the signer fingerprint (ctx slot + nj header decoders). A MaxAI-side
* chunk renumber is therefore self-healing, not a break.
*/
async function fetchSignerChunk(
origin: string,
candidates: string[],
fetchImpl: typeof fetch,
signal: AbortSignal | null | undefined,
maxScan: number
): Promise<string> {
for (const c of candidates.slice(0, maxScan)) {
const js = await fetchText(origin + c, fetchImpl, signal);
if (js && looksLikeSignerChunk(js)) return js;
}
return "";
}
/**
* Fetch + parse the live constants from MaxAI's public bundle. Returns a fully
* assembled, SHAPE-validated constants object, or null on any failure (network,
* missing chunk, unparseable, malformed values). Never throws. The definitive
* key validation is the caller's first live signed call.
*/
export async function fetchMaxaiConstants(
opts: FetchConstantsOptions = {}
): Promise<MaxaiSigningConstants | null> {
const fetchImpl = opts.fetchImpl ?? fetch;
const origin = opts.origin ?? MAXAI_WEBAPP_ORIGIN;
const maxScan = opts.maxScanChunks ?? 80;
const html = await fetchText(origin + MAXAI_WEBAPP_APP_PATH, fetchImpl, opts.signal);
if (!html) return null;
const { appChunk, candidateChunks } = findChunkUrls(html);
if (!appChunk) return null;
const appJs = await fetchText(origin + appChunk, fetchImpl, opts.signal);
if (!appJs) return null;
const signerJs = await fetchSignerChunk(
origin,
candidateChunks,
fetchImpl,
opts.signal,
maxScan
);
const parsed = parseMaxaiConstants(appJs, signerJs);
const assembled = assembleMaxaiConstants(parsed);
if (!assembled) return null;
if (!validateMaxaiConstants(assembled)) return null;
return assembled;
}

View File

@@ -1,156 +0,0 @@
/**
* MaxAI signing-constants store + `ensure` gate.
*
* This is the persistence + freshness layer around ./constants.ts:
* - `getStoredMaxaiConstants()` reads the last-extracted, validated constants
* from OmniRoute settings (the sole source of the two secret-shaped keys).
* - `persistMaxaiConstants()` writes a freshly-extracted+validated set.
* - `ensureMaxaiConstants()` is the gate every signed path calls: it returns a
* usable constants object, extracting + persisting on a cold store, and is
* cheap (in-process memo) on the hot path.
* - `refreshMaxaiConstants()` force re-extracts (used by the daily token
* refresh) so a MaxAI-side rotation is picked up within a day.
*
* Design (William's Option 2): there is NO hardcoded fallback for the secret
* keys. If the store is empty AND a live extraction cannot be validated, the
* signer has no keys and MaxAI is simply unconfigured (callers surface a clear
* auth error) — we never sign with a guessed/stale secret.
*/
import type { MaxaiSigningConstants, FetchConstantsOptions } from "./constants.ts";
import {
MAXAI_CONSTANTS_SETTINGS_KEY,
fetchMaxaiConstants,
validateMaxaiConstants,
MAXAI_DEFAULT_HEADER_NAMES,
} from "./constants.ts";
/** In-process memo so the hot signing path never touches the DB or network. */
let memo: MaxaiSigningConstants | null = null;
let inflight: Promise<MaxaiSigningConstants | null> | null = null;
/** Reset the in-process memo (tests + after a forced refresh). */
export function resetMaxaiConstantsMemo(): void {
memo = null;
inflight = null;
}
/**
* Test seam: directly seed the in-process memo so unit tests that exercise the
* signed network functions don't need to also mock the bundle fetch. Not used in
* production paths (production goes through ensure/refresh → store → extraction).
*/
export function __setMaxaiConstantsForTest(constants: MaxaiSigningConstants | null): void {
memo = constants;
inflight = null;
}
/** Shape-guard a persisted record before trusting it. */
function isUsableConstants(v: unknown): v is MaxaiSigningConstants {
if (!v || typeof v !== "object") return false;
const c = v as Partial<MaxaiSigningConstants>;
return (
typeof c.hmacKey === "string" &&
typeof c.aesKey === "string" &&
typeof c.appVersion === "string" &&
typeof c.ctxKey === "string" &&
typeof c.docIdKey === "string" &&
!!c.headerNames &&
typeof c.headerNames === "object"
);
}
/** Read the persisted constants from settings (validated). Null when absent/invalid. */
export async function getStoredMaxaiConstants(): Promise<MaxaiSigningConstants | null> {
try {
const { getSettings } = await import("@/lib/db/settings");
const settings = await getSettings();
const raw = (settings as Record<string, unknown>)[MAXAI_CONSTANTS_SETTINGS_KEY];
if (!isUsableConstants(raw)) return null;
// Re-validate on read: a persisted record must still reproduce the vector.
const withDefaults: MaxaiSigningConstants = {
...raw,
headerNames: { ...MAXAI_DEFAULT_HEADER_NAMES, ...raw.headerNames },
};
return validateMaxaiConstants(withDefaults) ? withDefaults : null;
} catch {
return null;
}
}
/** Persist a freshly-extracted+validated constants set to settings. */
export async function persistMaxaiConstants(
constants: MaxaiSigningConstants
): Promise<void> {
try {
const { updateSettings } = await import("@/lib/db/settings");
await updateSettings({ [MAXAI_CONSTANTS_SETTINGS_KEY]: constants });
} catch {
// Non-fatal: a persist failure just means the next process re-extracts.
}
}
/**
* Return usable MaxAI signing constants, extracting + persisting on a cold store.
* Order: in-process memo → persisted store → live extraction (validated) → null.
* Concurrent callers share a single in-flight extraction. Never throws.
*/
export async function ensureMaxaiConstants(
opts: FetchConstantsOptions = {}
): Promise<MaxaiSigningConstants | null> {
if (memo) return memo;
const stored = await getStoredMaxaiConstants();
if (stored) {
memo = stored;
return memo;
}
if (inflight) return inflight;
inflight = (async () => {
try {
const fresh = await fetchMaxaiConstants(opts);
if (fresh) {
memo = fresh;
await persistMaxaiConstants(fresh);
return fresh;
}
return null;
} finally {
inflight = null;
}
})();
return inflight;
}
/**
* Force a live re-extraction (used by the daily token refresh). If the fetched
* set validates AND differs from what's stored, it is persisted + memoized so a
* MaxAI-side rotation is picked up. Returns the current-best constants (the fresh
* set on success, else whatever was already stored/memoized). Never throws.
*/
export async function refreshMaxaiConstants(
opts: FetchConstantsOptions = {}
): Promise<MaxaiSigningConstants | null> {
let fresh: MaxaiSigningConstants | null = null;
try {
fresh = await fetchMaxaiConstants(opts);
} catch {
fresh = null;
}
if (fresh) {
const changed =
!memo ||
memo.hmacKey !== fresh.hmacKey ||
memo.aesKey !== fresh.aesKey ||
memo.appVersion !== fresh.appVersion ||
memo.ctxKey !== fresh.ctxKey ||
memo.docIdKey !== fresh.docIdKey;
memo = fresh;
if (changed) await persistMaxaiConstants(fresh);
return fresh;
}
// Fetch failed — keep serving whatever we already have (memo or store).
return memo ?? (await getStoredMaxaiConstants());
}

View File

@@ -1,96 +0,0 @@
/**
* MaxAI connection credential resolution.
*
* MaxAI's request signer needs three things bound together: the OpenAI-style
* `access_token` (Bearer, ~24h), the `device_id` that minted it (embedded in the
* signed `X-Authorization` — a mismatch is rejected), and the `user_id` (folded
* into the signature proof). OmniRoute stores these in the connection's
* `providerSpecificData` (minted by OmniRoute's own browser-mint flow — see
* maxaiBrowserLogin), so the router is self-contained and never reads any
* external (Hermes) token file.
*
* The access token is refreshed out-of-band by the browser-mint (the
* `/oauth/refresh_access_token` endpoint is deep-TLS-gated and cannot be called
* by any HTTP client — only a real browser passes), so this module only READS
* the stored credential; it does not attempt an HTTP refresh.
*/
export interface MaxaiCredential {
accessToken: string;
deviceId: string;
userId: string;
/** ~1-year refresh token used for browserless access-token refresh (optional). */
refreshToken?: string;
}
type ProviderSpecificData = Record<string, unknown> | null | undefined;
function firstString(...values: unknown[]): string | null {
for (const v of values) {
if (typeof v === "string") {
// Raw browser LocalStorage sometimes wraps the device id in quotes.
const trimmed = v.trim().replace(/^"|"$/g, "");
if (trimmed.length > 0) return trimmed;
}
}
return null;
}
/** Decode the `user_id` from a MaxAI access JWT (subject.user_id or sub). No verify. */
export function userIdFromJwt(accessToken: string): string | null {
try {
const seg = accessToken.split(".")[1];
if (!seg) return null;
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (seg.length % 4)) % 4);
const claims = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
const subject = claims?.subject as { user_id?: unknown } | undefined;
if (typeof subject?.user_id === "string") return subject.user_id;
if (typeof claims?.sub === "string") return claims.sub;
return null;
} catch {
return null;
}
}
/** Epoch seconds of the access-JWT `exp`, or 0 when undecodable. */
export function accessTokenExpiry(accessToken: string): number {
try {
const seg = accessToken.split(".")[1];
if (!seg) return 0;
const b64 = seg.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (seg.length % 4)) % 4);
const claims = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
return typeof claims?.exp === "number" ? claims.exp : 0;
} catch {
return 0;
}
}
/**
* Resolve the MaxAI credential from a connection's providerSpecificData (with the
* OpenAI-style `access_token` optionally supplied separately by the caller, which
* is how OmniRoute threads the stored connection token). Returns null when not
* fully configured (all three of accessToken/deviceId/userId required).
*/
export function resolveMaxaiCredential(
psd: ProviderSpecificData,
accessTokenFromConnection?: string | null
): MaxaiCredential | null {
const accessToken = firstString(
accessTokenFromConnection,
psd?.maxaiAccessToken,
psd?.accessToken
);
if (!accessToken) return null;
const deviceId = firstString(psd?.maxaiDeviceId, psd?.deviceId);
if (!deviceId) return null;
const userId =
firstString(psd?.maxaiUserId, psd?.userId) ?? userIdFromJwt(accessToken);
if (!userId) return null;
const refreshToken =
firstString(psd?.maxaiRefreshToken, psd?.refreshToken) ?? undefined;
return { accessToken, deviceId, userId, refreshToken };
}

View File

@@ -1,266 +0,0 @@
/**
* MaxAI doc-RAG — inline document parts → /app/upload_document → doc_list.
*
* OmniRoute delivers attached documents INLINE in the chat request as base64
* `file_data` content parts (OpenAI `{type:"file",file:{filename,file_data}}` /
* Responses `{type:"input_file",file_data}` / Claude `{type:"document",source}`).
* MaxAI's `/gpt/cwc/chat` cannot take binary docs inline; instead it references
* uploaded documents by a content-addressed `doc_id`. This module bridges the
* two: it detects inline base64 doc parts on the current turn, uploads each via
* the multipart `/app/upload_document` endpoint (signed like every MaxAI call),
* and returns the `doc_list` entries to attach to the chat body.
*
* doc_id is NOT random — MaxAI requires `doc_id = HMAC-SHA1(file_bytes, IT)` hex
* (createDocId/qM in the extension). A random id is rejected with a 400
* "Inconsistency between server doc_id and request doc_id". The IT key is a
* public web-app constant (ships in the bundle), same class as the signing
* constants; kept here as a named constant (not a secret).
*
* The doc_list item shape is exactly what the live web app sends
* (site chunk 41068): `{ doc_id, doc_type, file_name }`.
*/
import { createHmac } from "node:crypto";
import { buildMaxaiSignedHeaders } from "./signing.ts";
import { ensureMaxaiConstants } from "./constantsStore.ts";
import { maxaiStaticHeaders, MAXAI_BASE_URL } from "./protocol.ts";
export const MAXAI_UPLOAD_PATH = "/app/upload_document";
export interface MaxaiDocListEntry {
doc_id: string;
doc_type: string;
file_name: string;
}
/** An inline document extracted from an OpenAI/Responses/Claude content part. */
export interface InlineDoc {
filename: string;
mimeType: string;
bytes: Buffer;
}
/** doc_id = HMAC-SHA1(file_bytes, docIdKey) hex. Content-addressed; MaxAI verifies it. */
export function computeMaxaiDocId(bytes: Buffer, key: string): string {
if (!key) throw new Error("computeMaxaiDocId: missing docIdKey");
return createHmac("sha1", key).update(bytes).digest("hex");
}
const TEXTUAL_EXT = /\.(txt|md|markdown|csv|json|log|xml|yaml|yml|tsv)$/i;
const CODE_EXT =
/\.(py|ipynb|js|jsx|ts|tsx|html?|css|java|cs|php|c|cpp|cxx|h|hpp|go|rs|rb|swift|kt|sh|sql)$/i;
/** Classify the MaxAI doc_type from the filename/mime (extension taxonomy). */
export function maxaiDocType(filename: string, mimeType: string): string {
const f = filename.toLowerCase();
if (/\.pdf$/i.test(f) || mimeType === "application/pdf") return "page_content__pdf";
if (CODE_EXT.test(f)) return "chat_file_code";
return "chat_file"; // text / generic
}
/** Whether a doc_type requires the pure_text field (text-extractable docs). */
function requiresPureText(docType: string): boolean {
return docType === "chat_file" || docType === "chat_file_code";
}
/**
* Parse an OpenAI/Responses/Claude data-URL into raw bytes + mime. Returns null
* for anything that isn't an inline base64 payload (e.g. a remote URL or an
* already-uploaded file_id reference, which this bridge does not handle).
*/
export function parseInlineDataUrl(dataUrl: unknown): { mimeType: string; bytes: Buffer } | null {
if (typeof dataUrl !== "string") return null;
const m = /^data:([^;,]*)(;base64)?,(.*)$/s.exec(dataUrl);
if (!m) return null;
const mimeType = m[1] || "application/octet-stream";
const isBase64 = !!m[2];
try {
const bytes = isBase64
? Buffer.from(m[3], "base64")
: Buffer.from(decodeURIComponent(m[3]), "utf8");
if (bytes.length === 0) return null;
return { mimeType, bytes };
} catch {
return null;
}
}
/**
* Extract inline documents from the CURRENT (last user) turn of an OpenAI
* messages[] array. Recognizes the three OmniRoute-delivered shapes:
* OpenAI Chat: {type:"file", file:{filename, file_data|data}}
* Responses: {type:"input_file", filename, file_data}
* Claude: {type:"document", source:{type:"base64", media_type, data}}
* Only base64/data-URL payloads are handled (a bridge upload needs the bytes).
*/
export function extractCurrentTurnDocs(
messages: Array<{ role?: string; content?: unknown }>
): InlineDoc[] {
let content: unknown;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i]?.role === "user") {
content = messages[i]?.content;
break;
}
}
if (!Array.isArray(content)) return [];
const docs: InlineDoc[] = [];
for (const part of content) {
if (!part || typeof part !== "object") continue;
const p = part as Record<string, unknown>;
const type = p.type;
if (type === "file" && p.file && typeof p.file === "object") {
const file = p.file as Record<string, unknown>;
const filename = typeof file.filename === "string" ? file.filename : "upload.bin";
const raw = (file.file_data ?? file.data) as unknown;
const parsed = parseInlineDataUrl(raw);
if (parsed) docs.push({ filename, mimeType: parsed.mimeType, bytes: parsed.bytes });
} else if (type === "input_file") {
const filename = typeof p.filename === "string" ? p.filename : "upload.bin";
const parsed = parseInlineDataUrl(p.file_data);
if (parsed) docs.push({ filename, mimeType: parsed.mimeType, bytes: parsed.bytes });
} else if (type === "document" && p.source && typeof p.source === "object") {
const source = p.source as Record<string, unknown>;
if (source.type === "base64" && typeof source.data === "string") {
const mimeType =
typeof source.media_type === "string" ? source.media_type : "application/octet-stream";
try {
const bytes = Buffer.from(source.data, "base64");
if (bytes.length > 0) {
const filename =
typeof p.title === "string" && p.title ? p.title : `document.${mimeExt(mimeType)}`;
docs.push({ filename, mimeType, bytes });
}
} catch {
/* skip malformed base64 */
}
}
}
}
return docs;
}
function mimeExt(mime: string): string {
if (mime === "application/pdf") return "pdf";
if (mime.startsWith("text/")) return "txt";
return "bin";
}
/** Rough ~4-chars/token estimate; ceil, never 0 for non-empty text. */
function estimateTokens(text: string): number {
return text ? Math.max(1, Math.ceil(text.length / 4)) : 0;
}
/** Build the multipart/form-data body for /app/upload_document (fixed boundary). */
export function buildUploadMultipart(
doc: InlineDoc,
docId: string,
docType: string,
boundary: string
): Buffer {
const isTextual =
requiresPureText(docType) &&
(TEXTUAL_EXT.test(doc.filename) ||
CODE_EXT.test(doc.filename) ||
doc.mimeType.startsWith("text/"));
const pureText = isTextual ? doc.bytes.toString("utf8") : "";
const tokens = String(estimateTokens(pureText));
const parts: Buffer[] = [];
const dash = `--${boundary}\r\n`;
const field = (name: string, value: string): void => {
parts.push(
Buffer.from(`${dash}Content-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`)
);
};
field("doc_id", docId);
field("doc_type", docType);
field("pure_text", pureText);
field("tokens", tokens);
field("doc_type_dependent_data", "{}");
// The file part carries the raw bytes with a content-type.
parts.push(
Buffer.from(
`${dash}Content-Disposition: form-data; name="file"; filename="${doc.filename.replace(/"/g, "")}"\r\n` +
`Content-Type: ${doc.mimeType}\r\n\r\n`
)
);
parts.push(doc.bytes);
parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
return Buffer.concat(parts);
}
/** True if any SSE frame in the response is the terminal upload_done event. */
export function sawUploadDone(text: string): boolean {
return /"event"\s*:\s*"upload_done"/.test(text) || text.includes("upload_done");
}
/**
* Upload one inline document to MaxAI and return its doc_list entry, or null on
* failure (upload failures are non-fatal: the chat proceeds without the doc).
*/
export async function uploadMaxaiDocument(
doc: InlineDoc,
auth: { accessToken: string; userId: string; deviceId: string },
opts?: { fetchImpl?: typeof fetch; signal?: AbortSignal }
): Promise<MaxaiDocListEntry | null> {
const fetchImpl = opts?.fetchImpl ?? fetch;
const constants = await ensureMaxaiConstants({ fetchImpl, signal: opts?.signal });
if (!constants) return null;
const docId = computeMaxaiDocId(doc.bytes, constants.docIdKey);
const docType = maxaiDocType(doc.filename, doc.mimeType);
const boundary = `----maxai${Date.now().toString(16)}${Math.random().toString(16).slice(2)}`;
const bodyBuf = buildUploadMultipart(doc, docId, docType, boundary);
// Sign like any request, but DROP the JSON content-type so we can set the
// multipart boundary content-type ourselves (v3h.signed_headers pattern).
const { "Content-Type": _drop, ...staticHeaders } = maxaiStaticHeaders();
const headers: Record<string, string> = {
...staticHeaders,
...buildMaxaiSignedHeaders(
{ path: MAXAI_UPLOAD_PATH, userId: auth.userId, deviceId: auth.deviceId },
constants
),
Authorization: `Bearer ${auth.accessToken}`,
"Content-Type": `multipart/form-data; boundary=${boundary}`,
};
// Copy the multipart bytes into a fresh Uint8Array backed by a plain
// (non-shared) ArrayBuffer. `Buffer.buffer` is typed ArrayBufferLike
// (ArrayBuffer | SharedArrayBuffer) which isn't assignable to fetch's
// BodyInit; a freshly-allocated Uint8Array is the BodyInit shape the rest of
// the codebase uses for binary bodies (kimi-web.ts:397, conol-web.ts:529).
const bodyBytes = new Uint8Array(bodyBuf.byteLength);
bodyBytes.set(bodyBuf);
try {
const resp = await fetchImpl(MAXAI_BASE_URL + MAXAI_UPLOAD_PATH, {
method: "POST",
headers,
body: bodyBytes,
signal: opts?.signal,
});
if (!resp.ok) return null;
const text = await resp.text().catch(() => "");
if (!sawUploadDone(text)) return null;
return { doc_id: docId, doc_type: docType, file_name: doc.filename };
} catch {
return null;
}
}
/**
* Upload every inline document on the current turn and return the doc_list to
* attach to the chat body. Failures are skipped (best-effort); the chat still
* proceeds. Empty array when there are no inline docs.
*/
export async function resolveMaxaiDocList(
messages: Array<{ role?: string; content?: unknown }>,
auth: { accessToken: string; userId: string; deviceId: string },
opts?: { fetchImpl?: typeof fetch; signal?: AbortSignal }
): Promise<MaxaiDocListEntry[]> {
const docs = extractCurrentTurnDocs(messages);
if (docs.length === 0) return [];
const results = await Promise.all(docs.map((d) => uploadMaxaiDocument(d, auth, opts)));
return results.filter((r): r is MaxaiDocListEntry => r !== null);
}

View File

@@ -1,234 +0,0 @@
/**
* MaxAI email login — browserless, two signed HTTP calls (a codex-style
* device-pair flow, no browser / camoufox / Google navigation).
*
* MaxAI's web app offers email-code sign-in as an alternative to Google OAuth.
* Both steps are plain signed POSTs carrying the same per-request X-Authorization
* signature as every other MaxAI call (see ./signing.ts); both paths are in the
* signer's BLANK_USER_ROUTES (they sign with a blank user_id, correct — there is
* no user id yet before login). Ported byte-faithfully from the MaxAI web-app
* bundle (chunk 86042: signInWithEmail line ~5623, verifySecretCode line ~5665).
*
* Step 1 — request a code (POST /oauth/signin_with_email):
* body { email, app: "maxai_webapp" } -> { status: "OK" } (code emailed)
*
* Step 2 — verify the code (POST /oauth/verify_secret_code):
* body { email, secret_code, app: "maxai_webapp", env: "prod_co",
* client_user_id, ...nullable attribution fields }
* -> { auth_user: { accessToken, refreshToken, userId, email, clientUserId } }
*
* The `device_id` folded into the signature is a CLIENT-GENERATED UUID (the web
* app's getAPIFetchDeviceID = "return stored, else generate + persist"), so the
* caller mints one with randomUUID() and reuses it across BOTH steps and for all
* subsequent chat / refresh calls (the minted token is bound to that device id).
* `client_user_id` is likewise a client UUID.
*/
import { buildMaxaiSignedHeaders } from "./signing.ts";
import { maxaiStaticHeaders, MAXAI_BASE_URL } from "./protocol.ts";
import { ensureMaxaiConstants } from "./constantsStore.ts";
import type { MaxaiSigningConstants } from "./constants.ts";
export const MAXAI_SIGNIN_EMAIL_PATH = "/oauth/signin_with_email";
export const MAXAI_VERIFY_CODE_PATH = "/oauth/verify_secret_code";
/** The web app's env tag for production email verification. */
const MAXAI_VERIFY_ENV = "prod_co";
export interface MaxaiEmailRequestInput {
email: string;
/** Client device UUID (mint once, reuse for verify + all later calls). */
deviceId: string;
signal?: AbortSignal | null;
fetchImpl?: typeof fetch;
}
export interface MaxaiEmailVerifyInput {
email: string;
/** The 6-digit code the user received by email. */
code: string;
/** Same device UUID used in the request step. */
deviceId: string;
/** Client-user UUID (mint once alongside deviceId). */
clientUserId: string;
signal?: AbortSignal | null;
fetchImpl?: typeof fetch;
}
export interface MaxaiEmailRequestResult {
ok: boolean;
status: number;
error?: string;
}
/** The full credential set returned by a successful verify. */
export interface MaxaiLoginCredential {
accessToken: string;
refreshToken: string;
userId: string;
email: string;
deviceId: string;
clientUserId: string;
}
export interface MaxaiEmailVerifyResult {
ok: boolean;
status: number;
credential?: MaxaiLoginCredential;
error?: string;
}
/** Build signed headers for a blank-user OAuth route (user id is blanked in the proof). */
function signedOauthHeaders(
path: string,
deviceId: string,
constants: MaxaiSigningConstants
): Record<string, string> {
return {
...maxaiStaticHeaders(),
// userId is blanked inside computeMaxaiProof for BLANK_USER_ROUTES; pass "".
...buildMaxaiSignedHeaders({ path, userId: "", deviceId }, constants),
};
}
/** Pull a nested-or-top-level field from a MaxAI response body ({data:{...}} | {...}). */
function pick<T = unknown>(body: Record<string, unknown>, key: string): T | undefined {
const data = body?.data as Record<string, unknown> | undefined;
const nested = data?.[key];
if (nested !== undefined) return nested as T;
return body?.[key] as T | undefined;
}
/**
* Step 1: ask MaxAI to email a sign-in code. Never throws.
* Returns ok=true when the server acknowledges (status "OK").
*/
export async function requestMaxaiEmailCode(
input: MaxaiEmailRequestInput
): Promise<MaxaiEmailRequestResult> {
const doFetch = input.fetchImpl ?? fetch;
if (!input.email || !input.deviceId) {
return { ok: false, status: 0, error: "missing email or deviceId" };
}
// Initial login is the FIRST signed call — ensure we have live signing constants
// (extracted from MaxAI's public bundle) before signing. No keys = cannot sign.
const constants = await ensureMaxaiConstants({ fetchImpl: doFetch, signal: input.signal });
if (!constants) {
return { ok: false, status: 0, error: "MaxAI signing constants unavailable (extraction failed)" };
}
let res: Response;
try {
res = await doFetch(MAXAI_BASE_URL + MAXAI_SIGNIN_EMAIL_PATH, {
method: "POST",
headers: signedOauthHeaders(MAXAI_SIGNIN_EMAIL_PATH, input.deviceId, constants),
body: JSON.stringify({ email: input.email, app: "maxai_webapp" }),
signal: input.signal ?? undefined,
});
} catch (err) {
return { ok: false, status: 0, error: err instanceof Error ? err.message : String(err) };
}
const raw = await res.text().catch(() => "");
if (res.status !== 200) {
return { ok: false, status: res.status, error: raw.slice(0, 200) };
}
let body: Record<string, unknown> = {};
try {
body = JSON.parse(raw) as Record<string, unknown>;
} catch {
return { ok: false, status: res.status, error: "unparseable signin response" };
}
if (pick<string>(body, "status") === "OK") return { ok: true, status: 200 };
const detail = pick<string>(body, "detail") || pick<string>(body, "msg") || "sign-in request failed";
return { ok: false, status: res.status, error: String(detail).slice(0, 200) };
}
/**
* Step 2: verify the emailed code and return the full credential. Never throws.
* On success the caller persists the credential to the connection's
* providerSpecificData (accessToken/refreshToken/deviceId/userId).
*/
export async function verifyMaxaiEmailCode(
input: MaxaiEmailVerifyInput
): Promise<MaxaiEmailVerifyResult> {
const doFetch = input.fetchImpl ?? fetch;
if (!input.email || !input.code || !input.deviceId) {
return { ok: false, status: 0, error: "missing email, code, or deviceId" };
}
const constants = await ensureMaxaiConstants({ fetchImpl: doFetch, signal: input.signal });
if (!constants) {
return { ok: false, status: 0, error: "MaxAI signing constants unavailable (extraction failed)" };
}
const requestBody = {
email: input.email,
secret_code: input.code,
app: "maxai_webapp",
env: MAXAI_VERIFY_ENV,
invitation_code: null,
ref: "",
client_reference_id: null,
client_user_id: input.clientUserId,
client_price_version: null,
client_onboarding_version: null,
user_acquisition_channel: "",
gclid: null,
};
let res: Response;
try {
res = await doFetch(MAXAI_BASE_URL + MAXAI_VERIFY_CODE_PATH, {
method: "POST",
headers: signedOauthHeaders(MAXAI_VERIFY_CODE_PATH, input.deviceId, constants),
body: JSON.stringify(requestBody),
signal: input.signal ?? undefined,
});
} catch (err) {
return { ok: false, status: 0, error: err instanceof Error ? err.message : String(err) };
}
const raw = await res.text().catch(() => "");
if (res.status !== 200) {
return { ok: false, status: res.status, error: raw.slice(0, 200) };
}
let body: Record<string, unknown> = {};
try {
body = JSON.parse(raw) as Record<string, unknown>;
} catch {
return { ok: false, status: res.status, error: "unparseable verify response" };
}
const authUser = pick<Record<string, unknown>>(body, "auth_user");
const status = pick<string>(body, "status");
if (status === "OK" && authUser && typeof authUser === "object") {
const accessToken = String(authUser.accessToken ?? authUser.access_token ?? "");
const refreshToken = String(authUser.refreshToken ?? authUser.refresh_token ?? "");
const userId = String(authUser.userId ?? authUser.user_id ?? "");
if (!accessToken || !refreshToken) {
return { ok: false, status: 200, error: "verify OK but token fields missing" };
}
return {
ok: true,
status: 200,
credential: {
accessToken,
refreshToken,
userId,
email: String(authUser.email ?? input.email),
deviceId: input.deviceId,
clientUserId: String(authUser.clientUserId ?? authUser.client_user_id ?? input.clientUserId),
},
};
}
// 10119 is MaxAI's "code expired / too many attempts" signal; surface it.
const code = pick<number>(body, "code");
const detail = pick<string>(body, "detail") || pick<string>(body, "msg");
const error =
code === 10119
? "Code expired or too many attempts — request a new code."
: String(detail || "Invalid code. Check the code and try again.").slice(0, 200);
return { ok: false, status: res.status, error };
}

View File

@@ -1,266 +0,0 @@
/**
* MaxAI web-app protocol — request bodies, header assembly, and OpenAI→MaxAI
* context flattening. Ported from the MaxAI v3 Python client (chat/request.py,
* translation/openai_in.py, translation/turn_render.py) and live-verified against
* the real `/gpt/cwc/chat` endpoint.
*
* MaxAI is a stateless-full-history provider on the OmniRoute side: we send the
* ENTIRE flattened transcript in `message_content[0].text` every turn, always
* with `chat_history: []`, and mint a fresh `conversation_id` per request. The
* live probe proved a bare `/gpt/cwc/chat` (no upsert/add_messages bookkeeping)
* honors `model_name` and serves the real paid model, so no bookkeeping is sent.
*/
import { randomUUID } from "node:crypto";
export const MAXAI_BASE_URL = "https://api.maxai.me";
export const MAXAI_CHAT_PATH = "/gpt/cwc/chat";
export const MAXAI_MODELS_CONFIG_PATH = "/models/get_config";
/** Static Firefox-150 identity headers sent on every MaxAI request. */
export function maxaiStaticHeaders(): Record<string, string> {
return {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0",
Accept: "*/*",
"Accept-Language": "en-CA,en;q=0.9",
Origin: "https://www.maxai.co",
Referer: "https://www.maxai.co/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
"Content-Type": "application/json",
};
}
// ── Chat body ───────────────────────────────────────────────────────────────
// Field ORDER is pinned (it is part of the HTTP/2 request fingerprint).
const CHAT_FIELD_ORDER = [
"chat_mode",
"conversation_id",
"chat_history",
"message_content",
"chrome_extension_version",
"model_name",
"prompt_id",
"prompt_name",
"prompt_inputs",
"doc_list",
"event_source",
"streaming",
"prompt_type",
"feature_name",
"source_type",
"platform_feature",
] as const;
export function newConversationId(): string {
return randomUUID();
}
export function buildMaxaiChatBody(opts: {
conversationId: string;
text: string;
modelName: string;
language?: string;
relatedQuestionCnt?: string;
/** Extracted app_version for chrome_extension_version (from the signing constants). */
appVersion: string;
/**
* Vision input: current-turn image URLs (data: or http(s):) to attach to the
* request. MaxAI's `/gpt/cwc/chat` accepts inline OpenAI-shaped image parts in
* `message_content` alongside the text part. Empty/omitted = text-only (the
* default, byte-identical to the pre-vision body).
*/
imageUrls?: string[];
/**
* Doc-RAG: uploaded-document references (from /app/upload_document). Each entry
* carries at least `{ doc_id, doc_type, file_name }`. Typed as a loose object
* array so callers can pass their concrete `MaxaiDocListEntry[]` without an
* index-signature cast; the body only serializes it into `doc_list`.
* Empty/omitted = no docs (the default `doc_list: []`).
*/
docList?: ReadonlyArray<object>;
}): Record<string, unknown> {
// message_content is a typed-parts array: the text part ALWAYS leads (so the
// flattened transcript stays first and the no-image path is unchanged), then
// any image_url parts ride alongside. Mirrors the OpenAI multimodal shape,
// which MaxAI passes through (openai-to-cursor.ts vision pattern).
const messageContent: Array<Record<string, unknown>> = [{ type: "text", text: opts.text }];
for (const url of opts.imageUrls ?? []) {
if (typeof url === "string" && url) {
messageContent.push({ type: "image_url", image_url: { url } });
}
}
const values: Record<string, unknown> = {
chat_mode: "pro_chat",
conversation_id: opts.conversationId,
chat_history: [],
message_content: messageContent,
chrome_extension_version: opts.appVersion,
model_name: opts.modelName,
prompt_id: "chat",
prompt_name: "chat",
prompt_inputs: {
RELATED_QUESTION_CNT: opts.relatedQuestionCnt ?? "5",
AI_RESPONSE_LANGUAGE: opts.language ?? "English",
},
doc_list: opts.docList ?? [],
event_source: "web",
streaming: true,
prompt_type: "freestyle",
feature_name: "immersive_chat",
source_type: "NA",
platform_feature: "web_app",
};
const ordered: Record<string, unknown> = {};
for (const k of CHAT_FIELD_ORDER) ordered[k] = values[k];
return ordered;
}
// ── OpenAI messages[] → MaxAI single text block ──────────────────────────────
interface OpenAiMessage {
role?: string;
content?: unknown;
tool_calls?: unknown;
tool_call_id?: string;
}
const ROLE_LABEL: Record<string, string> = {
system: "System",
user: "User",
assistant: "Assistant",
};
const HISTORY_HEADER = "=== Conversation so far (for context) ===";
const CURRENT_HEADER = "=== Current request (respond to THIS) ===";
/** Flatten OpenAI `content` (string or multipart array) to text. */
export function contentToText(content: unknown): string {
if (typeof content === "string") return content;
if (Array.isArray(content)) {
return content
.map((part) =>
part && typeof part === "object" && (part as { type?: string }).type === "text"
? String((part as { text?: unknown }).text ?? "")
: ""
)
.filter(Boolean)
.join("\n");
}
return "";
}
/**
* Extract image_url URLs from the CURRENT (last user) turn of an OpenAI
* messages[] array. MaxAI is stateless-full-history, so we attach only the
* current turn's images (history images would be re-sent every request and
* bloat the body). Returns raw url strings (data: or http(s):) in order.
*/
export function extractCurrentTurnImages(messages: OpenAiMessage[]): string[] {
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i]?.role === "user") {
const content = messages[i]?.content;
if (!Array.isArray(content)) return [];
const urls: string[] = [];
for (const part of content) {
if (part && typeof part === "object" && (part as { type?: unknown }).type === "image_url") {
const imageUrl = (part as { image_url?: unknown }).image_url;
if (typeof imageUrl === "string" && imageUrl) {
urls.push(imageUrl);
} else if (
imageUrl &&
typeof imageUrl === "object" &&
typeof (imageUrl as { url?: unknown }).url === "string" &&
(imageUrl as { url: string }).url
) {
urls.push((imageUrl as { url: string }).url);
}
}
}
return urls;
}
}
return [];
}
/** Render OpenAI tool_calls[] as the prompted `<tool_call>` text MaxAI understands. */
function toolCallsToText(toolCalls: unknown): string {
if (!Array.isArray(toolCalls)) return "";
const blocks: string[] = [];
for (const call of toolCalls) {
const fn = (call as { function?: { name?: unknown; arguments?: unknown } })?.function;
if (!fn) continue;
const name = typeof fn.name === "string" ? fn.name : "";
let args = fn.arguments;
if (typeof args !== "string") {
try {
args = JSON.stringify(args ?? {});
} catch {
args = "{}";
}
}
blocks.push(`<tool_call>${JSON.stringify({ name, arguments: args })}</tool_call>`);
}
return blocks.join("\n");
}
/** Render one non-system turn as a labeled block, or null to skip. */
function renderTurn(message: OpenAiMessage): string | null {
const role = message.role;
const text = contentToText(message.content).trim();
if (role === "tool") {
const id = message.tool_call_id ? ` tool_call_id="${message.tool_call_id}"` : "";
return `<tool_response${id}>\n${text}\n</tool_response>`;
}
if (role === "assistant" && message.tool_calls) {
const calls = toolCallsToText(message.tool_calls);
const body = text ? `${text}\n${calls}`.trim() : calls;
return `Assistant: ${body}`;
}
if (!text) return null;
const label = ROLE_LABEL[role ?? "user"] ?? "User";
return `${label}: ${text}`;
}
/**
* Assemble the full structured context into one text block: system text leads,
* prior turns render as a labeled transcript, and the LAST user turn is fenced
* under a CURRENT header so a weak model answers THIS turn. Mirrors MaxAI v3
* translation/openai_in.py::assemble_context.
*/
export function assembleMaxaiContext(messages: OpenAiMessage[]): string {
// Find the last user turn (the current request).
let curIdx = -1;
let current = "";
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i]?.role === "user") {
curIdx = i;
current = contentToText(messages[i].content).trim();
break;
}
}
const systemParts: string[] = [];
const historyParts: string[] = [];
for (let i = 0; i < messages.length; i++) {
if (i === curIdx) continue;
const m = messages[i];
if (m?.role === "system") {
const t = contentToText(m.content).trim();
if (t) systemParts.push(t);
continue;
}
const block = renderTurn(m);
if (block) historyParts.push(block);
}
const out: string[] = [...systemParts];
if (historyParts.length && current) {
out.push(HISTORY_HEADER + "\n\n" + historyParts.join("\n\n"));
} else {
out.push(...historyParts);
}
if (current) {
const head = historyParts.length ? `${CURRENT_HEADER}\n\n` : "";
out.push(head + current);
}
if (out.length === 0) throw new Error("no content to send to MaxAI");
return out.join("\n\n");
}

View File

@@ -1,149 +0,0 @@
/**
* MaxAI access-token refresh — browserless, via one signed HTTP call.
*
* MaxAI issues two tokens: a ~24h `accessToken` and a ~1-year `refreshToken`.
* The web app refreshes the access token by POSTing the refresh token to
* `/oauth/refresh_access_token` (web-app chunk 86042, `refreshAccessToken`). That
* endpoint carries the SAME per-request `X-Authorization` signature as every other
* MaxAI call (see ./signing.ts) — it is NOT a browser-only OAuth hop. A residential
* Firefox-TLS client (wreq-js firefox_150, the OmniRoute egress overlay) passes the
* TLS gate, so OmniRoute mints fresh access tokens itself with no browser.
*
* The refresh token is minted out-of-band, once, by the browser Google-OAuth flow
* (see maxaiBrowserLogin) and only needs re-minting when it itself expires (~yearly).
* This module handles the routine daily refresh.
*
* Request shape (byte-faithful to the web app):
* POST https://api.maxai.me/oauth/refresh_access_token
* Authorization: Bearer <refreshToken> // the REFRESH token, not access
* noAuthLogout: true
* X-Authorization + X-App/X-Browser headers // standard signing
* body: {"app":"maxai_webapp"} // the app's `params` -> JSON body
* -> 200 { data: { access_token } } // a fresh ~24h access JWT
*
* The signed path is the BARE pathname (no query string); the `app` field travels
* in the body. `user_id` folds into the signature and is read from the refresh
* token's own JWT subject (per the web app), falling back to a provided userId.
*/
import { buildMaxaiSignedHeaders } from "./signing.ts";
import { maxaiStaticHeaders, MAXAI_BASE_URL } from "./protocol.ts";
import { userIdFromJwt, accessTokenExpiry } from "./credentials.ts";
import { refreshMaxaiConstants } from "./constantsStore.ts";
export const MAXAI_REFRESH_PATH = "/oauth/refresh_access_token";
/** How close to expiry (seconds) an access token may be before we refresh it. */
export const MAXAI_REFRESH_MARGIN_SECONDS = 60 * 60; // 1h
export interface MaxaiRefreshInput {
refreshToken: string;
deviceId: string;
/** Optional explicit user id; defaults to the refresh token's JWT subject. */
userId?: string;
signal?: AbortSignal | null;
/** Injectable fetch for tests (defaults to the ambient patched fetch). */
fetchImpl?: typeof fetch;
}
export interface MaxaiRefreshResult {
ok: boolean;
accessToken?: string;
/** access token expiry (epoch seconds), when a token was minted. */
expiresAt?: number;
status: number;
error?: string;
}
/** True when an access token is missing, unparseable, or within the margin of expiry. */
export function maxaiAccessTokenNeedsRefresh(
accessToken: string | null | undefined,
marginSeconds: number = MAXAI_REFRESH_MARGIN_SECONDS,
now: () => number = Date.now
): boolean {
if (!accessToken) return true;
const exp = accessTokenExpiry(accessToken);
if (!exp) return true;
return exp - now() / 1000 <= marginSeconds;
}
/**
* Mint a fresh access token from a refresh token via one signed HTTP POST.
* Never throws; returns a structured result the caller can branch on.
*/
export async function maxaiRefreshAccessToken(
input: MaxaiRefreshInput
): Promise<MaxaiRefreshResult> {
const doFetch = input.fetchImpl ?? fetch;
const userId = input.userId || userIdFromJwt(input.refreshToken) || "";
if (!input.refreshToken || !input.deviceId || !userId) {
return { ok: false, status: 0, error: "missing refreshToken, deviceId, or userId" };
}
// Daily refresh is our freshness checkpoint for the signing constants: re-extract
// from MaxAI's public bundle so a MaxAI-side key/app-version rotation is picked up
// within a day (self-heal). refreshMaxaiConstants persists a changed set and
// returns the current-best; on a fetch miss it returns whatever's already stored.
const constants = await refreshMaxaiConstants({ fetchImpl: doFetch, signal: input.signal });
if (!constants) {
return { ok: false, status: 0, error: "MaxAI signing constants unavailable (extraction failed)" };
}
const signed = buildMaxaiSignedHeaders(
{
path: MAXAI_REFRESH_PATH,
userId,
deviceId: input.deviceId,
},
constants
);
const headers: Record<string, string> = {
...maxaiStaticHeaders(),
...signed,
Authorization: `Bearer ${input.refreshToken}`,
noAuthLogout: "true",
"Content-Type": "application/json",
};
let res: Response;
try {
res = await doFetch(MAXAI_BASE_URL + MAXAI_REFRESH_PATH, {
method: "POST",
headers,
body: JSON.stringify({ app: "maxai_webapp" }),
signal: input.signal ?? undefined,
});
} catch (err) {
return {
ok: false,
status: 0,
error: err instanceof Error ? err.message : String(err),
};
}
const raw = await res.text().catch(() => "");
if (res.status !== 200) {
return { ok: false, status: res.status, error: raw.slice(0, 200) };
}
let accessToken = "";
try {
const parsed = JSON.parse(raw) as {
data?: { access_token?: unknown };
access_token?: unknown;
};
const candidate = parsed?.data?.access_token ?? parsed?.access_token;
if (typeof candidate === "string") accessToken = candidate;
} catch {
return { ok: false, status: res.status, error: "unparseable refresh response" };
}
if (!accessToken) {
return { ok: false, status: res.status, error: "refresh response had no access_token" };
}
return {
ok: true,
status: 200,
accessToken,
expiresAt: accessTokenExpiry(accessToken) || undefined,
};
}

View File

@@ -1,151 +0,0 @@
/**
* MaxAI web-app signing — the `X-Authorization` per-request signature.
*
* The scheme (validated byte-exact against real captured `X-Authorization` blobs):
*
* sign_str = `${appVersion}:${req_time}:${path}:${uid}`
* sha1 = HMAC_SHA1_hex(sign_str, key=`${req_time}:${hmacKey}`)
* p = SM3_hex(`${req_time}:${sha1}:${hmacKey}`)
* payload = { X-Client-Domain, X-Client-Path(page url), X-Random(6-digit),
* t(ms), p, d(device_id), <ctxKey>:{ a: context } }
* X-Authorization = base64( "Salted__" + salt8 + AES-256-CBC(payloadJSON) )
* with key/iv from OpenSSL EVP_BytesToKey(MD5, aesKey, salt)
*
* All primitives are in `node:crypto` (HMAC-SHA1, SM3 via OpenSSL 3, MD5,
* AES-256-CBC); no external dependency.
*
* KEYING MATERIAL IS NOT HARDCODED. The `hmacKey` and `aesKey` are the CLIENT-SIDE
* constants MaxAI's own web app ships verbatim in its public JS bundle. Rather
* than pin them here, OmniRoute extracts them live (see ./constants.ts) and passes
* a `MaxaiSigningConstants` object into every signing call. There is deliberately
* NO in-code default for the two keys: a signer with no extracted keys cannot sign
* (the caller surfaces a clear auth error) — we never sign with a guessed secret.
* The non-secret STRUCTURAL fields (appVersion, ctxKey, header names) carry safe
* defaults so a transient parse miss can't break an otherwise-working signer.
*/
import { createHmac, createHash, createCipheriv, randomBytes } from "node:crypto";
import type { MaxaiSigningConstants, MaxaiHeaderNames } from "./constants.ts";
import { MAXAI_DEFAULT_HEADER_NAMES } from "./constants.ts";
const CLIENT_DOMAIN = "maxai.co";
/** Default browser page URL recorded verbatim as X-Client-Path (NOT the API path). */
export const MAXAI_DEFAULT_PAGE = "https://www.maxai.co/app/";
/** Only /oauth/* routes blank the user_id inside the signature. */
const BLANK_USER_ROUTES = new Set([
"/oauth/signin_with_email",
"/oauth/signin_with_google",
"/oauth/verify_secret_code",
]);
const MAGIC = Buffer.from("Salted__", "ascii");
function hmacSha1Hex(message: string, key: string): string {
return createHmac("sha1", Buffer.from(key, "utf8")).update(Buffer.from(message, "utf8")).digest("hex");
}
function sm3Hex(message: string): string {
return createHash("sm3").update(Buffer.from(message, "utf8")).digest("hex");
}
/** OpenSSL EVP_BytesToKey with MD5 (CryptoJS default for a string passphrase). */
function evpBytesToKey(
passphrase: string,
salt: Buffer,
keyLen = 32,
ivLen = 16
): { key: Buffer; iv: Buffer } {
let derived = Buffer.alloc(0);
let block = Buffer.alloc(0);
const pass = Buffer.from(passphrase, "utf8");
while (derived.length < keyLen + ivLen) {
block = createHash("md5").update(Buffer.concat([block, pass, salt])).digest();
derived = Buffer.concat([derived, block]);
}
return { key: derived.subarray(0, keyLen), iv: derived.subarray(keyLen, keyLen + ivLen) };
}
/**
* Reproduce CryptoJS.AES.encrypt(text, passphrase).toString() (OpenSSL Salted__
* envelope). `passphrase` (the extracted aesKey) is REQUIRED — there is no default.
*/
export function maxaiAesEncrypt(plaintext: string, passphrase: string, salt?: Buffer): string {
if (!passphrase) throw new Error("maxaiAesEncrypt: missing aesKey");
const s = salt ?? randomBytes(8);
const { key, iv } = evpBytesToKey(passphrase, s);
const cipher = createCipheriv("aes-256-cbc", key, iv); // PKCS7 padding is the default
const body = Buffer.concat([cipher.update(Buffer.from(plaintext, "utf8")), cipher.final()]);
return Buffer.concat([MAGIC, s, body]).toString("base64");
}
/**
* Compute the SM3 `p` proof for an API `path` at `reqTime` ms. `hmacKey` and
* `appVersion` (both extracted) are REQUIRED — there is no in-code default.
*/
export function computeMaxaiProof(
path: string,
reqTime: number,
userId: string,
hmacKey: string,
appVersion: string
): string {
if (!hmacKey) throw new Error("computeMaxaiProof: missing hmacKey");
if (!appVersion) throw new Error("computeMaxaiProof: missing appVersion");
const p = path.endsWith("?") ? path.slice(0, -1) : path;
const uid = BLANK_USER_ROUTES.has(p) ? "" : userId;
const signStr = `${appVersion}:${reqTime}:${p}:${uid}`;
const sha1 = hmacSha1Hex(signStr, `${reqTime}:${hmacKey}`);
return sm3Hex(`${reqTime}:${sha1}:${hmacKey}`);
}
export interface MaxaiSignInput {
/** API path being signed, e.g. "/gpt/cwc/chat". */
path: string;
userId: string;
deviceId: string;
/** Browser page URL for X-Client-Path (defaults to the app page). */
pageUrl?: string;
/** Context slot value (defaults to "" — the wire default). */
context?: string;
/** Injectable clock/random for deterministic tests. */
now?: () => number;
random?: () => string;
}
/**
* Build the signing headers (X-Authorization plus the X-App and X-Browser
* companions) for one request. `device_id` MUST match the device that minted the
* token, or the server rejects the signature.
*
* `constants` carries the extracted keying material + structural labels. It is
* REQUIRED: callers resolve it via `ensureMaxaiConstants()` before signing.
*/
export function buildMaxaiSignedHeaders(
input: MaxaiSignInput,
constants: MaxaiSigningConstants
): Record<string, string> {
const reqTime = (input.now ?? (() => Date.now()))();
const random =
input.random?.() ?? String((randomBytes(4).readUInt32BE(0) % 900000) + 100000);
const h: MaxaiHeaderNames = { ...MAXAI_DEFAULT_HEADER_NAMES, ...constants.headerNames };
const ctxKey = constants.ctxKey;
const appVersion = constants.appVersion;
// Key ORDER matters — it is signed as a compact JSON string.
const payload: Record<string, unknown> = {
[h.clientDomain]: CLIENT_DOMAIN,
[h.clientPath]: input.pageUrl ?? MAXAI_DEFAULT_PAGE,
[h.random]: random,
[h.tSlot]: reqTime,
[h.pSlot]: computeMaxaiProof(input.path, reqTime, input.userId, constants.hmacKey, appVersion),
[h.dSlot]: input.deviceId,
[ctxKey]: { a: input.context ?? "" },
};
const blob = maxaiAesEncrypt(JSON.stringify(payload), constants.aesKey);
return {
[h.browserName]: "Firefox",
[h.browserVersion]: "150.0",
[h.browserMajor]: "150",
[h.appVersionHeader]: appVersion,
[h.appEnvHeader]: h.appEnvValue,
[h.authorization]: blob,
};
}

View File

@@ -1,101 +0,0 @@
/**
* MaxAI SSE stream handling — frame parsing, incremental `<think>` split, and
* token estimation. Ported from the MaxAI v3 Python client (translation/sse.py,
* translation/stream.py, translation/think_split.py, translation/token_usage.py).
*
* MaxAI's `/gpt/cwc/chat` response is `text/event-stream`: `data: {json}` frames
* separated by blank lines. A text delta is a frame with
* `data_key === "text" && need_merge` truthy; its content is `frame.text`.
* Reasoning is emitted inline wrapped in `<think>…</think>`; everything inside is
* reasoning, everything after the close tag is the visible answer. MaxAI returns
* no usage frame, so tokens are estimated (~4 chars/token).
*/
/** Parse the text deltas out of a raw SSE body (batch). */
export function parseMaxaiSseText(raw: string): string {
let out = "";
for (const line of raw.split("\n")) {
const s = line.trim();
if (!s.startsWith("data:")) continue;
const js = s.slice(5).trim();
if (!js || js === "[DONE]") continue;
try {
const frame = JSON.parse(js) as { data_key?: unknown; need_merge?: unknown; text?: unknown };
if (frame.data_key === "text" && frame.need_merge) {
out += typeof frame.text === "string" ? frame.text : "";
}
} catch {
/* ignore non-JSON keepalive frames */
}
}
return out;
}
/** True when a decoded SSE frame is a mergeable text delta. */
export function isMaxaiTextFrame(
frame: unknown
): frame is { data_key: "text"; need_merge: true; text: string } {
const f = frame as { data_key?: unknown; need_merge?: unknown; text?: unknown };
return f?.data_key === "text" && Boolean(f?.need_merge) && typeof f?.text === "string";
}
const OPEN = "<think>";
const CLOSE = "</think>";
const HOLD = Math.max(OPEN.length, CLOSE.length) - 1;
/**
* Stateful streaming classifier of text into (reasoning, answer). Handles a tag
* split across frames by holding a short tail. Before `<think>` opens, text is
* answer; if no `<think>` ever appears the whole stream is answer.
*/
export class ThinkSplitter {
private buf = "";
private inThink = false;
feed(delta: string): { reasoning: string; answer: string } {
this.buf += delta;
let reasoning = "";
let answer = "";
for (;;) {
const tag = this.inThink ? CLOSE : OPEN;
const idx = this.buf.indexOf(tag);
if (idx === -1) break;
const before = this.buf.slice(0, idx);
if (this.inThink) reasoning += before;
else answer += before;
this.buf = this.buf.slice(idx + tag.length);
this.inThink = !this.inThink;
}
// Emit everything except a short tail that might begin a tag.
const safe = this.buf.length > HOLD ? this.buf.slice(0, this.buf.length - HOLD) : "";
if (safe) {
this.buf = this.buf.slice(safe.length);
if (this.inThink) reasoning += safe;
else answer += safe;
}
return { reasoning, answer };
}
flush(): { reasoning: string; answer: string } {
const tail = this.buf;
this.buf = "";
if (!tail) return { reasoning: "", answer: "" };
return this.inThink ? { reasoning: tail, answer: "" } : { reasoning: "", answer: tail };
}
}
/** Split a fully-collected answer into { reasoning, answer } (batch/non-stream). */
export function splitThink(full: string): { reasoning: string; answer: string } {
const splitter = new ThinkSplitter();
const a = splitter.feed(full);
const b = splitter.flush();
return {
reasoning: a.reasoning + b.reasoning,
answer: a.answer + b.answer,
};
}
/** MaxAI returns no token counts; estimate ~4 chars/token. */
export function estimateMaxaiTokens(text: string): number {
return Math.max(0, Math.ceil((text?.length ?? 0) / 4));
}

View File

@@ -472,7 +472,6 @@ import {
isRpmExhausted,
} from "../services/geminiRateLimitTracker.ts";
import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
import { getProactiveCompressionRatio } from "@/lib/db/compression";
type ChatCoreExecutorResult = ReturnType<typeof normalizeExecutorResult> & {
_executionCredentials?: Record<string, unknown>;
@@ -1994,7 +1993,7 @@ export async function handleChatCore({
}
}
const COMPRESSION_THRESHOLD = getProactiveCompressionRatio();
const COMPRESSION_THRESHOLD = 0.7;
let reservedTokens = 0;
if (Array.isArray(body.tools)) {
reservedTokens = estimateTokens(body.tools);

View File

@@ -56,7 +56,6 @@ import { handleNvidiaNimImageGeneration } from "./imageGeneration/providers/nvid
import { handleSegmindImageGeneration } from "./imageGeneration/providers/segmind.ts";
import { handleCursorAgentImageGeneration } from "./imageGeneration/providers/cursorAgentImage.ts";
import { handleMinimaxImageGeneration } from "./imageGeneration/providers/minimax.ts";
import { handleMaxaiImageGeneration } from "./imageGeneration/providers/maxaiImage.ts";
import { handleAdobeFireflyImageGeneration } from "./imageGeneration/providers/adobeFirefly.ts";
import { handleAlibabaImageGeneration } from "./imageGeneration/providers/alibabaImage.ts";
import { handleAiHordeImageGeneration } from "./imageGeneration/providers/aihorde.ts";
@@ -617,17 +616,6 @@ export async function handleImageGeneration({
});
}
if (providerConfig.format === "maxai-image") {
return handleMaxaiImageGeneration({
model,
provider,
body,
credentials,
log,
signal,
});
}
if (providerConfig.format === "adobe-firefly-image") {
return handleAdobeFireflyImageGeneration({
model,

View File

@@ -1,230 +0,0 @@
// MaxAI (web-app) image-generation handler.
// Family: maxai-image | Provider: maxai
//
// MaxAI exposes 6 image models (gpt-image-1, dall-e-3, flux-1-schnell/dev/pro,
// sd3-medium) behind a SINGLE synchronous endpoint:
// POST https://api.maxai.me/gpt/get_image_generate_response
// body {prompt, style, size, n, model_name}
// -> {status:"OK", data:[{webp_url, png_url}]}
// No submit-then-poll (unlike Microsoft Designer) — one request returns the
// image URLs. Auth reuses the EXISTING signed-executor pieces (the same
// X-Authorization signer + Firefox-150 identity the chat path uses); the signer
// signs whatever `path` it is given, so image and chat share one auth module.
//
// Residential egress + Firefox-150 TLS are applied transparently at the infra
// layer (in-container TUN + TLS_FINGERPRINT_PROVIDERS), so nothing egress-
// specific lives here.
import { resolveMaxaiCredential } from "../../../executors/maxai/credentials.ts";
import { buildMaxaiSignedHeaders } from "../../../executors/maxai/signing.ts";
import { ensureMaxaiConstants } from "../../../executors/maxai/constantsStore.ts";
import { MAXAI_BASE_URL, maxaiStaticHeaders } from "../../../executors/maxai/protocol.ts";
import { sanitizeErrorMessage } from "../../../utils/error.ts";
import { saveImageErrorResult, saveImageSuccessResult } from "../../imageGeneration.ts";
export const MAXAI_IMAGE_PATH = "/gpt/get_image_generate_response";
const MAXAI_IMAGE_DEFAULT_SIZE = "1024x1024";
const MAXAI_IMAGE_N_MAX = 4;
// Models whose upstream REJECTS non-1024 sizes (verified: gpt-image-1/dall-e-3
// 500 on 256x256/512x512). The flux family + sd3-medium have no size constraint
// and pass the requested WxH through unchanged.
const MAXAI_STRICT_SIZE_MODELS: Record<string, Set<string>> = {
"gpt-image-1": new Set(["1024x1024", "1024x1536", "1536x1024", "auto"]),
"dall-e-3": new Set(["1024x1024", "1024x1792", "1792x1024"]),
};
const MAXAI_IMAGE_ALIASES: Record<string, string> = {
"stable-diffusion-v3": "sd3-medium",
"stable-diffusion-3-medium": "sd3-medium",
"flux-1-schneil": "flux-1-schnell", // tolerate a common typo
};
/** Strip a `maxai/` prefix and resolve size-name aliases to the canonical model id. */
export function resolveMaxaiImageModel(model: unknown): string {
let m = typeof model === "string" ? model.trim() : "";
if (m.startsWith("maxai/")) m = m.slice("maxai/".length);
return MAXAI_IMAGE_ALIASES[m] ?? m;
}
/**
* Snap an OpenAI-style "WxH" size to something MaxAI accepts. gpt-image-1 /
* dall-e-3 reject anything outside their bucket (→ upstream 500), so an
* unsupported size (e.g. 512x512 from a standard OpenAI client) is snapped to
* the model default. Models with no constraint pass the size through.
*/
export function snapMaxaiImageSize(model: string, size: unknown): string {
const requested = typeof size === "string" && size.trim() ? size.trim() : MAXAI_IMAGE_DEFAULT_SIZE;
const allowed = MAXAI_STRICT_SIZE_MODELS[model];
if (!allowed) return requested; // flux / sd3: no constraint
return allowed.has(requested) ? requested : MAXAI_IMAGE_DEFAULT_SIZE;
}
/** Pull image URLs out of MaxAI's response into OpenAI data[] items (prefer png_url). */
export function extractMaxaiImageUrls(json: unknown): string[] {
// Accept either the raw items array or a { data: [...] } wrapper. MaxAI's real
// response is { status:"OK", data:[{webp_url, png_url}] }, so both shapes occur
// depending on how far the caller unwrapped.
let items: unknown[] = [];
if (Array.isArray(json)) {
items = json;
} else if (json && typeof json === "object" && Array.isArray((json as Record<string, unknown>).data)) {
items = (json as Record<string, unknown>).data as unknown[];
}
const urls: string[] = [];
for (const it of items) {
if (it && typeof it === "object") {
const rec = it as Record<string, unknown>;
const url =
(typeof rec.png_url === "string" && rec.png_url) ||
(typeof rec.webp_url === "string" && rec.webp_url) ||
(typeof rec.url === "string" && rec.url) ||
"";
if (url) urls.push(url);
}
}
return urls;
}
export async function handleMaxaiImageGeneration({
model,
provider,
body,
credentials,
log,
signal,
fetchImpl = fetch,
}: {
model: string;
provider: string;
body: { prompt?: unknown; size?: unknown; n?: unknown; style?: unknown };
credentials: {
apiKey?: string;
accessToken?: string;
providerSpecificData?: Record<string, unknown> | null;
};
log?: { info?: (...args: unknown[]) => void; error?: (...args: unknown[]) => void };
signal?: AbortSignal;
fetchImpl?: typeof fetch;
}) {
const startTime = Date.now();
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
if (!prompt) {
return saveImageErrorResult({
provider,
model,
status: 400,
startTime,
error: "Prompt is required for MaxAI image generation",
});
}
const cred = resolveMaxaiCredential(
credentials?.providerSpecificData,
credentials?.accessToken || credentials?.apiKey
);
if (!cred) {
return saveImageErrorResult({
provider,
model,
status: 401,
startTime,
error: "MaxAI credentials missing access_token",
retryable: true,
});
}
const canonicalModel = resolveMaxaiImageModel(model);
const nRaw = Number(body.n);
const n = Number.isFinite(nRaw) && nRaw >= 1 ? Math.min(Math.floor(nRaw), MAXAI_IMAGE_N_MAX) : 1;
const requestBody = {
prompt,
style: typeof body.style === "string" && body.style ? body.style : "vivid",
size: snapMaxaiImageSize(canonicalModel, body.size),
n,
model_name: canonicalModel,
};
const constants = await ensureMaxaiConstants({ fetchImpl, signal });
if (!constants) {
return saveImageErrorResult({
provider,
model,
status: 401,
startTime,
error: "MaxAI signing constants unavailable (extraction failed).",
});
}
const headers: Record<string, string> = {
...maxaiStaticHeaders(),
...buildMaxaiSignedHeaders({ path: MAXAI_IMAGE_PATH, userId: cred.userId, deviceId: cred.deviceId }, constants),
Authorization: `Bearer ${cred.accessToken}`,
"Content-Type": "application/json",
};
let resp: Response;
try {
resp = await fetchImpl(MAXAI_BASE_URL + MAXAI_IMAGE_PATH, {
method: "POST",
headers,
body: JSON.stringify(requestBody),
signal,
});
} catch (err) {
const errorText = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
log?.error?.("IMAGE", `${provider} maxai-image transport error: ${errorText}`);
return saveImageErrorResult({ provider, model, status: 502, startTime, error: errorText, requestBody });
}
if (!resp.ok) {
const detail = (await resp.text().catch(() => "")).slice(0, 500);
log?.error?.("IMAGE", `${provider} maxai-image error ${resp.status}: ${detail}`);
return saveImageErrorResult({
provider,
model,
status: resp.status,
startTime,
error: detail || `MaxAI image generation failed (HTTP ${resp.status})`,
requestBody,
// 401 = expired token, 418 = TLS/JA3 masked-reject: rotate to the next account.
retryable: resp.status === 401 || resp.status === 418,
});
}
let json: unknown;
try {
json = await resp.json();
} catch {
return saveImageErrorResult({
provider,
model,
status: 502,
startTime,
error: "MaxAI returned a non-JSON image response",
requestBody,
});
}
const status = (json as Record<string, unknown>)?.status;
const urls = extractMaxaiImageUrls(json);
if (status !== "OK" || urls.length === 0) {
return saveImageErrorResult({
provider,
model,
status: 502,
startTime,
error: `MaxAI image generation returned no images (status=${String(status)})`,
requestBody,
});
}
return saveImageSuccessResult({
provider,
model,
startTime,
requestBody,
responseBody: { images_count: urls.length },
images: urls.map((url) => ({ url })),
});
}

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