feat(providers): add MaxAI — signed OpenAI-compatible provider (chat, tools, vision, image-gen, doc-RAG) (#11461)

MaxAI joins as a first-class signed provider: 13 chat models discovered live from /models/get_config plus 6 image models, routed through the standard /v1 endpoints with per-request X-Authorization signing, browserless onboarding, prompted tool-calling, vision input, image generation and document RAG.

Reconciled on merge — worth reading, because the branch forked 227 commits back and 77 files conflicted. Only five carried MaxAI content; the rest was drift from the older release line and took the tip's side, taking the diff from 113 files to 37 (then 93 as counted against the current base).

- executors/index.ts: the tip has since refactored the executor map to lazy dynamic imports, so MaxAI is registered in that shape rather than the branch's static import.
- imageRegistry.ts: kept only the maxai block. The branch still carried microsoft-designer-web, which #11754 retired.
- models/route.ts: the conflicting hunk was an unrelated Vertex/Anthropic URL change, not MaxAI — tip's side.
- volcengine agent-plan/coding-plan registries: git auto-merged both sides and produced a duplicated supportsVision key, which TypeScript rejects (TS1117). Removed.

One real integration break that only the combined state shows: the MaxAI entry declared no serviceKinds, which #11392 made required a few hours ago. Provider validation threw at load time and check:provider-consistency crashed outright. Declared ["llm"] — the image kinds derive from imageRegistry, per the convention in that PR's backfill.

Every count was measured rather than taken from the branch, and each would have been wrong: reserved prefixes are 402, not the 397 the branch computed from its stale 395 base; providers are 353, not 354. PROVIDER_REFERENCE.md regenerated, the count updated across README/AGENTS.md/llm.txt and its 42 mirrors, package.json and 6 SVGs — every changed line in those files is a digit substitution and nothing else, verified by masking digits and comparing the removed and added sets (90 lines, identical). The executor-map golden snapshot was regenerated: keyCount 133 -> 134.

The branch's file-size-baseline.json predates #12411's ratchet re-tightening, so it was discarded rather than merged — taking it would have silently undone that. The three files this PR grows (proxyFetch.ts +20 for the Windows/firefox_150 TLS profile, imageGeneration.ts +12, models/route.ts +48) were entered against the current baseline under one _rebaseline annotation; no other cap moves.

Verified: typecheck:core clean, check:provider-consistency OK (269 REGISTRY entries, 353 canonical providers), check:docs-counts exit 0, check-file-size OK, check:cycles OK, and 79/79 across the MaxAI suites plus 21/21 reserved-prefix and 2/2 executor-map-golden.

Thanks @arminanton — the provider work itself is thorough; it was the 227 commits of base that needed the attention.
This commit is contained in:
Armin Anton” ∴
2026-09-01 21:55:42 -07:00
committed by GitHub
parent 382e2e85d2
commit cabbbe410a
93 changed files with 5042 additions and 122 deletions

View File

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

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 → 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."/>
<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."/>
</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 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."/>
<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."/>
<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: 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."/>
<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."/>
<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

@@ -0,0 +1,6 @@
- **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

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

View File

@@ -1,4 +1,5 @@
{
"_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).",
@@ -406,7 +407,7 @@
"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": 3231,
"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,
@@ -415,7 +416,7 @@
"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": 1241,
"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,
@@ -432,7 +433,7 @@
"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": 2381,
"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,

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 (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.">
<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.">
<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: 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.">
<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.">
<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 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.">
<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.">
<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">352 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">353 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 352 providers in</text>
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 353 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 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.">
<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.">
<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">352 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">353 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

@@ -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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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 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.
> 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.
## Overview
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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

@@ -1,16 +1,16 @@
---
title: "Provider Reference"
version: 3.8.51
lastUpdated: 2026-08-30
lastUpdated: 2026-09-02
---
# 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-08-30
> **Last generated:** 2026-09-02
Total providers: **352**. See category breakdown below.
Total providers: **353**. 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 (31)
## Web Cookie Providers (32)
| ID | Alias | Name | Tags | Website | Notes | Tool calling |
|----|-------|------|------|---------|-------|--------------|
@@ -102,6 +102,7 @@ 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 |
@@ -440,7 +441,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/) (104 implementations)
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (107 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 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.
> 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.
## Overview
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
## Key Features (v3.8.50)
### Core Proxy
- **352 AI providers** with automatic format translation
- **353 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

@@ -256,6 +256,26 @@ 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

@@ -210,6 +210,7 @@ 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";
@@ -477,6 +478,7 @@ 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

@@ -0,0 +1,26 @@
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,
supportsReasoning: true,
supportsVision: true,
supportsReasoning: 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,
supportsReasoning: true,
supportsVision: true,
supportsReasoning: true,
},
{
id: "deepseek-v4-pro",

View File

@@ -44,6 +44,7 @@ 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": () =>

620
open-sse/executors/maxai.ts Normal file
View File

@@ -0,0 +1,620 @@
/**
* 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

@@ -0,0 +1,76 @@
/**
* 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

@@ -0,0 +1,427 @@
/**
* 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

@@ -0,0 +1,156 @@
/**
* 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

@@ -0,0 +1,96 @@
/**
* 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

@@ -0,0 +1,266 @@
/**
* 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

@@ -0,0 +1,234 @@
/**
* 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

@@ -0,0 +1,266 @@
/**
* 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

@@ -0,0 +1,149 @@
/**
* 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

@@ -0,0 +1,151 @@
/**
* 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

@@ -0,0 +1,101 @@
/**
* 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

@@ -56,6 +56,7 @@ 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";
@@ -616,6 +617,17 @@ 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

@@ -0,0 +1,230 @@
// 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 })),
});
}

View File

@@ -0,0 +1,172 @@
/**
* MaxAI model discovery — live model list + per-model context windows from the
* web app's own `/models/get_config` endpoint (the signed call the app makes on
* load). Feeds OmniRoute's model-discovery pipeline so the MaxAI catalog and its
* per-model context windows self-update instead of relying only on the static
* catalog (`open-sse/executors/maxai/catalog.ts`).
*
* The response's `chat_models[]` carries `model_name` (id), `ui_display_name`,
* `group`, `max_tokens` (the per-model context window), `is_deprecated`, and a
* `capabilities` block ({ vision, thinking_mode, artifacts, file_upload }). We
* map each non-deprecated chat model to a discovery record whose `inputTokenLimit`
* is `max_tokens`, so `persistDiscoveredModels` → `syncedAvailableModels` →
* `contextWindowResolver` reconciles the real window as an `auto:discovery`
* override.
*
* Signed + residential like every MaxAI call (see ./maxai/signing.ts). Never
* throws for the caller's convenience is NOT the contract here — the route wraps
* it in try/catch and falls back to the curated catalog — but it validates HTTP
* status and shape and throws a sanitized error on failure so the route logs it.
*/
import { resolveMaxaiCredential } from "../executors/maxai/credentials.ts";
import { buildMaxaiSignedHeaders } from "../executors/maxai/signing.ts";
import { ensureMaxaiConstants } from "../executors/maxai/constantsStore.ts";
import {
maxaiStaticHeaders,
MAXAI_BASE_URL,
MAXAI_MODELS_CONFIG_PATH,
} from "../executors/maxai/protocol.ts";
import { maxaiContextWindow, MAXAI_MODELS } from "../executors/maxai/catalog.ts";
// Re-export the registry-shaped catalog through this service so `src/app` routes
// can consume it WITHOUT importing the executor directly (the no-restricted-imports
// rule: "executor implementations must stay behind an open-sse handler or service
// boundary"). This service IS that boundary, and already owns the catalog import.
export { MAXAI_REGISTRY_MODELS } from "../executors/maxai/catalog.ts";
/** A discovered MaxAI model in the shape persistDiscoveredModels normalizes. */
export interface MaxaiDiscoveredModel {
id: string;
name: string;
/** Per-model context window (chars→tokens handled upstream); the reconciler key. */
inputTokenLimit: number;
group?: string;
supportsReasoning?: boolean;
supportsVision?: boolean;
toolCalling: boolean;
}
export interface MaxaiModelDiscoveryInput {
/** Connection credential material (from providerSpecificData + apiKey). */
providerSpecificData: Record<string, unknown> | null | undefined;
accessToken?: string | null;
signal?: AbortSignal | null;
/** Injectable fetch (the route passes a proxy/guard-wrapped safeOutboundFetch). */
fetchImpl?: typeof fetch;
}
export interface MaxaiModelDiscoveryResult {
models: MaxaiDiscoveredModel[];
warning?: string;
}
/** The curated paid-model id set — only these are surfaced (quality gate). */
const CURATED_IDS = new Set(MAXAI_MODELS.map((m) => m.id));
interface RawChatModel {
model_name?: unknown;
ui_display_name?: unknown;
type?: unknown;
group?: unknown;
max_tokens?: unknown;
is_deprecated?: unknown;
capabilities?: {
vision?: unknown;
thinking_mode?: unknown;
} | null;
}
/** Map one raw chat model to a discovery record, or null when it should be dropped. */
function toDiscovered(raw: RawChatModel): MaxaiDiscoveredModel | null {
const id = typeof raw.model_name === "string" ? raw.model_name : "";
if (!id) return null;
if (raw.is_deprecated === true) return null;
if (raw.type !== undefined && raw.type !== "chat") return null;
// Quality gate: only surface the curated paid models (the ones catalog.ts offers).
if (!CURATED_IDS.has(id)) return null;
const liveWindow =
typeof raw.max_tokens === "number" && Number.isFinite(raw.max_tokens) && raw.max_tokens > 0
? Math.trunc(raw.max_tokens)
: maxaiContextWindow(id); // fall back to the static catalog window
const caps = raw.capabilities ?? {};
return {
id,
name: typeof raw.ui_display_name === "string" ? raw.ui_display_name : id,
inputTokenLimit: liveWindow,
group: typeof raw.group === "string" ? raw.group : undefined,
supportsReasoning: caps.thinking_mode === true || undefined,
supportsVision: caps.vision === true || undefined,
toolCalling: true, // prompted tool-calling (see maxai.ts + webTools.ts)
};
}
/**
* Fetch MaxAI's live model catalog + per-model context windows. Throws a
* sanitized Error on auth/transport/shape failure (the route catches and falls
* back to the curated static catalog).
*/
export async function discoverMaxaiModels(
input: MaxaiModelDiscoveryInput
): Promise<MaxaiModelDiscoveryResult> {
const doFetch = input.fetchImpl ?? fetch;
const cred = resolveMaxaiCredential(input.providerSpecificData, input.accessToken);
if (!cred) {
throw new Error("MaxAI connection is not configured (missing token/device/user id).");
}
const path = MAXAI_MODELS_CONFIG_PATH;
const constants = await ensureMaxaiConstants({ fetchImpl: doFetch, signal: input.signal });
if (!constants) {
throw new Error("MaxAI signing constants unavailable (extraction failed).");
}
const res = await doFetch(MAXAI_BASE_URL + path, {
method: "POST",
headers: {
...maxaiStaticHeaders(),
...buildMaxaiSignedHeaders({ path, userId: cred.userId, deviceId: cred.deviceId }, constants),
Authorization: `Bearer ${cred.accessToken}`,
},
body: "{}",
signal: input.signal ?? undefined,
});
if (res.status !== 200) {
const detail = await res.text().catch(() => "");
throw new Error(`MaxAI /models/get_config ${res.status}: ${detail.slice(0, 160)}`);
}
let parsed: { data?: { chat_models?: unknown }; chat_models?: unknown };
try {
parsed = (await res.json()) as typeof parsed;
} catch {
throw new Error("MaxAI /models/get_config returned unparseable JSON.");
}
const data = parsed?.data ?? parsed;
const chatModels = (data as { chat_models?: unknown })?.chat_models;
if (!Array.isArray(chatModels)) {
throw new Error("MaxAI /models/get_config had no chat_models array.");
}
const models: MaxaiDiscoveredModel[] = [];
for (const raw of chatModels as RawChatModel[]) {
const mapped = toDiscovered(raw);
if (mapped) models.push(mapped);
}
if (models.length === 0) {
throw new Error("MaxAI /models/get_config yielded no usable curated models.");
}
// Note when the live list dropped a curated model (e.g. MaxAI deprecated it).
const liveIds = new Set(models.map((m) => m.id));
const missing = [...CURATED_IDS].filter((id) => !liveIds.has(id));
const warning =
missing.length > 0
? `MaxAI no longer offers ${missing.length} curated model(s): ${missing.join(", ")}`
: undefined;
return { models, warning };
}

View File

@@ -97,6 +97,13 @@ let initialized = false;
let currentRequestQueueSettings: RequestQueueSettings = DEFAULT_RESILIENCE_SETTINGS.requestQueue;
export const ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS = 60_000;
// MaxAI proxies reasoning models (deepseek-r1, gpt-5.6-thinking, grok-4.5,
// gemini-3.1-pro-preview, grok-4-1-fast-reasoning) whose single upstream turn
// legitimately runs tens of seconds to minutes. The 15s default execution
// expiration (Bottleneck `expiration`, applied AFTER dispatch) kills those mid
// think and surfaces a spurious local 504. Floor MaxAI at 5 min — the same
// ceiling waitForCooldown.budgetMs uses — so slow reasoning turns complete.
export const MAXAI_REQUEST_QUEUE_MAX_WAIT_MS = 300_000;
const limiterEffectiveSettings = new WeakMap<Bottleneck, Bottleneck.ConstructorOptions>();
const preservedReplacementSettings = new Map<string, Bottleneck.ConstructorOptions>();
@@ -166,10 +173,15 @@ export function resolveRequestQueueMaxWaitMs(
configuredMaxWaitMs: number = currentRequestQueueSettings.maxWaitMs,
connectionId?: string
): number {
const legacyDefault =
provider.trim().toLowerCase() === "zai-web"
? Math.max(configuredMaxWaitMs, ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS)
: configuredMaxWaitMs;
const p = provider.trim().toLowerCase();
let legacyDefault = configuredMaxWaitMs;
if (p === "zai-web") {
legacyDefault = Math.max(configuredMaxWaitMs, ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS);
} else if (p === "maxai" || p === "mx") {
// MaxAI's slow reasoning models legitimately need up to ~5 min; floor the
// per-request execution budget so they aren't cut off early.
legacyDefault = Math.max(configuredMaxWaitMs, MAXAI_REQUEST_QUEUE_MAX_WAIT_MS);
}
const override = connectionId
? connectionRateLimitOverrides.get(connectionId)?.maxWaitMs
: undefined;

View File

@@ -99,6 +99,24 @@ function tlsFingerprintProviderAllowed(
.some((candidate) => candidate.trim().toLowerCase() === normalizedProvider);
}
/**
* Per-provider TLS impersonation profile. Most providers use the default
* Chrome/macOS wreq profile; providers that must match a specific browser
* fingerprint (e.g. MaxAI expects a Windows Firefox-150 client) override it here.
* Returns undefined to keep the tlsClient default (chrome_124 / macos).
*/
const TLS_PROVIDER_PROFILE: Record<string, { browser: string; os: string }> = {
maxai: { browser: "firefox_150", os: "windows" },
};
function tlsProfileForProvider(
provider: string | null | undefined
): { browserProfile?: string; os?: string } {
if (!provider) return {};
const p = TLS_PROVIDER_PROFILE[provider.trim().toLowerCase()];
return p ? { browserProfile: p.browser, os: p.os } : {};
}
type TlsClientLike = {
available: boolean;
fetch: (url: string, options?: TlsFetchOptions) => Promise<Response>;
@@ -776,6 +794,7 @@ async function patchedFetch(
signal: getEffectiveSignal(input, options),
proxy: null,
sessionScope: tlsStore?.sessionScope,
...tlsProfileForProvider(tlsStore?.provider),
});
if (tlsStore) tlsStore.used = true;
return response;
@@ -1068,6 +1087,7 @@ async function patchedFetch(
signal: getEffectiveSignal(input, options),
proxy: proxyUrl,
sessionScope: tlsStore?.sessionScope,
...tlsProfileForProvider(tlsStore?.provider),
});
if (tlsStore) tlsStore.used = true;
return response;

View File

@@ -1,7 +1,7 @@
{
"name": "omniroute",
"version": "3.8.51",
"description": "Unified AI router with 352 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"description": "Unified AI router with 353 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"type": "module",
"bin": {
"omniroute": "bin/omniroute.mjs",

View File

@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, Segoe UI, Arial, Helvetica, sans-serif" role="img" aria-label="OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 352 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, Segoe UI, Arial, Helvetica, sans-serif" role="img" aria-label="OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 353 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.">
<title>OmniRoute 4-tier fallback</title>
<desc>OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 352 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.</desc>
<desc>OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 353 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.</desc>
<defs>
<marker id="arrow-dark" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#9ca3af"/>
@@ -15,7 +15,7 @@
<!-- Title -->
<text x="400" y="32" text-anchor="middle" font-size="18" font-weight="700" fill="#f9fafb">OmniRoute 4-tier fallback</text>
<text x="400" y="52" text-anchor="middle" font-size="12" fill="#6b7280">Never stop building — automatic zero-config failover across 352 providers</text>
<text x="400" y="52" text-anchor="middle" font-size="12" fill="#6b7280">Never stop building — automatic zero-config failover across 353 providers</text>
<!-- Client box -->
<rect x="275" y="70" width="250" height="52" rx="8" fill="#1f2937" stroke="#374151" stroke-width="1.5"/>

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,6 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, Segoe UI, Arial, Helvetica, sans-serif" role="img" aria-label="OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 352 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, Segoe UI, Arial, Helvetica, sans-serif" role="img" aria-label="OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 353 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.">
<title>OmniRoute 4-tier fallback</title>
<desc>OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 352 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.</desc>
<desc>OmniRoute 4-tier fallback: your IDE or CLI calls one local endpoint and the OmniRoute Smart Router fails over across 353 providers in 4 tiers — Tier 1 Subscription, Tier 2 API, Tier 3 Cheap, Tier 4 Free.</desc>
<defs>
<marker id="arrow-light" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
<path d="M0,0 L0,6 L9,3 z" fill="#6b7280"/>
@@ -15,7 +15,7 @@
<!-- Title -->
<text x="400" y="32" text-anchor="middle" font-size="18" font-weight="700" fill="#111827">OmniRoute 4-tier fallback</text>
<text x="400" y="52" text-anchor="middle" font-size="12" fill="#6b7280">Never stop building — automatic zero-config failover across 352 providers</text>
<text x="400" y="52" text-anchor="middle" font-size="12" fill="#6b7280">Never stop building — automatic zero-config failover across 353 providers</text>
<!-- Client box -->
<rect x="275" y="70" width="250" height="52" rx="8" fill="#ffffff" stroke="#d1d5db" stroke-width="1.5"/>

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -216,6 +216,12 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [
"bin/mcpStdioConsoleGuard.mjs",
"bin/nodeRuntimeSupport.mjs",
"bin/omniroute.mjs",
// #11437: bin/omniroute.mjs imports ./cli/utils/volatileEnvPath.mjs at startup
// (describeVolatileEnvWarning — flags a .env living inside the installed package).
// bin/cli/ is only an allowlist PREFIX, so its absence would never fail the
// unexpected-paths check; list it REQUIRED so a regression is loud (#7065 class,
// enforced by tests/unit/pack-artifact-entrypoint-closures.test.ts).
"bin/cli/utils/volatileEnvPath.mjs",
// #7808: aliasResolver + its hook file. bin/omniroute.mjs imports
// bin/aliasResolver.mjs at startup, which in turn registers
// bin/aliasResolverHook.mjs as the ESM loader. Both must ship in the tarball

View File

@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
const repoRoot = fileURLToPath(new URL("../..", import.meta.url));
const providerDir = join(repoRoot, "public", "providers");
const MAX_RASTER_BYTES = 128 * 1024;
const MAX_RASTER_DIMENSION = 256;
const MAX_RASTER_DIMENSION = 512;
const RASTER_EXTENSIONS = new Set([".png", ".jpg", ".jpeg"]);
function extensionOf(fileName) {

View File

@@ -157,6 +157,133 @@ async function loginAdobeFirefly(
}
}
// --- MaxAI: browserless email device-pair login -----------------------------
/**
* MaxAI email login is a two-step, browserless device-pair flow (no browser /
* camoufox / Google): step "request" emails a 6-digit code; step "verify"
* exchanges the code for the full credential (access + ~1-year refresh token).
*
* The signature is bound to a client-minted device id, so we mint it in the
* request step and persist it to the connection immediately, then read it back
* in the verify step (the route itself is stateless across the two calls).
*/
async function loginMaxaiEmail(
connectionId: string,
connection: Record<string, unknown>,
body: { step?: unknown; email?: unknown; code?: unknown }
): Promise<NextResponse> {
const { randomUUID } = await import("node:crypto");
const { requestMaxaiEmailCode, verifyMaxaiEmailCode } = await import(
"@omniroute/open-sse/executors/maxai/emailLogin.ts"
);
const psd = (connection.providerSpecificData ?? {}) as Record<string, unknown>;
const step = String(body.step || "request");
if (step === "request") {
const email = String(body.email || "").trim();
if (!email) {
return NextResponse.json(
{ success: false, error: "An email address is required." },
{ status: 400 }
);
}
// Mint (or reuse) the client identity and persist it BEFORE the request so
// the verify step signs with the same device id.
const deviceId = String(psd.maxaiDeviceId || psd.deviceId || randomUUID());
const clientUserId = String(psd.maxaiClientUserId || psd.clientUserId || randomUUID());
try {
await updateProviderConnection(connectionId, {
providerSpecificData: {
...psd,
maxaiDeviceId: deviceId,
maxaiClientUserId: clientUserId,
maxaiLoginEmail: email,
},
});
} catch {
/* non-fatal: fall through and still attempt the request */
}
const result = await requestMaxaiEmailCode({ email, deviceId });
if (!result.ok) {
return NextResponse.json(
{ success: false, error: result.error || "Failed to send the sign-in code." },
{ status: result.status && result.status >= 400 ? result.status : 400 }
);
}
return NextResponse.json({
success: true,
step: "request",
message: `A sign-in code was emailed to ${email}. Enter it to finish connecting.`,
email,
});
}
if (step === "verify") {
const code = String(body.code || "").trim();
const email = String(body.email || psd.maxaiLoginEmail || "").trim();
const deviceId = String(psd.maxaiDeviceId || psd.deviceId || "");
const clientUserId = String(psd.maxaiClientUserId || psd.clientUserId || "");
if (!code || !email || !deviceId) {
return NextResponse.json(
{
success: false,
error: !deviceId
? "No pending sign-in. Request a code first."
: "The email and the code are both required.",
},
{ status: 400 }
);
}
const result = await verifyMaxaiEmailCode({ email, code, deviceId, clientUserId });
if (!result.ok || !result.credential) {
return NextResponse.json(
{ success: false, error: result.error || "Code verification failed." },
{ status: result.status && result.status >= 400 ? result.status : 400 }
);
}
const cred = result.credential;
try {
await updateProviderConnection(connectionId, {
// The access token is replayed as `Authorization: Bearer` by the executor.
apiKey: cred.accessToken,
providerSpecificData: {
...psd,
maxaiAccessToken: cred.accessToken,
maxaiRefreshToken: cred.refreshToken,
maxaiDeviceId: cred.deviceId,
maxaiUserId: cred.userId,
maxaiClientUserId: cred.clientUserId,
maxaiLoginEmail: cred.email,
signedInAt: Date.now(),
},
});
} catch (err) {
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : err);
return NextResponse.json(
{ success: false, error: `Signed in but failed to persist: ${msg}` },
{ status: 500 }
);
}
return NextResponse.json({
success: true,
step: "verify",
persisted: true,
account: cred.email,
message: `Connected as ${cred.email}.`,
});
}
return NextResponse.json(
{ success: false, error: `Unknown login step: ${step}` },
{ status: 400 }
);
}
// --- POST: Start login flow -------------------------------------------------
export async function POST(
@@ -178,6 +305,24 @@ export async function POST(
};
const providerSlug = resolveProviderSlug(provider as Record<string, unknown>);
// MaxAI: browserless email device-pair login (no browser). Two-step:
// {step:"request",email} emails a code; {step:"verify",code} mints + persists.
if (providerSlug === "maxai" || providerSlug === "mx") {
try {
return await loginMaxaiEmail(id, provider as Record<string, unknown>, body as {
step?: unknown;
email?: unknown;
code?: unknown;
});
} catch (err) {
const msg = sanitizeErrorMessage(err instanceof Error ? err.message : err);
return NextResponse.json(
{ success: false, error: `MaxAI sign-in error: ${msg}` },
{ status: 500 }
);
}
}
// Adobe Firefly: dedicated JWT capture (never cookies/localStorage alone).
if (isAdobeFireflyProvider(provider as { provider?: unknown }, providerSlug)) {
try {

View File

@@ -50,6 +50,10 @@ import {
discoverNotionWebModels,
NOTION_WEB_FALLBACK_MODELS,
} from "@omniroute/open-sse/services/notionWebModels.ts";
import {
discoverMaxaiModels,
MAXAI_REGISTRY_MODELS,
} from "@omniroute/open-sse/services/maxaiModels.ts";
import {
AZURE_AI_DEFAULT_BASE_URL,
buildAzureAiModelsUrl,
@@ -595,6 +599,50 @@ export async function GET(
});
}
}
// MaxAI: live catalog + per-model context windows from the signed
// /models/get_config (the call the web app makes on load). Falls back to the
// curated static registry catalog on any auth/transport/shape failure.
if (provider === "maxai") {
const cachedResponse = maybeReturnCachedDiscovery();
if (cachedResponse) return cachedResponse;
const autoFetchDisabledResponse = maybeReturnAutoFetchDisabled();
if (autoFetchDisabledResponse) return autoFetchDisabledResponse;
try {
const discovery = await discoverMaxaiModels({
providerSpecificData: connection.providerSpecificData,
accessToken: apiKey || accessToken,
fetchImpl: (url, init) =>
safeOutboundFetch(url, {
...SAFE_OUTBOUND_FETCH_PRESETS.modelsDiscovery,
guard: getProviderOutboundGuard(),
proxyConfig: proxy,
...init,
}),
});
return buildApiDiscoveryResponse(discovery.models, discovery.warning);
} catch (error) {
console.log("Error fetching models from maxai", {
error: error instanceof Error ? error.message : String(error),
});
const fallback = buildDiscoveryFallbackResponse({
cacheWarning: "MaxAI models/get_config failed — using cached catalog",
localWarning: "MaxAI models/get_config failed — using curated catalog",
});
if (fallback) return fallback;
return buildResponse({
provider,
connectionId,
models: MAXAI_REGISTRY_MODELS,
source: "local_catalog",
intentional: true,
warning: "MaxAI catalog unavailable — using curated model list",
});
}
}
const conolResponse = await maybeHandleConolModelDiscovery({
provider,
connectionId,

View File

@@ -477,6 +477,27 @@ export const WEB_COOKIE_PROVIDERS = {
authHint:
"Use browser sign-in, or paste the full Cookie header from conol.ai. The __Secure-better-auth.session_token cookie is required.",
},
maxai: {
id: "maxai",
serviceKinds: ["llm"],
alias: "mx",
name: "MaxAI",
icon: "auto_awesome",
color: "#6D28D9",
textIcon: "MX",
website: "https://www.maxai.co",
// No subscriptionRisk / riskNoticeVariant / notice: MaxAI is TOKEN-authenticated
// (a bearer access token + a long-lived refresh token that OmniRoute refreshes
// browserlessly), NOT a fragile browser-cookie session, so the "webCookie"
// caveat ("may invalidate at any time, log in again, not for unattended use")
// and the "oauth" caveat ("official session not authorized for proxy use") are
// both inaccurate — MaxAI is a purpose-built aggregator whose token IS meant for
// API use. Treated like codex-app-server: no risk banner and no notice; the
// authHint carries the only guidance a connecting operator needs.
toolCalling: "emulated",
authHint:
"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.",
},
};
/** Resolved public site for a web-session provider (href + display host). */

View File

@@ -334,6 +334,22 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = {
acceptsFullCookieHeader: true,
storageKeys: ["cookie", "__Secure-better-auth.session_token"],
},
maxai: {
kind: "token",
credentialName: "MaxAI access token (Bearer) + device id",
placeholder:
"Use browser sign-in — OmniRoute mints the MaxAI access token, device id, and user id for you",
acceptsFullCookieHeader: false,
storageKeys: [
"accessToken",
"access_token",
"maxaiAccessToken",
"deviceId",
"maxaiDeviceId",
"userId",
"maxaiUserId",
],
},
} satisfies Record<string, WebSessionCredentialRequirement> &
Record<keyof typeof WEB_COOKIE_PROVIDERS, WebSessionCredentialRequirement>;

View File

@@ -340,6 +340,8 @@
"tests/unit/repro-9486.test.ts",
"tests/unit/repro-9630-combo-false-503.test.ts",
"tests/unit/repro-antigravity-404-family-cooldown-hijack.test.ts",
"tests/unit/repro-combo-persisted-cooldown-preskip.test.ts",
"tests/unit/repro-glm-iso-reset-24h-cap.test.ts",
"tests/unit/resilience-connections.test.ts",
"tests/unit/responses-handler.test.ts",
"tests/unit/responses-passthrough-openai-compatible.test.ts",

View File

@@ -420,6 +420,11 @@
"configSource": "<custom-config>",
"provider": "lmarena"
},
"maxai": {
"className": "MaxAiExecutor",
"configSource": "maxai",
"provider": "maxai"
},
"moonshot": {
"className": "MoonshotExecutor",
"configSource": "moonshot",
@@ -666,6 +671,6 @@
"provider": "zai-web"
}
},
"keyCount": 133,
"keyCount": 134,
"sharedInstances": []
}

View File

@@ -3614,6 +3614,29 @@
"stream": "https://chat.maritaca.ai/api"
}
},
"maxai": {
"format": "openai",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
}
},
"url": {
"nonStream": "https://api.maxai.me",
"stream": "https://api.maxai.me"
}
},
"meganova-ai": {
"format": "openai",
"headers": {

View File

@@ -0,0 +1,122 @@
/**
* Shared MOCK signing constants + synthetic bundle fixtures for MaxAI unit tests.
*
* IMPORTANT: nothing here is a real MaxAI value. Every id/key/version is an
* obviously-synthetic placeholder that is merely SHAPE-valid (hex / UUID /
* webpage_x.y.z) so it exercises the same validation/parse paths the real values
* would. The real constants are fetched at runtime and persisted to the DB; they
* never appear in the repo (source or tests).
*
* The signer tests prove the HMAC→SM3→AES ALGORITHM by comparing the production
* signer's output to an INDEPENDENT reference implementation (below) computed over
* the same mock key — algorithm correctness without pinning any captured vector.
*/
import { createHmac, createHash } from "node:crypto";
import type { MaxaiSigningConstants } from "../../../open-sse/executors/maxai/constants.ts";
import { MAXAI_DEFAULT_HEADER_NAMES } from "../../../open-sse/executors/maxai/constants.ts";
/** Obviously-fake, shape-valid mock constants (40+ hex, UUID, webpage_x.y.z). */
export const MOCK_HMAC_KEY = "a".repeat(56); // 56 hex chars, like the real key's shape
export const MOCK_AES_KEY = "b".repeat(56);
export const MOCK_CTX_KEY = "c".repeat(40); // 40 hex chars
export const MOCK_DOC_ID_KEY = "00000000-0000-4000-8000-000000000000"; // UUID shape
export const MOCK_APP_VERSION = "webpage_0.0.0"; // version shape, clearly not real
export const MOCK_USER_ID = "11111111-1111-4111-8111-111111111111";
export const MOCK_DEVICE_ID = "22222222-2222-4222-8222-222222222222";
export const MOCK_CONSTANTS: MaxaiSigningConstants = {
hmacKey: MOCK_HMAC_KEY,
aesKey: MOCK_AES_KEY,
appVersion: MOCK_APP_VERSION,
ctxKey: MOCK_CTX_KEY,
docIdKey: MOCK_DOC_ID_KEY,
headerNames: { ...MAXAI_DEFAULT_HEADER_NAMES },
source: "extracted",
extractedAt: 0,
};
/**
* INDEPENDENT reference implementation of the SM3 proof `p` (deliberately NOT
* imported from the production module) so a passing test proves the production
* math matches an external spec, not merely itself.
*/
export function referenceProof(
appVersion: string,
reqTime: number,
path: string,
userId: string,
hmacKey: string
): string {
const signStr = `${appVersion}:${reqTime}:${path}:${userId}`;
const sha1 = createHmac("sha1", Buffer.from(`${reqTime}:${hmacKey}`, "utf8"))
.update(Buffer.from(signStr, "utf8"))
.digest("hex");
return createHash("sm3")
.update(Buffer.from(`${reqTime}:${sha1}:${hmacKey}`, "utf8"))
.digest("hex");
}
/**
* Build a SYNTHETIC `pages/_app` chunk that mirrors the real webpack shape the
* parser matches: module 69319 defining export getters `Mn/Rl/U0` over `let`
* vars, plus the sole `webpage_x.y.z` literal. Uses only the MOCK values.
*/
export function makeSyntheticAppChunk(
c: {
hmacKey?: string;
aesKey?: string;
docIdKey?: string;
appVersion?: string;
} = {}
): string {
const hmac = c.hmacKey ?? MOCK_HMAC_KEY;
const aes = c.aesKey ?? MOCK_AES_KEY;
const doc = c.docIdKey ?? MOCK_DOC_ID_KEY;
const ver = c.appVersion ?? MOCK_APP_VERSION;
// Mirrors the real bundle: getters export short vars; the const run assigns the
// literals (kept in a separate region, exactly like the minified original).
return [
`(self.webpackChunk=self.webpackChunk||[]).push([[69319],{`,
`69319:function(e,t,a){"use strict";a.d(t,{`,
`Mn:function(){return u},Rl:function(){return s},U0:function(){return c},`,
`GB:function(){return m},$0:function(){return p}});`,
`let l="https://api.maxai.me",i="${ver}",o="MAXAI_APP",`,
`s="${aes}",u="${hmac}",c="${doc}",d="website-nextjs",p="prod",m=!1;`,
`}}]);`,
].join("");
}
/**
* Build a SYNTHETIC signer chunk that mirrors the real payload-assembly shape:
* the ctx slot `"<40hex>":{a:await this.getContext()}` plus `(0,r.nj)("<hex>")`
* header-name decoders. Uses only the MOCK ctx key.
*/
export function makeSyntheticSignerChunk(ctxKey: string = MOCK_CTX_KEY): string {
const nj = (s: string) => `(0,r.nj)("${Buffer.from(s, "utf8").toString("hex")}")`;
return [
`n.set(${nj("X-Browser-Name")},"Firefox");`,
`n.set(${nj("X-Authorization")},(0,r.P0)({`,
`[${nj("X-Client-Domain")}]:b,[${nj("X-Client-Path")}]:I,`,
`[${nj("X-Random")}]:Math.floor(1e5+9e5*Math.random()).toString(),`,
`[${nj("t")}]:m,[${nj("p")}]:T,[${nj("d")}]:await this.getAPIFetchDeviceID(),`,
`"${ctxKey}":{a:await this.getContext()}},i.Rl));`,
`n.set(${nj("X-App-Version")},i.F8);`,
`n.set(${nj("X-App-Env")},${nj("MaxAI-Browser-Extension")});`,
].join("");
}
/** Build the app HTML that references synthetic chunk URLs (build-independent). */
export function makeSyntheticAppHtml(
opts: { appChunk?: string; signerChunk?: string; extra?: string[] } = {}
): string {
const app = opts.appChunk ?? "/_next/static/chunks/pages/_app-deadbeef.js";
const signer = opts.signerChunk ?? "/_next/static/chunks/91234-cafebabe.js";
const extras = (opts.extra ?? ["/_next/static/chunks/webpack-1111.js"]).map(
(u) => `<script src="${u}"></script>`
);
return (
extras.join("") +
`<script src="${app}"></script>` +
`<script src="${signer}"></script>`
);
}

View File

@@ -0,0 +1,218 @@
import { test } from "node:test";
import assert from "node:assert";
import {
computeMaxaiDocId,
maxaiDocType,
parseInlineDataUrl,
extractCurrentTurnDocs,
buildUploadMultipart,
sawUploadDone,
uploadMaxaiDocument,
resolveMaxaiDocList,
} from "../../open-sse/executors/maxai/documents.ts";
import { __setMaxaiConstantsForTest } from "../../open-sse/executors/maxai/constantsStore.ts";
import { MOCK_CONSTANTS, MOCK_DOC_ID_KEY } from "./helpers/maxaiMockConstants.ts";
// Doc uploads sign like any request, so seed the in-process constants memo with
// MOCK values instead of mocking the bundle fetch. Nothing real is committed.
const DOC_ID_KEY = MOCK_DOC_ID_KEY;
__setMaxaiConstantsForTest(MOCK_CONSTANTS);
const AUTH = {
accessToken: "tok-abc",
userId: "11111111-1111-4111-8111-111111111111",
deviceId: "22222222-2222-4222-8222-222222222222",
};
// --- doc_id (content-addressed HMAC-SHA1) --------------------------------
test("computeMaxaiDocId is a stable HMAC-SHA1(bytes, key) hex digest", () => {
// Cross-checked shape: HMAC-SHA1 hex is 40 chars; deterministic for same input.
const id = computeMaxaiDocId(Buffer.from("hello world"), DOC_ID_KEY);
assert.equal(id.length, 40);
assert.match(id, /^[0-9a-f]{40}$/);
assert.equal(computeMaxaiDocId(Buffer.from("hello world"), DOC_ID_KEY), id);
// Different key or bytes → different id.
assert.notEqual(id, computeMaxaiDocId(Buffer.from("hello world"), "different-key"));
assert.notEqual(id, computeMaxaiDocId(Buffer.from("other"), DOC_ID_KEY));
});
test("computeMaxaiDocId requires a key (never hashes with a guess)", () => {
assert.throws(() => computeMaxaiDocId(Buffer.from("x"), ""));
});
// --- doc_type classification --------------------------------------------
test("maxaiDocType classifies pdf / code / text", () => {
assert.equal(maxaiDocType("report.pdf", "application/pdf"), "page_content__pdf");
assert.equal(maxaiDocType("script.py", "text/x-python"), "chat_file_code");
assert.equal(maxaiDocType("main.ts", "text/plain"), "chat_file_code");
assert.equal(maxaiDocType("notes.txt", "text/plain"), "chat_file");
assert.equal(maxaiDocType("data.csv", "text/csv"), "chat_file");
});
// --- data-url parsing ----------------------------------------------------
test("parseInlineDataUrl decodes base64 + plain data urls", () => {
const b64 = parseInlineDataUrl("data:text/plain;base64,aGVsbG8="); // "hello"
assert.equal(b64?.mimeType, "text/plain");
assert.equal(b64?.bytes.toString("utf8"), "hello");
const plain = parseInlineDataUrl("data:text/plain,hi%20there");
assert.equal(plain?.bytes.toString("utf8"), "hi there");
assert.equal(parseInlineDataUrl("https://example.com/x.pdf"), null);
assert.equal(parseInlineDataUrl("data:text/plain;base64,"), null); // empty
assert.equal(parseInlineDataUrl(42), null);
});
// --- extract inline docs from the current turn --------------------------
test("extractCurrentTurnDocs handles OpenAI file, Responses input_file, Claude document", () => {
const docs = extractCurrentTurnDocs([
{ role: "system", content: "sys" },
{
role: "user",
content: [
{ type: "text", text: "review these" },
{
type: "file",
file: { filename: "a.txt", file_data: "data:text/plain;base64,QQ==" }, // "A"
},
{ type: "input_file", filename: "b.md", file_data: "data:text/markdown;base64,Qg==" }, // "B"
{
type: "document",
title: "c.pdf",
source: { type: "base64", media_type: "application/pdf", data: "Qw==" }, // "C"
},
],
},
]);
assert.equal(docs.length, 3);
assert.equal(docs[0].filename, "a.txt");
assert.equal(docs[0].bytes.toString("utf8"), "A");
assert.equal(docs[1].filename, "b.md");
assert.equal(docs[2].filename, "c.pdf");
assert.equal(docs[2].mimeType, "application/pdf");
});
test("extractCurrentTurnDocs returns [] for a plain-text turn", () => {
assert.deepEqual(extractCurrentTurnDocs([{ role: "user", content: "just text" }]), []);
});
test("extractCurrentTurnDocs ignores image_url and remote-url file parts", () => {
const docs = extractCurrentTurnDocs([
{
role: "user",
content: [
{ type: "image_url", image_url: { url: "data:image/png;base64,AAAA" } },
{ type: "file", file: { filename: "x.pdf", file_data: "https://example.com/x.pdf" } },
],
},
]);
assert.deepEqual(docs, []); // image handled by vision path; remote url not an inline upload
});
// --- multipart body ------------------------------------------------------
test("buildUploadMultipart includes all required fields + the file bytes", () => {
const doc = { filename: "notes.txt", mimeType: "text/plain", bytes: Buffer.from("secret data") };
const body = buildUploadMultipart(doc, "docid123", "chat_file", "BOUND").toString("utf8");
assert.ok(body.includes('name="doc_id"\r\n\r\ndocid123'));
assert.ok(body.includes('name="doc_type"\r\n\r\nchat_file'));
assert.ok(body.includes('name="pure_text"\r\n\r\nsecret data')); // textual -> pure_text filled
assert.ok(body.includes('name="tokens"'));
assert.ok(body.includes('name="doc_type_dependent_data"\r\n\r\n{}'));
assert.ok(body.includes('name="file"; filename="notes.txt"'));
assert.ok(body.includes("Content-Type: text/plain"));
assert.ok(body.trimEnd().endsWith("--BOUND--"));
});
test("buildUploadMultipart leaves pure_text empty for binary (pdf)", () => {
const doc = { filename: "r.pdf", mimeType: "application/pdf", bytes: Buffer.from([1, 2, 3, 4]) };
const body = buildUploadMultipart(doc, "id", "page_content__pdf", "B").toString("latin1");
assert.ok(body.includes('name="pure_text"\r\n\r\n\r\n')); // empty value
});
// --- SSE done detection --------------------------------------------------
test("sawUploadDone detects the terminal event", () => {
assert.equal(sawUploadDone('data: {"event":"upload_done","data":{"doc_id":"x"}}'), true);
assert.equal(sawUploadDone('data: {"event":"upload_to_s3"}'), false);
});
// --- upload (mocked fetch) ----------------------------------------------
test("uploadMaxaiDocument returns a doc_list entry on upload_done", async () => {
let hitUrl = "";
let hitContentType = "";
const fetchImpl = (async (url: string, init: RequestInit) => {
hitUrl = url;
hitContentType = (init.headers as Record<string, string>)["Content-Type"];
return {
ok: true,
status: 200,
async text() {
return 'data: {"event":"upload_done","data":{"doc_id":"srv"}}\n';
},
} as unknown as Response;
}) as unknown as typeof fetch;
const entry = await uploadMaxaiDocument(
{ filename: "a.txt", mimeType: "text/plain", bytes: Buffer.from("hi") },
AUTH,
{ fetchImpl }
);
assert.ok(entry);
assert.equal(entry!.doc_id, computeMaxaiDocId(Buffer.from("hi"), DOC_ID_KEY));
assert.equal(entry!.doc_type, "chat_file");
assert.equal(entry!.file_name, "a.txt");
assert.match(hitUrl, /\/app\/upload_document$/);
assert.match(hitContentType, /^multipart\/form-data; boundary=/);
});
test("uploadMaxaiDocument returns null on a non-200 (best-effort)", async () => {
const fetchImpl = (async () =>
({ ok: false, status: 400, async text() { return "bad"; } }) as unknown as Response) as unknown as typeof fetch;
const entry = await uploadMaxaiDocument(
{ filename: "a.txt", mimeType: "text/plain", bytes: Buffer.from("hi") },
AUTH,
{ fetchImpl }
);
assert.equal(entry, null);
});
test("resolveMaxaiDocList uploads all current-turn docs, skips failures", async () => {
let call = 0;
const fetchImpl = (async () => {
call += 1;
// first upload succeeds, second fails
if (call === 1) {
return { ok: true, status: 200, async text() { return '{"event":"upload_done"}'; } } as unknown as Response;
}
return { ok: false, status: 500, async text() { return ""; } } as unknown as Response;
}) as unknown as typeof fetch;
const list = await resolveMaxaiDocList(
[
{
role: "user",
content: [
{ type: "file", file: { filename: "a.txt", file_data: "data:text/plain;base64,QQ==" } },
{ type: "file", file: { filename: "b.txt", file_data: "data:text/plain;base64,Qg==" } },
],
},
],
AUTH,
{ fetchImpl }
);
assert.equal(list.length, 1); // one succeeded, one skipped
assert.equal(list[0].file_name, "a.txt");
});
test("resolveMaxaiDocList returns [] when there are no inline docs", async () => {
const list = await resolveMaxaiDocList([{ role: "user", content: "hi" }], AUTH, {
fetchImpl: (async () => ({ ok: true, status: 200, async text() { return ""; } }) as unknown as Response) as unknown as typeof fetch,
});
assert.deepEqual(list, []);
});

View File

@@ -0,0 +1,169 @@
import { test } from "node:test";
import assert from "node:assert";
import {
resolveMaxaiImageModel,
snapMaxaiImageSize,
extractMaxaiImageUrls,
handleMaxaiImageGeneration,
MAXAI_IMAGE_PATH,
} from "../../open-sse/handlers/imageGeneration/providers/maxaiImage.ts";
import { IMAGE_PROVIDERS } from "../../open-sse/config/imageRegistry.ts";
import { __setMaxaiConstantsForTest } from "../../open-sse/executors/maxai/constantsStore.ts";
import { MOCK_CONSTANTS } from "./helpers/maxaiMockConstants.ts";
// Image generation signs like any request; seed the in-process constants memo
// with MOCK values so the handler doesn't try to fetch the live MaxAI bundle.
__setMaxaiConstantsForTest(MOCK_CONSTANTS);
// A minimal valid MaxAI credential (userId derives nothing here; the signer is
// exercised elsewhere). providerSpecificData carries the token + device id.
const CRED = {
providerSpecificData: {
maxaiAccessToken: "tok-abc",
maxaiDeviceId: "dev-123",
maxaiUserId: "11111111-1111-4111-8111-111111111111",
},
};
// --- Registry ------------------------------------------------------------
test("maxai is registered in IMAGE_PROVIDERS with the maxai-image format + 6 models", () => {
const entry = (IMAGE_PROVIDERS as Record<string, { format?: string; baseUrl?: string; models?: unknown[] }>)["maxai"];
assert.ok(entry, "maxai must exist in IMAGE_PROVIDERS");
assert.equal(entry.format, "maxai-image");
assert.match(String(entry.baseUrl), /api\.maxai\.me\/gpt\/get_image_generate_response/);
assert.equal((entry.models ?? []).length, 6);
});
// --- Pure helpers --------------------------------------------------------
test("resolveMaxaiImageModel strips maxai/ prefix and resolves aliases", () => {
assert.equal(resolveMaxaiImageModel("maxai/gpt-image-1"), "gpt-image-1");
assert.equal(resolveMaxaiImageModel("stable-diffusion-v3"), "sd3-medium");
assert.equal(resolveMaxaiImageModel("stable-diffusion-3-medium"), "sd3-medium");
assert.equal(resolveMaxaiImageModel("flux-1-schnell"), "flux-1-schnell");
});
test("snapMaxaiImageSize snaps unsupported sizes for strict models, passes flux through", () => {
// gpt-image-1 / dall-e-3 reject 512x512 -> snap to 1024x1024
assert.equal(snapMaxaiImageSize("gpt-image-1", "512x512"), "1024x1024");
assert.equal(snapMaxaiImageSize("dall-e-3", "256x256"), "1024x1024");
// supported sizes pass through
assert.equal(snapMaxaiImageSize("gpt-image-1", "1536x1024"), "1536x1024");
assert.equal(snapMaxaiImageSize("dall-e-3", "1792x1024"), "1792x1024");
// flux / sd3: no constraint, any size passes through
assert.equal(snapMaxaiImageSize("flux-1-schnell", "512x512"), "512x512");
assert.equal(snapMaxaiImageSize("sd3-medium", "768x768"), "768x768");
// missing size -> default
assert.equal(snapMaxaiImageSize("gpt-image-1", undefined), "1024x1024");
});
test("extractMaxaiImageUrls prefers png_url, falls back to webp_url", () => {
assert.deepEqual(
extractMaxaiImageUrls([{ png_url: "p.png", webp_url: "w.webp" }, { webp_url: "only.webp" }]),
["p.png", "only.webp"]
);
assert.deepEqual(extractMaxaiImageUrls([]), []);
assert.deepEqual(extractMaxaiImageUrls(null), []);
});
// --- Handler (mocked fetch) ---------------------------------------------
function mockFetch(status: number, jsonBody: unknown): typeof fetch {
return (async () =>
({
ok: status >= 200 && status < 300,
status,
async json() {
return jsonBody;
},
async text() {
return JSON.stringify(jsonBody);
},
}) as unknown as Response) as unknown as typeof fetch;
}
test("handleMaxaiImageGeneration returns OpenAI image data on success", async () => {
let capturedUrl = "";
let capturedBody: Record<string, unknown> = {};
const fetchImpl = (async (url: string, init: RequestInit) => {
capturedUrl = url;
capturedBody = JSON.parse(String(init.body));
return {
ok: true,
status: 200,
async json() {
return { status: "OK", data: [{ png_url: "https://cdn/x.png", webp_url: "https://cdn/x.webp" }] };
},
async text() {
return "";
},
} as unknown as Response;
}) as unknown as typeof fetch;
const result = (await handleMaxaiImageGeneration({
model: "flux-1-schnell",
provider: "maxai",
body: { prompt: "a red bicycle", size: "512x512", n: 2 },
credentials: CRED,
fetchImpl,
})) as { success: boolean; data?: { data: Array<{ url: string }> } };
assert.equal(result.success, true);
assert.deepEqual(result.data?.data, [{ url: "https://cdn/x.png" }]);
// Hit the image endpoint with the signed body.
assert.match(capturedUrl, new RegExp(MAXAI_IMAGE_PATH.replace(/\//g, "\\/")));
assert.equal(capturedBody.model_name, "flux-1-schnell");
assert.equal(capturedBody.size, "512x512"); // flux passes size through
assert.equal(capturedBody.n, 2);
});
test("handleMaxaiImageGeneration 401 is retryable (credential fallback)", async () => {
const result = (await handleMaxaiImageGeneration({
model: "gpt-image-1",
provider: "maxai",
body: { prompt: "x" },
credentials: CRED,
fetchImpl: mockFetch(401, { error: "expired" }),
})) as { success: boolean; status?: number; retryable?: boolean };
assert.equal(result.success, false);
assert.equal(result.status, 401);
assert.equal(result.retryable, true);
});
test("handleMaxaiImageGeneration rejects an empty prompt with 400", async () => {
const result = (await handleMaxaiImageGeneration({
model: "gpt-image-1",
provider: "maxai",
body: { prompt: " " },
credentials: CRED,
fetchImpl: mockFetch(200, {}),
})) as { success: boolean; status?: number };
assert.equal(result.success, false);
assert.equal(result.status, 400);
});
test("handleMaxaiImageGeneration 401s with no credential (retryable)", async () => {
const result = (await handleMaxaiImageGeneration({
model: "gpt-image-1",
provider: "maxai",
body: { prompt: "x" },
credentials: {},
fetchImpl: mockFetch(200, {}),
})) as { success: boolean; status?: number; retryable?: boolean };
assert.equal(result.success, false);
assert.equal(result.status, 401);
assert.equal(result.retryable, true);
});
test("handleMaxaiImageGeneration surfaces a no-images response as 502", async () => {
const result = (await handleMaxaiImageGeneration({
model: "sd3-medium",
provider: "maxai",
body: { prompt: "x" },
credentials: CRED,
fetchImpl: mockFetch(200, { status: "OK", data: [] }),
})) as { success: boolean; status?: number };
assert.equal(result.success, false);
assert.equal(result.status, 502);
});

1088
tests/unit/maxai.test.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -171,7 +171,7 @@ test("shared set size includes live REGISTRY and retired Designer + Felo + Qwen
// 1 and adds 2 distinct tombstones "qwen-web"/"qw", a net +1) on top of the
// live REGISTRY walk, minus the 3 GPL-derived Raycast/Hailuo Web
// ids/aliases removed from REGISTRY by #11691's migration 166.
assert.equal(RESERVED_PREFIX_COUNT, 400);
assert.equal(RESERVED_PREFIX_COUNT, 402);
});
test("isReservedProviderPrefix rejects non-string input", () => {

View File

@@ -239,6 +239,18 @@ test("#6593 a maxWaitMs override of 0 is treated as no override", () => {
}
});
test("maxai receives a provider-scoped 5min execution budget (slow reasoning models)", () => {
// The default 15s Bottleneck expiration kills MaxAI reasoning turns (30s-min+)
// mid-think; maxai (and its mx alias) floor at 300s so they complete.
assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("maxai", 15_000), 300_000);
assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("MaxAI", 15_000), 300_000);
assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("mx", 15_000), 300_000);
// A larger configured value is preserved (floor never lowers it).
assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("maxai", 600_000), 600_000);
// Other providers are unaffected.
assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("openai", 15_000), 15_000);
});
test("#6593 DEFAULT_REQUEST_QUEUE_MAX_DEPTH defaults to 0 (disabled) absent an env override", () => {
assert.equal(process.env.RATE_LIMIT_MAX_QUEUE_DEPTH, undefined);
assert.equal(resilienceSettings.DEFAULT_REQUEST_QUEUE_MAX_DEPTH, 0);