Compare commits
23 Commits
fix/v3851-
...
feat/chatg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c84e980402 | ||
|
|
6850e93002 | ||
|
|
d9041b7070 | ||
|
|
7183252e4b | ||
|
|
07c5e01b59 | ||
|
|
8483b98205 | ||
|
|
dc8e458dfe | ||
|
|
9ebd91cdb9 | ||
|
|
30eabbef3b | ||
|
|
6bcc31bdf2 | ||
|
|
4457f89c3f | ||
|
|
177d64da97 | ||
|
|
4ae94a9329 | ||
|
|
16288c9928 | ||
|
|
a853ce146e | ||
|
|
71651d8c0a | ||
|
|
1f061d7251 | ||
|
|
58d5d52c72 | ||
|
|
ed00c8c489 | ||
|
|
f16e0ed682 | ||
|
|
5851d6e287 | ||
|
|
cc85011dd4 | ||
|
|
baa45e9a67 |
@@ -2922,6 +2922,15 @@ QUOTA_STORE_DRIVER=sqlite
|
||||
# CODEX_CHATGPT_WEB_BUN=/absolute/path/to/bun
|
||||
# CODEX_WEB_GPT_BUN=/absolute/path/to/bun
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# ChatGPT Session provider — stream-open gate
|
||||
# Used by: open-sse/executors/chatgpt-session/bridge.ts
|
||||
# How long the stream-open gate waits for the first committing event before
|
||||
# opening the stream anyway. Falls back to the 30s default when unset, empty,
|
||||
# unparseable, non-finite, or not positive.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS=30000
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Browser-login VNC sessions (optional — src/lib/vncSession/manifest.ts)
|
||||
# Containerized Chromium+VNC used for interactive browser-login credential
|
||||
|
||||
@@ -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 |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
14
README.md
@@ -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 15–95% 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 15–95% tokens (~89% avg) — never hit limits. 353 AI providers · 150+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start."/>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<tr>
|
||||
<td align="right"><b>⚙️ Features</b></td>
|
||||
<td align="center"><a href="#-combos--the-flagship">🎯 Combos</a></td>
|
||||
<td align="center"><a href="#-352-ai-providers--154-catalog-marked-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-353-ai-providers--154-catalog-marked-free">🌐 Providers</a></td>
|
||||
<td align="center"><a href="#-full-cli--a2a--mcp">🔌 CLI & MCP</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -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 & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
|
||||
|
||||
@@ -561,7 +561,7 @@ the current catalog at **[radar.omniroute.online/planos](https://radar.omniroute
|
||||
- **🖼️ New endpoints** — `/v1/ocr` (Mistral OCR) and `/v1/audio/translations` (Whisper-style) round out the media surface. → [API Reference](docs/reference/API_REFERENCE.md)
|
||||
- **🎨 Image / video / audio generation** — one API for media: xAI Grok Imagine & Novita AI video, ComfyUI, Magnific, Adobe Firefly, Segmind, and speech providers such as ElevenLabs. → [API Reference](docs/reference/API_REFERENCE.md)
|
||||
- **🌍 Deployment & ops** — reverse-proxy `basePath`, browser-language auto-detect, per-key device tracking, root-less MITM trust, zh-TW localization. → [Environment](docs/reference/ENVIRONMENT.md)
|
||||
- **🤝 More providers & agents** — cloud agents (Codex Cloud, Cursor, Devin, Jules), Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **352-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
|
||||
- **🤝 More providers & agents** — cloud agents (Codex Cloud, Cursor, Devin, Jules), Grok Build (xAI) with browser + OAuth login, Ollama first-class card, Claude Opus 5 & Sonnet 5, Kimi official partnership (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… and a refreshed **353-provider catalog**. → [Providers](docs/reference/PROVIDER_REFERENCE.md)
|
||||
- **📡 Routing transparency** — every response carries an `X-OmniRoute-Decision` header naming the strategy/provider/latency that served it, a new `cache-optimized` combo strategy + Auto-Combo `cacheAffinity` factor route repeat requests back to the connection holding the cached prefix, and a read-only `/v1/auto-combo/{channel}/candidates` endpoint exposes an `auto/*` channel's live candidate pool. → [Auto-Combo](docs/routing/AUTO-COMBO.md)
|
||||
- **⚡ Local performance & infra** — one-click local Redis, Cloudflare Workers / Deno Deploy relay deployers, Bifrost & Mux as supervised embedded services. → [Embedded Services](docs/frameworks/EMBEDDED-SERVICES.md)
|
||||
- **🧩 Also in the box** — plugin framework + marketplace, Omni/Agent/GitHub skills frameworks, Obsidian vault integration (22 MCP tools), OpenAI-compatible Batch & Files APIs, semantic response cache, gamification with leaderboards, ACP agent discovery (15 built-in agents), scheduled log export to BigQuery, `auto/chaos` fault injection, a Telegram bot bridge, an in-app version manager and LMArena-ELO free-provider rankings. → [Docs](docs/README.md)
|
||||
@@ -645,11 +645,11 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md)
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🌐 352 AI Providers — 152 Catalog-Marked Free
|
||||
## 🌐 353 AI Providers — 152 Catalog-Marked Free
|
||||
|
||||
</div>
|
||||
|
||||
> **352 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **446 per-model rows**, **38 recurring pools** and **53 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
|
||||
> **353 registered providers** across the canonical chat, media, search, local, cloud-agent and system collections, including **152 carrying `hasFree: true` discovery metadata**. The chat model registry covers **229 providers / 2,554 distinct provider-model pairs / 1,283 raw model IDs**; the separate free-budget catalog has **446 per-model rows**, **38 recurring pools** and **53 recurring/keyless free-forever providers**. These are different denominators by design; definitions and pool-deduped calculations live in the [Provider Reference](docs/reference/PROVIDER_REFERENCE.md) and [Free Tiers](docs/reference/FREE_TIERS.md).
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
||||
3
changelog.d/features/pending-chatgpt-session-provider.md
Normal file
@@ -0,0 +1,3 @@
|
||||
- Added `chatgpt-session` (alias `cgpt-session`), a clean-room ChatGPT Web provider serving
|
||||
`/v1/chat/completions` from an authenticated browser session. The retired common
|
||||
`chatgpt-web` provider stays retired.
|
||||
@@ -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"/>
|
||||
@@ -6,7 +6,7 @@
|
||||
<path d="M 0 34 L 1200 34" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<circle cx="24" cy="17" r="6" fill="#ff5f56"/><circle cx="46" cy="17" r="6" fill="#ffbd2e"/><circle cx="68" cy="17" r="6" fill="#27c93f"/>
|
||||
<text x="600" y="22" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="13" fill="#71717a">omniroute — 86 top-level commands</text>
|
||||
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">… 348 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
|
||||
<g font-family="Consolas, 'Courier New', monospace" font-size="17"><animate attributeName="opacity" values="1;0;0" keyTimes="0;0.006;1" dur="18s" repeatCount="indefinite"/><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text><text x="40" y="100" font-weight="700" fill="#38bdf8">OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa">1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa">8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa">f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa">03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a">… 349 more providers</text></g><g opacity="1" font-family="Consolas, 'Courier New', monospace" font-size="17">
|
||||
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.315;0.33;1" dur="18s" repeatCount="indefinite"/>
|
||||
<text x="40" y="66" fill="#22c55e">$</text>
|
||||
<g clip-path="url(#tw0)"><text x="64" y="66" fill="#F7F6FC">omniroute providers list</text></g>
|
||||
@@ -14,7 +14,7 @@
|
||||
<animate attributeName="x" calcMode="discrete" values="64;95;125;156;186;217;248;278;309;309" keyTimes="0.000;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;0;1;0.2;1;0.2;1;0;0" keyTimes="0;0.011;0.012;0.022;0.032;0.042;0.052;0.074;1" dur="18s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>… 348 more providers</text>
|
||||
<text x="40" y="100" font-weight="700" fill="#38bdf8" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.045;0.047" dur="18s" repeatCount="indefinite"/>OmniRoute Providers</text><text x="40" y="128" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.053;0.055" dur="18s" repeatCount="indefinite"/>1f3a9c2e  anthropic   Claude Max 20x    <tspan fill='#22c55e'>active</tspan></text><text x="40" y="154" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.061;0.063" dur="18s" repeatCount="indefinite"/>8c2d5b1a  codex       Codex Pro (team)  <tspan fill='#22c55e'>active</tspan></text><text x="40" y="180" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.069;0.07100000000000001" dur="18s" repeatCount="indefinite"/>f4e0a97b  glm         GLM Coding Plan   <tspan fill='#22c55e'>active</tspan></text><text x="40" y="206" fill="#a1a1aa" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.077;0.079" dur="18s" repeatCount="indefinite"/>03bd6e5f  kimi        Kimi K2 free      <tspan fill='#22c55e'>active</tspan></text><text x="40" y="232" fill="#71717a" opacity="0"><animate attributeName="opacity" calcMode="discrete" values="0;0;1" keyTimes="0;0.085;0.08700000000000001" dur="18s" repeatCount="indefinite"/>… 349 more providers</text>
|
||||
</g><g opacity="0" font-family="Consolas, 'Courier New', monospace" font-size="17">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.333;0.34800000000000003;0.648;0.663;1" dur="18s" repeatCount="indefinite"/>
|
||||
<text x="40" y="66" fill="#22c55e">$</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -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>
|
||||
@@ -23,7 +23,7 @@
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.15s" fill="freeze"/>
|
||||
<text x="44" y="196" font-size="14.5" fill="#c9d1d9">Providers</text>
|
||||
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">352</text>
|
||||
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">353</text>
|
||||
<text x="604" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">40+</text>
|
||||
<text x="760" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">400+*</text>
|
||||
<text x="916" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">~5</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -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 |
@@ -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  →  <tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>
|
||||
@@ -66,7 +66,7 @@
|
||||
<!-- stat chips -->
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" text-anchor="middle">
|
||||
<rect x="48" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#6c5ce7" stroke-opacity="0.55" stroke-width="1.5"/>
|
||||
<text x="134" y="471" font-size="17" font-weight="800" fill="#a78bfa">352</text>
|
||||
<text x="134" y="471" font-size="17" font-weight="800" fill="#a78bfa">353</text>
|
||||
<text x="134" y="490" font-size="11" fill="#a1a1aa">AI PROVIDERS</text>
|
||||
<rect x="234" y="448" width="172" height="52" rx="12" fill="#161b22" stroke="#22c55e" stroke-opacity="0.55" stroke-width="1.5"/>
|
||||
<text x="320" y="471" font-size="17" font-weight="800" fill="#7ee787">90+</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -169,7 +169,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -478,7 +478,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
85
docs/providers/CHATGPT_SESSION.md
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "Providers — ChatGPT Web (Session)"
|
||||
version: 3.8.51
|
||||
lastUpdated: 2026-09-02
|
||||
---
|
||||
|
||||
# Providers — ChatGPT Web (Session)
|
||||
|
||||
`chatgpt-session` (alias `cgpt-session`) serves ordinary `/v1/chat/completions` requests from
|
||||
an authenticated ChatGPT browser session. It is a clean-room implementation and shares no code
|
||||
with the retired common `chatgpt-web` provider; the browser interaction reuses the MIT-noticed
|
||||
implementation under `open-sse/vendor/codex-chatgpt-web/`.
|
||||
|
||||
## Relationship to the other ChatGPT providers
|
||||
|
||||
| Provider | Endpoint | Client |
|
||||
| ----------------------- | ---------------------- | ---------------------------- |
|
||||
| `chatgpt-session` | `/v1/chat/completions` | any OpenAI-compatible client |
|
||||
| `chatgpt-web-codex` | `/v1/responses` | the native Codex CLI only |
|
||||
| `chatgpt-web` (retired) | — | fails closed with HTTP 410 |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- a full Cookie header from a signed-in ChatGPT session;
|
||||
- Chrome or Chromium, plus a graphical session or Xvfb on headless hosts;
|
||||
- with the Docker `web` profile, the internal Chromium service from `docker-compose.yml`.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Open the **ChatGPT Web (Session)** provider in the dashboard and add a connection.
|
||||
2. Paste the full ChatGPT Cookie header.
|
||||
3. Run the connection check. OmniRoute opens an isolated browser profile, verifies the session
|
||||
and detects whether Sol and Pro are available for the account.
|
||||
4. Save. The pasted cookie is replaced by the verified browser session state; the raw cookie is
|
||||
not retained.
|
||||
|
||||
When the session expires, paste a fresh Cookie header and rerun the check.
|
||||
|
||||
## Models
|
||||
|
||||
| Model | Account requirement |
|
||||
| ---------------------------- | -------------------------------- |
|
||||
| `chatgpt-session/luna` | Free/Go accounts (Luna selector) |
|
||||
| `chatgpt-session/think` | Free/Go accounts |
|
||||
| `chatgpt-session/instant` | Sol-capable accounts |
|
||||
| `chatgpt-session/medium` | Sol-capable accounts |
|
||||
| `chatgpt-session/high` | Sol-capable accounts |
|
||||
| `chatgpt-session/extra-high` | Pro-capable accounts |
|
||||
| `chatgpt-session/pro` | Pro-capable accounts |
|
||||
|
||||
Each model pins one backend model and one reasoning effort. Requesting a route the account does
|
||||
not expose fails closed with HTTP 400 instead of silently switching modes.
|
||||
|
||||
## Tool calling
|
||||
|
||||
Tool calling is prompt-emulated, the same contract `perplexity-web` and `gemini-web` use. Tool
|
||||
turns are answered non-streaming and then replayed as a terminal SSE stream when the client
|
||||
asked for one.
|
||||
|
||||
## Errors
|
||||
|
||||
| Condition | Status |
|
||||
| -------------------------------------------- | ------------------------------------ |
|
||||
| No Chrome or Chromium available | 503, with a connection-cooldown hint |
|
||||
| Missing or unreadable credentials | 401 |
|
||||
| Expired session | 401 |
|
||||
| ChatGPT usage limit reached | 429 |
|
||||
| Model not available for the account | 400 |
|
||||
| ChatGPT interface changed (selector timeout) | 400 |
|
||||
| Any other turn failure | 502 |
|
||||
|
||||
Cookies and session state never appear in responses or logs.
|
||||
|
||||
## Limitations
|
||||
|
||||
Phase 1 covers text chat only. Image generation and editing, citation links and conversation
|
||||
resume are not implemented.
|
||||
|
||||
Every turn is relayed to ChatGPT through the vendored adapter's own task-framing prompt
|
||||
(`open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/prompt.ts`), which wraps the request
|
||||
before it is typed into the web UI. That framing is not visible to API clients: what a client
|
||||
sends is not literally what the ChatGPT session receives. One consequence to watch for is that a
|
||||
tool turn may occasionally be answered with a refusal about local-tool access instead of a tool
|
||||
block. This has not been observed on a live turn yet — it is a property of the framing that live
|
||||
validation still has to confirm or rule out.
|
||||
@@ -24,6 +24,9 @@ invalidates their active session leases. It preserves connection history and API
|
||||
allowlists; it does not add replacement access to an allowlist. The Codex provider and
|
||||
its connections are not matched by this retirement.
|
||||
|
||||
For ordinary `/v1/chat/completions` traffic over a ChatGPT session, use the clean-room
|
||||
`chatgpt-session` provider instead — see [CHATGPT_SESSION.md](./CHATGPT_SESSION.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- a full Cookie header from a signed-in ChatGPT session;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"ALIBABA-QWEN-PROVIDER-FAMILIES",
|
||||
"CLAUDE_WEB",
|
||||
"CHATGPT_WEB",
|
||||
"CHATGPT_SESSION",
|
||||
"AGENTROUTER",
|
||||
"ZED-DOCKER",
|
||||
"CURSOR-DOCKER",
|
||||
|
||||
@@ -749,6 +749,7 @@ REQUEST_TIMEOUT_MS (global override)
|
||||
| `OMNIROUTE_CODEX_APPSERVER_AUTO_APPROVE` | `false` | Auto-approve the app-server's own approval prompts (command/file/permission execution on the host). Off by default — prompts are auto-denied; harness tool calls are unaffected (they travel the separate `item/tool/call` passthrough). Accepts `true`/`1`/`yes`. Per-connection override: `providerSpecificData.codexAppServerAutoApprove`. |
|
||||
| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. |
|
||||
| `OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS` | `30000` (30s) | Maximum response-start wait (ms) for each direct no-proxy attempt. A timeout retries once on a fresh socket; set `0` to disable the bound and retain the previous behavior. |
|
||||
| `OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS` | `30000` (30s) | How long the `chatgpt-session` executor's stream-open gate waits for the first committing event before opening the stream anyway (`open-sse/executors/chatgpt-session/bridge.ts`). Falls back to the 30s default when unset, empty, unparseable, non-finite, or not positive. |
|
||||
| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. |
|
||||
| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP connection establishment timeout. |
|
||||
| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. |
|
||||
|
||||
@@ -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,13 +80,14 @@ 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 |
|
||||
|----|-------|------|------|---------|-------|--------------|
|
||||
| `adapta-web` | `adp-web` | Adapta.org (Adapta One Web) | Web cookie | [link](https://agent.adapta.one) | Paste your __client cookie value from .clerk.agent.adapta.one (DevTools → Application → Cookies) | emulated |
|
||||
| `adobe-firefly` | `firefly` | Adobe Firefly (Image/Video) | Web cookie | [link](https://firefly.adobe.com) | RECOMMENDED: firefly.adobe.com signed-in → F12 → Network → click firefly-3p.ff.adobe.io (generate-async or models/discovery) → Request Headers → Authorization → copy the token AFTER 'Bearer ' (starts with eyJ…). Cookie-only from firefly.adobe.com mints a GUEST token → 401/403; only multi-domain IMS cookies (adobelogin.com) or that Bearer JWT work. Unofficial/experimental media + Limits. | — |
|
||||
| `blackbox-web` | `bb-web` | Blackbox Web (Subscription) | Web cookie | [link](https://app.blackbox.ai) | Paste your __Secure-authjs.session-token value or full cookie header from app.blackbox.ai | emulated |
|
||||
| `chatgpt-session` | `cgpt-session` | ChatGPT Web (Session) | Web cookie | [link](https://chatgpt.com) | Paste the full ChatGPT Cookie header. OmniRoute verifies it in an isolated browser profile and then stores only the verified session state. | emulated |
|
||||
| `chatgpt-web-codex` | `cgpt-codex` | ChatGPT Web (Codex) | Web cookie | [link](https://chatgpt.com) | Paste the full ChatGPT Cookie header. OmniRoute verifies it in an isolated headless browser profile. | native |
|
||||
| `claude-web` | `cw` | Claude Web | Web cookie | [link](https://claude.ai) | Paste your session cookie from claude.ai | none |
|
||||
| `conol-web` | `cnl` | Conol (Unofficial/Experimental) | Web cookie | [link](https://conol.ai) | Use browser sign-in, or paste the full Cookie header from conol.ai. The __Secure-better-auth.session_token cookie is required. | — |
|
||||
@@ -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
|
||||
|
||||
8
llm.txt
@@ -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
|
||||
|
||||
@@ -165,7 +165,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
│ │ └── manager.ts # MITM proxy manager
|
||||
│ ├── shared/ # Shared utilities, components, and constants
|
||||
│ │ ├── components/ # Reusable UI components (Card, Badge, Button, Modal, Sidebar, ProviderIcon, etc.)
|
||||
│ │ ├── constants/ # Provider definitions (352), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── constants/ # Provider definitions (353), model lists, pricing, routing strategies, MCP scopes
|
||||
│ │ ├── contracts/ # Shared API contracts
|
||||
│ │ ├── hooks/ # React hooks
|
||||
│ │ ├── middleware/ # Shared middleware utilities
|
||||
@@ -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
|
||||
@@ -474,7 +474,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **352-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **353-provider catalog** with 150+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **16-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -122,6 +122,7 @@ import { blackbox_webProvider } from "./registry/blackbox/web/index.ts";
|
||||
import { uncloseaiProvider } from "./registry/uncloseai/index.ts";
|
||||
import { nscaleProvider } from "./registry/nscale/index.ts";
|
||||
import { chatgpt_web_codexProvider } from "./registry/chatgpt-web-codex/index.ts";
|
||||
import { chatgpt_sessionProvider } from "./registry/chatgpt-session/index.ts";
|
||||
import { openrouterProvider } from "./registry/openrouter/index.ts";
|
||||
import { cheaperinferenceProvider } from "./registry/cheaperinference/index.ts";
|
||||
import { openvectaProvider } from "./registry/openvecta/index.ts";
|
||||
@@ -388,6 +389,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
uncloseai: uncloseaiProvider,
|
||||
nscale: nscaleProvider,
|
||||
"chatgpt-web-codex": chatgpt_web_codexProvider,
|
||||
"chatgpt-session": chatgpt_sessionProvider,
|
||||
openrouter: openrouterProvider,
|
||||
cheaperinference: cheaperinferenceProvider,
|
||||
openvecta: openvectaProvider,
|
||||
|
||||
27
open-sse/config/providers/registry/chatgpt-session/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
|
||||
const SESSION_CAPABILITIES = {
|
||||
toolCalling: true,
|
||||
supportsReasoning: true,
|
||||
supportsVision: false,
|
||||
} as const;
|
||||
|
||||
export const chatgpt_sessionProvider: RegistryEntry = {
|
||||
id: "chatgpt-session",
|
||||
alias: "cgpt-session",
|
||||
format: "openai",
|
||||
executor: "chatgpt-session",
|
||||
baseUrl: "https://chatgpt.com",
|
||||
reasoningTransport: "opaque",
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "luna", name: "ChatGPT Session — Luna", ...SESSION_CAPABILITIES },
|
||||
{ id: "think", name: "ChatGPT Session — Think", ...SESSION_CAPABILITIES },
|
||||
{ id: "instant", name: "ChatGPT Session — Instant", ...SESSION_CAPABILITIES },
|
||||
{ id: "medium", name: "ChatGPT Session — Medium", ...SESSION_CAPABILITIES },
|
||||
{ id: "high", name: "ChatGPT Session — High", ...SESSION_CAPABILITIES },
|
||||
{ id: "extra-high", name: "ChatGPT Session — Extra High", ...SESSION_CAPABILITIES },
|
||||
{ id: "pro", name: "ChatGPT Session — Pro", ...SESSION_CAPABILITIES },
|
||||
],
|
||||
};
|
||||
347
open-sse/executors/chatgpt-session.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
/**
|
||||
* ChatGptSessionExecutor — OpenAI chat completions over an authenticated ChatGPT browser
|
||||
* session.
|
||||
*
|
||||
* The vendored MIT browser adapter owns every anti-bot interaction (sentinel, turnstile,
|
||||
* proof-of-work) because a real signed-in browser performs them; this executor only translates
|
||||
* request and response shapes around it.
|
||||
*/
|
||||
|
||||
import { CHATGPT_WEB_CODEX_CONNECTOR_NAME } from "@/shared/constants/chatgptWebCodex";
|
||||
|
||||
import { FORMATS } from "../translator/formats.ts";
|
||||
import { prepareToolMessages } from "../translator/webTools.ts";
|
||||
import { buildErrorBody, sanitizeErrorMessage } from "../utils/error.ts";
|
||||
import { AsyncEventQueue } from "../vendor/codex-chatgpt-web/event-queue.ts";
|
||||
import type { AdapterEvent, CodexProviderConfig } from "../vendor/codex-chatgpt-web/types.ts";
|
||||
import { BaseExecutor, type ExecuteInput, type ExecutorExecuteResult } from "./base.ts";
|
||||
import { buildToolModeResponse } from "./chatgptWebTools.ts";
|
||||
import {
|
||||
decodeChatGptWebCodexSecrets,
|
||||
encodeChatGptWebCodexSecrets,
|
||||
} from "./chatgpt-web-codex/credentials.ts";
|
||||
import { connectionRuntimePaths } from "./chatgpt-web-codex/storageState.ts";
|
||||
import {
|
||||
buildChatGptSessionCompletion,
|
||||
openChatGptSessionStream,
|
||||
resolveChatGptSessionStreamOpenTimeoutMs,
|
||||
type ChatGptSessionResponseMeta,
|
||||
} from "./chatgpt-session/bridge.ts";
|
||||
import { classifyChatGptSessionError } from "./chatgpt-session/errors.ts";
|
||||
import { buildParsedRequest } from "./chatgpt-session/messages.ts";
|
||||
import { requireChatGptSessionRoute, type ChatGptSessionRoute } from "./chatgpt-session/models.ts";
|
||||
import {
|
||||
chatGptSessionRuntime,
|
||||
type ChatGptSessionLoginConfig,
|
||||
} from "./chatgpt-session/runtime.ts";
|
||||
|
||||
const BASE_URL = "https://chatgpt.com";
|
||||
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",
|
||||
};
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
function configuredString(data: Record<string, unknown>, ...keys: string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const value = data[key];
|
||||
if (typeof value === "string" && value.trim()) return value.trim();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A caller-supplied logger is foreign code and may throw. It never gets to decide whether the
|
||||
* request is answered, so every warn goes through here.
|
||||
*/
|
||||
function warn(log: ExecuteInput["log"], message: unknown): void {
|
||||
try {
|
||||
log?.warn?.(
|
||||
"CHATGPT_SESSION",
|
||||
sanitizeErrorMessage(message instanceof Error ? message.message : message)
|
||||
);
|
||||
} catch {
|
||||
// A broken logger must not fail the turn.
|
||||
}
|
||||
}
|
||||
|
||||
function wrapped(response: Response, body: unknown): ExecutorExecuteResult {
|
||||
return {
|
||||
response,
|
||||
url: BASE_URL,
|
||||
headers: {},
|
||||
transformedBody: body,
|
||||
transport: "chatgpt-session-browser",
|
||||
};
|
||||
}
|
||||
|
||||
function errorResponse(
|
||||
status: number,
|
||||
message: unknown,
|
||||
code: string,
|
||||
fallbackHint?: "connection_cooldown"
|
||||
): Response {
|
||||
return new Response(
|
||||
JSON.stringify(
|
||||
buildErrorBody(status, sanitizeErrorMessage(message), undefined, {
|
||||
type: status >= 500 ? "provider_error" : "invalid_request_error",
|
||||
code,
|
||||
})
|
||||
),
|
||||
{
|
||||
status,
|
||||
headers: fallbackHint
|
||||
? { ...JSON_HEADERS, "X-Omni-Fallback-Hint": fallbackHint }
|
||||
: JSON_HEADERS,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function buildChatGptSessionProviderConfig(args: {
|
||||
route: ChatGptSessionRoute;
|
||||
connectionId: string;
|
||||
storageStatePath: string;
|
||||
connectorName: string;
|
||||
chromeExecutablePath?: string;
|
||||
cdpEndpoint?: string;
|
||||
solAvailable: boolean;
|
||||
proAvailable: boolean;
|
||||
}): CodexProviderConfig {
|
||||
const paths = connectionRuntimePaths(args.connectionId);
|
||||
return {
|
||||
adapter: "chatgpt-web",
|
||||
baseUrl: BASE_URL,
|
||||
defaultModel: args.route.backendModel,
|
||||
models: [args.route.backendModel],
|
||||
chatgptWeb: {
|
||||
appName: args.connectorName,
|
||||
storageStatePath: args.storageStatePath,
|
||||
...(args.chromeExecutablePath ? { chromeExecutablePath: args.chromeExecutablePath } : {}),
|
||||
...(args.cdpEndpoint ? { cdpEndpoint: args.cdpEndpoint } : {}),
|
||||
brokerSocketPath: paths.brokerSocketPath,
|
||||
threadEnvironmentStatePath: paths.threadEnvironmentStatePath,
|
||||
lunaCheckpointStatePath: paths.lunaCheckpointStatePath,
|
||||
headed: true,
|
||||
// Prompt-emulated tools only: never attach the turn-bound Codex connector capability.
|
||||
localToolsEnabled: false,
|
||||
solAvailable: args.solAvailable,
|
||||
proAvailable: args.proAvailable,
|
||||
autoApproveToolCalls: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class ChatGptSessionExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("chatgpt-session", {
|
||||
id: "chatgpt-session",
|
||||
baseUrl: BASE_URL,
|
||||
format: FORMATS.OPENAI,
|
||||
});
|
||||
}
|
||||
|
||||
override async execute(input: ExecuteInput): Promise<ExecutorExecuteResult> {
|
||||
const runtime = chatGptSessionRuntime();
|
||||
const requestBody = record(input.body);
|
||||
|
||||
try {
|
||||
const route = requireChatGptSessionRoute(input.model);
|
||||
|
||||
const connectionId = input.credentials.connectionId?.trim();
|
||||
const encodedCredentials = input.credentials.apiKey?.trim();
|
||||
if (!connectionId || !encodedCredentials) {
|
||||
throw new Error("ChatGPT browser credentials are missing");
|
||||
}
|
||||
const secrets = decodeChatGptWebCodexSecrets(encodedCredentials);
|
||||
|
||||
const providerData = record(input.credentials.providerSpecificData);
|
||||
const cdpEndpoint =
|
||||
configuredString(providerData, "browserCdpEndpoint") ??
|
||||
process.env.CHATGPT_WEB_CODEX_CDP_URL?.trim();
|
||||
const chromeExecutablePath = runtime.detectChrome(
|
||||
configuredString(providerData, "chromeExecutablePath")
|
||||
);
|
||||
if (!chromeExecutablePath && !cdpEndpoint) {
|
||||
throw new Error("No supported Chrome or Chromium executable was found");
|
||||
}
|
||||
|
||||
const storageStatePath = runtime.ensureStorageState(connectionId, secrets);
|
||||
const connectorName =
|
||||
configuredString(providerData, "connectorName", "appName") ??
|
||||
CHATGPT_WEB_CODEX_CONNECTOR_NAME;
|
||||
|
||||
const loginConfig: ChatGptSessionLoginConfig = {
|
||||
appName: connectorName,
|
||||
storageStatePath,
|
||||
headed: true,
|
||||
proAvailable: providerData.proAvailable === true,
|
||||
autoApproveToolCalls: false,
|
||||
...(chromeExecutablePath ? { chromeExecutablePath } : {}),
|
||||
...(cdpEndpoint ? { cdpEndpoint } : {}),
|
||||
};
|
||||
|
||||
let solAvailable = providerData.solAvailable !== false;
|
||||
let proAvailable = providerData.proAvailable === true;
|
||||
if (!runtime.loginStateExists(loginConfig)) {
|
||||
const capabilities = await runtime.inspectLogin(loginConfig);
|
||||
solAvailable = capabilities.solAvailable;
|
||||
proAvailable = capabilities.proAvailable;
|
||||
await input.onCredentialsRefreshed?.({
|
||||
providerSpecificData: {
|
||||
...providerData,
|
||||
solAvailable,
|
||||
proAvailable,
|
||||
browserVerified: true,
|
||||
...(chromeExecutablePath ? { chromeExecutablePath } : {}),
|
||||
...(cdpEndpoint ? { browserCdpEndpoint: cdpEndpoint } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (route.sol !== solAvailable) {
|
||||
throw new Error(
|
||||
route.sol
|
||||
? `${route.id} is not available for this Luna-only connection`
|
||||
: `${route.id} is not available while the account exposes the Sol model selector`
|
||||
);
|
||||
}
|
||||
if (route.pro && !proAvailable) {
|
||||
throw new Error(`${route.id} is not available for this non-Pro connection`);
|
||||
}
|
||||
|
||||
const messages = Array.isArray(requestBody.messages)
|
||||
? (requestBody.messages as Array<{ role: string; content: unknown }>)
|
||||
: [];
|
||||
const { hasTools, requestedTools, effectiveMessages } = prepareToolMessages(
|
||||
requestBody,
|
||||
messages
|
||||
);
|
||||
|
||||
// No `rawBody` here on purpose: the adapter reads `_rawBody` as a native Codex Responses
|
||||
// body, so handing it the OpenAI chat-completions body failed every turn before any browser
|
||||
// work. `buildParsedRequest` synthesizes that envelope itself.
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: effectiveMessages,
|
||||
stream: Boolean(input.stream) && !hasTools,
|
||||
});
|
||||
|
||||
const provider = buildChatGptSessionProviderConfig({
|
||||
route,
|
||||
connectionId,
|
||||
storageStatePath,
|
||||
connectorName,
|
||||
solAvailable,
|
||||
proAvailable,
|
||||
...(chromeExecutablePath ? { chromeExecutablePath } : {}),
|
||||
...(cdpEndpoint ? { cdpEndpoint } : {}),
|
||||
});
|
||||
|
||||
const events = new AsyncEventQueue<AdapterEvent>();
|
||||
const incoming = {
|
||||
headers: new Headers(),
|
||||
...(input.signal ? { abortSignal: input.signal } : {}),
|
||||
};
|
||||
|
||||
const persistRotatedState = async () => {
|
||||
try {
|
||||
const storageState = runtime.readStorageState(storageStatePath);
|
||||
await input.onCredentialsRefreshed?.({
|
||||
apiKey: encodeChatGptWebCodexSecrets({
|
||||
storageState,
|
||||
...(secrets.runtimeKey ? { runtimeKey: secrets.runtimeKey } : {}),
|
||||
}),
|
||||
});
|
||||
} catch (refreshError) {
|
||||
warn(input.log, refreshError);
|
||||
}
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
await runtime.runTurn(parsed, incoming, (event) => events.push(event), provider);
|
||||
} catch (error) {
|
||||
const classified = classifyChatGptSessionError(error);
|
||||
events.push({
|
||||
type: "error",
|
||||
message: sanitizeErrorMessage(error instanceof Error ? error.message : error),
|
||||
status: classified.status,
|
||||
code: classified.code,
|
||||
});
|
||||
} finally {
|
||||
// The close MUST happen even if persisting (or a caller-supplied logger inside its
|
||||
// catch) throws: without it `events.collect()` and the streaming consumer both wait
|
||||
// on a queue that is never closed, and the streaming path — started as `void run()` —
|
||||
// turns the throw into an unhandled rejection on top of the hang.
|
||||
try {
|
||||
await persistRotatedState();
|
||||
} finally {
|
||||
events.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const meta: ChatGptSessionResponseMeta = {
|
||||
cid: `chatcmpl-cgpts-${crypto.randomUUID().slice(0, 12)}`,
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
model: input.model,
|
||||
};
|
||||
|
||||
if (!parsed.stream) {
|
||||
const running = run();
|
||||
const collected = await events.collect();
|
||||
await running;
|
||||
const built = buildChatGptSessionCompletion(collected, meta);
|
||||
const jsonResponse = new Response(JSON.stringify(built.body), {
|
||||
status: built.status,
|
||||
headers: JSON_HEADERS,
|
||||
});
|
||||
if (!hasTools || built.status !== 200) return wrapped(jsonResponse, input.body);
|
||||
const toolResponse = await buildToolModeResponse(
|
||||
jsonResponse,
|
||||
requestedTools,
|
||||
Boolean(input.stream),
|
||||
{ cid: meta.cid, created: meta.created, model: meta.model, idSeed: "cgpts" }
|
||||
);
|
||||
return wrapped(toolResponse, input.body);
|
||||
}
|
||||
|
||||
void run();
|
||||
const opened = await openChatGptSessionStream(events, meta, {
|
||||
streamOpenTimeoutMs: resolveChatGptSessionStreamOpenTimeoutMs(),
|
||||
});
|
||||
if (opened.kind === "error") {
|
||||
// Every field of the verdict comes from the bridge's classification of the real adapter
|
||||
// event — status, code and fallbackHint alike. Re-classifying the sanitized message here
|
||||
// would discard the event's own `status`/`code`/`name`.
|
||||
return wrapped(
|
||||
errorResponse(opened.status, opened.message, opened.code, opened.fallbackHint),
|
||||
input.body
|
||||
);
|
||||
}
|
||||
return wrapped(
|
||||
new Response(opened.stream, { status: 200, headers: SSE_HEADERS }),
|
||||
input.body
|
||||
);
|
||||
} catch (error) {
|
||||
const classified = classifyChatGptSessionError(error);
|
||||
warn(input.log, error);
|
||||
return wrapped(
|
||||
errorResponse(
|
||||
classified.status,
|
||||
error instanceof Error ? error.message : error,
|
||||
classified.code,
|
||||
classified.fallbackHint
|
||||
),
|
||||
input.body
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
336
open-sse/executors/chatgpt-session/bridge.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* Bridges the vendored adapter's event stream into OpenAI chat-completions payloads.
|
||||
*
|
||||
* Stream opening is gated on the first event that would ALSO count as committed output on the
|
||||
* buffered path, so a turn that fails before producing any assistant content can still be
|
||||
* answered with a real HTTP status instead of a 200 stream carrying an error chunk. The gate and
|
||||
* `buildChatGptSessionCompletion` must agree on what "output" means — transport framing
|
||||
* (heartbeats, assistant boundaries), commentary-phase text, empty text deltas and reasoning are
|
||||
* all non-committing on both paths. Once real content has been emitted the status line is
|
||||
* already committed, so a later failure just closes the stream cleanly.
|
||||
*
|
||||
* The gate is bounded: nothing at all can reach the client while it is closed (not even a
|
||||
* keepalive, since the first byte commits the 200), and a turn here runs in a real browser that
|
||||
* may think for a long time. Past `CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS` the stream opens
|
||||
* anyway and keeps consuming the same iterator, so a slow-but-healthy turn stays connected.
|
||||
*/
|
||||
|
||||
import { buildErrorBody, sanitizeErrorMessage } from "../../utils/error.ts";
|
||||
import { formatTranslatedStreamError } from "../../utils/streamErrorFormat.ts";
|
||||
import type {
|
||||
AdapterEvent,
|
||||
CodexMessagePhase,
|
||||
CodexUsage,
|
||||
} from "../../vendor/codex-chatgpt-web/types.ts";
|
||||
import { classifyChatGptSessionError } from "./errors.ts";
|
||||
|
||||
export interface ChatGptSessionResponseMeta {
|
||||
cid: string;
|
||||
created: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
export type ChatGptSessionStreamOpen =
|
||||
| {
|
||||
kind: "error";
|
||||
status: number;
|
||||
code: string;
|
||||
message: string;
|
||||
fallbackHint?: "connection_cooldown";
|
||||
}
|
||||
| { kind: "stream"; stream: ReadableStream<Uint8Array> };
|
||||
|
||||
/**
|
||||
* The adapter tags its own transport chatter with the commentary phase — most visibly the
|
||||
* "local Codex computer is unavailable" banner it emits on every fresh turn while
|
||||
* `localToolsEnabled` is false, which is this provider's permanent configuration. It is not
|
||||
* model output and it is not model reasoning, so it never reaches the client on either path.
|
||||
*/
|
||||
const COMMENTARY_PHASE: CodexMessagePhase = "commentary";
|
||||
|
||||
/**
|
||||
* How long the stream-open gate waits for the first committing event before opening the stream
|
||||
* anyway.
|
||||
*
|
||||
* The trade-off: while the gate is closed the client sees nothing, so an idle-timeout client can
|
||||
* hang up on a healthy turn that is simply thinking for a long time in the browser. Opening the
|
||||
* stream commits the 200, which costs the ability to answer with a real HTTP status if the turn
|
||||
* dies later — but every failure that needs a real status (no browser, missing or expired
|
||||
* credentials, rate limiting, an incompatible route) surfaces within seconds, far inside this
|
||||
* window. Only a genuinely long-running healthy turn reaches the deadline.
|
||||
*
|
||||
* Callers can override it per request through `options.streamOpenTimeoutMs`; a value that is not
|
||||
* a finite number greater than zero disables the deadline and the gate waits indefinitely.
|
||||
*/
|
||||
export const CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS = 30_000;
|
||||
|
||||
/**
|
||||
* Reads `OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS` and falls back to
|
||||
* {@link CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS} whenever the variable is unset, empty,
|
||||
* unparseable, non-finite, or not positive — mirrors `resolveDirectHeadersTimeoutMs`
|
||||
* (`open-sse/utils/directResponseStartTimeout.ts`). Callers resolve this per request so a
|
||||
* changed environment variable takes effect without a restart.
|
||||
*/
|
||||
export function resolveChatGptSessionStreamOpenTimeoutMs(
|
||||
env: Record<string, string | undefined> = process.env
|
||||
): number {
|
||||
const raw = env.OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
if (raw == null || raw.trim() === "") return CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
const parsed = Number(raw);
|
||||
return Number.isFinite(parsed) && parsed > 0
|
||||
? Math.floor(parsed)
|
||||
: CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/** Race marker for the gate deadline — distinguishable from any `IteratorResult`. */
|
||||
const GATE_TIMED_OUT = Symbol("chatgpt-session-gate-timeout");
|
||||
|
||||
export interface ChatGptSessionStreamOptions {
|
||||
/** Overrides {@link CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS} for this call (tests, tuning). */
|
||||
streamOpenTimeoutMs?: number;
|
||||
}
|
||||
|
||||
interface OpenAiUsage {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
completion_tokens_details?: { reasoning_tokens: number };
|
||||
}
|
||||
|
||||
function mapUsage(usage: CodexUsage | undefined): OpenAiUsage | undefined {
|
||||
if (!usage) return undefined;
|
||||
const prompt = usage.inputTokens ?? 0;
|
||||
const completion = usage.outputTokens ?? 0;
|
||||
return {
|
||||
prompt_tokens: prompt,
|
||||
completion_tokens: completion,
|
||||
total_tokens: usage.totalTokens ?? prompt + completion,
|
||||
...(typeof usage.reasoningOutputTokens === "number"
|
||||
? { completion_tokens_details: { reasoning_tokens: usage.reasoningOutputTokens } }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
function chunk(
|
||||
meta: ChatGptSessionResponseMeta,
|
||||
delta: Record<string, unknown>,
|
||||
finishReason: string | null,
|
||||
usage?: OpenAiUsage
|
||||
): string {
|
||||
const payload: Record<string, unknown> = {
|
||||
id: meta.cid,
|
||||
object: "chat.completion.chunk",
|
||||
created: meta.created,
|
||||
model: meta.model,
|
||||
choices: [{ index: 0, delta, finish_reason: finishReason }],
|
||||
};
|
||||
if (usage) payload.usage = usage;
|
||||
return `data: ${JSON.stringify(payload)}\n\n`;
|
||||
}
|
||||
|
||||
function finishReasonFor(event: AdapterEvent): string {
|
||||
if (event.type === "incomplete") return event.endTurn ? "stop" : "length";
|
||||
return "stop";
|
||||
}
|
||||
|
||||
export async function openChatGptSessionStream(
|
||||
events: AsyncIterable<AdapterEvent>,
|
||||
meta: ChatGptSessionResponseMeta,
|
||||
options?: ChatGptSessionStreamOptions
|
||||
): Promise<ChatGptSessionStreamOpen> {
|
||||
const iterator = events[Symbol.asyncIterator]();
|
||||
// Reasoning that arrives while the gate is still closed is real output the client must
|
||||
// receive; it just may not commit the status line, because the buffered path fails over on
|
||||
// absent CONTENT regardless of how much reasoning preceded it.
|
||||
const bufferedReasoning: AdapterEvent[] = [];
|
||||
let first: AdapterEvent | null = null;
|
||||
// The `iterator.next()` the deadline outran. It is still in flight and will settle with the
|
||||
// event the gate never saw, so the stream body must await THIS promise instead of asking the
|
||||
// iterator for another one — a second `next()` would queue behind it and the first event would
|
||||
// be lost with the abandoned promise.
|
||||
let pendingNext: Promise<IteratorResult<AdapterEvent>> | null = null;
|
||||
|
||||
const timeoutMs = options?.streamOpenTimeoutMs ?? CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS;
|
||||
const bounded = Number.isFinite(timeoutMs) && timeoutMs > 0;
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const deadline = bounded
|
||||
? new Promise<typeof GATE_TIMED_OUT>((resolve) => {
|
||||
timer = setTimeout(() => resolve(GATE_TIMED_OUT), timeoutMs);
|
||||
// A gate deadline must never be the reason the process stays alive.
|
||||
(timer as unknown as { unref?: () => void }).unref?.();
|
||||
})
|
||||
: null;
|
||||
|
||||
try {
|
||||
for (;;) {
|
||||
const step = iterator.next();
|
||||
const settled: IteratorResult<AdapterEvent> | typeof GATE_TIMED_OUT = deadline
|
||||
? await Promise.race([step, deadline])
|
||||
: await step;
|
||||
if (settled === GATE_TIMED_OUT) {
|
||||
pendingNext = step;
|
||||
break;
|
||||
}
|
||||
if (settled.done) break;
|
||||
const event = settled.value;
|
||||
if (event.type === "heartbeat" || event.type === "assistant_boundary") continue;
|
||||
if (event.type === "text_delta" && (!event.text || event.phase === COMMENTARY_PHASE)) {
|
||||
continue;
|
||||
}
|
||||
if (event.type === "thinking_delta") {
|
||||
bufferedReasoning.push(event);
|
||||
continue;
|
||||
}
|
||||
first = event;
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
// Every exit path clears the timer, so a pending deadline can neither fire after the gate
|
||||
// resolved nor hold a handle open.
|
||||
if (timer !== undefined) clearTimeout(timer);
|
||||
}
|
||||
|
||||
if (first && first.type === "error") {
|
||||
const classified = classifyChatGptSessionError(first);
|
||||
return {
|
||||
kind: "error",
|
||||
status: classified.status,
|
||||
code: classified.code,
|
||||
message: sanitizeErrorMessage(first.message),
|
||||
...(classified.fallbackHint ? { fallbackHint: classified.fallbackHint } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
const pending: AdapterEvent[] = first ? [...bufferedReasoning, first] : bufferedReasoning;
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new ReadableStream<Uint8Array>(
|
||||
{
|
||||
async start(controller) {
|
||||
const emit = (text: string) => controller.enqueue(encoder.encode(text));
|
||||
emit(chunk(meta, { role: "assistant" }, null));
|
||||
|
||||
let terminatedWithError = false;
|
||||
|
||||
const handle = (event: AdapterEvent): boolean => {
|
||||
switch (event.type) {
|
||||
case "heartbeat":
|
||||
emit(": keepalive\n\n");
|
||||
return true;
|
||||
case "text_delta":
|
||||
if (event.phase === COMMENTARY_PHASE) return true;
|
||||
if (event.text) emit(chunk(meta, { content: event.text }, null));
|
||||
return true;
|
||||
case "thinking_delta":
|
||||
if (event.thinking) emit(chunk(meta, { reasoning_content: event.thinking }, null));
|
||||
return true;
|
||||
case "assistant_boundary":
|
||||
// Internal framing between the adapter's guarded first pass and its one-shot
|
||||
// continuation (the vendor's Responses bridge only closes the open item here).
|
||||
// There is no chat-completions delta for it, and letting it reach `default` would
|
||||
// be indistinguishable from a real event we forgot to handle.
|
||||
return true;
|
||||
case "done":
|
||||
emit(chunk(meta, {}, "stop", mapUsage(event.usage)));
|
||||
return false;
|
||||
case "incomplete":
|
||||
emit(chunk(meta, {}, finishReasonFor(event), mapUsage(event.usage)));
|
||||
return false;
|
||||
case "error": {
|
||||
const classified = classifyChatGptSessionError(event);
|
||||
emit(
|
||||
formatTranslatedStreamError({
|
||||
status: classified.status,
|
||||
message: event.message,
|
||||
code: classified.code,
|
||||
type: classified.status >= 500 ? "provider_error" : "invalid_request_error",
|
||||
})
|
||||
);
|
||||
terminatedWithError = true;
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
let open = true;
|
||||
for (const event of pending) {
|
||||
open = handle(event);
|
||||
if (!open) break;
|
||||
}
|
||||
while (open) {
|
||||
const step = pendingNext ?? iterator.next();
|
||||
pendingNext = null;
|
||||
const next = await step;
|
||||
if (next.done) {
|
||||
emit(chunk(meta, {}, "stop"));
|
||||
break;
|
||||
}
|
||||
open = handle(next.value);
|
||||
}
|
||||
} finally {
|
||||
if (!terminatedWithError) emit("data: [DONE]\n\n");
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
void iterator.return?.();
|
||||
},
|
||||
},
|
||||
{ highWaterMark: 16384 }
|
||||
);
|
||||
|
||||
return { kind: "stream", stream };
|
||||
}
|
||||
|
||||
export function buildChatGptSessionCompletion(
|
||||
events: readonly AdapterEvent[],
|
||||
meta: ChatGptSessionResponseMeta
|
||||
): { status: number; body: Record<string, unknown> } {
|
||||
let content = "";
|
||||
let reasoning = "";
|
||||
let finishReason = "stop";
|
||||
let usage: CodexUsage | undefined;
|
||||
let failure: AdapterEvent | null = null;
|
||||
|
||||
for (const event of events) {
|
||||
if (event.type === "text_delta") {
|
||||
if (event.phase !== COMMENTARY_PHASE) content += event.text;
|
||||
} else if (event.type === "thinking_delta") reasoning += event.thinking;
|
||||
else if (event.type === "done") usage = event.usage;
|
||||
else if (event.type === "incomplete") {
|
||||
usage = event.usage;
|
||||
finishReason = finishReasonFor(event);
|
||||
} else if (event.type === "error") {
|
||||
usage = event.usage ?? usage;
|
||||
failure = event;
|
||||
}
|
||||
}
|
||||
|
||||
if (failure && failure.type === "error" && !content) {
|
||||
const classified = classifyChatGptSessionError(failure);
|
||||
return {
|
||||
status: classified.status,
|
||||
body: buildErrorBody(classified.status, sanitizeErrorMessage(failure.message), undefined, {
|
||||
type: classified.status >= 500 ? "provider_error" : "invalid_request_error",
|
||||
code: classified.code,
|
||||
}) as unknown as Record<string, unknown>,
|
||||
};
|
||||
}
|
||||
|
||||
const message: Record<string, unknown> = { role: "assistant", content };
|
||||
if (reasoning) message.reasoning_content = reasoning;
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
id: meta.cid,
|
||||
object: "chat.completion",
|
||||
created: meta.created,
|
||||
model: meta.model,
|
||||
choices: [{ index: 0, message, finish_reason: finishReason, logprobs: null }],
|
||||
...(mapUsage(usage) ? { usage: mapUsage(usage) } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
106
open-sse/executors/chatgpt-session/errors.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Maps every failure this provider can produce onto the HTTP contract the router expects.
|
||||
*
|
||||
* The distinction that matters: a 503 with `connection_cooldown` lets combo routing skip this
|
||||
* connection without opening the provider circuit breaker, while a 400 is terminal and must not
|
||||
* be retried (a changed ChatGPT DOM will not fix itself on a retry).
|
||||
*/
|
||||
|
||||
import { ChatGptSessionInputError } from "./messages.ts";
|
||||
|
||||
export interface ChatGptSessionErrorClass {
|
||||
status: number;
|
||||
code: string;
|
||||
fallbackHint?: "connection_cooldown";
|
||||
}
|
||||
|
||||
interface ErrorLike {
|
||||
message: string;
|
||||
name?: string;
|
||||
status?: number;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
function asErrorLike(error: unknown): ErrorLike {
|
||||
if (error instanceof Error) {
|
||||
const typed = error as Error & { status?: unknown; code?: unknown };
|
||||
return {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
...(typeof typed.status === "number" ? { status: typed.status } : {}),
|
||||
...(typeof typed.code === "string" ? { code: typed.code } : {}),
|
||||
};
|
||||
}
|
||||
if (error && typeof error === "object") {
|
||||
const typed = error as Record<string, unknown>;
|
||||
return {
|
||||
message: typeof typed.message === "string" ? typed.message : String(error),
|
||||
...(typeof typed.name === "string" ? { name: typed.name } : {}),
|
||||
...(typeof typed.status === "number" ? { status: typed.status } : {}),
|
||||
...(typeof typed.code === "string" ? { code: typed.code } : {}),
|
||||
};
|
||||
}
|
||||
return { message: String(error ?? "") };
|
||||
}
|
||||
|
||||
const BROWSER_UNAVAILABLE =
|
||||
/No supported Chrome|browserType\.launch|Executable doesn't exist|chromium.*not installed/i;
|
||||
const MISSING_CREDENTIALS =
|
||||
/credentials are missing|Cookie or verified browser storage state is required|Cookie header is missing/i;
|
||||
const SESSION_EXPIRED = /not authenticated|storage state is invalid|sign ?in|log ?in|logged out/i;
|
||||
const RATE_LIMITED = /rate limit|usage limit|too many requests|message limit/i;
|
||||
const ROUTE_UNAVAILABLE =
|
||||
/not available for this|not available while the account|is not supported/i;
|
||||
const UI_TIMEOUT = /waitForSelector|Timeout \d+ms exceeded|actionability|interception/i;
|
||||
|
||||
export function classifyChatGptSessionError(error: unknown): ChatGptSessionErrorClass {
|
||||
if (error instanceof ChatGptSessionInputError) {
|
||||
return { status: 400, code: error.code };
|
||||
}
|
||||
|
||||
const like = asErrorLike(error);
|
||||
|
||||
// The error's own constructor name is a stronger signal than substring matching on its
|
||||
// message: a Playwright TimeoutError is always a terminal DOM/selector timeout, even when
|
||||
// its message happens to name a login-related selector (which would otherwise look like an
|
||||
// expired session to the message-pattern checks below).
|
||||
if (like.name === "TimeoutError") {
|
||||
return { status: 400, code: "browser_ui_timeout" };
|
||||
}
|
||||
|
||||
// An explicit numeric status is the adapter's own authoritative verdict. The vendor documents
|
||||
// it that way on `AdapterEvent.error.status` ("Authoritative upstream/proxy status when known;
|
||||
// avoids message-based classification"), and trusting it first is what stops a 503 whose prose
|
||||
// happens to mention signing in from being reported as an expired session — which would mark a
|
||||
// healthy account's credentials dead and pull it out of rotation. Message matching still runs
|
||||
// below, because failures thrown by this executor itself carry no status at all.
|
||||
if (typeof like.status === "number" && like.status >= 400) {
|
||||
const status = like.status;
|
||||
return {
|
||||
status,
|
||||
code: like.code ?? "turn_failed",
|
||||
// 503/429 must cool this one connection down rather than trip the whole-provider breaker.
|
||||
...(status === 503 || status === 429 ? { fallbackHint: "connection_cooldown" as const } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (BROWSER_UNAVAILABLE.test(like.message)) {
|
||||
return { status: 503, code: "browser_unavailable", fallbackHint: "connection_cooldown" };
|
||||
}
|
||||
if (MISSING_CREDENTIALS.test(like.message)) {
|
||||
return { status: 401, code: "missing_credentials" };
|
||||
}
|
||||
if (SESSION_EXPIRED.test(like.message)) {
|
||||
return { status: 401, code: "session_expired" };
|
||||
}
|
||||
if (RATE_LIMITED.test(like.message)) {
|
||||
return { status: 429, code: "rate_limited" };
|
||||
}
|
||||
if (ROUTE_UNAVAILABLE.test(like.message)) {
|
||||
return { status: 400, code: "route_unavailable" };
|
||||
}
|
||||
if (UI_TIMEOUT.test(like.message)) {
|
||||
return { status: 400, code: "browser_ui_timeout" };
|
||||
}
|
||||
return { status: 502, code: "turn_failed" };
|
||||
}
|
||||
185
open-sse/executors/chatgpt-session/messages.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Pure translation from an OpenAI chat-completions message list into the synthetic
|
||||
* CodexParsedRequest the vendored browser adapter consumes.
|
||||
*
|
||||
* Tools are deliberately never placed in `context.tools`: this provider uses the shared
|
||||
* prompt-emulated tool contract (translator/webTools.ts), so the adapter must not try to
|
||||
* attach the turn-bound Codex connector capability.
|
||||
*
|
||||
* `_rawBody` is SYNTHESIZED here, never passed through from the OpenAI request. The vendored
|
||||
* adapter reads `_rawBody` as a native Codex *Responses* body and refuses to run a turn without
|
||||
* turn identity in it — an OpenAI chat-completions body makes every request fail with
|
||||
* "ChatGPT web requires native Codex turn_id metadata for browser-session replay" before any
|
||||
* browser work starts. Three things are load-bearing (see
|
||||
* vendor/codex-chatgpt-web/adapters/chatgpt-web/environment.ts):
|
||||
* 1. `client_metadata["x-codex-turn-metadata"]` carrying `thread_id` / `turn_id`
|
||||
* (`clientTurnMetadata`),
|
||||
* 2. `input` as an array of Responses items (`latestChatGptTurnUserRevision`,
|
||||
* `chatGptTurnRoundKey`, the Luna rolling checkpoint),
|
||||
* 3. `internal_chat_message_metadata_passthrough.turn_id` on the CURRENT user item
|
||||
* (`itemTurnId`).
|
||||
* This provider serves stateless chat completions, so a fresh thread/turn pair per request is
|
||||
* the truthful identity: every request genuinely is its own turn.
|
||||
*/
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import type {
|
||||
CodexMessage,
|
||||
CodexParsedRequest,
|
||||
CodexTextContent,
|
||||
} from "../../vendor/codex-chatgpt-web/types.ts";
|
||||
import type { ChatGptSessionRoute } from "./models.ts";
|
||||
|
||||
export class ChatGptSessionInputError extends Error {
|
||||
readonly code: string;
|
||||
|
||||
constructor(code: string, message: string) {
|
||||
super(message);
|
||||
this.name = "ChatGptSessionInputError";
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
interface OpenAiMessage {
|
||||
role: string;
|
||||
content: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* One Responses-shaped `input` item. Only the fields the vendored adapter actually reads are
|
||||
* emitted: `type`/`role`/`content[].text` (`rawMessageText`, `inputContentParts`) and the
|
||||
* current-turn marker (`itemTurnId`).
|
||||
*/
|
||||
interface ResponsesInputItem {
|
||||
type: "message";
|
||||
role: "user" | "assistant";
|
||||
content: Array<{ type: "input_text" | "output_text"; text: string }>;
|
||||
internal_chat_message_metadata_passthrough?: { turn_id: string };
|
||||
}
|
||||
|
||||
function textFromContent(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (!Array.isArray(content)) return "";
|
||||
const chunks: string[] = [];
|
||||
for (const part of content) {
|
||||
const typed =
|
||||
part && typeof part === "object" && !Array.isArray(part)
|
||||
? (part as Record<string, unknown>)
|
||||
: null;
|
||||
if (typed && typed.type === "text" && typeof typed.text === "string") {
|
||||
chunks.push(typed.text);
|
||||
continue;
|
||||
}
|
||||
// `{ type: "refusal", refusal: "…" }` is a legal ASSISTANT content part in the
|
||||
// chat-completions spec, and a client replaying its own conversation history sends it back
|
||||
// verbatim. It is text as far as the adapter is concerned. A `refusal` part whose payload is
|
||||
// missing or not a string falls through to the rejection below rather than being dropped.
|
||||
if (typed && typed.type === "refusal" && typeof typed.refusal === "string") {
|
||||
chunks.push(typed.refusal);
|
||||
continue;
|
||||
}
|
||||
if (typed && (typed.type === "image_url" || typed.type === "image")) {
|
||||
throw new ChatGptSessionInputError(
|
||||
"vision_unsupported",
|
||||
"ChatGPT Session does not accept image input yet"
|
||||
);
|
||||
}
|
||||
// Every other part — file, input_audio, a future part type, or anything malformed — would
|
||||
// otherwise be dropped in silence, and the model would answer about content it never
|
||||
// received. Reject loudly instead.
|
||||
throw new ChatGptSessionInputError(
|
||||
"unsupported_content_part",
|
||||
"ChatGPT Session does not accept this message content part type"
|
||||
);
|
||||
}
|
||||
return chunks.join("\n");
|
||||
}
|
||||
|
||||
function assistantParts(content: unknown): CodexTextContent[] {
|
||||
const text = textFromContent(content);
|
||||
return text ? [{ type: "text", text }] : [];
|
||||
}
|
||||
|
||||
export function buildParsedRequest(input: {
|
||||
route: ChatGptSessionRoute;
|
||||
messages: ReadonlyArray<OpenAiMessage>;
|
||||
stream: boolean;
|
||||
}): CodexParsedRequest {
|
||||
const systemPrompt: string[] = [];
|
||||
const messages: CodexMessage[] = [];
|
||||
const inputItems: ResponsesInputItem[] = [];
|
||||
let lastUserItem = -1;
|
||||
|
||||
for (const message of input.messages) {
|
||||
const role = typeof message.role === "string" ? message.role : "";
|
||||
if (role === "system" || role === "developer") {
|
||||
const text = textFromContent(message.content);
|
||||
if (text) systemPrompt.push(text);
|
||||
continue;
|
||||
}
|
||||
if (role === "assistant") {
|
||||
const content = assistantParts(message.content);
|
||||
if (content.length > 0) {
|
||||
messages.push({ role: "assistant", content, timestamp: Date.now() });
|
||||
inputItems.push({
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: content.map((part) => ({ type: "output_text", text: part.text })),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (role === "user" || role === "tool" || role === "function") {
|
||||
const text = textFromContent(message.content);
|
||||
if (!text) continue;
|
||||
messages.push({ role: "user", content: text, timestamp: Date.now() });
|
||||
lastUserItem = inputItems.length;
|
||||
inputItems.push({
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: [{ type: "input_text", text }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (lastUserItem < 0) {
|
||||
throw new ChatGptSessionInputError(
|
||||
"no_user_message",
|
||||
"ChatGPT Session requires at least one user message"
|
||||
);
|
||||
}
|
||||
|
||||
const threadId = `thread_omniroute_${randomUUID()}`;
|
||||
const turnId = `turn_omniroute_${randomUUID()}`;
|
||||
// Only the LAST user item is the current turn. Marking an earlier one would make the adapter
|
||||
// replay stale history as the live instruction.
|
||||
inputItems[lastUserItem] = {
|
||||
...inputItems[lastUserItem]!,
|
||||
internal_chat_message_metadata_passthrough: { turn_id: turnId },
|
||||
};
|
||||
|
||||
return {
|
||||
modelId: input.route.backendModel,
|
||||
context: {
|
||||
...(systemPrompt.length > 0 ? { systemPrompt } : {}),
|
||||
messages,
|
||||
},
|
||||
stream: input.stream,
|
||||
options: { reasoning: input.route.effort },
|
||||
_rawBody: {
|
||||
model: input.route.backendModel,
|
||||
// The adapter's Luna checkpoint path re-parses `_rawBody` through the vendored Responses
|
||||
// parser, which reads system prompts from `instructions` — keep the two representations
|
||||
// equivalent so a re-parse reproduces this same context.
|
||||
...(systemPrompt.length > 0 ? { instructions: systemPrompt.join("\n") } : {}),
|
||||
input: inputItems,
|
||||
// The real Codex client sends this metadata as a JSON string; the vendor accepts a plain
|
||||
// object too, but matching the client's wire shape keeps us on the path the vendor's own
|
||||
// tests and future tightening cover.
|
||||
client_metadata: {
|
||||
"x-codex-turn-metadata": JSON.stringify({ thread_id: threadId, turn_id: turnId }),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
47
open-sse/executors/chatgpt-session/models.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Public model routes for the ChatGPT Session provider.
|
||||
*
|
||||
* Each public slug pins one backend model plus one reasoning effort. The vendored browser
|
||||
* adapter accepts only the two backend ids and reads the effort from
|
||||
* `CodexParsedRequest.options.reasoning`, so the slug is the only knob a client turns.
|
||||
*/
|
||||
|
||||
export type ChatGptSessionEffort = "low" | "medium" | "high" | "xhigh" | "max";
|
||||
|
||||
export interface ChatGptSessionRoute {
|
||||
id: string;
|
||||
backendModel: "gpt-5.6-sol" | "gpt-5.6-luna";
|
||||
effort: ChatGptSessionEffort;
|
||||
/** Requires an account whose browser probe reported Pro. */
|
||||
pro: boolean;
|
||||
/** Requires the Sol model selector; Luna-only accounts get the luna/think routes. */
|
||||
sol: boolean;
|
||||
}
|
||||
|
||||
const ROUTES: ReadonlyMap<string, ChatGptSessionRoute> = new Map([
|
||||
["luna", { id: "luna", backendModel: "gpt-5.6-luna", effort: "low", pro: false, sol: false }],
|
||||
[
|
||||
"think",
|
||||
{ id: "think", backendModel: "gpt-5.6-luna", effort: "medium", pro: false, sol: false },
|
||||
],
|
||||
["instant", { id: "instant", backendModel: "gpt-5.6-sol", effort: "low", pro: false, sol: true }],
|
||||
[
|
||||
"medium",
|
||||
{ id: "medium", backendModel: "gpt-5.6-sol", effort: "medium", pro: false, sol: true },
|
||||
],
|
||||
["high", { id: "high", backendModel: "gpt-5.6-sol", effort: "high", pro: false, sol: true }],
|
||||
[
|
||||
"extra-high",
|
||||
{ id: "extra-high", backendModel: "gpt-5.6-sol", effort: "xhigh", pro: true, sol: true },
|
||||
],
|
||||
["pro", { id: "pro", backendModel: "gpt-5.6-sol", effort: "max", pro: true, sol: true }],
|
||||
] as const);
|
||||
|
||||
export const CHATGPT_SESSION_ROUTE_IDS: readonly string[] = [...ROUTES.keys()];
|
||||
|
||||
export function requireChatGptSessionRoute(model: string): ChatGptSessionRoute {
|
||||
const normalized = model.trim().replace(/^(?:chatgpt-session|cgpt-session)\//, "");
|
||||
const route = ROUTES.get(normalized);
|
||||
if (!route) throw new Error(`Unsupported ChatGPT Session model: ${model}`);
|
||||
return route;
|
||||
}
|
||||
73
open-sse/executors/chatgpt-session/runtime.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Indirection layer over every side-effecting dependency of the executor (browser detection,
|
||||
* storage-state IO, login probe, adapter turn). Tests swap the whole record so no unit test
|
||||
* ever launches Chrome; production resolves to the vendored implementations.
|
||||
*/
|
||||
|
||||
import { createChatGptWebAdapter } from "../../vendor/codex-chatgpt-web/adapters/chatgpt-web/index.ts";
|
||||
import {
|
||||
browserLoginStateExists,
|
||||
inspectBrowserLoginCapabilities,
|
||||
} from "../../vendor/codex-chatgpt-web/browser-login.ts";
|
||||
import type {
|
||||
AdapterEvent,
|
||||
CodexParsedRequest,
|
||||
CodexProviderConfig,
|
||||
} from "../../vendor/codex-chatgpt-web/types.ts";
|
||||
import { detectChromeExecutable } from "../chatgpt-web-codex.ts";
|
||||
import {
|
||||
ensureConnectionStorageStateFromCredential,
|
||||
readConnectionStorageState,
|
||||
} from "../chatgpt-web-codex/storageState.ts";
|
||||
|
||||
export interface ChatGptSessionLoginConfig {
|
||||
appName: string;
|
||||
storageStatePath: string;
|
||||
headed: boolean;
|
||||
proAvailable: boolean;
|
||||
autoApproveToolCalls: boolean;
|
||||
chromeExecutablePath?: string;
|
||||
cdpEndpoint?: string;
|
||||
}
|
||||
|
||||
export interface ChatGptSessionRuntime {
|
||||
detectChrome(explicit?: string): string | undefined;
|
||||
ensureStorageState(
|
||||
connectionId: string,
|
||||
credential: { cookie?: string; storageState?: Record<string, unknown> }
|
||||
): string;
|
||||
readStorageState(path: string): Record<string, unknown>;
|
||||
loginStateExists(config: ChatGptSessionLoginConfig): boolean;
|
||||
inspectLogin(
|
||||
config: ChatGptSessionLoginConfig
|
||||
): Promise<{ solAvailable: boolean; proAvailable: boolean }>;
|
||||
runTurn(
|
||||
parsed: CodexParsedRequest,
|
||||
incoming: { headers: Headers; abortSignal?: AbortSignal },
|
||||
emit: (event: AdapterEvent) => void,
|
||||
provider: CodexProviderConfig
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
const productionRuntime: ChatGptSessionRuntime = {
|
||||
detectChrome: (explicit) => detectChromeExecutable(explicit),
|
||||
ensureStorageState: (connectionId, credential) =>
|
||||
ensureConnectionStorageStateFromCredential(connectionId, credential),
|
||||
readStorageState: (path) => readConnectionStorageState(path),
|
||||
loginStateExists: (config) => browserLoginStateExists(config),
|
||||
inspectLogin: (config) => inspectBrowserLoginCapabilities(config),
|
||||
runTurn: (parsed, incoming, emit, provider) =>
|
||||
createChatGptWebAdapter(provider).runTurn(parsed, incoming, emit),
|
||||
};
|
||||
|
||||
let override: Partial<ChatGptSessionRuntime> | null = null;
|
||||
|
||||
export function __setChatGptSessionRuntimeForTesting(
|
||||
next: Partial<ChatGptSessionRuntime> | null
|
||||
): void {
|
||||
override = next;
|
||||
}
|
||||
|
||||
export function chatGptSessionRuntime(): ChatGptSessionRuntime {
|
||||
return override ? { ...productionRuntime, ...override } : productionRuntime;
|
||||
}
|
||||
@@ -48,6 +48,10 @@ const lazyExecutors: Record<string, () => Promise<BaseExecutor>> = {
|
||||
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
|
||||
"cgpt-codex": () =>
|
||||
import("./chatgpt-web-codex.ts").then((m) => new m.ChatGptWebCodexExecutor()),
|
||||
"chatgpt-session": () =>
|
||||
import("./chatgpt-session.ts").then((m) => new m.ChatGptSessionExecutor()),
|
||||
"cgpt-session": () =>
|
||||
import("./chatgpt-session.ts").then((m) => new m.ChatGptSessionExecutor()),
|
||||
cursor: () => import("./cursor.ts").then((m) => new m.CursorExecutor()),
|
||||
trae: () => import("./trae.ts").then((m) => new m.TraeExecutor()),
|
||||
glm: () => import("./glm.ts").then((m) => new m.GlmExecutor("glm")),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -2,7 +2,10 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button, Badge, Input, Modal, Toggle, TALL_MODAL_PROPS } from "@/shared/components";
|
||||
import { CHATGPT_WEB_CODEX_CONNECTOR_NAME } from "@/shared/constants/chatgptWebCodex";
|
||||
import {
|
||||
CHATGPT_WEB_CODEX_CONNECTOR_NAME,
|
||||
usesChatGptBrowserSessionCredentials,
|
||||
} from "@/shared/constants/chatgptWebCodex";
|
||||
import {
|
||||
providerAllowsOptionalApiKey,
|
||||
supportsBulkApiKey,
|
||||
@@ -97,6 +100,12 @@ export default function AddApiKeyModal({
|
||||
const isLocalSelfHostedProvider = !!localProviderMetadata;
|
||||
const isGooglePse = provider === "google-pse-search";
|
||||
const isChatGptWebCodex = provider === "chatgpt-web-codex";
|
||||
// The credential ENVELOPE is decided by the shared browser-session lifecycle predicate,
|
||||
// not by the codex id: `/api/providers` routes every provider this predicate accepts into
|
||||
// `finalizeValidatedChatGptWebCodexSecrets`, which starts with `JSON.parse`. Posting a raw
|
||||
// cookie for one of them fails the save with a JSON parse error, so client and server must
|
||||
// read the same predicate.
|
||||
const usesBrowserSessionCredential = usesChatGptBrowserSessionCredentials(provider);
|
||||
const isAwsPolly = provider === "aws-polly";
|
||||
const webSessionCredential = getWebSessionCredentialRequirement(provider);
|
||||
const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none";
|
||||
@@ -402,11 +411,12 @@ export default function AddApiKeyModal({
|
||||
...(validatedProviderSpecificData || {}),
|
||||
};
|
||||
|
||||
const encodedCredential = isChatGptWebCodex
|
||||
const encodedCredential = usesBrowserSessionCredential
|
||||
? JSON.stringify({
|
||||
version: 1,
|
||||
cookie: credentialInput.trim().replace(/^cookie\s*:\s*/i, ""),
|
||||
runtimeKey: formData.runtimeKey.trim(),
|
||||
// Only chatgpt-web-codex ever has a runtime key; omit the field entirely otherwise.
|
||||
...(formData.runtimeKey.trim() ? { runtimeKey: formData.runtimeKey.trim() } : {}),
|
||||
})
|
||||
: credentialInput.trim();
|
||||
const payload = {
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button, Badge, Input, Modal, Toggle, Select } from "@/shared/components";
|
||||
import { CHATGPT_WEB_CODEX_CONNECTOR_NAME } from "@/shared/constants/chatgptWebCodex";
|
||||
import {
|
||||
CHATGPT_WEB_CODEX_CONNECTOR_NAME,
|
||||
usesChatGptBrowserSessionCredentials,
|
||||
} from "@/shared/constants/chatgptWebCodex";
|
||||
import {
|
||||
isOpenAICompatibleProvider,
|
||||
isAnthropicCompatibleProvider,
|
||||
@@ -230,6 +233,11 @@ export default function EditConnectionModal({
|
||||
const isLocalSelfHostedProvider = !!localProviderMetadata;
|
||||
const isGooglePse = provider === "google-pse-search";
|
||||
const isChatGptWebCodex = provider === "chatgpt-web-codex";
|
||||
// The credential ENVELOPE is decided by the shared browser-session lifecycle predicate,
|
||||
// not by the codex id: `/api/providers/[id]` routes every provider this predicate accepts
|
||||
// into `decodeChatGptWebCodexSecrets` + `finalizeValidatedChatGptWebCodexSecrets`, both of
|
||||
// which expect the JSON envelope. Client and server must read the same predicate.
|
||||
const usesBrowserSessionCredential = usesChatGptBrowserSessionCredentials(provider);
|
||||
const isAwsPolly = provider === "aws-polly";
|
||||
const isM365TierCapable = isM365TierCapableProvider(provider);
|
||||
const webSessionCredential = getWebSessionCredentialRequirement(provider);
|
||||
@@ -648,7 +656,7 @@ export default function EditConnectionModal({
|
||||
}
|
||||
}
|
||||
if (isValid) {
|
||||
updates.apiKey = isChatGptWebCodex
|
||||
updates.apiKey = usesBrowserSessionCredential
|
||||
? JSON.stringify({
|
||||
version: 1,
|
||||
cookie: formData.apiKey.trim().replace(/^cookie\s*:\s*/i, ""),
|
||||
|
||||
@@ -34,7 +34,9 @@ import {
|
||||
decodeChatGptWebCodexSecrets,
|
||||
encodeChatGptWebCodexSecrets,
|
||||
} from "@omniroute/open-sse/services/chatgptWebCodexAdmin.ts";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
||||
import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse";
|
||||
import { usesChatGptBrowserSessionCredentials } from "@/shared/constants/chatgptWebCodex";
|
||||
|
||||
function normalizeCodexLimitPolicy(
|
||||
incoming: unknown,
|
||||
@@ -172,7 +174,7 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
|
||||
if (defaultModel !== undefined) updateData.defaultModel = defaultModel;
|
||||
if (isActive !== undefined) updateData.isActive = isActive;
|
||||
if (apiKey && canUpdateProviderApiKey(existing.authType, existing.provider)) {
|
||||
if (existing.provider === "chatgpt-web-codex") {
|
||||
if (usesChatGptBrowserSessionCredentials(existing.provider)) {
|
||||
const validationId =
|
||||
incomingPsd && typeof incomingPsd.validationId === "string"
|
||||
? incomingPsd.validationId
|
||||
@@ -191,10 +193,11 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
error: sanitizeErrorMessage(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Die ChatGPT-Browserprüfung konnte nicht abgeschlossen werden.",
|
||||
: "The browser session verification could not be completed."
|
||||
),
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
@@ -215,7 +218,8 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
|
||||
// the override (connection follows the global default); 0-1440 = explicit
|
||||
// per-connection minutes (0 opts this connection out of the sweep).
|
||||
if (healthCheckInterval === null) updateData.healthCheckInterval = null;
|
||||
else if (healthCheckInterval !== undefined) updateData.healthCheckInterval = healthCheckInterval;
|
||||
else if (healthCheckInterval !== undefined)
|
||||
updateData.healthCheckInterval = healthCheckInterval;
|
||||
if (group !== undefined) updateData.group = group;
|
||||
if (maxConcurrent !== undefined) updateData.maxConcurrent = maxConcurrent;
|
||||
if (incomingWindowThresholds !== undefined) {
|
||||
|
||||
@@ -48,6 +48,8 @@ import {
|
||||
getModelSyncInternalBaseUrl,
|
||||
} from "@/shared/services/modelSyncScheduler";
|
||||
import { finalizeValidatedChatGptWebCodexSecrets } from "@omniroute/open-sse/services/chatgptWebCodexAdmin.ts";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
||||
import { usesChatGptBrowserSessionCredentials } from "@/shared/constants/chatgptWebCodex";
|
||||
import { isAutoFetchModelsEnabled } from "@/lib/providerModels/modelDiscovery";
|
||||
import { testSingleConnection } from "./[id]/test/route";
|
||||
import { rejectRetiredCommonChatGptWebProvider } from "@/lib/providers/chatgptWebRetirementResponse";
|
||||
@@ -198,7 +200,7 @@ export async function POST(request: Request) {
|
||||
providerSpecificData = normalizeQoderPatProviderData(providerSpecificData || {});
|
||||
}
|
||||
|
||||
if (provider === "chatgpt-web-codex") {
|
||||
if (usesChatGptBrowserSessionCredentials(provider)) {
|
||||
const validationId =
|
||||
providerSpecificData && typeof providerSpecificData.validationId === "string"
|
||||
? providerSpecificData.validationId
|
||||
@@ -211,10 +213,11 @@ export async function POST(request: Request) {
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error:
|
||||
error: sanitizeErrorMessage(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Die ChatGPT-Browserprüfung konnte nicht abgeschlossen werden.",
|
||||
: "The browser session verification could not be completed."
|
||||
),
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
@@ -324,11 +327,16 @@ export async function POST(request: Request) {
|
||||
})
|
||||
.then((syncRes) => {
|
||||
if (!syncRes.ok) {
|
||||
console.log(`[providers] Auto-sync failed for ${newConnection.id}: ${syncRes.status}`);
|
||||
console.log(
|
||||
`[providers] Auto-sync failed for ${newConnection.id}: ${syncRes.status}`
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`[providers] Auto-sync error for ${newConnection.id}:`, err?.message || err);
|
||||
console.log(
|
||||
`[providers] Auto-sync error for ${newConnection.id}:`,
|
||||
err?.message || err
|
||||
);
|
||||
});
|
||||
} catch (syncSetupError) {
|
||||
// Defensive: if URL parsing or header construction itself throws, do
|
||||
|
||||
@@ -80,6 +80,7 @@ import {
|
||||
validatePoeProvider,
|
||||
} from "./validation/audioMiscProviders";
|
||||
import { validateChatGptWebCodexProvider } from "./validation/chatgptWebCodex";
|
||||
import { validateChatGptSessionProvider } from "./validation/chatgptSession";
|
||||
import { validateZaiWebProvider } from "./validation/zaiWeb";
|
||||
import { validateSearchProvider, SEARCH_VALIDATOR_CONFIGS } from "./validation/searchProviders";
|
||||
import {
|
||||
@@ -309,6 +310,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
|
||||
"grok-web": validateGrokWebProvider,
|
||||
"kimi-web": validateKimiWebProvider,
|
||||
"chatgpt-web-codex": validateChatGptWebCodexProvider,
|
||||
"chatgpt-session": validateChatGptSessionProvider,
|
||||
"perplexity-web": validatePerplexityWebProvider,
|
||||
"blackbox-web": validateBlackboxWebProvider,
|
||||
"muse-spark-web": validateMuseSparkWebProvider,
|
||||
|
||||
93
src/lib/providers/validation/chatgptSession.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { rmSync } from "node:fs";
|
||||
|
||||
import { CHATGPT_WEB_CODEX_CONNECTOR_NAME } from "@/shared/constants/chatgptWebCodex";
|
||||
import { inspectBrowserLoginCapabilities } from "@omniroute/open-sse/vendor/codex-chatgpt-web/browser-login.ts";
|
||||
import { decodeChatGptWebCodexSecrets } from "@omniroute/open-sse/executors/chatgpt-web-codex/credentials.ts";
|
||||
import { detectChromeExecutable } from "@omniroute/open-sse/executors/chatgpt-web-codex.ts";
|
||||
import {
|
||||
connectionRuntimePaths,
|
||||
ensureConnectionStorageState,
|
||||
ensureConnectionStorageStateFromCredential,
|
||||
} from "@omniroute/open-sse/executors/chatgpt-web-codex/storageState.ts";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
||||
|
||||
export async function validateChatGptSessionProvider({
|
||||
apiKey,
|
||||
providerSpecificData = {},
|
||||
}: {
|
||||
apiKey?: string;
|
||||
providerSpecificData?: Record<string, unknown>;
|
||||
}) {
|
||||
try {
|
||||
const secrets = decodeChatGptWebCodexSecrets(String(apiKey || ""));
|
||||
if (!secrets.cookie && !secrets.storageState) {
|
||||
return {
|
||||
valid: false,
|
||||
error: "A ChatGPT cookie header or a stored browser session is required.",
|
||||
};
|
||||
}
|
||||
|
||||
const cdpEndpoint = process.env.CHATGPT_WEB_CODEX_CDP_URL?.trim();
|
||||
const chromeExecutablePath = detectChromeExecutable(
|
||||
typeof providerSpecificData.chromeExecutablePath === "string"
|
||||
? providerSpecificData.chromeExecutablePath
|
||||
: undefined
|
||||
);
|
||||
if (!chromeExecutablePath && !cdpEndpoint) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
"No supported Chrome or Chromium was found. Install Chromium or configure the browser path.",
|
||||
};
|
||||
}
|
||||
|
||||
const validationId = `validation-${randomBytes(12).toString("hex")}`;
|
||||
const paths = connectionRuntimePaths(validationId);
|
||||
const freshCookie = Boolean(secrets.cookie);
|
||||
if (secrets.cookie) ensureConnectionStorageState(validationId, secrets.cookie);
|
||||
else ensureConnectionStorageStateFromCredential(validationId, secrets);
|
||||
|
||||
let capabilities;
|
||||
try {
|
||||
capabilities = await inspectBrowserLoginCapabilities({
|
||||
appName: CHATGPT_WEB_CODEX_CONNECTOR_NAME,
|
||||
...(chromeExecutablePath ? { chromeExecutablePath } : {}),
|
||||
...(cdpEndpoint ? { cdpEndpoint } : {}),
|
||||
storageStatePath: paths.storageStatePath,
|
||||
headed: false,
|
||||
proAvailable: false,
|
||||
autoApproveToolCalls: false,
|
||||
});
|
||||
} catch (error) {
|
||||
rmSync(paths.root, { recursive: true, force: true });
|
||||
throw error;
|
||||
}
|
||||
if (!freshCookie) rmSync(paths.root, { recursive: true, force: true });
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
error: null,
|
||||
method: "headless-browser",
|
||||
capabilities: {
|
||||
browser: "ready",
|
||||
storageState: "verified",
|
||||
login: "authenticated",
|
||||
solAvailable: capabilities.solAvailable,
|
||||
proAvailable: capabilities.proAvailable,
|
||||
},
|
||||
providerSpecificData: {
|
||||
solAvailable: capabilities.solAvailable,
|
||||
proAvailable: capabilities.proAvailable,
|
||||
browserVerified: true,
|
||||
...(chromeExecutablePath ? { chromeExecutablePath } : {}),
|
||||
...(freshCookie ? { validationId } : {}),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
valid: false,
|
||||
error: sanitizeErrorMessage(error instanceof Error ? error.message : error),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -329,6 +329,7 @@ const LOBE_PROVIDER_ALIASES = {
|
||||
"black-forest-labs": "Bfl",
|
||||
cerebras: "Cerebras",
|
||||
"chatgpt-web-codex": "OpenAI",
|
||||
"chatgpt-session": "OpenAI",
|
||||
claude: "ClaudeCode",
|
||||
"claude-web": "Claude",
|
||||
cline: "Cline",
|
||||
|
||||
@@ -10,3 +10,21 @@ export function isChatGptWebCodexModel(model: unknown): boolean {
|
||||
// persisted account session is valid. Keep runtime turns aligned with the headed browser
|
||||
// used to verify that same storage state.
|
||||
export const CHATGPT_WEB_CODEX_RUNTIME_HEADED = true;
|
||||
|
||||
// Both "chatgpt-web-codex" and "chatgpt-session" store a verified Playwright storage
|
||||
// state produced from a pasted ChatGPT cookie header — the browser-session credential
|
||||
// lifecycle (decode -> ensure storage state -> inspect -> finalize) is shared between
|
||||
// them. The finalize step is what discards the raw pasted cookie in favor of the
|
||||
// verified storage state, so any dashboard route that gates on that lifecycle must
|
||||
// recognize both provider ids, not just the codex one.
|
||||
export const CHATGPT_BROWSER_SESSION_PROVIDER_IDS = [
|
||||
CHATGPT_WEB_CODEX_PROVIDER_ID,
|
||||
"chatgpt-session",
|
||||
] as const;
|
||||
|
||||
export function usesChatGptBrowserSessionCredentials(provider: unknown): boolean {
|
||||
return (
|
||||
typeof provider === "string" &&
|
||||
(CHATGPT_BROWSER_SESSION_PROVIDER_IDS as readonly string[]).includes(provider)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,21 @@ export const WEB_COOKIE_PROVIDERS = {
|
||||
riskNoticeVariant: "webCookie",
|
||||
toolCalling: "native",
|
||||
},
|
||||
"chatgpt-session": {
|
||||
id: "chatgpt-session",
|
||||
serviceKinds: ["llm"],
|
||||
alias: "cgpt-session",
|
||||
name: "ChatGPT Web (Session)",
|
||||
icon: "chat",
|
||||
color: "#10A37F",
|
||||
textIcon: "CS",
|
||||
website: "https://chatgpt.com",
|
||||
authHint:
|
||||
"Paste the full ChatGPT Cookie header. OmniRoute verifies it in an isolated browser profile and then stores only the verified session state.",
|
||||
subscriptionRisk: true,
|
||||
riskNoticeVariant: "webCookie",
|
||||
toolCalling: "emulated",
|
||||
},
|
||||
"grok-web": {
|
||||
id: "grok-web",
|
||||
serviceKinds: ["llm"],
|
||||
|
||||
@@ -35,6 +35,13 @@ export const WEB_SESSION_CREDENTIAL_REQUIREMENTS = {
|
||||
acceptsFullCookieHeader: true,
|
||||
storageKeys: ["cookie", "sessionToken", "session-token", "__Secure-next-auth.session-token"],
|
||||
},
|
||||
"chatgpt-session": {
|
||||
kind: "cookie",
|
||||
credentialName: "ChatGPT Cookie header (full)",
|
||||
placeholder: "__Secure-next-auth.session-token=...; cf_clearance=...",
|
||||
acceptsFullCookieHeader: true,
|
||||
storageKeys: ["cookie", "sessionToken", "session-token", "__Secure-next-auth.session-token"],
|
||||
},
|
||||
"zenmux-free": {
|
||||
kind: "cookie",
|
||||
credentialName: "Cookie header (full)",
|
||||
|
||||
@@ -90,6 +90,16 @@
|
||||
"configSource": "<custom-config>",
|
||||
"provider": "chatgpt-web-codex"
|
||||
},
|
||||
"cgpt-session": {
|
||||
"className": "ChatGptSessionExecutor",
|
||||
"configSource": "<custom-config>",
|
||||
"provider": "chatgpt-session"
|
||||
},
|
||||
"chatgpt-session": {
|
||||
"className": "ChatGptSessionExecutor",
|
||||
"configSource": "<custom-config>",
|
||||
"provider": "chatgpt-session"
|
||||
},
|
||||
"chatgpt-web-codex": {
|
||||
"className": "ChatGptWebCodexExecutor",
|
||||
"configSource": "<custom-config>",
|
||||
@@ -666,6 +676,6 @@
|
||||
"provider": "zai-web"
|
||||
}
|
||||
},
|
||||
"keyCount": 133,
|
||||
"keyCount": 135,
|
||||
"sharedInstances": []
|
||||
}
|
||||
|
||||
@@ -842,6 +842,29 @@
|
||||
"stream": "https://api.chatanywhere.org/v1/chat/completions"
|
||||
}
|
||||
},
|
||||
"chatgpt-session": {
|
||||
"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://chatgpt.com",
|
||||
"stream": "https://chatgpt.com"
|
||||
}
|
||||
},
|
||||
"chatgpt-web-codex": {
|
||||
"format": "openai-responses",
|
||||
"headers": {
|
||||
|
||||
489
tests/unit/chatgpt-session-bridge.test.ts
Normal file
@@ -0,0 +1,489 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS,
|
||||
buildChatGptSessionCompletion,
|
||||
openChatGptSessionStream,
|
||||
resolveChatGptSessionStreamOpenTimeoutMs,
|
||||
} from "../../open-sse/executors/chatgpt-session/bridge.ts";
|
||||
import type { AdapterEvent } from "../../open-sse/vendor/codex-chatgpt-web/types.ts";
|
||||
|
||||
const META = { cid: "chatcmpl-test", created: 1_700_000_000, model: "chatgpt-session/high" };
|
||||
|
||||
async function* iterate(events: AdapterEvent[]): AsyncGenerator<AdapterEvent> {
|
||||
for (const event of events) yield event;
|
||||
}
|
||||
|
||||
async function readAll(stream: ReadableStream<Uint8Array>): Promise<string> {
|
||||
const decoder = new TextDecoder();
|
||||
let out = "";
|
||||
for await (const chunk of stream as unknown as AsyncIterable<Uint8Array>) {
|
||||
out += decoder.decode(chunk, { stream: true });
|
||||
}
|
||||
return out + decoder.decode();
|
||||
}
|
||||
|
||||
test("streams role, content and a terminal stop chunk", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "text_delta", text: "Hel" },
|
||||
{ type: "text_delta", text: "lo" },
|
||||
{ type: "done", usage: { inputTokens: 10, outputTokens: 2 } },
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"delta":\{"role":"assistant"\}/);
|
||||
assert.match(text, /"content":"Hel"/);
|
||||
assert.match(text, /"content":"lo"/);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
assert.match(text, /"prompt_tokens":10/);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
});
|
||||
|
||||
test("thinking deltas surface as reasoning_content", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "thinking_delta", thinking: "hmm" },
|
||||
{ type: "text_delta", text: "ok" },
|
||||
{ type: "done" },
|
||||
]),
|
||||
META
|
||||
);
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"reasoning_content":"hmm"/);
|
||||
});
|
||||
|
||||
test("heartbeats become SSE comments, never data chunks", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([{ type: "text_delta", text: "x" }, { type: "heartbeat" }, { type: "done" }]),
|
||||
META
|
||||
);
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /^: keepalive$/m);
|
||||
assert.doesNotMatch(text, /"heartbeat"/);
|
||||
});
|
||||
|
||||
test("an error before any output returns an error verdict instead of a stream", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([{ type: "error", message: "ChatGPT page is not authenticated" }]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "error");
|
||||
assert.equal((opened as { status: number }).status, 401);
|
||||
assert.equal((opened as { code: string }).code, "session_expired");
|
||||
});
|
||||
|
||||
test("an error verdict message is sanitized before it leaves the bridge", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{
|
||||
type: "error",
|
||||
message: "not authenticated\n at /app/open-sse/x.ts:1:1",
|
||||
},
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "error");
|
||||
assert.doesNotMatch((opened as { message: string }).message, /at \//);
|
||||
});
|
||||
|
||||
test("an error mid-stream terminates the stream after the emitted text", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "text_delta", text: "partial" },
|
||||
{ type: "error", message: "boom", status: 502 },
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"content":"partial"/);
|
||||
assert.match(text, /"error"/);
|
||||
assert.match(text, /"message":"boom"/);
|
||||
assert.match(text, /"code":"turn_failed"/);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
const doneCount = text.split("data: [DONE]").length - 1;
|
||||
assert.equal(doneCount, 1);
|
||||
});
|
||||
|
||||
test("incomplete maps to a length finish reason", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "text_delta", text: "x" },
|
||||
{ type: "incomplete", reason: "max_output" },
|
||||
]),
|
||||
META
|
||||
);
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"finish_reason":"length"/);
|
||||
});
|
||||
|
||||
test("a terminal event as the first meaningful event is replayed, not dropped", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([{ type: "done", usage: { inputTokens: 1, outputTokens: 1 } }]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"delta":\{"role":"assistant"\}/);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
});
|
||||
|
||||
test("a source that ends with no terminal event still emits a synthetic stop", async () => {
|
||||
const opened = await openChatGptSessionStream(iterate([{ type: "text_delta", text: "x" }]), META);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"content":"x"/);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
});
|
||||
|
||||
test("an empty source still opens a stream with role and a synthetic stop", async () => {
|
||||
const opened = await openChatGptSessionStream(iterate([]), META);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"delta":\{"role":"assistant"\}/);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
});
|
||||
|
||||
test("a heartbeat-only source still opens a stream and never leaks the heartbeat type", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([{ type: "heartbeat" }, { type: "heartbeat" }]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"delta":\{"role":"assistant"\}/);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
assert.doesNotMatch(text, /"heartbeat"/);
|
||||
});
|
||||
|
||||
test("incomplete with endTurn true maps to a stop finish reason", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "text_delta", text: "x" },
|
||||
{ type: "incomplete", reason: "x", endTurn: true },
|
||||
]),
|
||||
META
|
||||
);
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
assert.doesNotMatch(text, /"finish_reason":"length"/);
|
||||
});
|
||||
|
||||
test("buffered completion collects content, reasoning and usage", () => {
|
||||
const result = buildChatGptSessionCompletion(
|
||||
[
|
||||
{ type: "thinking_delta", thinking: "think" },
|
||||
{ type: "text_delta", text: "Hello" },
|
||||
{ type: "text_delta", text: " world" },
|
||||
{ type: "done", usage: { inputTokens: 7, outputTokens: 3, totalTokens: 10 } },
|
||||
],
|
||||
META
|
||||
);
|
||||
assert.equal(result.status, 200);
|
||||
const choice = (result.body.choices as Array<Record<string, unknown>>)[0];
|
||||
const message = choice.message as Record<string, unknown>;
|
||||
assert.equal(message.content, "Hello world");
|
||||
assert.equal(message.reasoning_content, "think");
|
||||
assert.equal(choice.finish_reason, "stop");
|
||||
assert.deepEqual(result.body.usage, {
|
||||
prompt_tokens: 7,
|
||||
completion_tokens: 3,
|
||||
total_tokens: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test("buffered completion surfaces an error event as a classified status", () => {
|
||||
const result = buildChatGptSessionCompletion(
|
||||
[{ type: "error", message: "ChatGPT reported a usage limit" }],
|
||||
META
|
||||
);
|
||||
assert.equal(result.status, 429);
|
||||
});
|
||||
|
||||
test("buffered error bodies never leak a stack trace", () => {
|
||||
const result = buildChatGptSessionCompletion(
|
||||
[{ type: "error", message: "failure\n at /app/open-sse/x.ts:1:1" }],
|
||||
META
|
||||
);
|
||||
const error = result.body.error as Record<string, unknown>;
|
||||
assert.doesNotMatch(String(error.message), /at \//);
|
||||
assert.match(String(error.message), /failure/);
|
||||
});
|
||||
|
||||
test("the commentary read-only banner never reaches streamed content", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "assistant_boundary" },
|
||||
{
|
||||
type: "text_delta",
|
||||
text: "⚠️ The local Codex computer is unavailable, so this turn is read-only.",
|
||||
phase: "commentary",
|
||||
},
|
||||
{ type: "assistant_boundary" },
|
||||
{ type: "text_delta", text: "real answer" },
|
||||
{ type: "done" },
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
const content = [...text.matchAll(/"content":"((?:[^"\\]|\\.)*)"/g)]
|
||||
.map((match) => JSON.parse(`"${match[1]}"`) as string)
|
||||
.join("");
|
||||
assert.equal(content, "real answer");
|
||||
assert.doesNotMatch(text, /read-only/);
|
||||
assert.doesNotMatch(text, /Codex computer/);
|
||||
// Commentary must not be laundered into reasoning either — it is transport chatter.
|
||||
assert.doesNotMatch(text, /"reasoning_content"/);
|
||||
});
|
||||
|
||||
test("the commentary read-only banner never reaches buffered content", () => {
|
||||
const result = buildChatGptSessionCompletion(
|
||||
[
|
||||
{ type: "assistant_boundary" },
|
||||
{
|
||||
type: "text_delta",
|
||||
text: "⚠️ The local Codex computer is unavailable, so this turn is read-only.",
|
||||
phase: "commentary",
|
||||
},
|
||||
{ type: "assistant_boundary" },
|
||||
{ type: "text_delta", text: "real answer" },
|
||||
{ type: "done" },
|
||||
],
|
||||
META
|
||||
);
|
||||
assert.equal(result.status, 200);
|
||||
const message = (result.body.choices as Array<Record<string, unknown>>)[0].message as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
assert.equal(message.content, "real answer");
|
||||
assert.equal("reasoning_content" in message, false);
|
||||
assert.doesNotMatch(JSON.stringify(result.body), /read-only/);
|
||||
});
|
||||
|
||||
// I1 — the streaming gate and the buffered failover must agree on what counts as committed
|
||||
// output. Reasoning alone commits neither, so a failure right after a thinking delta is a real
|
||||
// HTTP status on BOTH paths instead of a 200 stream carrying an in-band error chunk.
|
||||
test("reasoning before an error does not commit a 200 on the streaming path", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "thinking_delta", thinking: "weighing options" },
|
||||
{ type: "error", message: "ChatGPT reported a usage limit", status: 429 },
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "error");
|
||||
assert.equal((opened as { status: number }).status, 429);
|
||||
});
|
||||
|
||||
test("the buffered path returns the same status for reasoning followed by an error", () => {
|
||||
const result = buildChatGptSessionCompletion(
|
||||
[
|
||||
{ type: "thinking_delta", thinking: "weighing options" },
|
||||
{ type: "error", message: "ChatGPT reported a usage limit", status: 429 },
|
||||
],
|
||||
META
|
||||
);
|
||||
assert.equal(result.status, 429);
|
||||
});
|
||||
|
||||
test("reasoning buffered behind the gate is still streamed once the gate opens", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "thinking_delta", thinking: "first" },
|
||||
{ type: "thinking_delta", thinking: "second" },
|
||||
{ type: "text_delta", text: "answer" },
|
||||
{ type: "done" },
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "stream");
|
||||
const text = await readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
assert.match(text, /"reasoning_content":"first"/);
|
||||
assert.match(text, /"reasoning_content":"second"/);
|
||||
assert.match(text, /"content":"answer"/);
|
||||
});
|
||||
|
||||
test("an empty text delta does not commit a 200 ahead of an error", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([
|
||||
{ type: "text_delta", text: "" },
|
||||
{ type: "error", message: "ChatGPT reported a usage limit" },
|
||||
]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "error");
|
||||
assert.equal((opened as { status: number }).status, 429);
|
||||
});
|
||||
|
||||
test("a cooldown-hinted classification reaches the stream-open error verdict", async () => {
|
||||
const opened = await openChatGptSessionStream(
|
||||
iterate([{ type: "error", message: "upstream unavailable", status: 503 }]),
|
||||
META
|
||||
);
|
||||
assert.equal(opened.kind, "error");
|
||||
assert.equal((opened as { fallbackHint?: string }).fallbackHint, "connection_cooldown");
|
||||
});
|
||||
|
||||
// I2 — the gate must be bounded: while it is closed the client receives nothing at all (any byte
|
||||
// commits the 200), so a healthy turn that thinks for a long time in the browser would otherwise
|
||||
// look dead to a client with an idle timeout.
|
||||
interface ControllableSource {
|
||||
events: AsyncIterable<AdapterEvent>;
|
||||
push(event: AdapterEvent): void;
|
||||
end(): void;
|
||||
}
|
||||
|
||||
function controllable(): ControllableSource {
|
||||
const queued: AdapterEvent[] = [];
|
||||
const waiting: Array<(result: IteratorResult<AdapterEvent>) => void> = [];
|
||||
let ended = false;
|
||||
const iterator: AsyncIterator<AdapterEvent> = {
|
||||
next() {
|
||||
const event = queued.shift();
|
||||
if (event) return Promise.resolve({ value: event, done: false });
|
||||
if (ended) return Promise.resolve({ value: undefined, done: true });
|
||||
return new Promise((resolve) => waiting.push(resolve));
|
||||
},
|
||||
};
|
||||
return {
|
||||
events: { [Symbol.asyncIterator]: () => iterator },
|
||||
push(event) {
|
||||
const waiter = waiting.shift();
|
||||
if (waiter) waiter({ value: event, done: false });
|
||||
else queued.push(event);
|
||||
},
|
||||
end() {
|
||||
ended = true;
|
||||
for (const waiter of waiting.splice(0)) waiter({ value: undefined, done: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("the default stream-open deadline stays at 30s", () => {
|
||||
assert.equal(CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS, 30_000);
|
||||
});
|
||||
|
||||
test("a silent turn opens the stream once the gate deadline elapses", async () => {
|
||||
const source = controllable();
|
||||
const opened = await openChatGptSessionStream(source.events, META, {
|
||||
streamOpenTimeoutMs: 5,
|
||||
});
|
||||
assert.equal(opened.kind, "stream");
|
||||
const reading = readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
source.push({ type: "heartbeat" });
|
||||
source.push({ type: "text_delta", text: "late answer" });
|
||||
source.push({ type: "done" });
|
||||
source.end();
|
||||
const text = await reading;
|
||||
assert.match(text, /"delta":\{"role":"assistant"\}/);
|
||||
assert.match(text, /"content":"late answer"/);
|
||||
assert.match(text, /"finish_reason":"stop"/);
|
||||
// The role chunk still leads, and heartbeats keep flowing as SSE comments once the gate opened.
|
||||
assert.ok(text.indexOf('"role":"assistant"') < text.indexOf('"content":"late answer"'));
|
||||
assert.match(text, /^: keepalive$/m);
|
||||
assert.ok(text.trimEnd().endsWith("data: [DONE]"));
|
||||
});
|
||||
|
||||
test("reasoning buffered before the deadline is replayed in order after the gate opens", async () => {
|
||||
const source = controllable();
|
||||
source.push({ type: "thinking_delta", thinking: "first" });
|
||||
source.push({ type: "thinking_delta", thinking: "second" });
|
||||
const opened = await openChatGptSessionStream(source.events, META, {
|
||||
streamOpenTimeoutMs: 5,
|
||||
});
|
||||
assert.equal(opened.kind, "stream");
|
||||
const reading = readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
source.push({ type: "text_delta", text: "answer" });
|
||||
source.push({ type: "done" });
|
||||
source.end();
|
||||
const text = await reading;
|
||||
const roleAt = text.indexOf('"role":"assistant"');
|
||||
const firstAt = text.indexOf('"reasoning_content":"first"');
|
||||
const secondAt = text.indexOf('"reasoning_content":"second"');
|
||||
const answerAt = text.indexOf('"content":"answer"');
|
||||
assert.ok(roleAt >= 0 && firstAt > roleAt && secondAt > firstAt && answerAt > secondAt);
|
||||
});
|
||||
|
||||
test("an error before the deadline still returns an error verdict, never a timed-out stream", async () => {
|
||||
const source = controllable();
|
||||
const opening = openChatGptSessionStream(source.events, META, {
|
||||
streamOpenTimeoutMs: 1_000,
|
||||
});
|
||||
source.push({ type: "error", message: "ChatGPT reported a usage limit" });
|
||||
const opened = await opening;
|
||||
assert.equal(opened.kind, "error");
|
||||
assert.equal((opened as { status: number }).status, 429);
|
||||
});
|
||||
|
||||
test("no event is lost to the deadline race", async () => {
|
||||
const source = controllable();
|
||||
const opened = await openChatGptSessionStream(source.events, META, {
|
||||
streamOpenTimeoutMs: 5,
|
||||
});
|
||||
const reading = readAll((opened as { stream: ReadableStream<Uint8Array> }).stream);
|
||||
source.push({ type: "text_delta", text: "one" });
|
||||
source.push({ type: "text_delta", text: "two" });
|
||||
source.push({ type: "done" });
|
||||
source.end();
|
||||
const text = await reading;
|
||||
assert.match(text, /"content":"one"/);
|
||||
assert.match(text, /"content":"two"/);
|
||||
});
|
||||
|
||||
const STREAM_OPEN_TIMEOUT_ENV_VAR = "OMNIROUTE_CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS";
|
||||
|
||||
function withStreamOpenTimeoutEnv(raw: string | undefined, run: () => void): void {
|
||||
const saved = process.env[STREAM_OPEN_TIMEOUT_ENV_VAR];
|
||||
if (raw === undefined) delete process.env[STREAM_OPEN_TIMEOUT_ENV_VAR];
|
||||
else process.env[STREAM_OPEN_TIMEOUT_ENV_VAR] = raw;
|
||||
try {
|
||||
run();
|
||||
} finally {
|
||||
if (saved === undefined) delete process.env[STREAM_OPEN_TIMEOUT_ENV_VAR];
|
||||
else process.env[STREAM_OPEN_TIMEOUT_ENV_VAR] = saved;
|
||||
}
|
||||
}
|
||||
|
||||
test("resolveChatGptSessionStreamOpenTimeoutMs falls back to the default when the env var is unset", () => {
|
||||
withStreamOpenTimeoutEnv(undefined, () => {
|
||||
assert.equal(
|
||||
resolveChatGptSessionStreamOpenTimeoutMs(),
|
||||
CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("resolveChatGptSessionStreamOpenTimeoutMs honors a valid positive integer", () => {
|
||||
withStreamOpenTimeoutEnv("45000", () => {
|
||||
assert.equal(resolveChatGptSessionStreamOpenTimeoutMs(), 45_000);
|
||||
});
|
||||
});
|
||||
|
||||
const INVALID_STREAM_OPEN_TIMEOUT_INPUTS: Array<[label: string, raw: string]> = [
|
||||
["an empty string", ""],
|
||||
["non-numeric text", "not-a-number"],
|
||||
["zero", "0"],
|
||||
["a negative number", "-10"],
|
||||
["a non-finite value", "Infinity"],
|
||||
];
|
||||
|
||||
for (const [label, raw] of INVALID_STREAM_OPEN_TIMEOUT_INPUTS) {
|
||||
test(`resolveChatGptSessionStreamOpenTimeoutMs falls back to the default for ${label}`, () => {
|
||||
withStreamOpenTimeoutEnv(raw, () => {
|
||||
assert.equal(
|
||||
resolveChatGptSessionStreamOpenTimeoutMs(),
|
||||
CHATGPT_SESSION_STREAM_OPEN_TIMEOUT_MS
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
157
tests/unit/chatgpt-session-errors.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { classifyChatGptSessionError } from "../../open-sse/executors/chatgpt-session/errors.ts";
|
||||
import { ChatGptSessionInputError } from "../../open-sse/executors/chatgpt-session/messages.ts";
|
||||
|
||||
test("a missing browser is a cooldown-hinted 503", () => {
|
||||
const result = classifyChatGptSessionError(
|
||||
new Error("No supported Chrome or Chromium executable was found")
|
||||
);
|
||||
assert.equal(result.status, 503);
|
||||
assert.equal(result.code, "browser_unavailable");
|
||||
assert.equal(result.fallbackHint, "connection_cooldown");
|
||||
});
|
||||
|
||||
test("a playwright launch failure is also a cooldown-hinted 503", () => {
|
||||
const result = classifyChatGptSessionError(
|
||||
new Error("browserType.launch: Executable doesn't exist at /root/.cache/ms-playwright/x")
|
||||
);
|
||||
assert.equal(result.status, 503);
|
||||
assert.equal(result.fallbackHint, "connection_cooldown");
|
||||
});
|
||||
|
||||
test("missing credentials are a 401", () => {
|
||||
assert.equal(
|
||||
classifyChatGptSessionError(new Error("ChatGPT browser credentials are missing")).status,
|
||||
401
|
||||
);
|
||||
});
|
||||
|
||||
test("an expired session is a 401 session_expired", () => {
|
||||
const result = classifyChatGptSessionError(new Error("ChatGPT page is not authenticated"));
|
||||
assert.equal(result.status, 401);
|
||||
assert.equal(result.code, "session_expired");
|
||||
});
|
||||
|
||||
test("a rate-limit dialog is a 429", () => {
|
||||
const result = classifyChatGptSessionError(new Error("ChatGPT reported a usage limit"));
|
||||
assert.equal(result.status, 429);
|
||||
assert.equal(result.code, "rate_limited");
|
||||
});
|
||||
|
||||
test("an account-capability mismatch is a terminal 400", () => {
|
||||
const result = classifyChatGptSessionError(
|
||||
new Error("pro is not available for this non-Pro connection")
|
||||
);
|
||||
assert.equal(result.status, 400);
|
||||
assert.equal(result.code, "route_unavailable");
|
||||
assert.equal(result.fallbackHint, undefined);
|
||||
});
|
||||
|
||||
test("a DOM timeout is a terminal 400, not a retryable 5xx", () => {
|
||||
const timeout = new Error("locator.waitForSelector: Timeout 30000ms exceeded");
|
||||
timeout.name = "TimeoutError";
|
||||
const result = classifyChatGptSessionError(timeout);
|
||||
assert.equal(result.status, 400);
|
||||
assert.equal(result.code, "browser_ui_timeout");
|
||||
});
|
||||
|
||||
test("a TimeoutError naming a login selector is a UI timeout, not an expired session", () => {
|
||||
const timeout = new Error(
|
||||
'page.waitForSelector: Timeout 15000ms exceeded ... selector "button:has-text(\\"Log in\\")"'
|
||||
);
|
||||
timeout.name = "TimeoutError";
|
||||
const result = classifyChatGptSessionError(timeout);
|
||||
assert.equal(result.status, 400);
|
||||
assert.equal(result.code, "browser_ui_timeout");
|
||||
});
|
||||
|
||||
test("input errors map to their own 400 codes", () => {
|
||||
const result = classifyChatGptSessionError(
|
||||
new ChatGptSessionInputError("vision_unsupported", "no images")
|
||||
);
|
||||
assert.equal(result.status, 400);
|
||||
assert.equal(result.code, "vision_unsupported");
|
||||
});
|
||||
|
||||
test("an explicit upstream status is used when no message pattern matches", () => {
|
||||
const result = classifyChatGptSessionError({ message: "anything", status: 502, code: "x" });
|
||||
assert.equal(result.status, 502);
|
||||
});
|
||||
|
||||
// CONTRACT CHANGE (deliberate inversion of the previous expectation). This test used to assert
|
||||
// that a recognised message beat an explicit upstream status; the precedence is now the other way
|
||||
// round. The vendor documents `AdapterEvent.error.status` as "Authoritative upstream/proxy status
|
||||
// when known; avoids message-based classification", and honouring that is what stops a 503 whose
|
||||
// prose happens to mention signing in from being reported as an expired session — which would
|
||||
// mark a HEALTHY account's credentials dead and pull it out of rotation. Message matching still
|
||||
// runs, just as the fallback for failures that carry no status at all (everything this executor
|
||||
// throws itself).
|
||||
test("an explicit upstream status wins over a matching message", () => {
|
||||
const result = classifyChatGptSessionError({
|
||||
message: "ChatGPT reported a usage limit",
|
||||
status: 500,
|
||||
});
|
||||
assert.equal(result.status, 500);
|
||||
assert.equal(result.code, "turn_failed");
|
||||
});
|
||||
|
||||
test("a session-expired message cannot downgrade an explicit 503 to a 401", () => {
|
||||
const result = classifyChatGptSessionError({
|
||||
message: "Please sign in to continue",
|
||||
status: 503,
|
||||
});
|
||||
assert.equal(result.status, 503);
|
||||
assert.notEqual(result.code, "session_expired");
|
||||
});
|
||||
|
||||
test("an explicit 503 carries the connection cooldown hint", () => {
|
||||
const result = classifyChatGptSessionError({ message: "upstream unavailable", status: 503 });
|
||||
assert.equal(result.status, 503);
|
||||
assert.equal(result.fallbackHint, "connection_cooldown");
|
||||
});
|
||||
|
||||
test("an explicit 429 carries the connection cooldown hint", () => {
|
||||
const result = classifyChatGptSessionError({ message: "slow down", status: 429 });
|
||||
assert.equal(result.status, 429);
|
||||
assert.equal(result.fallbackHint, "connection_cooldown");
|
||||
});
|
||||
|
||||
test("an explicit 400 carries no cooldown hint", () => {
|
||||
const result = classifyChatGptSessionError({ message: "bad request", status: 400 });
|
||||
assert.equal(result.status, 400);
|
||||
assert.equal(result.fallbackHint, undefined);
|
||||
});
|
||||
|
||||
test("an explicit status keeps the event's own code when it carries one", () => {
|
||||
const result = classifyChatGptSessionError({
|
||||
message: "anything",
|
||||
status: 503,
|
||||
code: "upstream_unavailable",
|
||||
});
|
||||
assert.equal(result.code, "upstream_unavailable");
|
||||
});
|
||||
|
||||
test("a TimeoutError still outranks an explicit upstream status", () => {
|
||||
const timeout = new Error("locator.waitForSelector: Timeout 30000ms exceeded") as Error & {
|
||||
status?: number;
|
||||
};
|
||||
timeout.name = "TimeoutError";
|
||||
timeout.status = 503;
|
||||
const result = classifyChatGptSessionError(timeout);
|
||||
assert.equal(result.status, 400);
|
||||
assert.equal(result.code, "browser_ui_timeout");
|
||||
});
|
||||
|
||||
test("message patterns still classify failures that carry no status", () => {
|
||||
const result = classifyChatGptSessionError(new Error("ChatGPT reported a usage limit"));
|
||||
assert.equal(result.status, 429);
|
||||
assert.equal(result.code, "rate_limited");
|
||||
});
|
||||
|
||||
test("an unrecognised failure is a retryable 502", () => {
|
||||
const result = classifyChatGptSessionError(new Error("something odd happened"));
|
||||
assert.equal(result.status, 502);
|
||||
assert.equal(result.code, "turn_failed");
|
||||
});
|
||||
292
tests/unit/chatgpt-session-executor.test.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { ChatGptSessionExecutor } from "../../open-sse/executors/chatgpt-session.ts";
|
||||
import { __setChatGptSessionRuntimeForTesting } from "../../open-sse/executors/chatgpt-session/runtime.ts";
|
||||
import type { AdapterEvent } from "../../open-sse/vendor/codex-chatgpt-web/types.ts";
|
||||
|
||||
const CREDENTIALS = {
|
||||
connectionId: "conn-1",
|
||||
apiKey: JSON.stringify({ version: 2, storageState: { cookies: [], origins: [] } }),
|
||||
providerSpecificData: { solAvailable: true, proAvailable: false, browserVerified: true },
|
||||
};
|
||||
|
||||
function stubRuntime(events: AdapterEvent[], overrides: Record<string, unknown> = {}) {
|
||||
__setChatGptSessionRuntimeForTesting({
|
||||
detectChrome: () => "/usr/bin/chromium",
|
||||
ensureStorageState: () => "/tmp/state.json",
|
||||
readStorageState: () => ({ cookies: [], origins: [] }),
|
||||
loginStateExists: () => true,
|
||||
inspectLogin: async () => ({ solAvailable: true, proAvailable: false }),
|
||||
runTurn: async (_parsed, _incoming, emit) => {
|
||||
for (const event of events) emit(event);
|
||||
},
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function body(extra: Record<string, unknown> = {}) {
|
||||
return { messages: [{ role: "user", content: "Hi" }], ...extra };
|
||||
}
|
||||
|
||||
test.afterEach(() => {
|
||||
__setChatGptSessionRuntimeForTesting(null);
|
||||
});
|
||||
|
||||
test("returns a streaming completion for a normal turn", async () => {
|
||||
stubRuntime([{ type: "text_delta", text: "Hello" }, { type: "done" }]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: true,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.headers.get("Content-Type"), "text/event-stream; charset=utf-8");
|
||||
const text = await response.text();
|
||||
assert.match(text, /"content":"Hello"/);
|
||||
});
|
||||
|
||||
test("returns a buffered completion when stream is false", async () => {
|
||||
stubRuntime([{ type: "text_delta", text: "Hello" }, { type: "done" }]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: false,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
const json = (await response.json()) as Record<string, unknown>;
|
||||
assert.equal(json.object, "chat.completion");
|
||||
assert.equal(
|
||||
((json.choices as Array<Record<string, unknown>>)[0].message as Record<string, unknown>)
|
||||
.content,
|
||||
"Hello"
|
||||
);
|
||||
});
|
||||
|
||||
test("answers 503 with a cooldown hint when no browser is installed", async () => {
|
||||
stubRuntime([], { detectChrome: () => undefined });
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: true,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 503);
|
||||
assert.equal(response.headers.get("X-Omni-Fallback-Hint"), "connection_cooldown");
|
||||
});
|
||||
|
||||
test("answers 401 when the connection has no credentials", async () => {
|
||||
stubRuntime([]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: true,
|
||||
credentials: { connectionId: "conn-1" },
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 401);
|
||||
});
|
||||
|
||||
test("rejects a Pro route on a non-Pro account without touching the browser", async () => {
|
||||
let ran = false;
|
||||
stubRuntime([], {
|
||||
runTurn: async () => {
|
||||
ran = true;
|
||||
},
|
||||
});
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "pro",
|
||||
body: body(),
|
||||
stream: true,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 400);
|
||||
assert.equal(ran, false);
|
||||
});
|
||||
|
||||
test("an expired session before output becomes a 401, not a 200 stream", async () => {
|
||||
stubRuntime([{ type: "error", message: "ChatGPT page is not authenticated" }]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: true,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 401);
|
||||
const json = (await response.json()) as { error: { message: string } };
|
||||
assert.doesNotMatch(json.error.message, /at \//);
|
||||
});
|
||||
|
||||
test("persists rotated storage state through onCredentialsRefreshed", async () => {
|
||||
stubRuntime([{ type: "text_delta", text: "x" }, { type: "done" }], {
|
||||
readStorageState: () => ({ cookies: [{ name: "rotated" }], origins: [] }),
|
||||
});
|
||||
const patches: Array<Record<string, unknown>> = [];
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: false,
|
||||
credentials: CREDENTIALS,
|
||||
onCredentialsRefreshed: async (patch) => {
|
||||
patches.push(patch);
|
||||
},
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
await response.text();
|
||||
const persisted = patches.find((patch) => typeof patch.apiKey === "string");
|
||||
assert.ok(persisted, "expected an apiKey patch");
|
||||
assert.match(String(persisted.apiKey), /rotated/);
|
||||
});
|
||||
|
||||
test("emulated tool calls go through the shared web-tools contract", async () => {
|
||||
stubRuntime([
|
||||
{ type: "text_delta", text: '<tool>{"name": "get_time", "arguments": {}}</tool>' },
|
||||
{ type: "done" },
|
||||
]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body({
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "get_time", description: "time", parameters: { type: "object" } },
|
||||
},
|
||||
],
|
||||
}),
|
||||
stream: false,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
const json = (await response.json()) as Record<string, unknown>;
|
||||
const choice = (json.choices as Array<Record<string, unknown>>)[0];
|
||||
assert.equal(choice.finish_reason, "tool_calls");
|
||||
});
|
||||
|
||||
test("forwards the abort signal to the adapter", async () => {
|
||||
let seenSignal: AbortSignal | undefined;
|
||||
stubRuntime([{ type: "done" }], {
|
||||
runTurn: async (
|
||||
_parsed: unknown,
|
||||
incoming: { abortSignal?: AbortSignal },
|
||||
emit: (e: AdapterEvent) => void
|
||||
) => {
|
||||
seenSignal = incoming.abortSignal;
|
||||
emit({ type: "done" });
|
||||
},
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: false,
|
||||
credentials: CREDENTIALS,
|
||||
signal: controller.signal,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
await response.text();
|
||||
assert.equal(seenSignal, controller.signal);
|
||||
});
|
||||
|
||||
test("keeps the bridge's own status when a first-event error is not message-classifiable", async () => {
|
||||
// A Playwright TimeoutError is classified by `name`/`status`, not by its message. Re-deriving
|
||||
// the class from the message alone would fall through to 502 and trip the provider breaker.
|
||||
stubRuntime([
|
||||
{
|
||||
type: "error",
|
||||
message: "locator.click: Target closed",
|
||||
status: 400,
|
||||
code: "browser_ui_timeout",
|
||||
} as AdapterEvent,
|
||||
]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: true,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 400);
|
||||
const json = (await response.json()) as { error: { code: string } };
|
||||
assert.equal(json.error.code, "browser_ui_timeout");
|
||||
});
|
||||
|
||||
test(
|
||||
"closes the event queue even when persisting state and its logger both throw",
|
||||
{ timeout: 5000 },
|
||||
async () => {
|
||||
stubRuntime([{ type: "text_delta", text: "Hello" }, { type: "done" }], {
|
||||
readStorageState: () => {
|
||||
throw new Error("storage state is unreadable");
|
||||
},
|
||||
});
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body(),
|
||||
stream: false,
|
||||
credentials: CREDENTIALS,
|
||||
log: {
|
||||
warn: () => {
|
||||
throw new Error("logger exploded");
|
||||
},
|
||||
},
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.ok(response instanceof Response);
|
||||
assert.equal(typeof response.status, "number");
|
||||
}
|
||||
);
|
||||
|
||||
test("replays emulated tool calls as an SSE stream when the client asked to stream", async () => {
|
||||
stubRuntime([
|
||||
{ type: "text_delta", text: '<tool>{"name": "get_time", "arguments": {}}</tool>' },
|
||||
{ type: "done" },
|
||||
]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body({
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "get_time", description: "time", parameters: { type: "object" } },
|
||||
},
|
||||
],
|
||||
}),
|
||||
stream: true,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 200);
|
||||
assert.match(response.headers.get("Content-Type") ?? "", /text\/event-stream/);
|
||||
const text = await response.text();
|
||||
assert.match(text, /"tool_calls"/);
|
||||
assert.match(text, /get_time/);
|
||||
assert.match(text, /"finish_reason":"tool_calls"/);
|
||||
});
|
||||
|
||||
test("skips the tool-mode replay when the buffered turn did not return 200", async () => {
|
||||
stubRuntime([{ type: "error", message: "ChatGPT page is not authenticated" }]);
|
||||
const result = await new ChatGptSessionExecutor().execute({
|
||||
model: "high",
|
||||
body: body({
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: { name: "get_time", description: "time", parameters: { type: "object" } },
|
||||
},
|
||||
],
|
||||
}),
|
||||
stream: false,
|
||||
credentials: CREDENTIALS,
|
||||
});
|
||||
const response = "response" in result ? result.response : result;
|
||||
assert.equal(response.status, 401);
|
||||
const json = (await response.json()) as Record<string, unknown>;
|
||||
assert.ok(json.error, "expected the error body to survive the tool-mode path");
|
||||
assert.equal(json.choices, undefined);
|
||||
});
|
||||
312
tests/unit/chatgpt-session-messages.test.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
ChatGptSessionInputError,
|
||||
buildParsedRequest,
|
||||
} from "../../open-sse/executors/chatgpt-session/messages.ts";
|
||||
import { requireChatGptSessionRoute } from "../../open-sse/executors/chatgpt-session/models.ts";
|
||||
|
||||
const route = requireChatGptSessionRoute("high");
|
||||
|
||||
test("system messages become the system prompt, not conversation turns", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "system", content: "Be terse." },
|
||||
{ role: "user", content: "Hi" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
assert.deepEqual(parsed.context.systemPrompt, ["Be terse."]);
|
||||
assert.equal(parsed.context.messages.length, 1);
|
||||
assert.equal(parsed.context.messages[0].role, "user");
|
||||
});
|
||||
|
||||
test("developer messages join the system prompt in order", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "system", content: "A" },
|
||||
{ role: "developer", content: "B" },
|
||||
{ role: "user", content: "Hi" },
|
||||
],
|
||||
stream: false,
|
||||
});
|
||||
assert.deepEqual(parsed.context.systemPrompt, ["A", "B"]);
|
||||
});
|
||||
|
||||
test("pins the backend model and the route effort", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route: requireChatGptSessionRoute("luna"),
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
stream: true,
|
||||
});
|
||||
assert.equal(parsed.modelId, "gpt-5.6-luna");
|
||||
assert.equal(parsed.options.reasoning, "low");
|
||||
assert.equal(parsed.stream, true);
|
||||
});
|
||||
|
||||
test("preserves multi-turn history with assistant text parts", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{ role: "assistant", content: "two" },
|
||||
{ role: "user", content: "three" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
assert.equal(parsed.context.messages.length, 3);
|
||||
assert.deepEqual(parsed.context.messages[1], {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "two" }],
|
||||
timestamp: parsed.context.messages[1].timestamp,
|
||||
});
|
||||
});
|
||||
|
||||
test("flattens OpenAI text content parts into one string", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "a" },
|
||||
{ type: "text", text: "b" },
|
||||
],
|
||||
},
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
assert.equal(parsed.context.messages[0].content, "a\nb");
|
||||
});
|
||||
|
||||
test("rejects image parts in phase 1", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "image_url", image_url: { url: "data:image/png;base64,AA" } }],
|
||||
},
|
||||
],
|
||||
stream: true,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof ChatGptSessionInputError && error.code === "vision_unsupported"
|
||||
);
|
||||
});
|
||||
|
||||
test("rejects a request with no user turn", () => {
|
||||
assert.throws(
|
||||
() => buildParsedRequest({ route, messages: [{ role: "system", content: "x" }], stream: true }),
|
||||
(error: unknown) =>
|
||||
error instanceof ChatGptSessionInputError && error.code === "no_user_message"
|
||||
);
|
||||
});
|
||||
|
||||
test("never carries tools into the parsed context", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
stream: true,
|
||||
});
|
||||
assert.equal(parsed.context.tools, undefined);
|
||||
});
|
||||
|
||||
test("rejects any content part that is neither text nor an image", () => {
|
||||
for (const part of [
|
||||
{ type: "file", file: { file_id: "f-1" } },
|
||||
{ type: "input_audio", input_audio: { data: "AA", format: "wav" } },
|
||||
]) {
|
||||
assert.throws(
|
||||
() =>
|
||||
buildParsedRequest({
|
||||
route,
|
||||
messages: [{ role: "user", content: [part] }],
|
||||
stream: false,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof ChatGptSessionInputError && error.code === "unsupported_content_part"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("an assistant refusal part is folded into the mapped content as text", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "refusal", refusal: "I can't help with that." },
|
||||
{ type: "text", text: "Here is something else." },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "three" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
assert.equal(parsed.context.messages.length, 3);
|
||||
assert.deepEqual(parsed.context.messages[1], {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "I can't help with that.\nHere is something else." }],
|
||||
timestamp: parsed.context.messages[1].timestamp,
|
||||
});
|
||||
});
|
||||
|
||||
test("a refusal part whose refusal field is not a string is still rejected", () => {
|
||||
for (const part of [
|
||||
{ type: "refusal" },
|
||||
{ type: "refusal", refusal: null },
|
||||
{ type: "refusal", refusal: { text: "nope" } },
|
||||
{ type: "refusal", text: "wrong field" },
|
||||
]) {
|
||||
assert.throws(
|
||||
() =>
|
||||
buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{ role: "assistant", content: [part] },
|
||||
],
|
||||
stream: false,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof ChatGptSessionInputError && error.code === "unsupported_content_part"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The synthesized `_rawBody` envelope. Without it the vendored adapter refuses every turn
|
||||
* ("ChatGPT web requires native Codex turn_id metadata for browser-session replay") before any
|
||||
* browser work, so these assertions guard a live-fatal defect that adapter mocking cannot see.
|
||||
*/
|
||||
function rawBody(parsed: { _rawBody?: unknown }): Record<string, unknown> {
|
||||
const body = parsed._rawBody;
|
||||
assert.ok(body && typeof body === "object" && !Array.isArray(body), "_rawBody must be an object");
|
||||
return body as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function turnMetadata(parsed: { _rawBody?: unknown }): Record<string, unknown> {
|
||||
const clientMetadata = rawBody(parsed).client_metadata;
|
||||
assert.ok(
|
||||
clientMetadata && typeof clientMetadata === "object" && !Array.isArray(clientMetadata),
|
||||
"client_metadata must be an object"
|
||||
);
|
||||
const raw = (clientMetadata as Record<string, unknown>)["x-codex-turn-metadata"];
|
||||
assert.equal(
|
||||
typeof raw,
|
||||
"string",
|
||||
"x-codex-turn-metadata must be the JSON string the client sends"
|
||||
);
|
||||
const decoded: unknown = JSON.parse(raw as string);
|
||||
assert.ok(decoded && typeof decoded === "object" && !Array.isArray(decoded));
|
||||
return decoded as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function inputItems(parsed: { _rawBody?: unknown }): Array<Record<string, unknown>> {
|
||||
const input = rawBody(parsed).input;
|
||||
assert.ok(Array.isArray(input), "_rawBody.input must be an array");
|
||||
return input.map((item) => {
|
||||
assert.ok(item && typeof item === "object" && !Array.isArray(item));
|
||||
return item as Record<string, unknown>;
|
||||
});
|
||||
}
|
||||
|
||||
function passthroughTurnId(item: Record<string, unknown>): unknown {
|
||||
const passthrough = item.internal_chat_message_metadata_passthrough;
|
||||
if (passthrough === undefined) return undefined;
|
||||
assert.ok(passthrough && typeof passthrough === "object" && !Array.isArray(passthrough));
|
||||
return (passthrough as Record<string, unknown>).turn_id;
|
||||
}
|
||||
|
||||
test("the turn metadata is a JSON string carrying a thread id and a turn id", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [{ role: "user", content: "Hi" }],
|
||||
stream: true,
|
||||
});
|
||||
const metadata = turnMetadata(parsed);
|
||||
assert.equal(typeof metadata.thread_id, "string");
|
||||
assert.equal(typeof metadata.turn_id, "string");
|
||||
assert.match(String(metadata.thread_id), /^thread_omniroute_/);
|
||||
assert.match(String(metadata.turn_id), /^turn_omniroute_/);
|
||||
});
|
||||
|
||||
test("_rawBody.input mirrors the parsed messages, in order and in Responses item shape", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "system", content: "Be terse." },
|
||||
{ role: "user", content: "one" },
|
||||
{ role: "assistant", content: "two" },
|
||||
{ role: "user", content: "three" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
const items = inputItems(parsed);
|
||||
assert.equal(items.length, parsed.context.messages.length);
|
||||
assert.deepEqual(
|
||||
items.map((item) => item.role),
|
||||
["user", "assistant", "user"]
|
||||
);
|
||||
assert.deepEqual(
|
||||
items.map((item) => item.type),
|
||||
["message", "message", "message"]
|
||||
);
|
||||
assert.deepEqual(items[0].content, [{ type: "input_text", text: "one" }]);
|
||||
assert.deepEqual(items[1].content, [{ type: "output_text", text: "two" }]);
|
||||
assert.deepEqual(items[2].content, [{ type: "input_text", text: "three" }]);
|
||||
// System prompts stay out of `input`; the vendored parser reads them from `instructions`.
|
||||
assert.equal(rawBody(parsed).instructions, "Be terse.");
|
||||
assert.equal(rawBody(parsed).model, parsed.modelId);
|
||||
});
|
||||
|
||||
test("only the last user item carries the current-turn passthrough id", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{ role: "assistant", content: "two" },
|
||||
{ role: "user", content: "three" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
const items = inputItems(parsed);
|
||||
const turnId = turnMetadata(parsed).turn_id;
|
||||
assert.equal(passthroughTurnId(items[0]), undefined);
|
||||
assert.equal(passthroughTurnId(items[1]), undefined);
|
||||
assert.equal(passthroughTurnId(items[2]), turnId);
|
||||
});
|
||||
|
||||
test("a trailing assistant turn leaves the marker on the last USER item", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{ role: "assistant", content: "two" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
const items = inputItems(parsed);
|
||||
const turnId = turnMetadata(parsed).turn_id;
|
||||
assert.equal(passthroughTurnId(items[0]), turnId);
|
||||
assert.equal(passthroughTurnId(items[1]), undefined);
|
||||
});
|
||||
|
||||
test("every request gets its own thread id and turn id", () => {
|
||||
const first = turnMetadata(
|
||||
buildParsedRequest({ route, messages: [{ role: "user", content: "Hi" }], stream: true })
|
||||
);
|
||||
const second = turnMetadata(
|
||||
buildParsedRequest({ route, messages: [{ role: "user", content: "Hi" }], stream: true })
|
||||
);
|
||||
assert.notEqual(first.thread_id, second.thread_id);
|
||||
assert.notEqual(first.turn_id, second.turn_id);
|
||||
});
|
||||
48
tests/unit/chatgpt-session-models.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
CHATGPT_SESSION_ROUTE_IDS,
|
||||
requireChatGptSessionRoute,
|
||||
} from "../../open-sse/executors/chatgpt-session/models.ts";
|
||||
|
||||
test("exposes the seven public routes", () => {
|
||||
assert.deepEqual(
|
||||
[...CHATGPT_SESSION_ROUTE_IDS],
|
||||
["luna", "think", "instant", "medium", "high", "extra-high", "pro"]
|
||||
);
|
||||
});
|
||||
|
||||
test("maps a slug to its backend model and effort", () => {
|
||||
assert.deepEqual(requireChatGptSessionRoute("high"), {
|
||||
id: "high",
|
||||
backendModel: "gpt-5.6-sol",
|
||||
effort: "high",
|
||||
pro: false,
|
||||
sol: true,
|
||||
});
|
||||
});
|
||||
|
||||
test("strips the provider prefix from a qualified model id", () => {
|
||||
assert.equal(requireChatGptSessionRoute("chatgpt-session/pro").id, "pro");
|
||||
assert.equal(requireChatGptSessionRoute("cgpt-session/pro").id, "pro");
|
||||
});
|
||||
|
||||
test("luna routes target the luna backend and never require sol", () => {
|
||||
const luna = requireChatGptSessionRoute("luna");
|
||||
assert.equal(luna.backendModel, "gpt-5.6-luna");
|
||||
assert.equal(luna.sol, false);
|
||||
assert.equal(requireChatGptSessionRoute("think").effort, "medium");
|
||||
});
|
||||
|
||||
test("pro routes are flagged pro", () => {
|
||||
assert.equal(requireChatGptSessionRoute("pro").pro, true);
|
||||
assert.equal(requireChatGptSessionRoute("extra-high").pro, true);
|
||||
});
|
||||
|
||||
test("rejects an unknown slug", () => {
|
||||
assert.throws(
|
||||
() => requireChatGptSessionRoute("gpt-4"),
|
||||
/Unsupported ChatGPT Session model: gpt-4/
|
||||
);
|
||||
});
|
||||
153
tests/unit/chatgpt-session-providers-route.test.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* C2 regression guard — creating a `chatgpt-session` connection from the dashboard.
|
||||
*
|
||||
* `POST /api/providers` routes every provider accepted by
|
||||
* `usesChatGptBrowserSessionCredentials()` into `finalizeValidatedChatGptWebCodexSecrets`,
|
||||
* whose first statement is `JSON.parse`. The dashboard modals used to build the
|
||||
* `{version, cookie}` envelope only for `chatgpt-web-codex` and post the pasted Cookie header
|
||||
* verbatim for anything else, so every `chatgpt-session` save died with
|
||||
* `Unexpected token '_', "__Secure-n"... is not valid JSON`. These tests pin both halves of the
|
||||
* contract: the envelope the fixed modals send is accepted and stored as the VERIFIED storage
|
||||
* state (never the raw cookie), and a raw header is rejected — which is exactly why the client
|
||||
* must key its encoding off the same shared predicate the route uses.
|
||||
*
|
||||
* The browser probe is not run: the verification artifacts the real Playwright inspector leaves
|
||||
* behind are seeded on disk, and a CDP endpoint is configured so nothing in this file can ever
|
||||
* reach `chromium.launch()`.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { makeManagementSessionRequest } from "../helpers/managementSession.ts";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-chatgpt-session-route-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
// Hard "never launch Chrome" guarantee for the fire-and-forget auto-test the POST kicks off:
|
||||
// with a CDP endpoint set, the vendored login inspector connects over CDP (to a dead loopback
|
||||
// port here) instead of spawning a browser.
|
||||
process.env.CHATGPT_WEB_CODEX_CDP_URL = "http://127.0.0.1:1";
|
||||
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const providersDb = await import("../../src/lib/db/providers.ts");
|
||||
const storageState = await import("../../open-sse/executors/chatgpt-web-codex/storageState.ts");
|
||||
const browserLogin = await import("../../open-sse/vendor/codex-chatgpt-web/browser-login.ts");
|
||||
const providersRoute = await import("../../src/app/api/providers/route.ts");
|
||||
|
||||
const RAW_COOKIE = "__Secure-next-auth.session-token=session-value-abc; _cfuvid=cf-value";
|
||||
|
||||
test.after(() => {
|
||||
core.resetDbInstance();
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
||||
});
|
||||
|
||||
/**
|
||||
* Reproduce, without Playwright, exactly what a successful browser validation leaves on disk:
|
||||
* the cookie-derived storage state plus the verification marker the inspector writes on success.
|
||||
*/
|
||||
function seedVerifiedValidation(cookie: string): string {
|
||||
const validationId = `validation-${randomBytes(12).toString("hex")}`;
|
||||
const paths = storageState.connectionRuntimePaths(validationId);
|
||||
storageState.ensureConnectionStorageState(validationId, cookie);
|
||||
browserLogin.writeVerificationMarker(paths.storageStatePath, {
|
||||
solAvailable: false,
|
||||
proAvailable: false,
|
||||
});
|
||||
return validationId;
|
||||
}
|
||||
|
||||
async function createConnection(apiKey: string, validationId: string): Promise<Response> {
|
||||
return providersRoute.POST(
|
||||
await makeManagementSessionRequest("http://localhost/api/providers", {
|
||||
method: "POST",
|
||||
headers: { "x-skip-model-sync": "true" },
|
||||
body: {
|
||||
provider: "chatgpt-session",
|
||||
name: `ChatGPT session ${randomBytes(4).toString("hex")}`,
|
||||
apiKey,
|
||||
providerSpecificData: { validationId },
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
test("POST creates a chatgpt-session connection from a pasted cookie header", async () => {
|
||||
const validationId = seedVerifiedValidation(RAW_COOKIE);
|
||||
// What the fixed modals send for ANY provider `usesChatGptBrowserSessionCredentials()`
|
||||
// accepts — the raw pasted header wrapped in the shared credential envelope, with no
|
||||
// `runtimeKey` (this provider never has one).
|
||||
const envelope = JSON.stringify({ version: 1, cookie: RAW_COOKIE });
|
||||
|
||||
const response = await createConnection(envelope, validationId);
|
||||
const body = (await response.json()) as { connection?: { id: string }; error?: string };
|
||||
|
||||
assert.equal(response.status, 201, `expected 201, got ${response.status}: ${body.error ?? ""}`);
|
||||
assert.ok(body.connection?.id);
|
||||
|
||||
const stored = (await providersDb.getProviderConnectionById(body.connection.id)) as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
assert.ok(stored);
|
||||
const persisted = JSON.parse(String(stored.apiKey)) as Record<string, unknown>;
|
||||
// The raw cookie is discarded in favour of the verified Playwright storage state.
|
||||
assert.equal(persisted.version, 2);
|
||||
assert.equal("cookie" in persisted, false);
|
||||
assert.equal("runtimeKey" in persisted, false);
|
||||
const state = persisted.storageState as Record<string, unknown>;
|
||||
assert.ok(Array.isArray(state.cookies));
|
||||
assert.equal(String(stored.apiKey).includes("session-value-abc"), true);
|
||||
assert.equal(
|
||||
String(stored.apiKey).includes(RAW_COOKIE),
|
||||
false,
|
||||
"the pasted Cookie header itself must never be persisted"
|
||||
);
|
||||
|
||||
// The one-shot validation scratch directory is consumed by the finalize step.
|
||||
assert.equal(
|
||||
fs.existsSync(storageState.connectionRuntimePaths(validationId).storageStatePath),
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("POST rejects a raw cookie header and never echoes a stack or a path", async () => {
|
||||
const validationId = seedVerifiedValidation(RAW_COOKIE);
|
||||
|
||||
const response = await createConnection(RAW_COOKIE, validationId);
|
||||
const body = (await response.json()) as { error?: string };
|
||||
|
||||
assert.equal(response.status, 400);
|
||||
assert.ok(body.error);
|
||||
// Routed through sanitizeErrorMessage: no stack tail, no absolute source path.
|
||||
assert.doesNotMatch(String(body.error), /\n\s+at /);
|
||||
assert.doesNotMatch(String(body.error), /at \//);
|
||||
// The German fallback is gone — this provider is labelled in English.
|
||||
assert.doesNotMatch(String(body.error), /Browserprüfung/);
|
||||
});
|
||||
|
||||
test("both dashboard modals derive the credential envelope from the shared predicate", () => {
|
||||
// The client/server drift that caused C2 is only detectable at the source level: the modals
|
||||
// are React components with no unit-testable seam. Pin that neither of them gates the
|
||||
// ENVELOPE on the codex-only id any more.
|
||||
const modals = [
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx",
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx",
|
||||
];
|
||||
for (const modal of modals) {
|
||||
const source = fs.readFileSync(modal, "utf8");
|
||||
assert.match(
|
||||
source,
|
||||
/usesChatGptBrowserSessionCredentials\(provider\)/,
|
||||
`${modal} must derive the envelope from the shared browser-session predicate`
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/=\s*isChatGptWebCodex\s*\n?\s*\?\s*JSON\.stringify\(\{/,
|
||||
`${modal} must not gate the credential envelope on the codex-only provider id`
|
||||
);
|
||||
}
|
||||
});
|
||||
35
tests/unit/chatgpt-session-registry.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { REGISTRY } from "../../open-sse/config/providerRegistry.ts";
|
||||
import { CHATGPT_SESSION_ROUTE_IDS } from "../../open-sse/executors/chatgpt-session/models.ts";
|
||||
import {
|
||||
RETIRED_COMMON_CHATGPT_WEB_PROVIDER_IDS,
|
||||
isCommonChatGptWebRetiredProviderId,
|
||||
} from "../../src/shared/constants/chatgptWebRetirement.ts";
|
||||
|
||||
test("the registry exposes chatgpt-session with its seven routes", () => {
|
||||
const entry = REGISTRY["chatgpt-session"];
|
||||
assert.ok(entry, "chatgpt-session must be registered");
|
||||
assert.equal(entry.alias, "cgpt-session");
|
||||
assert.equal(entry.format, "openai");
|
||||
assert.equal(entry.executor, "chatgpt-session");
|
||||
assert.deepEqual(
|
||||
entry.models.map((model) => model.id),
|
||||
[...CHATGPT_SESSION_ROUTE_IDS]
|
||||
);
|
||||
});
|
||||
|
||||
test("the new ids are not the retired ones", () => {
|
||||
assert.equal(isCommonChatGptWebRetiredProviderId("chatgpt-session"), false);
|
||||
assert.equal(isCommonChatGptWebRetiredProviderId("cgpt-session"), false);
|
||||
assert.deepEqual([...RETIRED_COMMON_CHATGPT_WEB_PROVIDER_IDS], ["chatgpt-web", "cgpt-web"]);
|
||||
});
|
||||
|
||||
test("both executor aliases resolve to the session executor", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
for (const id of ["chatgpt-session", "cgpt-session"]) {
|
||||
const executor = await getExecutor(id);
|
||||
assert.equal(executor.getProvider(), "chatgpt-session");
|
||||
}
|
||||
});
|
||||
57
tests/unit/chatgpt-session-validation.test.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { WEB_COOKIE_PROVIDERS } from "../../src/shared/constants/providers/web-cookie.ts";
|
||||
import { WEB_SESSION_CREDENTIAL_REQUIREMENTS } from "../../src/shared/providers/webSessionCredentials.ts";
|
||||
import { usesChatGptBrowserSessionCredentials } from "../../src/shared/constants/chatgptWebCodex.ts";
|
||||
|
||||
test("the dashboard card describes the session provider", () => {
|
||||
const card = (WEB_COOKIE_PROVIDERS as Record<string, Record<string, unknown>>)["chatgpt-session"];
|
||||
assert.ok(card, "chatgpt-session must have a web-cookie card");
|
||||
assert.equal(card.alias, "cgpt-session");
|
||||
assert.equal(card.website, "https://chatgpt.com");
|
||||
assert.equal(card.subscriptionRisk, true);
|
||||
assert.equal(card.riskNoticeVariant, "webCookie");
|
||||
assert.equal(card.toolCalling, "emulated");
|
||||
});
|
||||
|
||||
test("the credential requirement accepts a full cookie header", () => {
|
||||
const requirement = (
|
||||
WEB_SESSION_CREDENTIAL_REQUIREMENTS as Record<string, Record<string, unknown>>
|
||||
)["chatgpt-session"];
|
||||
assert.ok(requirement);
|
||||
assert.equal(requirement.kind, "cookie");
|
||||
assert.equal(requirement.acceptsFullCookieHeader, true);
|
||||
});
|
||||
|
||||
test("validateProviderApiKey dispatch resolves chatgpt-session to its own validator", async () => {
|
||||
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
|
||||
// "cookie:" decodes to an empty cookie (decodeChatGptWebCodexSecrets strips the
|
||||
// "cookie:" prefix), so validateChatGptSessionProvider returns its own
|
||||
// cookie-required rejection immediately — before any Chrome/CDP detection or
|
||||
// browser launch. A non-empty credential is required here: an empty apiKey is
|
||||
// intercepted by validateProviderApiKey's own "Provider and API key required"
|
||||
// gate before dispatch ever reaches the SPECIALTY_VALIDATORS map, which would
|
||||
// prove nothing about chatgpt-session's own registration.
|
||||
const result = await validateProviderApiKey({
|
||||
provider: "chatgpt-session",
|
||||
apiKey: "cookie:",
|
||||
});
|
||||
assert.equal(result.valid, false);
|
||||
assert.equal(result.error, "A ChatGPT cookie header or a stored browser session is required.");
|
||||
});
|
||||
|
||||
test("validation rejects an empty credential without launching a browser", async () => {
|
||||
const { validateChatGptSessionProvider } =
|
||||
await import("../../src/lib/providers/validation/chatgptSession.ts");
|
||||
const result = await validateChatGptSessionProvider({ apiKey: "" });
|
||||
assert.equal(result.valid, false);
|
||||
assert.match(String(result.error), /cookie|credential/i);
|
||||
});
|
||||
|
||||
test("usesChatGptBrowserSessionCredentials recognizes both browser-session providers", () => {
|
||||
assert.equal(usesChatGptBrowserSessionCredentials("chatgpt-web-codex"), true);
|
||||
assert.equal(usesChatGptBrowserSessionCredentials("chatgpt-session"), true);
|
||||
assert.equal(usesChatGptBrowserSessionCredentials("openai"), false);
|
||||
assert.equal(usesChatGptBrowserSessionCredentials(undefined), false);
|
||||
});
|
||||
@@ -171,7 +171,12 @@ 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);
|
||||
// 2026-09-02: 400 → 402 with the ChatGPT Session provider
|
||||
// (feat/chatgpt-session-provider) — its REGISTRY entry contributes 2
|
||||
// distinct new members: id "chatgpt-session" and alias "cgpt-session".
|
||||
assert.equal(RESERVED_PREFIX_COUNT, 402);
|
||||
assert.equal(RESERVED_PROVIDER_PREFIXES.has("chatgpt-session"), true);
|
||||
assert.equal(RESERVED_PROVIDER_PREFIXES.has("cgpt-session"), true);
|
||||
});
|
||||
|
||||
test("isReservedProviderPrefix rejects non-string input", () => {
|
||||
|
||||